diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/Parsedown.php | 1712 | ||||
-rw-r--r-- | includes/fetcher/index.js | 132 | ||||
-rw-r--r-- | includes/fetcher/projects.json | 624 | ||||
-rw-r--r-- | includes/functions.php | 49 | ||||
-rw-r--r-- | includes/header.php | 10 | ||||
-rw-r--r-- | includes/navigation.php | 3 |
6 files changed, 1994 insertions, 536 deletions
diff --git a/includes/Parsedown.php b/includes/Parsedown.php new file mode 100644 index 0000000..1d80e42 --- /dev/null +++ b/includes/Parsedown.php @@ -0,0 +1,1712 @@ +<?php + +# +# +# Parsedown +# http://parsedown.org +# +# (c) Emanuil Rusev +# http://erusev.com +# +# For the full license information, view the LICENSE file that was distributed +# with this source code. +# +# + +class Parsedown +{ + # ~ + + const version = '1.7.4.ProjectCloudsdale'; + + # ~ + + function text($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + if (strpos($line, "\t") !== false) + { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) + { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + $Blocks []= $CurrentBlock; + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Blocks []= $CurrentBlock; + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + + # ~ + + $Blocks []= $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) + { + if (isset($Block['hidden'])) + { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block'.$Type.'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block'.$Type.'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + { + return void; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + { + $Block = array( + 'markup' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[1])) + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); + + $class = 'language-'.$language; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # List + + protected function blockList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + if($name === 'ol') + { + $listStart = stristr($matches[0], '.', true); + + if($listStart !== '1') + { + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $text, + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['text'] as &$li) + { + if (end($li['text']) !== '') + { + $li['text'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ); + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + + $Block['void'] = true; + } + } + else + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'])) # close + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) + { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) + { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: '.$Block['alignments'][$index].';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!"*_&[:<>`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables=array()) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) + { + continue; + } + + $Inline = $this->{'inline'.$inlineType}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + foreach ($nonNestables as $non_nestable) + { + $Inline['element']['nonNestables'][] = $non_nestable; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + # + # ~ + # + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches)) + { + $text = $matches[2]; + $text = preg_replace("/[ ]*\n/", ' ', $text); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = 'mailto:' . $url; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'nonNestables' => array('Url', 'Link'), + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)]/', $remainder, $matches)) + { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['text']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) + { + return array( + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) + { + $text = preg_replace('/[ ]*\n/', "<br />\n", $text); + } + else + { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + # + # Handlers + # + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.self::escape($value).'"'; + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + if (isset($text)) + { + $markup .= '>'; + + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (isset($Element['handler'])) + { + $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); + } + elseif (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + + $markup .= '</'.$Element['name'].'>'; + } + else + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) + { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "</p>"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected string $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected array $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected array $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/includes/fetcher/index.js b/includes/fetcher/index.js index 0f3dcac..0b36111 100644 --- a/includes/fetcher/index.js +++ b/includes/fetcher/index.js @@ -12,9 +12,9 @@ let gitlabProjectsRaw; if (smallestId > 0) { - gitlabProjectsRaw = (await axios.get(`https://gitlab.minteck.org/api/v4/projects?order_by=id&archived=false&simple=true&id_before=${smallestId}`)).data; + gitlabProjectsRaw = (await axios.get(`https://gitlab.minteck.org/api/v4/users/minteck/projects?order_by=id&archived=false&simple=true&id_before=${smallestId}`)).data; } else { - gitlabProjectsRaw = (await axios.get(`https://gitlab.minteck.org/api/v4/projects?order_by=id&archived=false&simple=true`)).data; + gitlabProjectsRaw = (await axios.get(`https://gitlab.minteck.org/api/v4/users/minteck/projects?order_by=id&archived=false&simple=true`)).data; } for (let project of gitlabProjectsRaw) { gitlabProjects.push({ @@ -25,7 +25,9 @@ issues: null, vcs: project.http_url_to_repo, web: project.web_url, - showcase: project.topics.includes("Showcase") + icon: project.avatar_url, + showcase: project.topics.includes("Showcase"), + date: project.last_activity_at }) smallestId = project.id; } @@ -35,37 +37,84 @@ } console.log("Fetching projects... YouTrack"); - const youtrackProjectsRaw = (await axios.get(`https://youtrack.minteck.org/api/admin/projects?fields=id,name,shortName,description`)).data; let youtrackProjects = []; - - for (let project of youtrackProjectsRaw) { - youtrackProjects.push({ - gitlab_id: null, - youtrack_id: project.id, - name: project.name, - description: project.description, - issues: project.shortName, - vcs: null, - web: null, - showcase: false - }) - } - - console.log("Merging data...") + let unusedBase = []; + let unusedYoutrackProjects = []; let projects = {}; let projectsPlusYoutrack = {}; - for (let project of youtrackProjects) { - nameCompareYoutrack = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); - descCompareYoutrack = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); - for (let gprj of gitlabProjects) { - nameCompareGitlab = gprj.name.toLowerCase().replace(/[^a-z]+/gm, ""); - descCompareGitlab = gprj.description.toLowerCase().replace(/[^a-z]+/gm, ""); + try { + const youtrackProjectsRaw = (await axios.get(`https://youtrack.minteck.org/api/admin/projects?fields=id,name,shortName,description`)).data; + for (let project of youtrackProjectsRaw) { + youtrackProjects.push({ + gitlab_id: null, + youtrack_id: project.id, + name: project.name, + description: project.description, + issues: project.shortName, + vcs: null, + web: null, + icon: null, + showcase: false, + date: null + }) + } + + console.log("Merging data...") + for (let project of youtrackProjects) { + nameCompareYoutrack = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); + descCompareYoutrack = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); + + for (let gprj of gitlabProjects) { + nameCompareGitlab = gprj.name.toLowerCase().replace(/[^a-z]+/gm, ""); + descCompareGitlab = gprj.description.toLowerCase().replace(/[^a-z]+/gm, ""); - if (nameCompareGitlab === nameCompareYoutrack || descCompareGitlab === descCompareYoutrack) { - gprj.youtrack_id = project.youtrack_id; + if (nameCompareGitlab === nameCompareYoutrack || descCompareGitlab === descCompareYoutrack) { + gprj.youtrack_id = project.youtrack_id; + gprj.issues = "https://youtrack.minteck.org/issues/" + project.youtrack_id; + } + + if (gprj.youtrack_id === null) { + id = crypto.createHash('sha1').update(gprj.gitlab_id.toString() + "null").digest('hex'); + } else { + id = crypto.createHash('sha1').update(gprj.gitlab_id.toString() + gprj.youtrack_id.toString()).digest('hex'); + projectsPlusYoutrack[id] = gprj; + } + projects[id] = gprj; } + } + const knownYoutrackIds = Object.keys(projectsPlusYoutrack).map((i) => { return projectsPlusYoutrack[i].youtrack_id; }); + for (let project of youtrackProjectsRaw) { + if (!knownYoutrackIds.includes(project.id)) { + project.name_compare = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); + project.description_compare = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); + unusedYoutrackProjects.push(project); + } + } + + for (let project of gitlabProjects) { + project.name_compare = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); + project.description_compare = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); + unusedBase.push(project); + } + + projects = Object.keys(projects).map((i) => { + return { + id: i, + ...projects[i] + } + }) + projects.sort((a, b) => (new Date(b.date) - new Date(a.date))); + + fs.writeFileSync("projects.json", JSON.stringify(projects, null, 4)); + fs.writeFileSync("unused-live.json", JSON.stringify(unusedYoutrackProjects, null, 4)); + fs.writeFileSync("unused-base.json", JSON.stringify(unusedBase, null, 4)); + console.log("Done merging, found " + Object.keys(projects).length + " projects (" + Object.keys(projectsPlusYoutrack).length + " on YouTrack, " + unusedYoutrackProjects.length + " unused)"); + } catch (e) { + console.log("Failed to fetch YouTrack projects (" + e.message + ")"); + + for (let gprj of gitlabProjects) { if (gprj.youtrack_id === null) { id = crypto.createHash('sha1').update(gprj.gitlab_id.toString() + "null").digest('hex'); } else { @@ -74,27 +123,16 @@ } projects[id] = gprj; } - } - const knownYoutrackIds = Object.keys(projectsPlusYoutrack).map((i) => { return projectsPlusYoutrack[i].youtrack_id; }); - let unusedYoutrackProjects = []; - for (let project of youtrackProjectsRaw) { - if (!knownYoutrackIds.includes(project.id)) { - project.name_compare = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); - project.description_compare = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); - unusedYoutrackProjects.push(project); - } - } + projects = Object.keys(projects).map((i) => { + return { + id: i, + ...projects[i] + } + }) + projects.sort((a, b) => (new Date(b.date) - new Date(a.date))); - let unusedBase = []; - for (let project of gitlabProjects) { - project.name_compare = project.name.toLowerCase().replace(/[^a-z]+/gm, ""); - project.description_compare = project.description.toLowerCase().replace(/[^a-z]+/gm, ""); - unusedBase.push(project); + fs.writeFileSync("projects.json", JSON.stringify(projects, null, 4)); + console.log("Done fetching, found " + Object.keys(projects).length + " projects"); } - - fs.writeFileSync("projects.json", JSON.stringify(projects, false, 4)); - fs.writeFileSync("unused-live.json", JSON.stringify(unusedYoutrackProjects, false, 4)); - fs.writeFileSync("unused-base.json", JSON.stringify(unusedBase, false, 4)); - console.log("Done merging, found " + Object.keys(projects).length + " projects (" + Object.keys(projectsPlusYoutrack).length + " on YouTrack, " + unusedYoutrackProjects.length + " unused)"); })()
\ No newline at end of file diff --git a/includes/fetcher/projects.json b/includes/fetcher/projects.json index 081b099..373200a 100644 --- a/includes/fetcher/projects.json +++ b/includes/fetcher/projects.json @@ -1,209 +1,19 @@ -{ - "980aaf996775f99f3ccac3c010eeb9982470ce53": { - "gitlab_id": 73, - "youtrack_id": "0-52", - "name": "WebX - Website v10", - "description": "Yet another modern website for me", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/webx.git", - "web": "http://gitlab.minteck.org/minteck/webx", - "showcase": false, - "name_compare": "webxwebsitev", - "description_compare": "yetanothermodernwebsiteforme" - }, - "1b10549feef5a95c59f9a09f11eff72f380c9049": { - "gitlab_id": 72, - "youtrack_id": "0-51", - "name": "AutoDocs", - "description": "Publishing documentation for your projects is sometimes hard, AutoDocs got you covered!", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/autodocs.git", - "web": "http://gitlab.minteck.org/minteck/autodocs", - "showcase": false, - "name_compare": "autodocs", - "description_compare": "publishingdocumentationforyourprojectsissometimeshardautodocsgotyoucovered" - }, - "a2702e2f2bcf561027ccf0eb85708f0460db3ebc": { - "gitlab_id": 71, - "youtrack_id": null, - "name": "Familine Planning", - "description": "Plan, manage and sort events", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/familine-planning.git", - "web": "http://gitlab.minteck.org/minteck/familine-planning", - "showcase": false, - "name_compare": "familineplanning", - "description_compare": "planmanageandsortevents" - }, - "179b3939dcdf48591a9faae3ff7e5aece2bb0034": { - "gitlab_id": 70, - "youtrack_id": null, - "name": "Familine Session Manager", - "description": "Authentication session management system for Familine", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/familine-session.git", - "web": "http://gitlab.minteck.org/minteck/familine-session", - "showcase": false, - "name_compare": "familinesessionmanager", - "description_compare": "authenticationsessionmanagementsystemforfamiline" - }, - "b8380a76e7f90112398d58d72fe88184696982c4": { - "gitlab_id": 69, - "youtrack_id": null, - "name": "Familine Media", - "description": "Family media center", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/familine-media.git", - "web": "http://gitlab.minteck.org/minteck/familine-media", - "showcase": false, - "name_compare": "familinemedia", - "description_compare": "familymediacenter" - }, - "6039ddda5676adfcc1ea25ba5fc8438f37904151": { - "gitlab_id": 68, - "youtrack_id": null, - "name": "Familine Public Introduction", - "description": "Publicly accessible homepage for Familine", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/familine-intro.git", - "web": "http://gitlab.minteck.org/minteck/familine-intro", - "showcase": false, - "name_compare": "familinepublicintroduction", - "description_compare": "publiclyaccessiblehomepageforfamiline" - }, - "30c6eeba4139ca4d5330f6a54d535111443d2a84": { - "gitlab_id": 67, +[ + { + "id": "e8ed872c86e59f4bf70fb023695a714a6973d11d", + "gitlab_id": 25, "youtrack_id": null, - "name": "Familine CDN", - "description": "Content delivery network and static assets for Familine", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/familine-cdn.git", - "web": "http://gitlab.minteck.org/minteck/familine-cdn", - "showcase": false, - "name_compare": "familinecdn", - "description_compare": "contentdeliverynetworkandstaticassetsforfamiline" - }, - "fa344572018fda9017ac256070f4ad6610cad084": { - "gitlab_id": 66, - "youtrack_id": "0-11", - "name": "Snowjail", - "description": "Sandboxing technology for Twilight Package Manager packages", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/snowjail.git", - "web": "http://gitlab.minteck.org/minteck/snowjail", - "showcase": false, - "name_compare": "snowjail", - "description_compare": "sandboxingtechnologyfortwilightpackagemanagerpackages" - }, - "b33700d38f096fc476edcfbb84b0d475639a9adf": { - "gitlab_id": 65, - "youtrack_id": "0-12", - "name": "Voicer", - "description": "An open-source offline-first voice assistant", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/voicer.git", - "web": "http://gitlab.minteck.org/minteck/voicer", - "showcase": false, - "name_compare": "voicer", - "description_compare": "anopensourceofflinefirstvoiceassistant" - }, - "63c80d908c7b3d056d5ebf9b83034381eef5ddd4": { - "gitlab_id": 62, - "youtrack_id": "0-13", - "name": "Website for the Cloudburst System", - "description": "A website made in collaboration and for [Cloudburst](https://github.com/CloudburstSys). https://cloudburst-system.test.minteck.net.eu.org/ Future readers: this is not a commission, please don't ask me to create a website for you", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/cloudsdale.git", - "web": "http://gitlab.minteck.org/minteck/cloudsdale", - "showcase": false, - "name_compare": "websiteforthecloudburstsystem", - "description_compare": "awebsitemadeincollaborationandforcloudbursthttpsgithubcomcloudburstsyshttpscloudburstsystemtestminteckneteuorgfuturereadersthisisnotacommissionpleasedontaskmetocreateawebsiteforyou" - }, - "a8169ad64935e63ee65c1b38d4b625a315f7a0be": { - "gitlab_id": 61, - "youtrack_id": "0-10", - "name": "r-Place archive", - "description": "An archive viewer for r/place (Reddit's April Fools 2022)", + "name": "Neutron", + "description": "A simple, lightweight and easy PHP content management system", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/placearchive.git", - "web": "http://gitlab.minteck.org/minteck/placearchive", + "vcs": "http://gitlab.minteck.org/minteck/neutron.git", + "web": "http://gitlab.minteck.org/minteck/neutron", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/25/5-6b0fab376f30ad8eea6bc3e8fa15de6f.png", "showcase": true, - "name_compare": "rplacearchive", - "description_compare": "anarchiveviewerforrplaceredditsaprilfools" - }, - "7adce3f27d6625c6867fcb46756dc520a58a2e70": { - "gitlab_id": 60, - "youtrack_id": "0-3", - "name": "Argon", - "description": "Frontend and Web client for the Argon Music Platform", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon.git", - "web": "http://gitlab.minteck.org/minteck/argon", - "showcase": false, - "name_compare": "argon", - "description_compare": "frontendandwebclientfortheargonmusicplatform" - }, - "1cf12f94d06a8807a63151ae38ed6b76820a4f72": { - "gitlab_id": 59, - "youtrack_id": "0-4", - "name": "Argon 3pAD", - "description": "3rd-party analytics data federation daemon for the Argon Music Platform", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon-3pad.git", - "web": "http://gitlab.minteck.org/minteck/argon-3pad", - "showcase": false, - "name_compare": "argonpad", - "description_compare": "rdpartyanalyticsdatafederationdaemonfortheargonmusicplatform" - }, - "54d9cb53bb467dfe3b0552c5e72cfb638f29299b": { - "gitlab_id": 58, - "youtrack_id": "0-2", - "name": "Alicorn Operating System", - "description": "The next-generation operating system using Web technologies", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/alicorn.git", - "web": "http://gitlab.minteck.org/minteck/alicorn", - "showcase": false, - "name_compare": "alicornoperatingsystem", - "description_compare": "thenextgenerationoperatingsystemusingwebtechnologies" + "date": "2022-04-20T14:48:41.736Z" }, - "bf78ae1ff90298f79d212242dd33183cb770fadf": { - "gitlab_id": 57, - "youtrack_id": "0-15", - "name": "Argon Transcoding Engine", - "description": "An automated transcoding engine for the Argon Music Platform, using ffmpeg as a backend", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon-transcode.git", - "web": "http://gitlab.minteck.org/minteck/argon-transcode", - "showcase": false, - "name_compare": "argontranscodingengine", - "description_compare": "anautomatedtranscodingenginefortheargonmusicplatformusingffmpegasabackend" - }, - "6471f1f106be87f080990c7eea042c81ac78dee8": { - "gitlab_id": 55, - "youtrack_id": null, - "name": "twilight-setup", - "description": "", - "issues": null, - "vcs": "http://gitlab.minteck.org/twipkg-bin/twilight-setup.git", - "web": "http://gitlab.minteck.org/twipkg-bin/twilight-setup", - "showcase": false, - "name_compare": "twilightsetup", - "description_compare": "" - }, - "aabeadbcd02bcf622394c945562d4d4537e61c10": { - "gitlab_id": 54, - "youtrack_id": "0-44", - "name": "Twilight Setup Utility", - "description": "A self-extracting online installer/repairer/uninstaller for the [Twilight Package Manager](/minteck/twilight)", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/twilight-setup.git", - "web": "http://gitlab.minteck.org/minteck/twilight-setup", - "showcase": false, - "name_compare": "twilightsetuputility", - "description_compare": "aselfextractingonlineinstallerrepaireruninstallerforthetwilightpackagemanagermintecktwilight" - }, - "52c292243fe696711ae37e155b011733eb2e6d0a": { + { + "id": "52c292243fe696711ae37e155b011733eb2e6d0a", "gitlab_id": 50, "youtrack_id": null, "name": "Twilight", @@ -211,107 +21,51 @@ "issues": null, "vcs": "http://gitlab.minteck.org/minteck/twilight.git", "web": "http://gitlab.minteck.org/minteck/twilight", + "icon": null, "showcase": false, - "name_compare": "twilight", - "description_compare": "agitbasedpackagemanagermadeformintecksinfrastructurenotforproductionseereadmemintecktwilightblobtrunkreadmemdfordetails" + "date": "2022-04-17T15:37:40.719Z" }, - "3345c862bdf8957517e4ce4fc72ba6587d34bc64": { - "gitlab_id": 46, - "youtrack_id": "0-19", - "name": "Cobalt", - "description": "A powerful, extensible and developer-friendly Markdown-based content management system", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/cobalt.git", - "web": "http://gitlab.minteck.org/minteck/cobalt", - "showcase": false, - "name_compare": "cobalt", - "description_compare": "apowerfulextensibleanddeveloperfriendlymarkdownbasedcontentmanagementsystem" - }, - "2a7293fcaee5b5edfdd442f35144977eb9a4f266": { - "gitlab_id": 44, - "youtrack_id": "0-40", - "name": "Ponyfind", - "description": "A pony Discord bot, made by an Equestrian.", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/ponyfind.git", - "web": "http://gitlab.minteck.org/minteck/ponyfind", - "showcase": true, - "name_compare": "ponyfind", - "description_compare": "aponydiscordbotmadebyanequestrian" - }, - "834d30d1fa3e5e08ad4a7556d6edfec903d12ccf": { - "gitlab_id": 43, - "youtrack_id": "0-39", - "name": "pony.minteck.org", - "description": "Ponies! 🦄", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/pony.git", - "web": "http://gitlab.minteck.org/minteck/pony", - "showcase": false, - "name_compare": "ponyminteckorg", - "description_compare": "ponies" - }, - "f5f92ad74f4dc911b6f8dde9404f0123a5322784": { - "gitlab_id": 38, + { + "id": "980aaf996775f99f3ccac3c010eeb9982470ce53", + "gitlab_id": 73, "youtrack_id": null, - "name": "Familine Desktop", - "description": "A desktop app for Familine", + "name": "Ember - Website v10", + "description": "Yet another modern website for me, but this time it's actually good | Now live on [staging.minteck.org](https://staging.minteck.org)", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/desktop.git", - "web": "http://gitlab.minteck.org/minteck/desktop", + "vcs": "http://gitlab.minteck.org/minteck/ember.git", + "web": "http://gitlab.minteck.org/minteck/ember", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/73/Ember__22we_could_keep_working_together_22_S6E5_copy_2.png", "showcase": false, - "name_compare": "familinedesktop", - "description_compare": "adesktopappforfamiline" + "date": "2022-04-16T15:35:40.241Z" }, - "f22d052294fa1896a776076c9fec6614f463a6d9": { + { + "id": "f22d052294fa1896a776076c9fec6614f463a6d9", "gitlab_id": 35, - "youtrack_id": "0-30", + "youtrack_id": null, "name": "Foxperson", "description": "A new game made using Godot 3.", "issues": null, "vcs": "http://gitlab.minteck.org/minteck/foxperson.git", "web": "http://gitlab.minteck.org/minteck/foxperson", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/35/icon.png", "showcase": false, - "name_compare": "foxperson", - "description_compare": "anewgamemadeusinggodot" + "date": "2022-04-15T15:06:31.707Z" }, - "e8ed872c86e59f4bf70fb023695a714a6973d11d": { - "gitlab_id": 25, - "youtrack_id": "0-1", - "name": "Neutron", - "description": "A simple, lightweight and easy PHP content management system — JetBrains Request ID: 11042022/9185466", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/neutron.git", - "web": "http://gitlab.minteck.org/minteck/neutron", - "showcase": true, - "name_compare": "neutron", - "description_compare": "asimplelightweightandeasyphpcontentmanagementsystemjetbrainsrequestid" - }, - "f3f83c9c2dab262308e04c6424de960b3f8fa259": { + { + "id": "f3f83c9c2dab262308e04c6424de960b3f8fa259", "gitlab_id": 22, - "youtrack_id": "0-41", + "youtrack_id": null, "name": "Rainbow - Website v9", - "description": "A new dynamic and blazing fast Web server for Minteck, default `htdocs` includes [staging.minteck.org](https://staging.minteck.org)", + "description": "A new dynamic and blazing fast Web server for Minteck, default `htdocs` includes [minteck.org](https://minteck.org)", "issues": null, "vcs": "http://gitlab.minteck.org/minteck/rainbow.git", "web": "http://gitlab.minteck.org/minteck/rainbow", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/22/icon.png", "showcase": false, - "name_compare": "rainbowwebsitev", - "description_compare": "anewdynamicandblazingfastwebserverforminteckdefaulthtdocsincludesstagingminteckorghttpsstagingminteckorg" + "date": "2022-04-14T14:08:13.616Z" }, - "de0c2e523b1680eeabbf388fed48e0ac73d86f8e": { - "gitlab_id": 15, - "youtrack_id": null, - "name": "Familine Share", - "description": "(French) Share files with the entire world right from Familine and safely", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/share.git", - "web": "http://gitlab.minteck.org/minteck/share", - "showcase": false, - "name_compare": "familineshare", - "description_compare": "frenchsharefileswiththeentireworldrightfromfamilineandsafely" - }, - "58fed8a4724d0c41eebc6dfc1617930d7f773f61": { + { + "id": "58fed8a4724d0c41eebc6dfc1617930d7f773f61", "gitlab_id": 12, "youtrack_id": null, "name": "Me", @@ -319,260 +73,164 @@ "issues": null, "vcs": "http://gitlab.minteck.org/minteck/minteck.git", "web": "http://gitlab.minteck.org/minteck/minteck", + "icon": null, "showcase": false, - "name_compare": "me", - "description_compare": "someofmystuff" + "date": "2022-04-13T17:13:01.625Z" }, - "1f88f38480fa8008aa31d91ce15c870b65da2257": { - "gitlab_id": 11, - "youtrack_id": null, - "name": "Familine Movies", - "description": "(French) Share movie productions from your family with your family; easy and simple to use.", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/movies.git", - "web": "http://gitlab.minteck.org/minteck/movies", - "showcase": false, - "name_compare": "familinemovies", - "description_compare": "frenchsharemovieproductionsfromyourfamilywithyourfamilyeasyandsimpletouse" - }, - "f378984db5bb0f340a6e0150128acd67b8aed5ba": { - "gitlab_id": 9, + { + "id": "2a7293fcaee5b5edfdd442f35144977eb9a4f266", + "gitlab_id": 44, "youtrack_id": null, - "name": "Familine Help", - "description": "(French) General help center to get help and tips about all Familine services.", + "name": "Ponyfind", + "description": "A pony Discord bot, made by an Equestrian.", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/help.git", - "web": "http://gitlab.minteck.org/minteck/help", - "showcase": false, - "name_compare": "familinehelp", - "description_compare": "frenchgeneralhelpcentertogethelpandtipsaboutallfamilineservices" + "vcs": "http://gitlab.minteck.org/minteck/ponyfind.git", + "web": "http://gitlab.minteck.org/minteck/ponyfind", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/44/vlcsnap-2022-01-08-22h16m17s487.png", + "showcase": true, + "date": "2022-04-13T17:12:51.739Z" }, - "8267dde1cad2da4c8641ed08d88cd846bb2ffccf": { - "gitlab_id": 7, + { + "id": "bf78ae1ff90298f79d212242dd33183cb770fadf", + "gitlab_id": 57, "youtrack_id": null, - "name": "Familine Genealogy", - "description": "View GEDCOM files in a shiny cool Web interface.", + "name": "Argon Transcoding Engine", + "description": "An automated transcoding engine for the Argon Music Platform, using ffmpeg as a backend", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/genealogy.git", - "web": "http://gitlab.minteck.org/minteck/genealogy", + "vcs": "http://gitlab.minteck.org/minteck/argon-transcode.git", + "web": "http://gitlab.minteck.org/minteck/argon-transcode", + "icon": null, "showcase": false, - "name_compare": "familinegenealogy", - "description_compare": "viewgedcomfilesinashinycoolwebinterface" + "date": "2022-04-13T17:12:42.388Z" }, - "c03ff9948d214d8d35959266521f6640ac330686": { - "gitlab_id": 6, + { + "id": "63c80d908c7b3d056d5ebf9b83034381eef5ddd4", + "gitlab_id": 62, "youtrack_id": null, - "name": "Familine Core", - "description": "(French) Core files making Familine actually work.", + "name": "Website for the Cloudburst System", + "description": "A website made in collaboration and for [Cloudburst](https://github.com/CloudburstSys). https://conep.one/ Future readers: this is not a commission, please don't ask me to create a website for you", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/core.git", - "web": "http://gitlab.minteck.org/minteck/core", + "vcs": "http://gitlab.minteck.org/minteck/cloudsdale.git", + "web": "http://gitlab.minteck.org/minteck/cloudsdale", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/62/android-chrome-512x512.png", "showcase": false, - "name_compare": "familinecore", - "description_compare": "frenchcorefilesmakingfamilineactuallywork" + "date": "2022-04-13T17:12:37.329Z" }, - "68b82313efe7ebb0088b3adc1790fac41ae9598c": { - "gitlab_id": 2, + { + "id": "b33700d38f096fc476edcfbb84b0d475639a9adf", + "gitlab_id": 65, "youtrack_id": null, - "name": "WolfEye Backend", - "description": "WolfEye's API and backend processing code", - "issues": null, - "vcs": "http://gitlab.minteck.org/jae/wolfeye-api.git", - "web": "http://gitlab.minteck.org/jae/wolfeye-api", - "showcase": false, - "name_compare": "wolfeyebackend", - "description_compare": "wolfeyesapiandbackendprocessingcode" - }, - "f884c41416235588a43e31bd28ea89bcbed28e87": { - "gitlab_id": 60, - "youtrack_id": "0-3", - "name": "Argon", - "description": "Frontend and Web client for the Argon Music Platform", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon.git", - "web": "http://gitlab.minteck.org/minteck/argon", - "showcase": false, - "name_compare": "argon", - "description_compare": "frontendandwebclientfortheargonmusicplatform" - }, - "1aa018e88d06be0c8f3aa0d8caaa70d7c17a2fb8": { - "gitlab_id": 59, - "youtrack_id": "0-4", - "name": "Argon 3pAD", - "description": "3rd-party analytics data federation daemon for the Argon Music Platform", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon-3pad.git", - "web": "http://gitlab.minteck.org/minteck/argon-3pad", - "showcase": false, - "name_compare": "argonpad", - "description_compare": "rdpartyanalyticsdatafederationdaemonfortheargonmusicplatform" - }, - "1149c45380143aae738bf71c0bf599c2c621d962": { - "gitlab_id": 57, - "youtrack_id": "0-15", - "name": "Argon Transcoding Engine", - "description": "An automated transcoding engine for the Argon Music Platform, using ffmpeg as a backend", + "name": "Voicer", + "description": "An open-source offline-first voice assistant", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/argon-transcode.git", - "web": "http://gitlab.minteck.org/minteck/argon-transcode", + "vcs": "http://gitlab.minteck.org/minteck/voicer.git", + "web": "http://gitlab.minteck.org/minteck/voicer", + "icon": null, "showcase": false, - "name_compare": "argontranscodingengine", - "description_compare": "anautomatedtranscodingenginefortheargonmusicplatformusingffmpegasabackend" + "date": "2022-04-13T17:12:35.143Z" }, - "cb1a6f17454740b360a469470efe8d74e66f18df": { + { + "id": "1b10549feef5a95c59f9a09f11eff72f380c9049", "gitlab_id": 72, - "youtrack_id": "0-51", + "youtrack_id": null, "name": "AutoDocs", "description": "Publishing documentation for your projects is sometimes hard, AutoDocs got you covered!", "issues": null, "vcs": "http://gitlab.minteck.org/minteck/autodocs.git", "web": "http://gitlab.minteck.org/minteck/autodocs", + "icon": null, "showcase": false, - "name_compare": "autodocs", - "description_compare": "publishingdocumentationforyourprojectsissometimeshardautodocsgotyoucovered" + "date": "2022-04-13T17:12:26.656Z" }, - "bd3a87a69db21ed761f1f3a6ed60b11a0205b173": { - "gitlab_id": 46, - "youtrack_id": "0-19", - "name": "Cobalt", - "description": "A powerful, extensible and developer-friendly Markdown-based content management system", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/cobalt.git", - "web": "http://gitlab.minteck.org/minteck/cobalt", - "showcase": false, - "name_compare": "cobalt", - "description_compare": "apowerfulextensibleanddeveloperfriendlymarkdownbasedcontentmanagementsystem" - }, - "5130f04d775705f9228157e0e04efffd2f308f90": { - "gitlab_id": 35, - "youtrack_id": "0-30", - "name": "Foxperson", - "description": "A new game made using Godot 3.", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/foxperson.git", - "web": "http://gitlab.minteck.org/minteck/foxperson", - "showcase": false, - "name_compare": "foxperson", - "description_compare": "anewgamemadeusinggodot" - }, - "50ccf062ed15056cc37a12ed2aff3f922712b56b": { - "gitlab_id": 25, - "youtrack_id": "0-1", - "name": "Neutron", - "description": "A simple, lightweight and easy PHP content management system — JetBrains Request ID: 11042022/9185466", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/neutron.git", - "web": "http://gitlab.minteck.org/minteck/neutron", - "showcase": true, - "name_compare": "neutron", - "description_compare": "asimplelightweightandeasyphpcontentmanagementsystemjetbrainsrequestid" - }, - "a86ecc4a9b6ada137b3aed7cfadfd9f00ca6ebcf": { - "gitlab_id": 43, - "youtrack_id": "0-39", - "name": "pony.minteck.org", - "description": "Ponies! 🦄", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/pony.git", - "web": "http://gitlab.minteck.org/minteck/pony", - "showcase": false, - "name_compare": "ponyminteckorg", - "description_compare": "ponies" - }, - "2d14c68f00f86b7477520fafdfce54da9e6614db": { - "gitlab_id": 44, - "youtrack_id": "0-40", - "name": "Ponyfind", - "description": "A pony Discord bot, made by an Equestrian.", - "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/ponyfind.git", - "web": "http://gitlab.minteck.org/minteck/ponyfind", - "showcase": true, - "name_compare": "ponyfind", - "description_compare": "aponydiscordbotmadebyanequestrian" - }, - "6b5e94ee9ecb841221410185fe42e31000032c50": { + { + "id": "a8169ad64935e63ee65c1b38d4b625a315f7a0be", "gitlab_id": 61, - "youtrack_id": "0-10", + "youtrack_id": null, "name": "r-Place archive", "description": "An archive viewer for r/place (Reddit's April Fools 2022)", "issues": null, "vcs": "http://gitlab.minteck.org/minteck/placearchive.git", "web": "http://gitlab.minteck.org/minteck/placearchive", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/61/logo.png", "showcase": true, - "name_compare": "rplacearchive", - "description_compare": "anarchiveviewerforrplaceredditsaprilfools" + "date": "2022-04-13T14:33:17.289Z" }, - "f742b3fbcfa54dc144098930971b156d57426e94": { - "gitlab_id": 22, - "youtrack_id": "0-41", - "name": "Rainbow - Website v9", - "description": "A new dynamic and blazing fast Web server for Minteck, default `htdocs` includes [staging.minteck.org](https://staging.minteck.org)", + { + "id": "7adce3f27d6625c6867fcb46756dc520a58a2e70", + "gitlab_id": 60, + "youtrack_id": null, + "name": "Argon", + "description": "Frontend and Web client for the Argon Music Platform", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/rainbow.git", - "web": "http://gitlab.minteck.org/minteck/rainbow", + "vcs": "http://gitlab.minteck.org/minteck/argon.git", + "web": "http://gitlab.minteck.org/minteck/argon", + "icon": null, "showcase": false, - "name_compare": "rainbowwebsitev", - "description_compare": "anewdynamicandblazingfastwebserverforminteckdefaulthtdocsincludesstagingminteckorghttpsstagingminteckorg" + "date": "2022-04-12T11:37:10.147Z" }, - "25638a6855b4502ae04fe092ee26f62f08df45f0": { + { + "id": "fa344572018fda9017ac256070f4ad6610cad084", "gitlab_id": 66, - "youtrack_id": "0-11", + "youtrack_id": null, "name": "Snowjail", "description": "Sandboxing technology for Twilight Package Manager packages", "issues": null, "vcs": "http://gitlab.minteck.org/minteck/snowjail.git", "web": "http://gitlab.minteck.org/minteck/snowjail", + "icon": null, "showcase": false, - "name_compare": "snowjail", - "description_compare": "sandboxingtechnologyfortwilightpackagemanagerpackages" + "date": "2022-04-09T16:38:58.915Z" }, - "118c4e7f568d30340f0cd63fa921bab7f5dd71d2": { - "gitlab_id": 54, - "youtrack_id": "0-44", - "name": "Twilight Setup Utility", - "description": "A self-extracting online installer/repairer/uninstaller for the [Twilight Package Manager](/minteck/twilight)", + { + "id": "6d7e1ed7601aec219fc7eaac56c55a0fb138184a", + "gitlab_id": 58, + "youtrack_id": null, + "name": "Alicorn Operating System", + "description": "The next-generation operating system using Web technologies", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/twilight-setup.git", - "web": "http://gitlab.minteck.org/minteck/twilight-setup", + "vcs": "http://gitlab.minteck.org/minteck/alicorn.git", + "web": "http://gitlab.minteck.org/minteck/alicorn", + "icon": null, "showcase": false, - "name_compare": "twilightsetuputility", - "description_compare": "aselfextractingonlineinstallerrepaireruninstallerforthetwilightpackagemanagermintecktwilight" + "date": "2022-04-08T20:01:08.451Z" }, - "6546bc8f3da6d606b1a99953e3a664dd5e0bee75": { - "gitlab_id": 65, - "youtrack_id": "0-12", - "name": "Voicer", - "description": "An open-source offline-first voice assistant", + { + "id": "1cf12f94d06a8807a63151ae38ed6b76820a4f72", + "gitlab_id": 59, + "youtrack_id": null, + "name": "Argon 3pAD", + "description": "3rd-party analytics data federation daemon for the Argon Music Platform", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/voicer.git", - "web": "http://gitlab.minteck.org/minteck/voicer", + "vcs": "http://gitlab.minteck.org/minteck/argon-3pad.git", + "web": "http://gitlab.minteck.org/minteck/argon-3pad", + "icon": null, "showcase": false, - "name_compare": "voicer", - "description_compare": "anopensourceofflinefirstvoiceassistant" + "date": "2022-04-08T12:10:24.064Z" }, - "cf8c67740a78e7328b870797a9b32cdf5f04e3d2": { - "gitlab_id": 62, - "youtrack_id": "0-13", - "name": "Website for the Cloudburst System", - "description": "A website made in collaboration and for [Cloudburst](https://github.com/CloudburstSys). https://cloudburst-system.test.minteck.net.eu.org/ Future readers: this is not a commission, please don't ask me to create a website for you", + { + "id": "3345c862bdf8957517e4ce4fc72ba6587d34bc64", + "gitlab_id": 46, + "youtrack_id": null, + "name": "Cobalt", + "description": "A powerful, extensible and developer-friendly Markdown-based content management system", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/cloudsdale.git", - "web": "http://gitlab.minteck.org/minteck/cloudsdale", + "vcs": "http://gitlab.minteck.org/minteck/cobalt.git", + "web": "http://gitlab.minteck.org/minteck/cobalt", + "icon": null, "showcase": false, - "name_compare": "websiteforthecloudburstsystem", - "description_compare": "awebsitemadeincollaborationandforcloudbursthttpsgithubcomcloudburstsyshttpscloudburstsystemtestminteckneteuorgfuturereadersthisisnotacommissionpleasedontaskmetocreateawebsiteforyou" + "date": "2022-04-08T12:02:51.617Z" }, - "8508e56751f4ad1d7f6b5fd5dae2e282eea8a865": { - "gitlab_id": 73, - "youtrack_id": "0-52", - "name": "WebX - Website v10", - "description": "Yet another modern website for me", + { + "id": "834d30d1fa3e5e08ad4a7556d6edfec903d12ccf", + "gitlab_id": 43, + "youtrack_id": null, + "name": "pony.minteck.org", + "description": "Ponies! 🦄", "issues": null, - "vcs": "http://gitlab.minteck.org/minteck/webx.git", - "web": "http://gitlab.minteck.org/minteck/webx", + "vcs": "http://gitlab.minteck.org/minteck/pony.git", + "web": "http://gitlab.minteck.org/minteck/pony", + "icon": "http://gitlab.minteck.org/uploads/-/system/project/avatar/43/icon.png", "showcase": false, - "name_compare": "webxwebsitev", - "description_compare": "yetanothermodernwebsiteforme" + "date": "2022-04-03T08:15:08.565Z" } -}
\ No newline at end of file +]
\ No newline at end of file diff --git a/includes/functions.php b/includes/functions.php index defa152..38dda54 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1,6 +1,6 @@ <?php -function version() { +function version(): string { if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/.version")) { return substr(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/.version")), 0, 8); } else if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/.git/refs/heads/trunk")) { @@ -10,10 +10,53 @@ function version() { } } -function build() { +function build(): string { if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/.build")) { return substr(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/.build")), 0, 8); } else { return "dev"; } -}
\ No newline at end of file +} + +function getLetters(string $project): string { + $words = explode(" ", preg_replace('/#+/m', "#", preg_replace('/[^a-z0-9 ]/m', "#", strtolower(trim(preg_replace('/[A-Z]/m', ' $0', $project)))))); + + $words = array_slice(array_filter($words, function ($v) { + return trim($v); + }), 0); + + return substr($words[0], 0, 1); +} + +function timeAgo($time): string { + if (!is_numeric($time)) { + $time = strtotime($time); + } + + $periods = array("second", "minute", "hour", "day", "week", "month", "year", "age"); + $lengths = array("60", "60", "24", "7", "4.35", "12", "100"); + + $now = time(); + + $difference = $now - $time; + if ($difference <= 10 && $difference >= 0) { + return $tense = 'just now'; + } elseif ($difference > 0) { + $tense = 'ago'; + } else { + $tense = 'later'; + } + + for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) { + $difference /= $lengths[$j]; + } + + $difference = round($difference); + + $period = $periods[$j] . ($difference >1 ? 's' :''); + return "{$difference} {$period} {$tense} "; +} + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/Parsedown.php"; +global $Parsedown; +$Parsedown = new Parsedown();
\ No newline at end of file diff --git a/includes/header.php b/includes/header.php index 099267a..09257fe 100644 --- a/includes/header.php +++ b/includes/header.php @@ -16,5 +16,15 @@ <link rel="stylesheet" href="/assets/css/main.css"> <script src="/assets/js/bootstrap.bundle.min.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="apple-touch-icon" sizes="180x180" href="/assets/favicon/apple-touch-icon.png"> + <link rel="shortcut icon" type="image/x-icon" href="/assets/favicon/favicon.ico"> + <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon/favicon-32x32.png"> + <link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon/favicon-48x48.png"> + <link rel="icon" type="image/png" sizes="64x64" href="/assets/favicon/favicon-64x64.png"> + <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png"> + <link rel="manifest" href="/assets/favicon/manifest.json"> + <link rel="mask-icon" href="/assets/favicon/safari-pinned-tab.svg" color="#a56510"> + <meta name="msapplication-TileColor" content="#a56510"> + <meta name="theme-color" content="#a56510"> </head> <body class="bg-dark">
\ No newline at end of file diff --git a/includes/navigation.php b/includes/navigation.php index b670067..2d0fabf 100644 --- a/includes/navigation.php +++ b/includes/navigation.php @@ -21,9 +21,6 @@ <li class="nav-item"> <a class="nav-link" href="/social">Social</a> </li> - <li class="nav-item"> - <a class="nav-link" href="/faq">FAQ</a> - </li> </ul> </div> </div> |