diff --git a/engine/sphinx.php b/engine/sphinx.php index da07e5d..34bed9b 100644 --- a/engine/sphinx.php +++ b/engine/sphinx.php @@ -71,10 +71,10 @@ function sphinx_client(): Sphinx\SphinxClient { function _sphinx_normalize(string $origstr): string { $buf = preg_replace('/[Ёё]/iu', 'е', $origstr); - if (!pcre_check_error($buf, no_error: true)) { + if (!pcre_no_error($buf, no_error: true)) { $origstr = mb_convert_encoding($origstr, 'UTF-8', 'UTF-8'); $buf = preg_replace('/[Ёё]/iu', 'е', $origstr); - pcre_check_error($buf); + pcre_no_error($buf); } if ($buf === null) { logError(__METHOD__.': preg_replace() failed with error: '.preg_last_error().': '.preg_last_error_msg()); diff --git a/functions.php b/functions.php index fd2270c..0b0161c 100644 --- a/functions.php +++ b/functions.php @@ -297,7 +297,7 @@ function is_retina(): bool { return isset($_COOKIE['is_retina']) && $_COOKIE['is function jsonEncode($obj): ?string { return json_encode($obj, JSON_UNESCAPED_UNICODE) ?: null; } function jsonDecode($json) { return json_decode($json, true); } -function pcre_check_error(mixed &$result, bool $no_error = false): bool { +function pcre_no_error(mixed &$result, bool $no_error = false): bool { if ($result === null) { if (preg_last_error() !== PREG_NO_ERROR) { if (!$no_error) diff --git a/htdocs/scss/app/blog.scss b/htdocs/scss/app/blog.scss index 1abb189..c065e9d 100644 --- a/htdocs/scss/app/blog.scss +++ b/htdocs/scss/app/blog.scss @@ -505,4 +505,33 @@ body.wide .blog-post { margin-bottom: 0; } } +} + +span.blog-footnote-ref, a.blog-ref { + display: inline-block; + color: $blog-ref-color; + font-family: "Liberation Mono", monospace; + font-size: $fs - 2px; + letter-spacing: -0.5px; +} + +a.blog-ref { + @include no-underline(); + padding: 0 1px; + margin-left: -2px; +} +@media (hover: hover) { + a.blog-ref:hover { + color: $blog-ref-color-hover; + background-color: $blog-ref-bg-hover; + border-radius: 3px; + } +} + +p.blog-footnote-line:target { + background-color: $blog-ref-bg-hover; + border-radius: 3px; + a { + @include no-underline(true); + } } \ No newline at end of file diff --git a/htdocs/scss/app/common.scss b/htdocs/scss/app/common.scss index 42f53c3..fe7da04 100644 --- a/htdocs/scss/app/common.scss +++ b/htdocs/scss/app/common.scss @@ -9,6 +9,9 @@ height: 0; } +html { + scroll-behavior: smooth; +} html, body { padding: 0; margin: 0; diff --git a/htdocs/scss/colors/dark.scss b/htdocs/scss/colors/dark.scss index 76ff973..3ecbf18 100644 --- a/htdocs/scss/colors/dark.scss +++ b/htdocs/scss/colors/dark.scss @@ -50,6 +50,10 @@ $pn-button-current-text-color: $bg; $head-items-separator: #5e6264; +$blog-ref-color: $dark-grey; +$blog-ref-color-hover: #8c9aab; +$blog-ref-bg-hover: $hover-hl; + // colors from https://github.com/Kelbster/highlightjs-material-dark-theme/blob/master/css/materialdark.css $hljs_fg: #CDD3D8; $hljs_bg: #2B2B2D; diff --git a/htdocs/scss/colors/light.scss b/htdocs/scss/colors/light.scss index 33211bd..6162e8f 100644 --- a/htdocs/scss/colors/light.scss +++ b/htdocs/scss/colors/light.scss @@ -52,6 +52,10 @@ $pn-button-current-text-color: $fg; $head-items-separator: #d0d0d0; +$blog-ref-color: #888; +$blog-ref-color-hover: #6a88ab; +$blog-ref-bg-hover: #f0f5fa; + // github.com style (c) Vasily Polovnyov $hljs_fg: #333; $hljs_bg: #f8f8f8; diff --git a/lib/markup.php b/lib/markup.php index d7f1f9a..067a523 100644 --- a/lib/markup.php +++ b/lib/markup.php @@ -11,9 +11,35 @@ class markup { bool $no_paragraph = false): string { $pd = new MyParsedown(useImagePreviews: $use_image_previews, lang: $lang); $html = $pd->text($md); - if ($no_paragraph) { + + if ($no_paragraph) $html = preg_replace('/

(.*?)<\/p>/', '$1', $html); + + else { + // collect references + $re = '/^

(\[([io]?\d{1,2})]) (.*?)<\/p>/m'; + $result = preg_match_all($re, $html, $matches); + if (pcre_no_error($result)) { + $reftitles_map = []; + foreach ($matches[2] as $i => $refname) { + $reftitles_map[$refname] = trim(strip_tags($matches[3][$i])); + } + $span_opening_tag = ''; + $html = preg_replace($re, '

'.$span_opening_tag.'$1 $3

', $html); + $re = '/'.implode('|', array_map(fn($m) => '(?:'.$span_opening_tag.')?'.preg_quote($m, '/'), $matches[1])).'/'; + $html = preg_replace_callback($re, + function($match) use ($span_opening_tag, $reftitles_map) { + if (str_starts_with($match[0], $span_opening_tag)) + return $match[0]; + if (!preg_match('/\[([io]?\d{1,2})]/', $match[0], $refmatch)) + return $match[0]; + $refname = $refmatch[1]; + $reftitle = $reftitles_map[$refname]; + return ''.$match[0].''; + }, $html); + } } + return $html; }