\1', // quote + '/`(.*?)`/' => '
\1', // inline code
+ '/\n\*(.*)/' => self::class .'::ul_list', // ul lists
+ '/\n[0-9]+\.(.*)/' => self::class .'::ol_list', // ol lists
+ '/\n(>|\>)(.*)/' => self::class .'::blockquote', // blockquotes
+ '/\n-{5,}/' => "\n", // horizontal rule + '/\n([^\n]+)\n/' => self::class .'::para', // add paragraphs + '/<\/ul>\s?
- /' => '', // fix extra ul
+ '/<\/ol>\s?
- %s \n
- %s \n
- %s \n
- %s \n
- /' => '', // fix extra ol
+ '/<\/blockquote>
/' => "\n", // fix extra blockquote + '//' => self::class .'::fix_link', // fix links + '/self::class .'::fix_img', // fix images + '/
{{{([0-9]+)}}}<\/p>/s' => self::class .'::reinsert_code_blocks' // re-insert code blocks + ); + + private static $code_blocks = []; + + private static function code_parse ($regs) { + $item = $regs[1]; + $item = htmlentities ($item, ENT_COMPAT); + $item = str_replace ("\n\n", '
', $item); + $item = str_replace ("\n", '
', $item); + while (mb_substr ($item, 0, 4) === '
') { + $item = mb_substr ($item, 4); + } + while (mb_substr ($item, -4) === '
') { + $item = mb_substr ($item, 0, -4); + } + // Store code blocks with placeholders to avoid other regexes affecting them + self::$code_blocks[] = sprintf ("", trim ($item)); + return sprintf ("{{{%d}}}", count (self::$code_blocks) - 1); + } + + private static function reinsert_code_blocks ($regs) { + // Reinsert the stored code blocks at the end + $index = $regs[1]; + return self::$code_blocks[$index]; + } + + private static function para ($regs) { + $line = $regs[1]; + $trimmed = trim ($line); + if (preg_match ('/^<\/?(ul|ol|li|h|p|bl|table|tr|th|td|code)/', $trimmed)) { + return "\n" . $line . "\n"; + } + if (! empty ($trimmed)) { + //return sprintf ("\n%s%s
\n", $trimmed); + } + return $trimmed; + } + + private static function ul_list ($regs) { + $item = $regs[1]; + return sprintf ("\n\n\t
", trim ($item)); + } + + private static function ol_list ($regs) { + $item = $regs[1]; + return sprintf ("\n\n\t
", trim ($item)); + } + + private static function blockquote ($regs) { + $item = $regs[2]; + return sprintf ("\n%s", trim ($item)); + } + + private static function header ($regs) { + list ($tmp, $chars, $header) = $regs; + $level = strlen ($chars); + return sprintf ('%s ', $level, trim ($header), $level); + } + + private static function link ($regs) { + list ($tmp, $text, $link) = $regs; + // Substitute _ and * in links so they don't break the URLs + $link = str_replace (['_', '*'], ['{^^^}', '{~~~}'], $link); + return sprintf ('%s', $link, $text); + } + + private static function img ($regs) { + list ($tmp, $text, $link) = $regs; + // Substitute _ and * in links so they don't break the URLs + $link = str_replace (['_', '*'], ['{^^^}', '{~~~}'], $link); + return sprintf ('', $link, $text); + } + + private static function fix_link ($regs) { + // Replace substitutions so links are preserved + $fixed_link = str_replace (['{^^^}', '{~~~}'], ['_', '*'], $regs[1]); + return sprintf ('', $fixed_link); + } + + private static function fix_img ($regs) { + // Replace substitutions so links are preserved + $fixed_link = str_replace (['{^^^}', '{~~~}'], ['_', '*'], $regs[1]); + return sprintf ('
$replacement) { + if (is_callable ( $replacement)) { + $text = preg_replace_callback ($regex, $replacement, $text); + } else { + $text = preg_replace ($regex, $replacement, $text); + } + } + return trim ($text); + } +} diff --git a/libs/_Slimdown.php b/libs/_Slimdown.php new file mode 100644 index 0000000..2284349 --- /dev/null +++ b/libs/_Slimdown.php @@ -0,0 +1,156 @@ + + * Website: https://github.com/jbroadway/slimdown + * License: MIT + */ +class Slimdown { + public static $rules = array ( + '/```(.*?)```/s' => self::class .'::code_parse', // code blocks + //'/\n(#+)(.*)/' => self::class .'::header', // headers + '/\n(#\s+)(.*)/' => self::class .'::header', // headers - only with a space between # and text, to avoid matching with `#hashtags` + //'/\!\[([^\[]+)\]\(([^\)]+)\)/' => self::class .'::img', // images + '/\!\[(.*?)\]\(([^\)]+)\)/' => self::class .'::img', // images 2 + '/\[([^\[]+)\]\(([^\)]+)\)/' => self::class .'::link', // links + '/(\*\*|__)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '\2', // bold + '/(\*|_)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '\2', // emphasis + '/(\~\~)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/' => '
\2', // del + '/\:\"(.*?)\"\:/' => '\1', // quote + '/`(.*?)`/' => '\1', // inline code + '/\n\*(.*)/' => self::class .'::ul_list', // ul lists + '/\n[0-9]+\.(.*)/' => self::class .'::ol_list', // ol lists + '/\n(>|\>)(.*)/' => self::class .'::blockquote', // blockquotes + '/\n-{5,}/' => "\n
", // horizontal rule + '/\n([^\n]+)\n/' => self::class .'::para', // add paragraphs + '/<\/ul>\s?/' => '', // fix extra ul + '/<\/ol>\s?
/' => '', // fix extra ol + '/<\/blockquote>
/' => "\n", // fix extra blockquote + '//' => self::class .'::fix_link', // fix links + '/self::class .'::fix_img', // fix images + '/
{{{([0-9]+)}}}<\/p>/s' => self::class .'::reinsert_code_blocks' // re-insert code blocks + ); + + private static $code_blocks = []; + + private static function code_parse ($regs) { + $item = $regs[1]; + $item = htmlentities ($item, ENT_COMPAT); + $item = str_replace ("\n\n", '
', $item); + $item = str_replace ("\n", '
', $item); + while (mb_substr ($item, 0, 4) === '
') { + $item = mb_substr ($item, 4); + } + while (mb_substr ($item, -4) === '
') { + $item = mb_substr ($item, 0, -4); + } + // Store code blocks with placeholders to avoid other regexes affecting them + self::$code_blocks[] = sprintf ("", trim ($item)); + return sprintf ("{{{%d}}}", count (self::$code_blocks) - 1); + } + + private static function reinsert_code_blocks ($regs) { + // Reinsert the stored code blocks at the end + $index = $regs[1]; + return self::$code_blocks[$index]; + } + + private static function para ($regs) { + $line = $regs[1]; + $trimmed = trim ($line); + if (preg_match ('/^<\/?(ul|ol|li|h|p|bl|table|tr|th|td|code)/', $trimmed)) { + return "\n" . $line . "\n"; + } + if (! empty ($trimmed)) { + //return sprintf ("\n%s%s
\n", $trimmed); + return sprintf ("\n%s\n", $trimmed); // avoind addin-tags and extra vetical margins + } + return $trimmed; + } + + private static function ul_list ($regs) { + $item = $regs[1]; + return sprintf ("\n
\n\t
", trim ($item)); + } + + private static function ol_list ($regs) { + $item = $regs[1]; + return sprintf ("\n\n\t
", trim ($item)); + } + + private static function blockquote ($regs) { + $item = $regs[2]; + return sprintf ("\n%s", trim ($item)); + } + + private static function header ($regs) { + list ($tmp, $chars, $header) = $regs; + $level = strlen ($chars); + return sprintf ('%s ', $level, trim ($header), $level); + } + + private static function link ($regs) { + list ($tmp, $text, $link) = $regs; + // Substitute _ and * in links so they don't break the URLs + $link = str_replace (['_', '*'], ['{^^^}', '{~~~}'], $link); + return sprintf ('%s', $link, $text); + } + + private static function img ($regs) { + list ($tmp, $text, $link) = $regs; + // Substitute _ and * in links so they don't break the URLs + $link = str_replace (['_', '*'], ['{^^^}', '{~~~}'], $link); + return sprintf ('', $link, $text); + } + + private static function fix_link ($regs) { + // Replace substitutions so links are preserved + $fixed_link = str_replace (['{^^^}', '{~~~}'], ['_', '*'], $regs[1]); + return sprintf ('', $fixed_link); + } + + private static function fix_img ($regs) { + // Replace substitutions so links are preserved + $fixed_link = str_replace (['{^^^}', '{~~~}'], ['_', '*'], $regs[1]); + return sprintf ('
$replacement) { + if (is_callable ( $replacement)) { + $text = preg_replace_callback ($regex, $replacement, $text); + } else { + $text = preg_replace ($regex, $replacement, $text); + } + } + return trim ($text); + } +} diff --git a/libs/twtxt.php b/libs/twtxt.php index 214d34a..f9b680e 100644 --- a/libs/twtxt.php +++ b/libs/twtxt.php @@ -123,15 +123,17 @@ function replaceMentionsFromTwt(string $twtString): string { // Example input: 'Hello @
, how are you? @ '; // Example output: Hello @eapl.mx@eapl.mx/twtxt.txt, how are you? @nick@server.com/something/twtxt.txt - #$pattern = '/@<([^ ]+)\s([^>]+)>/'; - #$replacement = '@$1'; + $pattern = '/@<([^ ]+)\s([^>]+)>/'; + $replacement = '@$1'; #$twtString = '@ '; - $pattern = '/@<([^ ]+) ([^>]+)>/'; - $replacement = '@$1'; - + #$pattern = '/@<([^ ]+) ([^>]+)>/'; + #$replacement = '@$1'; $result = preg_replace($pattern, $replacement, $twtString); - return $result; + + // from https://github.com/hxii/picoblog/blob/master/picoblog.php + //$pattern = '/\@<([a-zA-Z0-9\.]+)\W+(https?:\/\/[^>]+)>/'; + //return preg_replace($pattern,'@$1',$twtString); } function replaceLinksFromTwt(string $twtString) { @@ -156,12 +158,23 @@ function replaceMarkdownLinksFromTwt(string $twtString) { function replaceImagesFromTwt(string $twtString) { $pattern = '/!\[(.*?)\]\((.*?)\)/'; - $replacement = ' '; + //$replacement = '
'; + $replacement = '
'; $result = preg_replace($pattern, $replacement, $twtString); return $result; } +function replaceTagsFromTwt(string $twtString) { + $pattern = '/#(\w+)?/'; + $replacement = '#\1'; // Dummy link + //$replacement = '#${1}'; + $result = preg_replace($pattern, $replacement, $twtString); + + return $result; +} + + function getTimeElapsedString($timestamp, $full = false) { $now = new DateTime; $ago = new DateTime; @@ -320,7 +333,7 @@ function getTwtsFromTwtxtString($url) { $twtContent = replaceMentionsFromTwt($twtContent); // Convert HTML problematic characters - $twtContent = htmlentities($twtContent); + //$twtContent = htmlentities($twtContent); // TODO: Messing up rendering of @mentions #BUG // Replace the Line separator character (U+2028) // \u2028 is \xE2 \x80 \xA8 in UTF-8 @@ -331,8 +344,9 @@ function getTwtsFromTwtxtString($url) { // that's why I leave the UTF-8 representation for future reference $twtContent = str_replace("\u{2028}", "\n
\n", $twtContent); $twtContent = replaceLinksFromTwt($twtContent); - $twtContent = replaceImagesFromTwt($twtContent); - $twtContent = replaceMarkdownLinksFromTwt($twtContent); + //$twtContent = replaceMarkdownLinksFromTwt($twtContent); + //$twtContent = replaceImagesFromTwt($twtContent); + $twtContent = Slimdown::render($twtContent); // Get and remote the hash $hash = getReplyHashFromTwt($twtContent); @@ -340,10 +354,12 @@ function getTwtsFromTwtxtString($url) { $twtContent = str_replace("(#$hash)", '', $twtContent); } + // TODO: Make ?tag= filtering feature + $twtContent = replaceTagsFromTwt($twtContent); + // TODO: Get mentions $mentions = getMentionsFromTwt($twtContent); - // Get Lang metadata if (($timestamp = strtotime($dateStr)) === false) { @@ -370,7 +386,7 @@ function getTwtsFromTwtxtString($url) { $twt->mainURL = $twtxtData->mainURL; $twtxtData->twts[$timestamp] = $twt; - // TODO: Interpret the content as markdown + // TODO: Interpret the content as markdown -- @DONE using Slimdown.php above } } } diff --git a/partials/base.php b/partials/base.php index 383f441..340698d 100644 --- a/partials/base.php +++ b/partials/base.php @@ -20,6 +20,7 @@ declare(strict_types=1); require_once("libs/session.php"); // TODO: Move all to base.php require_once('libs/twtxt.php'); require_once('libs/hash.php'); +require_once('libs/Slimdown.php'); const TWTS_PER_PAGE = 50; diff --git a/partials/header.php b/partials/header.php index d55afcb..bab8a96 100644 --- a/partials/header.php +++ b/partials/header.php @@ -1,13 +1,18 @@\ No newline at end of file diff --git a/partials/listSelect.php b/partials/listSelect.php index cdb0c07..068de12 100644 --- a/partials/listSelect.php +++ b/partials/listSelect.php @@ -5,6 +5,8 @@ Selected for both public and private lists + if( isset($_SESSION['password'])) { if($_SESSION['password']=="$password") { // Hacky login @@ -25,11 +27,11 @@ } } - foreach (glob("lists/twtxt-*.txt") as $filename) { + foreach (glob("twtxt-*.txt") as $filename) { if($filename == $_GET['lists']) $attr="selected"; else $attr = ""; $listName = $filename; - $listName = str_replace("lists/twtxt-", "", $listName); + $listName = str_replace("twtxt-", "", $listName); $listName = str_replace("_", " ", $listName); $listName = str_replace(".txt", "", $listName); echo ""; diff --git a/partials/profile.php b/partials/profile.php index 7134f40..25cce40 100644 --- a/partials/profile.php +++ b/partials/profile.php @@ -21,15 +21,6 @@ $profile = getTwtsFromTwtxtString($url); = $profile->description ?>- - + + \ No newline at end of file diff --git a/partials/timeline.php b/partials/timeline.php index af4116e..cb4ed83 100644 --- a/partials/timeline.php +++ b/partials/timeline.php @@ -7,28 +7,32 @@ --> --
+= $twt->nick ?>@= parse_url($twt->mainURL, PHP_URL_HOST); ?>diff --git a/private/config.ini b/private/config.ini index f5342bd..4fa3b2c 100644 --- a/private/config.ini +++ b/private/config.ini @@ -40,4 +40,4 @@ totp_secret = "LZM25BDJPRVTNFZQBDOPQKFSKUAAS6BI" secure_cookies = true ; Simple password for unnamed user -password = "" \ No newline at end of file +password = "brandbil" \ No newline at end of file diff --git a/style.css b/style.css index a2f38e7..8afa582 100644 --- a/style.css +++ b/style.css @@ -25,13 +25,23 @@ body { background-color: var(--code-bg); font-family: sans-serif; +/* max-width: 700px; + margin: 1rem auto; */ + line-height: 1.25; +} + +main { max-width: 700px; margin: 1rem auto; - line-height: 1.25; } a, button, body, h1, h2, h3, h4, h5, h6 { color: var(--primary); + text-decoration: none; +} + +a:hover { + text-decoration: underline; } /* == Forms (from https://adi.tilde.institute/default.css/) ==================== */ @@ -50,7 +60,7 @@ input, textarea, select { box-sizing: border-box; display: inline-block; /* margin: .5ex 0 1ex 0;*/ -margin-top: -0.5rem; + margin-top: -0.5rem; padding: 1ex .5rem; border: thin solid var(--border); border-radius: var(--radius); @@ -111,14 +121,19 @@ fieldset { hr { - background: var(--border-color); + background: var(--border); border: 0; height: 1px; width: 100%; } +header { + margin-bottom: 2rem; + border-bottom: thin solid var(--tertiary); +} nav { + display: flex; flex-wrap: wrap; justify-content: space-between; @@ -132,8 +147,22 @@ nav ul { display: flex; justify-content: space-between; padding-left: 0.5rem; + margin: 0.25rem 0rem; } + +nav ul.secondary { + padding: 0 0.75rem; + font-size: small; +} + +/* +nav ul.secondary a { + text-decoration: underline; + font-weight: normal; +} +*/ + nav ul li { display: inline-block; list-style: none; @@ -155,9 +184,11 @@ nav .link-btn { cursor: pointer; margin: 0; padding: 0; + width: 100%; color: var(--primary); font-weight: bold; -/* text-decoration: underline;*/ + /*text-decoration: underline;*/ + border-radius: 0; } img { @@ -203,11 +234,29 @@ a.author { color: var(--secondary); } -.profile nav a { +/*.profile nav a { font-size: small; text-decoration: underline; +}*/ + +nav.profile { + padding: 0; +/* display: flex;*/ +/* justify-content: space-around;*/ } + +nav.profile a { +/* font-size: small;*/ +/* text-decoration: underline;*/ +/* border: thin solid var(--border); + border-radius: var(--radius); + background-color: var(--entry); + padding: 0.5rem 1rem;*/ + margin: 0 0.5rem; +} + + article { background-color: var(--entry); color: var(--secondary); @@ -227,6 +276,18 @@ article .twt-msg { padding: 0.5rem 0; } +article .twt-msg a{ + text-decoration: underline; +} + +article .twt-msg > blockquote { + margin: 0; + border-left: thick solid var(--border); + padding: 0.25rem 0.5rem; + display: inline-block; + font-style: italic; +} + article .twt-msg img { margin: 0.25rem -0.25rem; border: thin solid var(--border); @@ -241,11 +302,19 @@ article .twt-msg > img:last-child { } article small { - padding-left: 0.15rem ; +/* padding-left: 0.15rem;*/ } -article small .right{ +/* +article small a { + text-decoration: none; + font-weight: bold; +} +*/ + +article small a.right { padding-right: 0.25rem; + font-weight: normal; } nav.pagnation {= $twt->content ?> + +replyToHash) { ?> Conversation | + Reply + (via email) = $twt->displayDate ?>