diff options
Diffstat (limited to 'src')
38 files changed, 1402 insertions, 1618 deletions
diff --git a/src/themes/serene/.gitignore b/src/themes/serene/.gitignore index 87760eb..b3fa3a8 100644 --- a/src/themes/serene/.gitignore +++ b/src/themes/serene/.gitignore @@ -1,8 +1,9 @@ +.zed/ +.claude/ + public/ content/ static/img/ -static/hl-light.css -static/hl-dark.css -config.toml +static/assets/ .DS_Store diff --git a/src/themes/serene/CHANGELOG.md b/src/themes/serene/CHANGELOG.md index 872470b..77a1b42 100644 --- a/src/themes/serene/CHANGELOG.md +++ b/src/themes/serene/CHANGELOG.md @@ -2,6 +2,158 @@ All notable changes to this project will be documented in this file. +## [6.0.0] - 2026-09-06 + +- feat: migrate to zola v0.23 / Tera v2, the minimum zola version required is now `v0.23.4`, shortcodes are rewritten as Tera components +- feat: tag links are now generated via zola's taxonomy API instead of hardcoded `/tags/` paths, so the `taxonomy_root` config option (e.g. `taxonomy_root = "blog"` for `/blog/tags/xxx` URLs) is respected +- feat: support multiple blog-like list sections — post pages now read config from their own parent section instead of the `blog_section_path` one, and each list section can have its own feed (`generate_feeds = true` in its `_index.md`); `blog_section_path` now only designates the main section used by the home page's recent posts and the tags pages +- refactor: `blog.html` template is renamed to `posts.html`, change `template = "blog.html"` to `template = "posts.html"` in your blog section's `_index.md` +- feat: display options (`toc` / `code_copy` / `comment` / `math` / `mermaid` / `reaction` / `outdated_alert` / `date_format`, etc.) now follow a unified fallback chain: post front-matter → section `_index.md` → `[extra]` of `zola.toml`, the closest one wins — notably `math` / `mermaid` can now be enabled for a whole section, and site-wide defaults can be set in `zola.toml` +- refactor: several options are renamed for clarity: `force_theme` (`false | "light" | "dark"`) → `color_scheme` (`"auto" | "light" | "dark"`), `copy` → `code_copy`, `outdate_alert*` → `outdated_alert*`, and `id` of the home section → `handle` +- refactor: the `sections` config option is renamed to `nav`, and its `is_external` field is removed — a `path` starting with `/` is an internal link, anything else (e.g. an `https://` URL) is treated as an external link automatically; external nav links now show a `ne-resize` cursor +- refactor: `name` / `handle` / `bio` / `avatar` / `links` of the home page are moved from the home section's `_index.md` to `[extra]` of `zola.toml` +- refactor: the example config file is renamed from `config.example.toml` to `zola.toml.example`, following zola v0.23's new default config file name `zola.toml` (the old `config.toml` name still works) +- refactor: the callout components (`note` / `tip` / `important` / `warning` / `caution`) are removed in favor of zola's native [GitHub alert syntax](https://github.com/orgs/community/discussions/16925) (`> [!NOTE]`), styled with icon and title by the theme; the title texts can be customized via css variables (`--callout-note-title`, etc.) +- feat: collections are redesigned — item appearance (`layout = "card" | "row" | "tile" | "gallery"`) and arrangement (`flow = "stack" | "inline" | "grid"`, the grid adapts its column count to the available width) are now declared at the collection level, and all layouts share one unified set of item fields (`title` / `subtitle` / `content` / `icon` / `image` / `link` / `badge` / `tags` / `featured`) with graceful degradation; item `link`s are auto-detected as internal or external, and `image` supports section-colocated files; `card` items can additionally show a picture on the left via `image` (the small `icon` before the title is a separate field) +- chore: upgrade Mermaid to 11.17.2 and KaTeX to 0.18.6, refreshing KaTeX resource integrity hashes + +### Migrate from zola v0.22 + +Zola v0.23 removed shortcodes and Tera macros in favor of [Tera components](https://www.getzola.org/documentation/content/overview/#templating-your-content), and your markdown content is now itself a Tera template. Once you update zola and this theme, you need to update your content accordingly: + +- Shortcode calls in your markdown must be rewritten in component syntax. Arguments other than strings are wrapped in `{...}`: + - `{{ figure(src="...", width="600") }}` → `{{ <figure src="..." width="600" /> }}` + - `{% quote(cite="...") %} ... {% end %}` → `{% <quote cite="..."> %} ... {% </quote> %}` + - `{{ youtube(id="...", autoplay=true) }}` → `{{ <youtube id="..." autoplay={true} /> }}` + - `{{ collection(file="projects.toml") }}` → `{{ <collection file="projects.toml" section /> }}` (note the extra `section`) +- The callout shortcodes are removed, rewrite them with the GitHub alert syntax (custom titles are not supported, the title is always the type name): + + ```md + {% note(title="Note") %} + note text + {% end %} + ``` + + becomes: + + ```md + > [!NOTE] + > note text + ``` + +- For `figure` with a colocated image, pass the page context: `{{ <figure src="img.png" page /> }}` (or `section` instead of `page` when used in a section's `_index.md`) +- `width` / `height` of `figure` must be strings: `width="600"`, not `width=600` +- Literal `{{` or `{%` in your content (e.g. code blocks showing template syntax, GitHub Actions `${{ ... }}`) must be wrapped with `{% raw %}` ... `{% endraw %}`, otherwise zola will try to interpret them as Tera syntax. For files full of such content, you can instead list them in the `skip_content_templating` config option (glob patterns) to opt them out of templating entirely (components won't work in those files) +- Component calls must be at the top level of your markdown content — nesting them inside a list item is no longer supported (the component's HTML output breaks the list's indentation rules and produces broken HTML). If you had e.g. a callout inside a list item, move it out of the list +- The `date` filter is now backed by [jiff](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) instead of chrono. Common `date_format` values like `"%b %-d, %Y"` still work, but chrono-specific specifiers (e.g. `%+`) are no longer supported and will fail the build — check your `date_format` options against the jiff docs +- Syntax highlighting CSS files (`giallo-light.css` / `giallo-dark.css`) are now generated directly into the output directory instead of `static/`, so you can remove them from your `static/` folder and `.gitignore` +- If you have custom templates that override serene's, they must be migrated to Tera v2 as well, see the [Tera migration guide](https://github.com/Keats/tera/blob/master/MIGRATION.md) +- The `blog.html` template is renamed to `posts.html`: change `template = "blog.html"` to `template = "posts.html"` in your blog section's `_index.md` +- Zola's default config file name is now `zola.toml` — you can rename your `config.toml` to `zola.toml` (optional, the old name still works) +- Several options are renamed, update them in your `zola.toml` / section `_index.md` / post front-matter: + - `force_theme = false | "light" | "dark"` → `color_scheme = "auto" | "light" | "dark"` (`false` becomes `"auto"`) + - `copy` → `code_copy` + - `outdate_alert` / `outdate_alert_days` / `outdate_alert_text_before` / `outdate_alert_text_after` → `outdated_alert` / `outdated_alert_days` / `outdated_alert_text_before` / `outdated_alert_text_after` + - `id` in the home section's `_index.md` → `handle` + - `sections` in `zola.toml` → `nav`, and remove the `is_external` field from its entries (external links are now detected automatically from the `path`) + - `name` / `handle` / `bio` / `avatar` / `links` move from the home section's `_index.md` to `[extra]` of `zola.toml` +- Collection toml files use a new schema, update them as follows: + - each `[[collection]]` becomes `[[item]]`, and the per-item `type` moves to a single collection-level `layout` at the top of the file: `card` → `layout = "card"`, `card_simple` → `layout = "row"` plus `flow = "stack"` (rename its `content` to `subtitle`), `entry` → `layout = "row"`, `box` → `layout = "tile"`, `art` → `layout = "gallery"`, `art_simple` → `layout = "gallery"` plus `flow = "grid"` + - rename item fields: `img` → `image` (`icon` keeps its name), and both `date` and `footer` → `badge` (a short mark rendered as-is: a year, a date range, a rating, a status...) + - remove `type = "br"` items — arrangement is now controlled by the collection-level `flow` (`"stack"` / `"inline"` / `"grid"`) option + +## [5.7.0] - 2026-08-09 + +- feat: add open graph & twitter card meta tags and canonical link +- fix: resolve colocated figure image URLs [@uchouT](https://github.com/uchouT) ([#109](https://github.com/isunjn/serene/pull/109)) +- fix: art collection content margin + +## [5.6.3] - 2026-02-14 + +- fix: back link behavior on new tab + +## [5.6.2] - 2026-02-13 + +- feat: add markdown support for collection's content and subtitle [@paulhdk](https://github.com/paulhdk) ([#104](https://github.com/isunjn/serene/pull/104)) + +## [5.6.1] - 2026-02-01 + +- fix: codeblock with filename + +## [5.6.0] - 2026-02-01 + +- feat: adapt zola v0.22 new code highlighting, custom highlight themes (`serene-light`/`serene-dark`) are removed in favor of built-in themes +- feat: add `github_alerts` color styling (zola v0.21+ feature) +- feat: add `rel_me` option for links (useful for Mastodon verification) +- feat: add `youtube` shortcode [@gloomydumber](https://github.com/gloomydumber) ([#92](https://github.com/isunjn/serene/pull/92)) +- fix: input element color scheme should be changed along with light/dark mode change + +### Migrate from zola v0.21 + +Since this version, the minimum zola version required has changed to `v0.22.1`. + +Replace the old `[markdown]` section in your `config.toml`: + +```toml +# Old (remove these) +[markdown] +highlight_code = true +highlight_theme = "css" +extra_syntaxes_and_themes = ["themes/serene/highlight_themes"] +highlight_themes_css = [ + { theme = "serene-light", filename = "hl-light.css" }, + { theme = "serene-dark", filename = "hl-dark.css" }, +] +``` + +With the new format: + +```toml +# New +[markdown] +github_alerts = true # optional, enables GitHub-style callout syntax + +[markdown.highlighting] +style = "class" +light_theme = "github-light" # or any zola built-in theme +dark_theme = "github-dark" # or any zola built-in theme +``` + +You should also delete the generated `hl-light.css` and `hl-dark.css` files from your `static/` directory if you have them. +The new generated css files for code highlighting will be `giallo-light.css` and `giallo-dark.css`. (giallo is zola's new syntax highlighter) + +## [5.5.0] - 2025-10-18 + +- feat: shortcode `figure`'s `caption` can use markdown now, the `via` prop is removed, use `caption="[via](https://example.com)"` instead +- fix: shortcode `figure` now will keep img's ratio on small screens when `width` and `height` is set +- ui: a few tweaks + +## [5.4.3] - 2025-09-29 + +- fix: correct back link button behavior when there is a hash in url + +## [5.4.2] - 2025-09-27 + +- fix: feed.xml template build error when there is no post + +## [5.4.1] - 2025-09-21 + +- feat: add overflow scroll indicator for table-of-contents + +## [5.4.0] - 2025-09-06 + +- ui: a few tweaks +- feat: back link button now do `history.back()` if appropriate +- feat: add custom height and width options for `figure` shortcode [@znxftw](https://github.com/znxftw) ([#83](https://github.com/isunjn/serene/pull/83)) + +## [5.3.1] - 2025-05-22 + +- fix: `extra_syntaxes_and_themes` defaults to theme's, so you don't need to copy that path [@makai410](https://github.com/makai410) ([#80](https://github.com/isunjn/serene/pull/80)) + +## [5.3.0] - 2025-05-07 + +- ui: a few tweaks + ## [5.2.1] - 2025-04-19 - fix: homepage avatar style @@ -315,6 +467,19 @@ All notable changes to this project will be documented in this file. First release 🎉 +[6.0.0]: https://github.com/isunjn/serene/compare/v5.7.0...v6.0.0 +[5.7.0]: https://github.com/isunjn/serene/compare/v5.6.3...v5.7.0 +[5.6.3]: https://github.com/isunjn/serene/compare/v5.6.2...v5.6.3 +[5.6.2]: https://github.com/isunjn/serene/compare/v5.6.1...v5.6.2 +[5.6.1]: https://github.com/isunjn/serene/compare/v5.6.0...v5.6.1 +[5.6.0]: https://github.com/isunjn/serene/compare/v5.5.0...v5.6.0 +[5.5.0]: https://github.com/isunjn/serene/compare/v5.4.3...v5.5.0 +[5.4.3]: https://github.com/isunjn/serene/compare/v5.4.2...v5.4.3 +[5.4.2]: https://github.com/isunjn/serene/compare/v5.4.1...v5.4.2 +[5.4.1]: https://github.com/isunjn/serene/compare/v5.4.0...v5.4.1 +[5.4.0]: https://github.com/isunjn/serene/compare/v5.3.1...v5.4.0 +[5.3.1]: https://github.com/isunjn/serene/compare/v5.3.0...v5.3.1 +[5.3.0]: https://github.com/isunjn/serene/compare/v5.2.1...v5.3.0 [5.2.1]: https://github.com/isunjn/serene/compare/v5.2.0...v5.2.1 [5.2.0]: https://github.com/isunjn/serene/compare/v5.1.0...v5.2.0 [5.1.0]: https://github.com/isunjn/serene/compare/v5.0.1...v5.1.0 diff --git a/src/themes/serene/README.md b/src/themes/serene/README.md index 4fad7a7..7620101 100644 --- a/src/themes/serene/README.md +++ b/src/themes/serene/README.md @@ -1,10 +1,5 @@ -<p align="center"> - A minimal blog theme for <a href="https://www.getzola.org">zola</a>, well crafted -</p> +A minimal blog theme for [Zola](https://www.getzola.org), well crafted. -<p align="center"> - <a href="https://serene-demo.pages.dev">Demo</a> · - <a href="https://github.com/isunjn/serene/blob/latest/USAGE.md">Usage</a> -</p> +[Demo](https://serene-demo.pages.dev) | [Usage](https://github.com/isunjn/serene/blob/latest/USAGE.md) -<img alt="Screenshot" src="https://github.com/user-attachments/assets/fa0c9529-f83b-4e4d-aed9-5327a44bc828" /> + diff --git a/src/themes/serene/USAGE.md b/src/themes/serene/USAGE.md index 1b91d88..88e73a1 100644 --- a/src/themes/serene/USAGE.md +++ b/src/themes/serene/USAGE.md @@ -1,5 +1,7 @@ This is the detailed guide on how to use zola-theme-serene. You should also check zola's [documentation](https://www.getzola.org/documentation/getting-started/overview/). +Serene requires zola `0.23.4` or later. + ## Installation Create a zola site and add serene theme (assuming your site is called `myblog`): @@ -11,13 +13,13 @@ git init git submodule add -b latest https://github.com/isunjn/serene.git themes/serene ``` -Copy the content of `myblog/themes/serene/config.example.toml` to `myblog/config.toml`. +Copy the content of `myblog/themes/serene/zola.toml.example` to `myblog/zola.toml`. ## Sections and Pages -There is a `sections` config option in your `config.toml`, which enumerates the sections your site has. You should have at least one `blog` section. +There is a `nav` config option in your `zola.toml`, which enumerates the navigation entries of the home page. An entry whose `path` starts with `/` links to a section of your site, any other path (e.g. an `https://` URL) is treated as an external link and opens in a new tab. You should have at least one `blog` section. -The name and path can be changed, be noticed that if you changed the blog section path (e.g. from `/posts` to `/blog`), then you should also change `blog_section_path` option. +The name and path can be changed, note that if you changed the blog section path (e.g. from `/posts` to `/blog`), then you should also change `blog_section_path` option. For home page, create `myblog/content/_index.md`: @@ -31,22 +33,10 @@ lang = 'en' # Show footer in home page footer = false -# If you don't want to display id/bio/avatar, simply comment out that line -name = "Serene Demo" -id = "serene" -bio = "programmer, he/him, we are young and life is fun" -avatar = "img/avatar.webp" -links = [ - { name = "GitHub", icon = "github", url = "https://github.com/<your-username>" }, - { name = "Twitter", icon = "twitter", url = "https://twitter.com/<your-username>" }, - { name = "Email", icon = "email", url = "mailto:<your-email-address>" }, -] - # Show a few recent posts in home page recent = false recent_max = 15 recent_more_text = "more »" -date_format = "%b %-d, %Y" +++ Hi, I'm ... @@ -59,7 +49,7 @@ For blog section, create `myblog/content/posts/_index.md`: title = "My Blog" description = "My blog site." sort_by = "date" -template = "blog.html" +template = "posts.html" page_template = "post.html" insert_anchor_links = "right" generate_feeds = true @@ -70,22 +60,14 @@ lang = "en" title = "Posts" subtitle = "I write about ...." -date_format = "%b %-d, %Y" - categorized = false # posts can be categorized back_to_top = true # show back-to-top button -toc = true # show table-of-contents -comment = false # enable comment -copy = true # show copy button in code block - -outdate_alert = false -outdate_alert_days = 12 -outdate_alert_text_before = "This article was last updated " -outdate_alert_text_after = " days ago and may be out of date." +++ ``` -Blog section is defined by `blog.html` and `post.html`. Serene also has a special template called `prose.html`, it applies the same styles of blog post page. You can use it as a section template for a custom section page, for example if you want a separate `about` page, you can add a `{ name = "about", path = "/about", is_external = false }` to the `sections` and create a `myblog/content/about/_index.md`: +Display options like `toc` / `code_copy` / `comment` / `date_format` have site-wide defaults in `zola.toml`, you can override them here for this section (e.g. `toc = false`), see [Front Matter](#front-matter) for details. + +Blog section is defined by `posts.html` and `post.html`. Serene also has a special template called `prose.html`, it applies the same styles of blog post page. You can use it as a section template for a custom section page, for example if you want a separate `about` page, you can add a `{ name = "about", path = "/about" }` to the `nav` and create a `myblog/content/about/_index.md`: ``` +++ @@ -97,25 +79,25 @@ insert_anchor_links = "none" [extra] lang = 'en' -title = "Posts" -subtitle = "I write about ...." - -math = false -mermaid = false -copy = false -comment = false -reaction = false +title = "About" +subtitle = "About this site" +++ Hi, My name is .... ``` -The default date format is "%b %-d, %Y", e.g. "Dec 13, 2025", check [this page](https://docs.rs/chrono/0.4.40/chrono/format/strftime/index.html) if you want to customize it, for example change to "%Y-%-m-%-d", e.g. "2025-2-13". +The default date format is "%b %-d, %Y", e.g. "Dec 13, 2025", check [this page](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) if you want to customize it, for example change to "%Y-%-m-%-d", e.g. "2025-2-13". + +### Multiple list sections + +You can have more than one blog-like list section, e.g. a `series` section alongside `posts`. Just create `myblog/content/series/_index.md` the same way as the blog section (with `template = "posts.html"` and `page_template = "post.html"`), and add it to `nav` in `zola.toml`. Each list section has its own options in `[extra]` (`date_format`, `toc`, `categorized`, etc.), and can have its own feed by setting `generate_feeds = true` in its `_index.md` (available at e.g. `/series/feed.xml`). + +The `blog_section_path` option in `zola.toml` points to your *main* blog section, it is used by the recent posts list of the home page and by the tags pages (tags are site-wide: posts from all list sections that share a tag will be listed together). -Now the myblog directory may looks like this: +Now the myblog directory may look like this: ``` -├── config.toml +├── zola.toml ├── content/ │ ├── posts/ │ │ └── _index.md @@ -146,9 +128,9 @@ Create a new directory `img` under `myblog/static`, put favicon related files he ## Icon -The default icons are placed in `myblog/themes/serene/static/icon`, the `icon` value in `links` of home section is the file name of the svg file. +The default icons are placed in `myblog/themes/serene/static/icon`, the `icon` value in `links` of `zola.toml` is the file name of the svg file. -To customize, find the svg file you want, modify (in case you don't kown, a svg file is just a plian text file) its width and height to `18`, and the color to `currentColor`: +To customize, find the svg file you want, modify (in case you don't know, a svg file is just a plain text file) its width and height to `18`, and the color to `currentColor`: `... width="18" height="18" ... fill="currentColor" ...` @@ -158,25 +140,28 @@ The default icons mostly came from [Remix Icon](https://remixicon.com/). ## Theme -By default there is theme toggle button to switch between light and dark mode, you can set `force_theme` in `config.toml` to force a specific mode only. +The `color_scheme` option in `zola.toml` controls the light/dark mode behavior: `"auto"` (default) follows the visitor's system preference and shows a theme toggle button, while `"light"` / `"dark"` locks the site to a single mode and hides the button. -## Code Highlighting +## RSS -Copy `myblog/themes/serene/highlight_themes` directory to `myblog/highlight_themes`. +There are two ways to provide feeds: -By default serene use different highlight themes for light/dark mode, configured by `highlight_theme`, `extra_syntaxes_and_themes` and `highlight_themes_css`. The default highlight theme `serene-light` `serene-dark` is a modified version of [Tomorrow](https://github.com/ChrisKempson/Tomorrow-Theme) theme. +- **Per-section feeds** (recommended): set `generate_feeds = false` in `zola.toml`, and `generate_feeds = true` in the `_index.md` of your list sections. Each of these sections gets its own feed (e.g. `/posts/feed.xml`, `/series/feed.xml`), using the `title` and `description` of that section. The RSS button in the footer links to the feed of the section the current page belongs to. +- **A single site-wide feed**: set `generate_feeds = true` in `zola.toml`, and `generate_feeds = false` in section `_index.md` files. The feed is located in the root directory (e.g. `/feed.xml`), contains posts from all sections, and uses the `title` and `description` of `zola.toml`. The RSS button in the footer links to it on all pages. -If you set `highlight_theme` in `config.toml` to one of zola's [built-in highlight themes](https://www.getzola.org/documentation/getting-started/configuration/#syntax-highlighting), you will get that theme used in both light and dark mode. +`feed_filenames` can be set to `["feed.xml"]` (serene's own atom template), or `["atom.xml"]` / `["rss.xml"]` (zola's built-in templates), corresponding to different feed standards. -If you want a different theme, find the `.tmTheme` TextMate file of your theme, put it in `myblog/static/highlight_themes`, and then modify the `theme` value of `highlight_themes_css` to that file's name. This will generate a `hl-light.css` and a `hl-dark.css` file in `myblog/static/`, you may have to delete them first before you change the `theme` value, so zola can re-generate. You can find some TextMate themes on [this website](https://tmtheme-editor.glitch.me/). +## Open Graph -## RSS +Each page has [Open Graph](https://ogp.me/) and Twitter Card meta tags (plus a canonical link), so links shared to social media and chat apps can show a rich preview card with title, description and image. -Zola's default feed file is located in the root directory of the site, set `generate_feeds = true` in `config.toml`, `feed_filenames` can be set to `["atom.xml"]` or `["rss.xml"] ` , corresponding to two different rss file standards, you should also set `generate_feeds = false` in `myblog/content/posts/_index.md` +Title and description come from the same sources as the page's `<title>` and meta description. The preview image is resolved as follows: -The serene theme looks more like a personal website, the posts are in the `/posts` directory, you may want the feed file to be in the `/posts` directory instead of the root directory, this requires you to set `generate_feeds = false ` `feed_filenames = ["feed.xml"]` in `config.toml`, and set `generate_feeds = true` in `myblog/content/posts/_index.md`. +- Post pages use `og_image` in the `[extra]` section of front matter, if set. It can be a full URL, a path in the `static` folder (starting with `/`), or the filename of a [colocated asset](https://www.getzola.org/documentation/content/overview/#asset-colocation) of the post. +- Otherwise, the site-wide default `og_image` in the `[extra]` section of `zola.toml` is used, if set. It can be a full URL or a path in the `static` folder, e.g. `og_image = "img/og.png"`. +- If neither is set, image related tags are omitted. -`feed.xml` uses `title` and `description` from `myblog/content/posts/_index.md`, the other two use `config.toml`'s. +An image around 1200x630 is recommended for the best display on most platforms. ## Analytics @@ -186,9 +171,9 @@ To add scripts for analytics tools (such as Google Analytics, Umami, etc.), you Copy `myblog/themes/serene/templates/_custom_css.html` to `myblog/templates/_custom_css.html`, variables in this file are used to control styles, such as the theme color `--primary-color`, modify them as you want. -If you want to customize more, you need to copy that file under the `templates`, `static`, `sass` directory in the corresponding `themes/serene` to the same name directory of `myblog`, and modify it. Be careful not to directly modify the files under the serene directory, because these modifications may cause conflicts if the theme is updated. +If you want to customize more, copy the file you want to change from `themes/serene`'s `templates` / `static` / `sass` directory to the same-named directory of `myblog`, and modify it there. Be careful not to directly modify the files under the serene directory, because these modifications may cause conflicts if the theme is updated. -If you want to use a custom font, create a new `myblog/templates/_custom_font.html` and put the font link tags (for eample, from [google fonts](https://fonts.google.com/)) into it, and then modify `--main-font` or `--code-font` in `myblog/sass/templates/_custom_css.html`. For performance reason, you may want to self-host font files, but it's optional: +If you want to use a custom font, create a new `myblog/templates/_custom_font.html` and put the font link tags (for example, from [google fonts](https://fonts.google.com/)) into it, and then modify `--main-font` or `--code-font` in `myblog/templates/_custom_css.html`. For performance reasons, you may want to self-host font files, but it's optional: 1. Open [google-webfonts-helper](https://gwfh.mranftl.com) and choose your font. 2. Modify `Customize folder prefix` of step 3 to `/font/` and then copy the css. @@ -215,25 +200,26 @@ tags = ["one", "two", "three"] lang = "en" toc = true comment = false -copy = true -outdate_alert = true -outdate_alert_days = 120 +code_copy = true +outdated_alert = true +outdated_alert_days = 120 math = false mermaid = false featured = false reaction = false +og_image = "cover.png" +++ new post about something... ``` -Some of these options can also be configured in `myblog/content/posts/_index.md`, as the default value for all posts. +Display options follow a unified fallback chain: **post front-matter → the post's section `_index.md` → `[extra]` of `zola.toml`**, the closest one wins. This applies to `toc`, `code_copy`, `comment`, `math`, `mermaid`, `reaction`, `outdated_alert` and `outdated_alert_days` (`date_format` and `outdated_alert_text_before/after` follow a section → config chain). So you can set site-wide defaults in `zola.toml`, override them per section, and override again per post. -If you set `blog_categorized = true`, posts will be sorted alphabetically by default, you can manually set the order by adding a prefix `__[0-9]{2}__` in front of the category name, for example, `categories = ["__01__CatXXX"]` +If you set `categorized = true`, posts are grouped by category, and categories are sorted alphabetically by default, you can manually set the order by adding a prefix `__[0-9]{2}__` in front of the category name, for example, `categories = ["__01__CatXXX"]` ## Table of Contents -Set `toc = true` to display of table-of-contents. +Set `toc = true` to display the table-of-contents. ## Math & Chart @@ -245,19 +231,19 @@ Set `mermaid = true` to enable chart rendering with Mermaid. Set `featured = true` to display an asterisk(*) mark in front of the title. -## Outdate Alert +## Outdated Alert -If one of your posts has strong timeliness, you can display an outdate alert after certain days. +If one of your posts has strong timeliness, you can display an outdated alert after certain days. -Set `outdate_alert` and `outdate_alert_days` to enable the alert. +Set `outdated_alert` and `outdated_alert_days` to enable the alert. -In `myblog/content/posts/_index.md`, options `outdate_alert_text_before` and `outdate_alert_text_after` are the text content of the alert. +Options `outdated_alert_text_before` and `outdated_alert_text_after` are the text content of the alert, they can be set in `[extra]` of `zola.toml`, or per section in its `_index.md`. ## Comment You can use [giscus](https://giscus.app) as the comment system. -To enable it, you need to create `myblog/templates/_giscus_script.html` and put the script configured on the giscus website into it, then change the value of `data-theme` to `https://<your-domain-name>/giscus_light.css`, replace `<your-domain-name>` with you domain name, same as `base_url` in `config.toml`, if you set `force_theme` to `dark`, replace `giscus_light.css` with `giscus_dark.css`. +To enable it, you need to create `myblog/templates/_giscus_script.html` and put the script configured on the giscus website into it, then change the value of `data-theme` to `https://<your-domain-name>/giscus_light.css`, replace `<your-domain-name>` with you domain name, same as `base_url` in `zola.toml`, if you set `color_scheme` to `"dark"`, replace `giscus_light.css` with `giscus_dark.css`. Then set `comment = true` to enable comment. @@ -302,7 +288,7 @@ You need to setup a backend api endpoint to enable it. Your endpoint should hand } ``` -For conivence, you can use one template repo to to setup your own endpoint: +For convenience, you can use one template repo to setup your own endpoint: - [isunjn/reaction](https://github.com/isunjn/reaction): All you need is a [Cloudflare](https://cloudflare.com) account. The free tier is good enough for a low-traffic personal blog. @@ -317,169 +303,156 @@ Giscus also support a reaction feature, but it requires visitors to log in to Gi Zola supports some [annotations for code blocks](https://www.getzola.org/documentation/content/syntax-highlighting/#annotations). -## Shortcodes +## Callouts + +Callouts use the [GitHub alert syntax](https://github.com/orgs/community/discussions/16925), there are 5 types: `NOTE` `TIP` `IMPORTANT` `WARNING` `CAUTION`: + +```md +> [!NOTE] +> note text +``` + +Serene styles them with an icon and a title. The title texts default to "Note" / "Tip" / "Important" / "Warning" / "Caution", you can change them (e.g. for a non-English site) by setting css variables in your `_custom_css.html`: -[Shortcodes](https://www.getzola.org/documentation/content/shortcodes/) are some special templates. +```css +--callout-note-title: "注意"; +--callout-tip-title: "提示"; +--callout-important-title: "重要"; +--callout-warning-title: "警告"; +--callout-caution-title: "当心"; +``` + +## Components + +Since zola `0.23`, your markdown content is itself a [Tera](https://keats.github.io/tera/) template, and shortcodes were replaced by [Tera components](https://www.getzola.org/documentation/content/overview/#templating-your-content). Serene provides some built-in components. + +Note that component arguments other than strings are wrapped in `{...}`, e.g. `autoplay={true}`. -- Use `figure` to add caption to the image: +- Use `figure` to add caption or width/height to an image, `alt` `caption` `width` `height` are all optional (`width` and `height` take strings): ```md - {{ figure(src="/path/to/img", alt="alt text", caption="caption text") }} + {{ <figure src="/path/to/img" alt="alt text" caption="caption text" width="600" height="400" /> }} ``` - Adding attribution information to an image is very common, you can directly use the `via` attribute, which will display a link named 'via' below the image: + If `src` is the filename of a [colocated asset](https://www.getzola.org/documentation/content/overview/#asset-colocation), pass `page` (or `section` when used in a section's `_index.md`) so the image URL can be resolved: ```md - {{ figure(src="/path/to/img", alt="some alt text", via="https://example.com") }} + {{ <figure src="colocated-img.png" caption="caption text" page /> }} ``` + The caption is parsed as markdown so you can use bold / italic / link, for example `caption="[via](https://example.com)"` + + Adding height to an image is always recommended, as this can avoid page layout shift. When you use ``, browser cannot determine the image's dimensions before it loads. + - Use `quote` to display a special quote block, `cite` is optional: ```md - {% quote(cite="") %} + {% <quote cite=""> %} // content... - {% end %} + {% </quote> %} ``` - Use `detail` to add an expandable detail block, `default_open` is optional: ```md - {% detail(title="", default_open=false) %} + {% <detail title="" default_open={false}> %} // content... - {% end %} - ``` - -- As you can see in [this page](https://serene-demo.pages.dev/posts/callouts) of the demo site, callouts are special blockquote blocks, just like [github's](https://github.com/orgs/community/discussions/16925). There are currently 5 types: `note` `tip` `important` `warning` `caution`. - - `title` is optional: - - ```md - {% note(title="Note") %} - note text - {% end %} + {% </detail> %} ``` - Use `mermaid` to add a mermaid chart: ```md - {% mermaid() %} + {% <mermaid> %} flowchart LR A[Hard] -->|Text| B(Round) B --> C{Decision} C -->|One| D[Result 1] C -->|Two| E[Result 2] - {% end %} + {% </mermaid> %} ``` -## Collection +- Use `youtube` to embed a youtube video, `autoplay` is optional, default to `false`: -This theme has several special shortcodes for creating a collection of items. These collections can be used to showcase various types of your list, such as projects, publications, blogroll, bookmarks, etc. Check [this page](http://serene-demo.pages.dev/collections) on demo site to see some examples. + ```md + {{ <youtube id="<youtube-video-id>" autoplay={true} /> }} + ``` -Currently, there are 7 types of collection item: +Since your markdown content is now a Tera template, if you want to write literal `{{` or `{%` in your content (e.g. in a code block), wrap it with `{% raw %}` and `{% endraw %}`. -- `card` +Note that component calls must be at the top level of your content — don't nest them inside a list item, as the component's HTML output would break the list's indentation rules and produce broken HTML. - ```toml - [[collection]] - type = "card" - title = "Title" - subtitle = "Subtitle" # optional - date = "Date" # optional - link = "https://example.com" # optional - icon = "https://example.com/image.png" # optional - content = "Content" - tags = ["tag1", "tag2"] # optional - featured = false # optional - ``` +## Collection -- `card_simple` +This theme has a special component for creating a collection of items. Collections can be used to showcase various types of lists, such as projects, publications, blogroll, bookmarks, books, etc. Check [this page](https://serene-demo.pages.dev/collections) on demo site to see some examples. - ```toml - [[collection]] - type = "card_simple" - title = "Title" - date = "Date" # optional - link = "https://example.com" # optional - icon = "https://example.com/image.png" # optional - content = "Content" - featured = false # optional - ``` +A collection is described by a toml file. Two collection-level options decide how it looks: -- `entry` +- `layout`: the appearance of each item + - `card`: rich block item, with title / subtitle / content / tags etc. + - `row`: compact one-line item + - `tile`: small bordered block, text on the left and a 48x48 image on the right + - `gallery`: item with a poster image +- `flow`: how items are arranged in the container + - `stack`: vertically stacked, one item per line (default for `card` and `gallery`); a stacked `row` takes the full width with its `badge` aligned to the right + - `inline`: items take their content width and wrap horizontally (default for `row` and `tile`) + - `grid`: an even grid, the number of columns adapts to the available width automatically - ```toml - [[collection]] - type = "entry" - title = "Title" # optional - subtitle = "Subtitle" # optional - link = "https://example.com" # optional - icon = "https://example.com/image.png" # optional - ``` +Not every combination makes sense: `card` doesn't work with `flow = "inline"` (it is treated as `grid`), and an unknown `layout` / `flow` value falls back to the default. -- `box` +All layouts share the same set of item fields, every field except `title` is optional and simply omitted from rendering when absent. Each layout renders the fields that fit its density and ignores the rest: - ```toml - [[collection]] - type = "box" - title = "Title" - subtitle = "Subtitle" # optional - link = "https://example.com" # optional - img = "https://example.com/image.png" # optional - ``` - -- `art` +| field | card | row | tile | gallery | +| ---------- | ------------ | ---- | --------- | -------- | +| `title` | ✓ | ✓ | ✓ | ✓ | +| `subtitle` | ✓ | ✓ | ✓ | ✓ | +| `content` | ✓ | - | - | ✓ | +| `icon` | ✓ | ✓ | - | - | +| `image` | ✓ (left) | - | ✓ (48x48) | ✓ (poster) | +| `link` | title | title | whole item | title | +| `badge` | ✓ (right) | ✓ | - | ✓ (bottom) | +| `tags` | ✓ | - | - | - | +| `featured` | ✓ | ✓ | - | - | +| `rotate` | - | - | ✓ | - | - ```toml - [[collection]] - type = "art" - title = "Title" - subtitle = "Subtitle" # optional - link = "https://example.com" # optional - img = "https://example.com/image.png" - content = "Content" # optional - footer = "Footer" # optional - ``` - -- `art_simple` - - ```toml - [[collection]] - type = "art_simple" - title = "Title" - subtitle = "Subtitle" # optional - link = "https://example.com" # optional - img = "https://example.com/image.png" - ``` +```toml +layout = "card" # "card" | "row" | "tile" | "gallery" +# flow = "grid" # "stack" | "inline" | "grid" +[[item]] +title = "Title" +subtitle = "Subtitle" # supports inline markdown +content = "Content" # supports markdown +icon = "https://example.com/icon.png" # a small 16x16 icon shown before the title (card and row layouts) +image = "https://example.com/image.png" # a picture: shown on the left for card, as the 48x48 block for tile, as the poster for gallery +link = "https://example.com" # makes the title clickable (or the whole item for tile), external links open in a new tab automatically +# `icon` and `image` can be a full URL, a path in `static` (starting with `/`), or a file colocated with the section +badge = "2025" # a short mark rendered as-is: a year, a date range, a rating, a status... +tags = ["tag1", "tag2"] +featured = true # show an asterisk mark (card and row layouts) +rotate = true # playfully rotate the image (tile layout) +``` -List your items in a toml file and then use a `collection` shortcode to render them. +Note that `gallery` changes its look with the flow: `stack` puts the poster on the left with text on the right, while `grid` / `inline` puts the poster on top with text centered below. For example, to create a "projects" section page: 1. Create `myblog/content/projects/projects.toml`: ```toml - [[collection]] - type = "card" + layout = "card" + + [[item]] title = "Tokio" link = "https://example.com" - content = "Tokio is an asynchronous runtime for the Rust programming language. It provides the building blocks needed for writing network applications. It gives the flexibility to target a wide range of systems, from large servers with dozens of cores to small embedded devices." + content = "Tokio is an asynchronous runtime for the Rust programming language." tags = ["rust", "async", "runtime"] - [[collection]] - type = "card" + [[item]] title = "Kubernetes" link = "https://example.com" - content = "Kubernetes, also known as K8s, is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for the deployment, maintenance, and scaling of applications." + content = "Kubernetes, also known as K8s, is an open source system for managing containerized applications." tags = ["k8s", "golang"] - - [[collection]] - type = "card" - title = "Next.js" - link = "https://example.com" - content = "Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations." - tags = ["typescript", "react", "frontend"] - ``` 2. Create `myblog/content/projects/_index.md`: @@ -495,18 +468,20 @@ For example, to create a "projects" section page: subtitle = "Some cool projects I made" +++ - {{ collection(file="projects.toml") }} + {{ <collection file="projects.toml" section /> }} ``` -3. Add projects section in `sections` of `config.toml` +3. Add projects section in `nav` of `zola.toml` ```toml - sections = [ + nav = [ # ... - { name = "projects", path = "/projects", is_external = false }, + { name = "projects", path = "/projects" }, ] ``` +A page can have multiple collections: put several toml files in the section directory and call the component for each, with markdown headings in between to group them. + ## Build & Deploy Local preview: @@ -527,7 +502,7 @@ To deploy a static site, refer to zola's [documentation about deployment](https: Check the [CHANGELOG.md](https://github.com/isunjn/serene/blob/main/CHANGELOG.md) on github for breaking changes before you update. -If you copied some files from `myblog/themes/serene` to `myblog/` for customization, such as `_custom_css.html` or `main.scss`, then you should record what you have modified before you update, re-copy those files and re-apply your modification after updating. The `config.toml` should be re-copied too. +If you copied some files from `myblog/themes/serene` to `myblog/` for customization, such as `_custom_css.html` or `main.scss`, then you should record what you have modified before you update, re-copy those files and re-apply your modification after updating. The `zola.toml` should be re-copied too. You can watch (`watch > custom > releases > apply`) this project on github to be reminded of a new release. diff --git a/src/themes/serene/config.example.toml b/src/themes/serene/config.example.toml deleted file mode 100644 index e35d6c4..0000000 --- a/src/themes/serene/config.example.toml +++ /dev/null @@ -1,62 +0,0 @@ -# serene v5.2.1 -# -# - docs: https://github.com/isunjn/serene/blob/latest/USAGE.md -# - check for updates: https://github.com/isunjn/serene/releases -# -#========================================================================================= - -base_url = "https://example.com" -title = "xxxx" -description = "xxxx xxxx xxxx" -default_language = "en" -theme = "serene" -output_dir = "public" -compile_sass = true -minify_html = false # Keep this false, as it may cause issues with some styles -build_search_index = false # Keep this false, search is temporarily unsupported -generate_feeds = false # Whether to generate a feed file in root, read docs for more info about rss feed -feed_filenames = ["feed.xml"] # "feed.xml" | "atom.xml" | "rss.xml", read docs for more info -taxonomies = [{ name = "tags" }, { name = "categories" }] - -[markdown] -highlight_code = true -highlight_theme = "css" -extra_syntaxes_and_themes = ["highlight_themes"] -highlight_themes_css = [ - { theme = "serene-light", filename = "hl-light.css" }, - { theme = "serene-dark", filename = "hl-dark.css" }, -] -render_emoji = false -external_links_target_blank = false -external_links_no_follow = true -external_links_no_referrer = true -smart_punctuation = false - -[slugify] -paths = "on" -taxonomies = "on" -anchors = "on" - -#========================================================================================= - -[extra] - -sections = [ - { name = "posts", path = "/posts", is_external = false }, - # { name = "tags", path = "/tags", is_external = false }, - # { name = "github", path = "https://github.com/<your-username>", is_external = true }, -] -blog_section_path = "/posts" - -back_link_text = "Back" # Text of the back button -force_theme = false # false | "light" | "dark" - -footer_copyright = "© 2025 <your-name>" -footer_credits = true # Whether to show "Built with zola and serene" in footer - -not_found_error_text = "404 Not Found" -not_found_recover_text = "« back to home »" - -reaction = false # Whether to enable anonymous emoji reactions (Note: You need to set up a working api endpoint to enable this feature) -reaction_align = "right" # "left" | "center" | "right" -reaction_endpoint = "https://example.com/api/reaction" diff --git a/src/themes/serene/highlight_themes/serene-dark.tmTheme b/src/themes/serene/highlight_themes/serene-dark.tmTheme deleted file mode 100644 index f4fd49c..0000000 --- a/src/themes/serene/highlight_themes/serene-dark.tmTheme +++ /dev/null @@ -1,283 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<!-- Generated by: TmTheme-Editor --> -<!-- ============================================ --> -<!-- app: http://tmtheme-editor.herokuapp.com --> -<!-- code: https://github.com/aziz/tmTheme-Editor --> -<plist version="1.0"> -<dict> - <key>comment</key> - <string>http://chriskempson.com</string> - <key>name</key> - <string>Tomorrow Night</string> - <key>settings</key> - <array> - <dict> - <key>settings</key> - <dict> - <key>caret</key> - <string>#AEAFAD</string> - <key>foreground</key> - <string>#C5C8C6</string> - <key>invisibles</key> - <string>#4B4E55</string> - <key>lineHighlight</key> - <string>#282A2E</string> - <key>selection</key> - <string>#373B41</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Comment</string> - <key>scope</key> - <string>comment, string.quoted.double.block.python</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#999999</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Foreground</string> - <key>scope</key> - <string>keyword.operator.class, constant.other, source.php.embedded.line</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#CED1CF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Variable, String Link, Regular Expression, Tag Name</string> - <key>scope</key> - <string>variable, support.other.variable, string.other.link, string.regexp, entity.name.tag, entity.other.attribute-name, meta.tag, declaration.tag</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#A67878</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Number, Constant, Function Argument, Tag Attribute, Embedded</string> - <key>scope</key> - <string>constant.numeric, constant.language, support.constant, constant.character, variable.parameter, punctuation.section.embedded, keyword.other.unit</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#E08355</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Class, Support</string> - <key>scope</key> - <string>type, -entity.name.class, entity.name.type.class, support.type, support.class</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#83aaa5</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>String, Symbols, Inherited Class, Markup Heading</string> - <key>scope</key> - <string>string, constant.other.symbol, entity.other.inherited-class, markup.heading</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#85AD74</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Operator, Misc</string> - <key>scope</key> - <string>keyword.operator, constant.other.color</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#83aaa5</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Function, Special Method, Block Level</string> - <key>scope</key> - <string>entity.name.function, meta.function-call, support.function, keyword.other.special-method, meta.block-level</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#81A2BE</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Keyword, Storage</string> - <key>scope</key> - <string>keyword, storage, storage.type, entity.name.tag.css</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#B294BB</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Invalid</string> - <key>scope</key> - <string>invalid</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#DF5F5F</string> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#CED2CF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Separator</string> - <key>scope</key> - <string>meta.separator</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#82A3BF</string> - <key>foreground</key> - <string>#CED2CF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Deprecated</string> - <key>scope</key> - <string>invalid.deprecated</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#B798BF</string> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#CED2CF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff foreground</string> - <key>scope</key> - <string>markup.inserted.diff, markup.deleted.diff, meta.diff.header.to-file, meta.diff.header.from-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#FFFFFF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff insertion</string> - <key>scope</key> - <string>markup.inserted.diff, meta.diff.header.to-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#4baf5c</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff deletion</string> - <key>scope</key> - <string>markup.deleted.diff, meta.diff.header.from-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#D46565</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff header</string> - <key>scope</key> - <string>meta.diff.header.from-file, meta.diff.header.to-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#4271ae</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff range</string> - <key>scope</key> - <string>meta.diff.range</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string>italic</string> - <key>foreground</key> - <string>#3e999f</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>diff.deleted</string> - <key>scope</key> - <string>markup.deleted</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#F92672</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>diff.inserted</string> - <key>scope</key> - <string>markup.inserted</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#A6E22E</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>diff.changed</string> - <key>scope</key> - <string>markup.changed</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#967EFB</string> - </dict> - </dict> - </array> - <key>uuid</key> - <string>F96223EB-1A60-4617-92F3-D24D4F13DB09</string> - <key>colorSpaceName</key> - <string>sRGB</string> - <key>semanticClass</key> - <string>theme.dark.tomorrow_night</string> -</dict> -</plist>
\ No newline at end of file diff --git a/src/themes/serene/highlight_themes/serene-light.tmTheme b/src/themes/serene/highlight_themes/serene-light.tmTheme deleted file mode 100644 index c4b8d1b..0000000 --- a/src/themes/serene/highlight_themes/serene-light.tmTheme +++ /dev/null @@ -1,239 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<!-- Generated by: TmTheme-Editor --> -<!-- ============================================ --> -<!-- app: http://tmtheme-editor.herokuapp.com --> -<!-- code: https://github.com/aziz/tmTheme-Editor --> -<plist version="1.0"> -<dict> - <key>comment</key> - <string>http://chriskempson.com</string> - <key>name</key> - <string>Tomorrow</string> - <key>settings</key> - <array> - <dict> - <key>settings</key> - <dict> - <key>caret</key> - <string>#AEAFAD</string> - <key>foreground</key> - <string>#4D4D4C</string> - <key>invisibles</key> - <string>#D1D1D1</string> - <key>lineHighlight</key> - <string>#EFEFEF</string> - <key>selection</key> - <string>#D6D6D6</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Comment</string> - <key>scope</key> - <string>comment, string.quoted.double.block.python</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#999999</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Foreground</string> - <key>scope</key> - <string>keyword.operator.class, constant.other, source.php.embedded.line</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#666969</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Variable, String Link, Regular Expression, Tag Name</string> - <key>scope</key> - <string>variable, support.other.variable, string.other.link, string.regexp, entity.name.tag, entity.other.attribute-name, meta.tag, declaration.tag</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#A67878</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Number, Constant, Function Argument, Tag Attribute, Embedded</string> - <key>scope</key> - <string>constant.numeric, constant.language, support.constant, constant.character, variable.parameter, punctuation.section.embedded, keyword.other.unit</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#E08355</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Type, Class, Support</string> - <key>scope</key> - <string>type, -entity.name.class, entity.name.type.class, support.type, support.class</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#568A8F</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>String, Symbols, Inherited Class, Markup Heading</string> - <key>scope</key> - <string>string, constant.other.symbol, entity.other.inherited-class, markup.heading</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#85AD74</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Operator, Misc</string> - <key>scope</key> - <string>keyword.operator, constant.other.color</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#568A8F</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Function, Special Method, Block Level</string> - <key>scope</key> - <string>entity.name.function, meta.function-call, support.function, keyword.other.special-method, meta.block-level</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#4271AE</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Keyword, Storage</string> - <key>scope</key> - <string>keyword, storage, storage.type</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#8959A8</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Invalid</string> - <key>scope</key> - <string>invalid</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#DF5F5F</string> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#FFFFFF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Separator</string> - <key>scope</key> - <string>meta.separator</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#4271AE</string> - <key>foreground</key> - <string>#FFFFFF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Deprecated</string> - <key>scope</key> - <string>invalid.deprecated</string> - <key>settings</key> - <dict> - <key>background</key> - <string>#8959A8</string> - <key>fontStyle</key> - <string></string> - <key>foreground</key> - <string>#FFFFFF</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff insertion</string> - <key>scope</key> - <string>markup.inserted.diff, meta.diff.header.to-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#229545</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff deletion</string> - <key>scope</key> - <string>markup.deleted.diff, meta.diff.header.from-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#C8282966</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff header</string> - <key>scope</key> - <string>meta.diff.header.from-file, meta.diff.header.to-file</string> - <key>settings</key> - <dict> - <key>foreground</key> - <string>#4271ae</string> - </dict> - </dict> - <dict> - <key>name</key> - <string>Diff range</string> - <key>scope</key> - <string>meta.diff.range</string> - <key>settings</key> - <dict> - <key>fontStyle</key> - <string>italic</string> - <key>foreground</key> - <string>#3e999f</string> - </dict> - </dict> - </array> - <key>uuid</key> - <string>82CCD69C-F1B1-4529-B39E-780F91F07604</string> - <key>colorSpaceName</key> - <string>sRGB</string> - <key>semanticClass</key> - <string>theme.light.tomorrow</string> -</dict> -</plist>
\ No newline at end of file diff --git a/src/themes/serene/sass/main.scss b/src/themes/serene/sass/main.scss index f5628a2..79eb9ac 100644 --- a/src/themes/serene/sass/main.scss +++ b/src/themes/serene/sass/main.scss @@ -31,7 +31,7 @@ a:focus-visible { ::-webkit-scrollbar { width: 8px; - height: 8px; + height: 6px; background-color: transparent; } ::-webkit-scrollbar-track { @@ -112,6 +112,7 @@ a:focus-visible { img { max-width: 100%; + height: auto; display: block; margin: 0 auto; border-radius: var(--img-border-radius); @@ -138,17 +139,119 @@ a:focus-visible { p { margin: 1em 0; } + + &[class*="markdown-alert-"] { + margin: 1.5em 0; + display: grid; + grid-template-columns: 18px 1fr; + column-gap: 0.5em; + + &::before { + // the icon, colored via mask so it follows currentColor + content: ""; + grid-row: 1; + grid-column: 1; + align-self: center; + width: 18px; + height: 18px; + background-color: currentColor; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + } + + &::after { + // the title + grid-row: 1; + grid-column: 2; + align-self: center; + font-weight: 600; + } + + > * { + grid-column: 1 / -1; + } + + > p:first-of-type { + margin-top: 0.5em; + } + > p:last-of-type { + margin-bottom: 0; + } + } + + &.markdown-alert-note { + color: var(--callout-note-color); + border-color: var(--callout-note-color); + &::before { + mask-image: url("icon/note.svg"); + -webkit-mask-image: url("icon/note.svg"); + } + &::after { + content: var(--callout-note-title, "Note"); + } + } + + &.markdown-alert-tip { + color: var(--callout-tip-color); + border-color: var(--callout-tip-color); + &::before { + mask-image: url("icon/tip.svg"); + -webkit-mask-image: url("icon/tip.svg"); + } + &::after { + content: var(--callout-tip-title, "Tip"); + } + } + + &.markdown-alert-important { + color: var(--callout-important-color); + border-color: var(--callout-important-color); + &::before { + mask-image: url("icon/important.svg"); + -webkit-mask-image: url("icon/important.svg"); + } + &::after { + content: var(--callout-important-title, "Important"); + } + } + + &.markdown-alert-warning { + color: var(--callout-warning-color); + border-color: var(--callout-warning-color); + &::before { + mask-image: url("icon/warning.svg"); + -webkit-mask-image: url("icon/warning.svg"); + } + &::after { + content: var(--callout-warning-title, "Warning"); + } + } + + &.markdown-alert-caution { + color: var(--callout-caution-color); + border-color: var(--callout-caution-color); + &::before { + mask-image: url("icon/caution.svg"); + -webkit-mask-image: url("icon/caution.svg"); + } + &::after { + content: var(--callout-caution-title, "Caution"); + } + } } - ol, - ul { - padding-left: 1.5em; + ol, ul { + padding-left: 1.4em; } li { - margin: 1em 0; + margin: 0.5em 0; p { - margin: 1em 0; + margin: 0.5em 0; } } @@ -169,6 +272,7 @@ a:focus-visible { border-spacing: 0; border-collapse: collapse; margin: 1.5em 0; + border: 1.5px solid var(--primary-color); } th, @@ -190,7 +294,7 @@ a:focus-visible { } pre { - font-size: 0.8em; + font-size: 0.85em; margin: 1.25em 0; padding: 12px 48px 12px 16px; line-height: 1.5; @@ -203,58 +307,14 @@ a:focus-visible { font-family: var(--code-font); } - &[data-linenos] { - padding-left: 0px; - } - - table { - width: 100%; - margin: 0; - border-collapse: collapse; - border: none; - th, td { - line-height: 1.5; - } - } - - - table tr td:first-of-type { - color: var(--text-decoration-color); - } - - table td { - padding: 0; - padding-right: 48px; - text-align: initial; - border: initial; - } - - table td:nth-of-type(1) { - text-align: right; + .giallo-ln { + padding-right: 12px; user-select: none; - padding-right: 1em; - mark::before { - left: -8px; - width: calc(100% + 1em + 8px); - } } - mark { - display: block; - color: inherit; - background-color: transparent; - position: relative; - overflow: visible; - - &::before { - pointer-events: none; - content: ''; - position: absolute; - top: 0; - bottom: 0; - width: calc(100% + 48px + 48px); - background-color: var(--highlight-mark-color); - } + &.z-l-code, + &.z-d-code { + background-color: var(--bg-color); } &.mermaid { @@ -267,11 +327,6 @@ a:focus-visible { } } - pre > code > mark::before { - width: calc(100% + 48px + 16px); - left: -16px; - } - .codeblock { margin: 1.5em 0; position: relative; @@ -280,19 +335,19 @@ a:focus-visible { pre { margin: 0; } - pre[data-name] { + code[data-name]::before { + content: attr(data-name); + display: block; + position: absolute; + left: 0px; + top: 0px; + padding: 12px 16px; + color: var(--text-pale-color); + width: 100%; + border-bottom: 1px solid var(--primary-pale-color); + } + pre:has(code[data-name]) { padding-top: calc(36px + 1em * var(--line-height)); - &::before { - content: attr(data-name); - display: block; - position: absolute; - left: 0px; - top: 0px; - padding: 12px 16px; - color: var(--text-pale-color); - width: 100%; - border-bottom: 1px solid var(--primary-pale-color); - } } .copy { @@ -359,69 +414,6 @@ a:focus-visible { } } - .callout { - margin: 1.5em 0; - - .icon { - height: 1.75em; - display: flex; - align-items: center; - } - - p { - margin: 0; - + p { - margin: 1em 0; - } - } - - &.has-title { - padding-left: 1em; - .title { - display: flex; - align-items: center; - gap: 0.5em; - margin-bottom: 0.5em; - } - } - - &.no-title { - padding-left: 0; - border: none; - display: flex; - align-items: start; - gap: 0.75em; - .content { - max-width: calc(100% - 30px); - } - } - - &.note { - color: var(--callout-note-color); - border-color: var(--callout-note-color); - } - - &.tip { - color: var(--callout-tip-color); - border-color: var(--callout-tip-color); - } - - &.important { - color: var(--callout-important-color); - border-color: var(--callout-important-color); - } - - &.warning { - color: var(--callout-warning-color); - border-color: var(--callout-warning-color); - } - - &.caution { - color: var(--callout-caution-color); - border-color: var(--callout-caution-color); - } - } - .quote { border: none; position: relative; @@ -470,6 +462,26 @@ a:focus-visible { .mermaid { background: #fff; } + + .youtube { + position: relative; + height: 0; + padding-bottom: 56.25%; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + } + + + input { + color-scheme: light; + } } body.dark .prose { @@ -479,6 +491,9 @@ body.dark .prose { .mermaid { filter: brightness(var(--dark-mode-chart-brightness)); } + input { + color-scheme: dark; + } } /* prose pages @@ -508,365 +523,380 @@ body.prose-page { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ .prose { - .collection-wrapper { + .collection { margin: 1.5em 0; - } - .collection { - border: none; - margin: 0; - padding: 0; - color: var(--text-color); - img { - margin: 0; + &.flow-inline { + display: flex; + flex-wrap: wrap; + align-items: center; + } + + &.flow-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); + gap: 1.5em; } - a, button { + + a { -webkit-tap-highlight-color: transparent; } - } - .collection.card { - margin: 3em 0; - position: relative; + .image img { + margin: 0; + } - &.featured::before { - content: '*'; - position: absolute; - top: 0; - bottom: 0; - left: 0; - transform: translateX(-200%); - line-height: 2; - color: var(--primary-color); + span.title { + color: var(--text-color); } - @media (max-width: 768px) { - &.featured::before { - transform: translateX(-150%); + + a.title { + border-bottom: none; + > span { + border-bottom: 1.5px solid var(--primary-color); } } - .meta { + .subtitle, + .badge { + font-size: 0.9em; + color: var(--text-pale-color); + } + } + + /* card: rich block item */ + .layout-card { + .item { + margin: 3em 0; + position: relative; display: flex; - gap: 0.5em; - .icon-wrapper { - height: calc(1em * var(--line-height)); - display: flex; - align-items: center; + align-items: center; + gap: 1.5em; + + > .image { flex-shrink: 0; + width: 160px; + img { + width: 100%; + height: auto; + border-radius: var(--img-border-radius); + } } - .icon { - height: 16px; - width: 16px; + + @media (max-width: 425px) { + flex-direction: column; + align-items: stretch; + gap: 1em; + + > .image { + width: 100%; + } } - .title { + + .body { + flex: 1; + min-width: 0; + } + + &.featured::before { + content: '*'; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transform: translateX(-200%); + line-height: 2; color: var(--primary-color); } - a.title { - text-decoration: none; - border-bottom: 1.5px solid transparent; - &:hover { - opacity: unset; - border-color: var(--primary-color); + @media (max-width: 768px) { + &.featured::before { + transform: translateX(-150%); } } - .date { - margin-left: auto; - flex-shrink: 0; - font-size: 0.9em; - color: var(--text-pale-color); - } - } - .subtitle { - font-size: 0.9em; - color: var(--text-pale-color); - margin: 0.75em 0; - } + .meta { + display: flex; + gap: 0.5em; - .content { - font-size: 1em; - p { + .icon { + height: calc(1em * var(--line-height)); + display: flex; + align-items: center; + flex-shrink: 0; + img { + height: 16px; + width: 16px; + } + } + .badge { + margin-left: auto; + flex-shrink: 0; + } + } + + .subtitle { + display: block; margin: 0.75em 0; } - } - .tags { - font-size: 0.9em; - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.75em 1em; - color: var(--text-pale-color); + .content { + font-size: 1em; + p { + margin: 0.75em 0; + } + } - div { - span { + .tags { + font-size: 0.9em; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.75em 1em; + color: var(--text-pale-color); + + .tag span { font-size: 0.9em; margin-right: 4px; } } } - } - .collection.card-simple { - margin: 0.5em 0; - position: relative; - - &.featured::before { - content: '*'; - position: absolute; - top: 0; - bottom: 0; - left: 0; - transform: translateX(-200%); - line-height: 2; - color: var(--primary-color); + &.flow-grid .item, + &.flow-inline .item { + margin: 0; } - @media (max-width: 768px) { - &.featured::before { - transform: translateX(-150%); - } + &.flow-grid { + grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); } + } - .meta { - display: flex; - align-items: start; - gap: 0.5em; + /* row: compact one-line item */ + .layout-row { + &.flow-inline { + gap: 0.5em 1.5em; } - .icon-wrapper { - height: calc(1em * var(--line-height)); - display: flex; - align-items: center; - flex-shrink: 0; - } - .icon { - height: 16px; - width: 16px; - } - .title { - flex-shrink: 0; + .item { + display: inline-flex; + width: fit-content; max-width: 100%; - color: var(--primary-color); - margin-right: 0.5em; - } - a.title { - text-decoration: none; - border-bottom: 1.5px solid transparent; - &:hover { - opacity: unset; - border-color: var(--primary-color); + gap: 0.5em; + padding: 4px 0; + color: var(--text-color); + position: relative; + + &.featured::before { + content: '*'; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transform: translateX(-200%); + line-height: 2; + color: var(--primary-color); } - } - .content, - .content-narrow { - p { - margin: 0; - + p { - margin-top: 0.5em; + @media (max-width: 768px) { + &.featured::before { + transform: translateX(-150%); } } - } - .content-narrow { - display: none; - margin: 0.25em 0 2em; - } - @media (max-width: 425px) { - .content { - display: none; - } - .content-narrow { - display: block; + + .icon { + flex-shrink: 0; + display: flex; + align-items: center; + height: calc(1em * var(--line-height)); + img { + width: 16px; + height: 16px; + } } - } - .date { - margin-left: auto; - flex-shrink: 0; - font-size: 0.9em; - line-height: calc(var(--line-height) * 1.111); - color: var(--text-pale-color); - } - } + .main { + display: inline-flex; + gap: 0.5em; + min-width: 0; + } - .collection.entry { - display: inline-flex; - width: fit-content; - gap: 0.5em; - padding: 4px 0; - margin: 0.25em 1.5em 0.25em 0; + .main:has(+ .subtitle) { + margin-right: 0.5em; + } + } - .icon-wrapper { - flex-shrink: 0; + &.flow-stack .item { display: flex; - align-items: center; - height: calc(1em * var(--line-height)); - } - .icon { - width: 16px; - height: 16px; - } + width: auto; + margin: 0.25em 0; - .text div { - display: inline; - } - .title:has(+.subtitle) { - margin-right: 0.5em; - } - .subtitle { - font-size: 0.9em; - color: var(--text-pale-color); - } - } - a.collection.entry { - -webkit-tap-highlight-color: transparent; - color: var(--text-color); - border-bottom: 1.5px solid transparent; - &:hover { - border-color: var(--primary-color); - opacity: unset; - cursor: pointer; + .badge { + margin-left: auto; + padding-left: 1em; + flex-shrink: 0; + } } - } - .collection.box { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - border: 1px solid var(--primary-color); - overflow: hidden; + @media (max-width: 425px) { + .item:has(.subtitle) { + display: flex; + width: 100%; + flex-wrap: wrap; + } - img { - width: 48px; - height: 48px; - &.rotate { - transition: transform 0.15s; - transform: rotate(-20deg) translate(-2px, 12px); + .item:has(.badge) .subtitle { + order: 10; + flex-basis: 100%; } } - .placehold { - height: 48px; + + } + + /* tile: small bordered block, text left + image right */ + .layout-tile { + &.flow-inline { + gap: 0.8em 0.5em; } - .text { - min-height: 48px; - line-height: 1.5; - padding: 0 1em; - display: flex; - flex-direction: column; - justify-content: center; + .item { + max-width: 100%; + display: inline-flex; + align-items: center; + justify-content: space-between; + border: 1px solid var(--primary-color); overflow: hidden; + text-decoration: none; - .title { - font-size: 0.9em; - color: var(--text-color); + img { + width: 48px; + height: 48px; + margin: 0; + &.rotate { + transition: transform 0.15s; + transform: rotate(-20deg) translate(-2px, 12px); + } + } + .placehold { + height: 48px; } - .subtitle { - font-size: 0.8em; - color: var(--text-pale-color); + .text { + min-height: 48px; + line-height: 1.5; + padding: 0 1em; + display: flex; + flex-direction: column; + justify-content: center; overflow: hidden; - text-wrap: nowrap; - text-overflow: ellipsis; + + .title { + font-size: 0.9em; + color: var(--text-color); + } + + .subtitle { + font-size: 0.8em; + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; + } } - } - @media (hover: hover) { - &:hover { - opacity: unset; - img.rotate { - transform: rotate(0deg) translate(0px, 0px); + @media (hover: hover) { + &:hover { + opacity: unset; + img.rotate { + transform: rotate(0deg) translate(0px, 0px); + } } } } - } - a.collection.box { - -webkit-tap-highlight-color: transparent; - } - .collection-box-wrapper { - max-width: 100%; - width: fit-content; - display: inline-flex; - align-items: center; - justify-content: space-between; - border: 0.5px solid transparent; - overflow: hidden; - margin-right: 0.5em; - margin-bottom: 0.8em; - @media (hover: hover) { - &:has(a):hover { - border-color: var(--primary-color); + + a.item { + @media (hover: hover) { + &:hover { + box-shadow: 0 0 0 1px var(--primary-color); + } } } + + &.flow-stack .item { + display: flex; + width: 100%; + margin: 0.5em 0; + } + @media (max-width: 425px) { - & { + .item { display: flex; width: 100%; } } } - .collection.art { - --art-h: 160px; - --art-w: 115px; + /* gallery: item with a poster image */ + .layout-gallery { + --poster-h: 160px; + --poster-w: 115px; - margin: 2em 0; - display: flex; - gap: 1.5em; + &.flow-grid { + grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); + } + + a.title { + width: fit-content; + } - .img-wrapper { + .image { flex-shrink: 0; - width: var(--art-w); - height: var(--art-h); + width: var(--poster-w); + height: var(--poster-h); img { height: 100%; width: 100%; object-fit: contain; } } - .text { - height: var(--art-h); - display: flex; - flex-direction: column; - gap: 3px; - } - .title { - color: var(--primary-color); - } - a.title { - width: fit-content; - border-color: transparent; - &:hover { - opacity: unset; - border-color: var(--primary-color); - } - } - .subtitle, - .footer { - font-size: 0.8em; - color: var(--text-pale-color); - } + .content { font-size: 0.9em; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; + p { + margin: 0; + } } - .footer { - margin-top: auto; + + .subtitle, + .badge { + font-size: 0.8em; } - @media (max-width: 425px) { - & { - --art-h: 120px; - --art-w: 85px; + /* stack: poster left, text right */ + &.flow-stack .item { + margin: 2em 0; + display: flex; + gap: 1.5em; + + .text { + height: var(--poster-h); + display: flex; + flex-direction: column; + gap: 3px; + } + .badge { + margin-top: auto; + } + + @media (max-width: 425px) { + --poster-h: 120px; + --poster-w: 85px; gap: 0.75em; .text { gap: 2px; } .subtitle, - .footer { + .badge { font-size: 0.7em; } .content { @@ -876,63 +906,36 @@ body.prose-page { } } - } - - - .collection.art-simple { - --art-h: 160px; - --art-w: 115px; - - max-width: calc(50vw - 30px); - margin: 1.5em 0.75em; - display: inline-flex; + /* grid / inline: poster on top, text centered below */ + &.flow-grid .item, + &.flow-inline .item { + display: flex; flex-direction: column; align-items: center; gap: 1em; - .img-wrapper { - flex-shrink: 0; - width: var(--art-w); - height: var(--art-h); - img { - height: 100%; - width: 100%; - object-fit: contain; - } + .title { + font-size: 0.9em; } .text { - max-width: calc(var(--art-w) * 1.5); + max-width: calc(var(--poster-w) * 1.5); display: flex; flex-direction: column; align-items: center; + text-align: center; gap: 3px; } - .title { - font-size: 0.9em; - color: var(--primary-color); - } - a.title { - width: fit-content; - border-color: transparent; - &:hover { - opacity: unset; - border-color: var(--primary-color); - } - } - .subtitle { - font-size: 0.8em; - color: var(--text-pale-color); - } @media (max-width: 425px) { - & { - --art-h: 120px; - --art-w: 85px; - width: calc(50vw - 50px); - } + --poster-h: 120px; + --poster-w: 85px; } - } + &.flow-inline { + gap: 1.5em 2em; + align-items: start; + } + } } /* layout post list ( home / blog / tag ) @@ -957,10 +960,9 @@ body.prose-page { display: flex; justify-content: space-between; align-items: flex-end; - gap: 0.5em; - padding: 4px 0px; - margin: 6px 0; - line-height: 1.2; + gap: 2em; + padding: 0.25em 0px; + /* margin: 0.25em 0; */ text-decoration: none; color: var(--primary-color); @@ -982,13 +984,27 @@ body.prose-page { top: 0; bottom: 0; left: 0; - transform: translateX(-200%); - line-height: 2; - height: 100%; + transform: translate(-200%, 6px); + height: calc(var(--line-height) * 1em); } @media (max-width: 768px) { &.featured::before { - transform: translateX(-150%); + transform: translate(-150%, 6px); + } + } + + @media (max-width: 425px) { + flex-direction: column-reverse; + align-items: start; + gap: 0.25em; + margin: 1em 0; + .date { + margin-left: initial; + color: var(--text-pale-color); + font-size: 0.8em; + } + &.featured::before { + transform: translate(-150%, 115%); } } } @@ -1070,6 +1086,10 @@ body.homepage { gap: 1em; } + img + #text { + gap: 0.75em; + } + #id { margin-left: 0.75em; color: var(--primary-color); @@ -1109,6 +1129,10 @@ body.homepage { a { border-bottom: 1.5px solid var(--primary-color); line-height: 1.5; + + &.external { + cursor: ne-resize; + } } } @@ -1382,7 +1406,6 @@ body.post { } #outdate_alert { - font-style: italic; &.hidden { display: none; } @@ -1411,11 +1434,20 @@ body.post { min-width: 60%; overflow-y: auto; max-height: calc(100vh - 6em); - scrollbar-width: none; + scrollbar-width: none; &::-webkit-scrollbar { width: 0; } + + background: + linear-gradient(var(--bg-color) 50%, transparent) center top, + linear-gradient(transparent, var(--bg-color) 50%) center bottom, + radial-gradient(farthest-side at 50% 0, var(--primary-decoration-color), var(--bg-color)) center top, + radial-gradient(farthest-side at 50% 100%, var(--primary-decoration-color), var(--bg-color)) center bottom; + background-repeat: no-repeat; + background-size: 100% 4em, 100% 4em, 100% 1.5em, 100% 1.5em; + background-attachment: local, local, scroll, scroll; } ul { @@ -1436,27 +1468,13 @@ body.post { padding: 0 1em; &.h3 { - padding-left: 2em; - } - - &::before { - display: block; - content: ""; - width: 1.5px; - position: absolute; - top: 0.7em; - bottom: 0.7em; - left: 0em; - background: transparent; + margin-left: 1.5em; + border-left: 1px solid var(--text-decoration-color); } &:hover { color: var(--primary-color); } - - &:hover::before { - background-color: var(--primary-color); - } } #back-to-top { diff --git a/src/themes/serene/screenshot.png b/src/themes/serene/screenshot.png Binary files differindex b824cde..2856540 100644 --- a/src/themes/serene/screenshot.png +++ b/src/themes/serene/screenshot.png diff --git a/src/themes/serene/static/icon/email.svg b/src/themes/serene/static/icon/email.svg index 0c64d95..1593667 100644 --- a/src/themes/serene/static/icon/email.svg +++ b/src/themes/serene/static/icon/email.svg @@ -1,4 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"> - <path d="M3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM20 7.23792L12.0718 14.338L4 7.21594V19H20V7.23792ZM4.51146 5L12.0619 11.662L19.501 5H4.51146Z" fill="currentColor"> - </path> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM20 7.23792L12.0718 14.338L4 7.21594V19H20V7.23792ZM4.51146 5L12.0619 11.662L19.501 5H4.51146Z" fill="currentColor"></path></svg> diff --git a/src/themes/serene/static/js/main.js b/src/themes/serene/static/js/main.js index b0aee0c..df2ef33 100644 --- a/src/themes/serene/static/js/main.js +++ b/src/themes/serene/static/js/main.js @@ -5,7 +5,7 @@ function enableThemeToggle() { const preferDark = window.matchMedia("(prefers-color-scheme: dark)"); function toggleTheme(theme) { if (theme == "dark") document.body.classList.add('dark'); else document.body.classList.remove('dark'); - if (hlLink) hlLink.href = `/hl-${theme}.css`; + if (hlLink) hlLink.href = `/giallo-${theme}.css`; sessionStorage.setItem("theme", theme); toggleGiscusTheme(theme); } @@ -243,9 +243,21 @@ function enableReaction() { init(); } +function enableBackLink() { + const backLink = document.querySelector('#back-link'); + if (!backLink) return; + backLink.addEventListener('click', (e) => { + if (document.referrer && location.href.startsWith(document.referrer) && !location.hash && history.length > 1) { + e.preventDefault(); + history.back(); + } + }); +} + enableThemeToggle(); enablePrerender(); enableRssMask(); +enableBackLink(); if (document.body.classList.contains('post')) { enableOutdateAlert(); addBackToTopBtn(); diff --git a/src/themes/serene/templates/_base.html b/src/themes/serene/templates/_base.html index 0bd7b3b..d1f2d48 100644 --- a/src/themes/serene/templates/_base.html +++ b/src/themes/serene/templates/_base.html @@ -1,3 +1,4 @@ +{% set color_scheme = config.extra.color_scheme if config.extra.color_scheme is defined else "auto" %} <!DOCTYPE html> <html lang="{% block lang %}{% endblock lang %}"> @@ -18,15 +19,15 @@ {% include "_head_extend.html" %} </head> -<body class="{% block page %}{% endblock page%}{% if config.extra.force_theme == "dark" %} dark{% endif %}"> - {% if not config.extra.force_theme %} +<body class="{% block page %}{% endblock page%}{% if color_scheme == "dark" %} dark{% endif %}"> + {% if color_scheme == "auto" %} <script> const theme = sessionStorage.getItem('theme'); - const match = window.matchMedia("(prefers-color-scheme: dark)").matches + const match = window.matchMedia("(prefers-color-scheme: dark)").matches; if ((theme && theme == 'dark') || (!theme && match)) { document.body.classList.add('dark'); const hl = document.querySelector('link#hl'); - if (hl) hl.href = '{{ get_url(path="hl-dark.css") }}'; + if (hl) hl.href = '{{ get_url(path="giallo-dark.css") }}'; } </script> {% endif %} diff --git a/src/themes/serene/templates/_components/collection.html b/src/themes/serene/templates/_components/collection.html new file mode 100644 index 0000000..70a1d31 --- /dev/null +++ b/src/themes/serene/templates/_components/collection.html @@ -0,0 +1,129 @@ +{# + This component is used inside markdown content, which is rendered as a Tera + template first and *then* processed as markdown (zola >= 0.23). Its HTML + output must therefore stay markdown-safe: no blank lines in the middle of + an HTML block, and no lines indented by 4+ spaces. + + Collection file schema: + layout = "card" | "row" | "tile" | "gallery" (default "card") + flow = "stack" | "inline" | "grid" (default: card/gallery -> "stack", row/tile -> "inline") + [[item]] + title / subtitle / content / icon / image / link / badge / tags / featured / rotate +#} + +{% component collection(file: string, section) -%} +{%- set data = load_data(path="content" ~ section.path ~ file, format="toml") -%} +{%- set layout = data.layout if data.layout is defined else "card" -%} +{%- if layout not in ["card", "row", "tile", "gallery"] -%} +{%- set layout = "card" -%} +{%- endif -%} +{%- if data.flow is defined and data.flow in ["stack", "inline", "grid"] -%} +{%- set flow = data.flow -%} +{%- elif layout == "row" or layout == "tile" -%} +{%- set flow = "inline" -%} +{%- else -%} +{%- set flow = "stack" -%} +{%- endif -%} +{%- if layout == "card" and flow == "inline" -%} +{%- set flow = "grid" -%} +{%- endif -%} +<div class="collection layout-{{ layout }} flow-{{ flow }}"> +{%- for item in data.item -%} +{%- set img_src = "" -%} +{%- if item.image is defined -%} +{%- if item.image is starting_with(pat="http") or item.image is starting_with(pat="/") or item.image is starting_with(pat="data:") -%} +{%- set img_src = item.image -%} +{%- else -%} +{%- set img_src = get_url(path=section.colocated_path ~ item.image) -%} +{%- endif -%} +{%- endif -%} +{%- set icon_src = "" -%} +{%- if item.icon is defined -%} +{%- if item.icon is starting_with(pat="http") or item.icon is starting_with(pat="/") or item.icon is starting_with(pat="data:") -%} +{%- set icon_src = item.icon -%} +{%- else -%} +{%- set icon_src = get_url(path=section.colocated_path ~ item.icon) -%} +{%- endif -%} +{%- endif -%} +{%- set external = item.link is defined and not item.link is starting_with(pat="/") -%} +{%- if layout == "card" -%} +<div class="item{% if item.featured %} featured{% endif %}"> +{%- if img_src -%}<div class="image"><img src="{{ img_src }}" alt="{{ item.title }}"></div>{%- endif -%} +<div class="body"> +<div class="meta"> +{%- if icon_src -%}<span class="icon"><img class="no-lightense" src="{{ icon_src }}" alt="" width="16" height="16"></span>{%- endif -%} +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</div> +{%- if item.subtitle -%} +<div class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</div> +{%- endif -%} +{%- if item.content -%} +<div class="content"> +{{ item.content | trim | markdown | trim | safe }} +</div> +{%- endif -%} +{%- if item.tags -%} +<div class="tags"> +{%- for tag in item.tags -%} +<span class="tag"><span>#</span>{{ tag }}</span> +{%- endfor -%} +</div> +{%- endif -%} +</div> +</div> +{%- elif layout == "row" -%} +<span class="item{% if item.featured %} featured{% endif %}"> +<span class="main"> +{%- if icon_src -%}<span class="icon"><img class="no-lightense" src="{{ icon_src }}" alt="" width="16" height="16"></span>{%- endif -%} +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +</span> +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</span> +{%- elif layout == "tile" -%} +{%- if item.link -%} +<a class="item" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}> +{%- else -%} +<span class="item"> +{%- endif -%} +<span class="text"> +<span class="title">{{ item.title }}</span> +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +</span> +{%- if img_src -%} +<img class="no-lightense{% if item.rotate %} rotate{% endif %}" src="{{ img_src }}" alt="{{ item.title }}" width="48" height="48"> +{%- else -%} +<span class="placehold"></span> +{%- endif -%} +{%- if item.link -%}</a>{%- else -%}</span>{%- endif -%} +{%- elif layout == "gallery" -%} +<div class="item"> +{%- if img_src -%}<div class="image"><img src="{{ img_src }}" alt="{{ item.title }}"></div>{%- endif -%} +<div class="text"> +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +{%- if item.content -%} +<div class="content"> +{{ item.content | trim | markdown | trim | safe }} +</div> +{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</div> +</div> +{%- endif -%} +{%- endfor -%} +</div> +{%- endcomponent collection %} diff --git a/src/themes/serene/templates/_components/prose.html b/src/themes/serene/templates/_components/prose.html new file mode 100644 index 0000000..c478a58 --- /dev/null +++ b/src/themes/serene/templates/_components/prose.html @@ -0,0 +1,72 @@ +{# + Components in this file are used inside markdown content, which is rendered + as a Tera template first and *then* processed as markdown (zola >= 0.23). + Their HTML output must therefore stay markdown-safe: no blank lines in the + middle of an HTML block, and no lines indented by 4+ spaces. +#} + +{% component back_link(path: string, config) %} +<header> + <nav> + <a id="back-link" href="{{ path }}">← {{ config.extra.back_link_text }}</a> + </nav> +</header> +{% endcomponent back_link %} + + +{% component quote(cite: string = "") -%} +{%- set icon = load_data(path="icon/quote.svg") -%} +<blockquote class="quote"> +<div class="icon" style="display: none;">{{ icon | trim | safe }}</div> +<div class="content"> +{{ body | markdown | trim | safe }} +</div> +{%- if cite -%} +{%- set from_text = "— " ~ cite -%} +<div class="from"> +{{ from_text | markdown | trim | safe }} +</div> +{%- endif -%} +</blockquote> +{%- endcomponent quote %} + + +{% component detail(title: string = "", default_open: bool = false) -%} +<details{% if default_open %} open{% endif %}> +<summary><span>{{ title }}</span></summary> +{{ body | markdown | trim | safe }} +</details> +{%- endcomponent detail %} + + +{% component figure(src: string, alt: string = "", caption: string = "", width: string = "", height: string = "", page = {}, section = {}) -%} +{%- if src is starting_with(pat="/") or src is starting_with(pat="http://") or src is starting_with(pat="https://") or src is starting_with(pat="//") or src is starting_with(pat="data:") -%} +{%- set image_src = src -%} +{%- elif page?.colocated_path -%} +{%- set image_src = get_url(path=page.colocated_path ~ src) -%} +{%- elif section?.colocated_path -%} +{%- set image_src = get_url(path=section.colocated_path ~ src) -%} +{%- else -%} +{%- set image_src = src -%} +{%- endif -%} +<figure> +<img src="{{ image_src }}"{% if alt %} alt="{{ alt }}"{% endif %}{% if height %} height="{{ height }}"{% endif %}{% if width %} width="{{ width }}"{% endif %}> +{%- if caption -%} +<figcaption>{{ caption | trim | markdown | trim | safe }}</figcaption> +{%- endif -%} +</figure> +{%- endcomponent figure %} + + +{% component mermaid() -%} +<pre class="mermaid"> +{{ body }} +</pre> +{%- endcomponent mermaid %} + + +{% component youtube(id: string, autoplay: bool = false) -%} +<div class="youtube"> +<iframe src="https://www.youtube.com/embed/{{ id }}{% if autoplay %}?autoplay=1&mute=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> +</div> +{%- endcomponent youtube %} diff --git a/src/themes/serene/templates/_components/seo.html b/src/themes/serene/templates/_components/seo.html new file mode 100644 index 0000000..361d7d2 --- /dev/null +++ b/src/themes/serene/templates/_components/seo.html @@ -0,0 +1,39 @@ +{% component seo(title, desc, config, url = "", page_type = "website", image = "", colocated_path = "", published = "", updated = "", tags = [], lang = "") %} +{%- set og_image = "" -%} +{%- if image -%} + {%- if image is starting_with(pat="http://") or image is starting_with(pat="https://") or image is starting_with(pat="//") -%} + {%- set og_image = image -%} + {%- elif image is starting_with(pat="/") -%} + {%- set og_image = get_url(path=image) -%} + {%- elif colocated_path -%} + {%- set og_image = get_url(path=colocated_path ~ image) -%} + {%- else -%} + {%- set og_image = get_url(path=image) -%} + {%- endif -%} +{%- elif config.extra.og_image -%} + {%- if config.extra.og_image is starting_with(pat="http://") or config.extra.og_image is starting_with(pat="https://") or config.extra.og_image is starting_with(pat="//") -%} + {%- set og_image = config.extra.og_image -%} + {%- else -%} + {%- set og_image = get_url(path=config.extra.og_image) -%} + {%- endif -%} +{%- endif -%} +<meta name="description" content="{{ desc | striptags | trim }}"> +{% if url %}<link rel="canonical" href="{{ url | safe }}">{% endif %} +<meta property="og:site_name" content="{{ config.title }}"> +<meta property="og:type" content="{{ page_type }}"> +<meta property="og:title" content="{{ title }}"> +<meta property="og:description" content="{{ desc | striptags | trim }}"> +{% if url %}<meta property="og:url" content="{{ url | safe }}">{% endif %} +{% if lang %}<meta property="og:locale" content="{{ lang | replace(from="-", to="_") }}">{% endif %} +{% if og_image %}<meta property="og:image" content="{{ og_image | safe }}">{% endif %} +{% if page_type == "article" %} +{% if published %}<meta property="article:published_time" content="{{ published }}">{% endif %} +{% if updated %}<meta property="article:modified_time" content="{{ updated }}">{% endif %} +{% if tags %}{% for tag in tags %}<meta property="article:tag" content="{{ tag }}"> +{% endfor %}{% endif %} +{% endif %} +<meta name="twitter:card" content="{% if og_image %}summary_large_image{% else %}summary{% endif %}"> +<meta name="twitter:title" content="{{ title }}"> +<meta name="twitter:description" content="{{ desc | striptags | trim }}"> +{% if og_image %}<meta name="twitter:image" content="{{ og_image | safe }}">{% endif %} +{% endcomponent seo %} diff --git a/src/themes/serene/templates/_footer.html b/src/themes/serene/templates/_footer.html index e9c1dd1..78d62c8 100644 --- a/src/themes/serene/templates/_footer.html +++ b/src/themes/serene/templates/_footer.html @@ -1,5 +1,10 @@ -{% if config.extra.blog_section_path is defined %} -{% set blog_section_path = config.extra.blog_section_path %} +{% set show_rss = false %} +{% if section is defined and section.generate_feeds %} + {% set rss_path = section.path ~ config.feed_filenames[0] %} + {% set show_rss = true %} +{% elif config.generate_feeds %} + {% set rss_path = "/" ~ config.feed_filenames[0] %} + {% set show_rss = true %} {% endif %} <footer> <div class="left"> @@ -13,18 +18,11 @@ </div> <div class="right"> - {% if blog_section_path is defined and section.path is starting_with(blog_section_path) %} - {% if section.generate_feeds %} - {% set_global rss_path = blog_section_path ~ "/" ~ config.feed_filenames.0 %} - {% elif config.generate_feeds %} - {% set_global rss_path = "/" ~ config.feed_filenames.0 %} - {% endif %} - {% if section.generate_feeds or config.generate_feeds %} + {% if show_rss %} <a id="rss-btn" href="{{ get_url(path=rss_path) }}">RSS</a> {% endif %} - {% endif %} - {% if not config.extra.force_theme %} + {% if color_scheme == "auto" %} {% set moon_icon = load_data(path="icon/moon.svg") %} {% set sun_icon = load_data(path="icon/sun.svg") %} <button id="theme-toggle" aria-label="theme switch"> @@ -35,8 +33,7 @@ </div> </footer> -{% if blog_section_path is defined and section.path is starting_with(blog_section_path) %} -{% if section.generate_feeds or config.generate_feeds %} +{% if show_rss %} {% set link = get_url(path=rss_path) %} <dialog id="rss-mask"> <div> @@ -49,4 +46,3 @@ </div> </dialog> {% endif %} -{% endif %} diff --git a/src/themes/serene/templates/feed.xml b/src/themes/serene/templates/feed.xml index fe1f139..00aebe5 100644 --- a/src/themes/serene/templates/feed.xml +++ b/src/themes/serene/templates/feed.xml @@ -1,32 +1,34 @@ <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}"> - <title>{% if section.title %}{{ section.title }}{% else %}{{ config.title }}{% endif %}</title> - {%- if section.description %} + <title>{% if section is defined and section.title %}{{ section.title }}{% else %}{{ config.title }}{% endif %}</title> + {%- if section is defined and section.description %} <subtitle>{{ section.description }}</subtitle> {%- elif config.description %} <subtitle>{{ config.description }}</subtitle> {%- endif %} - <link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/> - <link href=" - {%- if section -%} - {{ section.permalink | escape_xml | safe }} - {%- else -%} - {{ config.base_url | escape_xml | safe }} - {%- endif -%} - "/> - <updated>{{ last_updated | date(format="%+") }}</updated> + <link rel="self" type="application/atom+xml" href="{{ feed_url | safe }}"/> + <link rel="alternate" type="text/html" href=" + {%- if section is defined -%} + {{ section.permalink | escape_xml | safe }} + {%- else -%} + {{ config.base_url | escape_xml | safe }} + {%- endif -%} + "/> + {% if last_updated is defined %} + <updated>{{ last_updated | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</updated> + {% endif %} <id>{{ feed_url | safe }}</id> {%- for page in pages %} <entry xml:lang="{{ page.lang }}"> <title>{{ page.title }}</title> - <published>{{ page.date | date(format="%+") }}</published> - <updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated> - {%- if page.summary %} - <summary>{{ page.summary }}</summary> - {%- endif %} - <link href="{{ page.permalink | safe }}" type="text/html"/> + <published>{{ page.date | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</published> + <updated>{{ (page.updated or page.date) | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</updated> + <link rel="alternate" type="text/html" href="{{ page.permalink | safe }}"/> <id>{{ page.permalink | safe }}</id> - <content type="html">{{ page.content }}</content> + {% if page.summary %} + <summary>{{ page.summary }}</summary> + {% endif %} + <content type="html" xml:base="{{ page.permalink | escape_xml | safe }}">{{ page.content }}</content> </entry> {%- endfor %} -</feed>
\ No newline at end of file +</feed> diff --git a/src/themes/serene/templates/home.html b/src/themes/serene/templates/home.html index f899884..e074268 100644 --- a/src/themes/serene/templates/home.html +++ b/src/themes/serene/templates/home.html @@ -4,22 +4,28 @@ {% block lang %}{% if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ section.lang }}{% endif %}{% endblock lang %} {% block title %}{{ config.title }}{% endblock title %} {% block desc %} - <meta name="description" content="{{ config.description }}"> + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={config.title} + desc={config.description} + url={get_url(path="/")} + lang={og_lang} /> }} {% endblock desc %} {% block head %} -{% if config.markdown.highlight_theme == "css" %} - {% if config.extra.force_theme == "dark" %} - <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='hl-dark.css') }}" /> +{% if config.markdown.highlighting.style == "class" %} + {% if color_scheme == "dark" %} + <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='giallo-dark.css') }}" /> {% else %} - <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='hl-light.css') }}" /> + <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='giallo-light.css') }}" /> {% endif %} {% endif %} -{% if section.extra.math %} -<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous"> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/copy-tex.min.js" integrity="sha384-HORx6nWi8j5/mYA+y57/9/CZc5z8HnEw4WUZWy5yOn9ToKBv1l58vJaufFAn9Zzi" crossorigin="anonymous"></script> +{% set enable_math = section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> <script> document.addEventListener("DOMContentLoaded", function () { renderMathInElement(document.body, { @@ -40,36 +46,40 @@ <div id="wrapper"> <main> <section id="info"> - {% if section.extra.avatar %} - <img src="{{ get_url(path=section.extra.avatar) }}" alt="avatar"> + {% if config.extra.avatar %} + <img src="{{ get_url(path=config.extra.avatar) }}" alt="avatar"> {% endif %} <div id="text"> <div> - <span id="name">{{ section.extra.name }}</span> - {% if section.extra.id %} - <span id="id">@{{ section.extra.id }}</span> + <span id="name">{{ config.extra.name }}</span> + {% if config.extra.handle %} + <span id="id">@{{ config.extra.handle }}</span> {% endif %} </div> - {% if section.extra.bio %} - <div id="bio"> {{ section.extra.bio }} </div> + {% if config.extra.bio %} + <div id="bio"> {{ config.extra.bio }} </div> {% endif %} </div> </section> <section id="links"> <div id="left"> - {% for section in config.extra.sections %} - <a {% if section.is_external %}href="{{ section.path }}" target="_blank" rel='noreferrer noopener'{% else %}href="{{ get_url(path=section.path) }}" class="instant"{% endif %}>{{ section.name }}</a> + {% for item in config.extra.nav %} + {% if item.path is starting_with(pat="/") and item.path is not starting_with(pat="//") %} + <a href="{{ get_url(path=item.path) }}" class="instant">{{ item.name }}</a> + {% else %} + <a href="{{ item.path }}" class="external" target="_blank" rel='noreferrer noopener'>{{ item.name }}</a> + {% endif %} {% endfor %} </div> <div id="right"> - {% for link in section.extra.links %} - <a href="{{ link.url }}" aria-label="{{ link.name }}" target="_blank" rel='noreferrer noopener'> + {% for link in config.extra.links %} + <a href="{{ link.url }}" aria-label="{{ link.name }}" target="_blank" rel='noreferrer noopener{% if link.rel_me%} me{% endif %}'> {% set icon_path = "icon/" ~ link.icon ~ ".svg" %} {% set icon = load_data(path=icon_path) %} {{ icon | safe }} </a> {% endfor %} - {% if not section.extra.footer and not config.extra.force_theme %} + {% if not section.extra.footer and color_scheme == "auto" %} {% set moon_icon = load_data(path="icon/moon.svg") %} {% set sun_icon = load_data(path="icon/sun.svg") %} <button id="theme-toggle" aria-label="theme switch"> @@ -83,16 +93,16 @@ {{ section.content | trim | safe }} </section> {% if section.extra.recent %} - {% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} + {% set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") %} {% set section_md_path = blog_section_path ~ "/_index.md" %} {% set blog_section = get_section(path=section_md_path) %} + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} <section class="layout-list"> <div class="post-list"> - {% for post in blog_section.pages | slice(end=section.extra.recent_max) %} - <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink | safe }}"> + {% for post in blog_section.pages[:section.extra.recent_max] %} + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> <span>{{ post.title }}</span> - <span class="line"></span> - <span class="date">{{ post.date | date(format=section.extra.date_format) }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> </a> {% endfor %} </div> @@ -110,9 +120,10 @@ {% block script %} <script src="{{ get_url(path='js/lightense.min.js') }}"></script> -{% if section.extra.mermaid %} +{% set enable_mermaid = section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false) %} +{% if enable_mermaid %} <script type="module"> - import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {% endif %} diff --git a/src/themes/serene/templates/macros/collection.html b/src/themes/serene/templates/macros/collection.html deleted file mode 100644 index 796eae1..0000000 --- a/src/themes/serene/templates/macros/collection.html +++ /dev/null @@ -1,153 +0,0 @@ -{% macro card(item) %} -<blockquote class="collection card{% if item.featured %} featured{% endif %}"> - <div class="meta"> - {% if item.icon %} - <div class="icon-wrapper"> - <img class="icon no-lightense" src="{{ item.icon }}" alt="icon" width="16" height="16"> - </div> - {% endif %} - {% if item.link %} - <a class="title" href="{{ item.link }}" target="_blank" rel="noreferrer noopener">{{ item.title }}</a> - {% else %} - <div class="title">{{ item.title }}</div> - {% endif %} - {% if item.date %} - <div class="date">{{ item.date }}</div> - {% endif %} - </div> - {% if item.subtitle %} - <div class="subtitle">{{ item.subtitle }}</div> - {% endif %} - <div class="content">{{ item.content | trim | markdown | safe }}</div> - {% if item.tags %} - <div class="tags"> - {% for tag in item.tags %} - <div><span>#</span>{{ tag }}</div> - {% endfor %} - </div> - {% endif %} -</blockquote> -{% endmacro %} - - -{% macro card_simple(item) %} -<blockquote class="collection card-simple{% if item.featured %} featured{% endif %}"> - <div class="meta"> - {% if item.icon %} - <div class="icon-wrapper"> - <img class="icon no-lightense" src="{{ item.icon }}" alt="icon" width="16" height="16"> - </div> - {% endif %} - {% if item.link %} - <a class="title" href="{{ item.link }}" target="_blank" rel="noreferrer noopener">{{ item.title }}</a> - {% else %} - <div class="title">{{ item.title }}</div> - {% endif %} - <div class="content">{{ item.content | trim | markdown | safe }}</div> - {% if item.date %} - <div class="date">{{ item.date }}</div> - {% endif %} - </div> - <div class="content-narrow">{{ item.content | trim | markdown | safe }}</div> -</blockquote> -{% endmacro %} - - -{% macro entry(item) %} -{% if item.link %} -<a href="{{ item.link }}" target="_blank" rel="noreferrer noopener" class="collection entry"> -{% else %} -<blockquote class="collection entry"> -{% endif %} - {% if item.icon %} - <div class="icon-wrapper"> - <img class="icon no-lightense" src="{{ item.icon }}" alt="icon" width="16" height="16" /> - </div> - {% endif %} - {% if item.title or item.subtitle %} - <div class="text"> - {% if item.title %} - <div class="title">{{ item.title }}</div> - {% endif %} - {% if item.subtitle %} - <div class="subtitle">{{ item.subtitle }}</div> - {% endif %} - </div> - {% endif %} -{% if item.link %} -</a> -{% else %} -</blockquote> -{% endif %} -{% endmacro %} - - -{% macro box(item) %} -<div class="collection-box-wrapper"> -{% if item.link %} -<a href="{{ item.link }}" target="_blank" rel="noreferrer noopener" class="collection box"> -{% else %} -<blockquote class="collection box"> -{% endif %} - <div class="text"> - <div class="title">{{ item.title }}</div> - {% if item.subtitle %} - <div class="subtitle">{{ item.subtitle }}</div> - {% endif %} - </div> - {% if item.img %} - <img class="no-lightense{% if item.rotate %} rotate{% endif %}" src="{{ item.img }}" alt="{{ item.title }}" width="48" height="48" /> - {% else %} - <div class="placehold"></div> - {% endif %} -{% if item.link %} -</a> -{% else %} -</blockquote> -{% endif %} -</div> -{% endmacro %} - - -{% macro art(item) %} -<blockquote class="collection art"> - <div class="img-wrapper"> - <img src="{{ item.img }}" alt="{{ item.title }}" height="175"> - </div> - <div class="text"> - {% if item.link %} - <a class="title" href="{{ item.link }}" target="_blank" rel="noreferrer noopener" >{{ item.title }}</a> - {% else %} - <div class="title">{{ item.title }}</div> - {% endif %} - {% if item.subtitle %} - <div class="subtitle">{{ item.subtitle }}</div> - {% endif %} - {% if item.content %} - <div class="content">{{ item.content }}</div> - {% endif %} - {% if item.footer %} - <div class="footer">{{ item.footer }}</div> - {% endif %} - </div> -</blockquote> -{% endmacro %} - - -{% macro art_simple(item) %} -<blockquote class="collection art-simple"> - <div class="img-wrapper"> - <img src="{{ item.img }}" alt="{{ item.title }}" height="175"> - </div> - <div class="text"> - {% if item.link %} - <a class="title" href="{{ item.link }}" target="_blank" rel="noreferrer noopener" >{{ item.title }}</a> - {% else %} - <div class="title">{{ item.title }}</div> - {% endif %} - {% if item.subtitle %} - <div class="subtitle">{{ item.subtitle }}</div> - {% endif %} - </div> -</blockquote> -{% endmacro %} diff --git a/src/themes/serene/templates/macros/prose.html b/src/themes/serene/templates/macros/prose.html deleted file mode 100644 index bd9a894..0000000 --- a/src/themes/serene/templates/macros/prose.html +++ /dev/null @@ -1,32 +0,0 @@ -{% macro back_link(path) %} -<header> - <nav> - <a href="{{ path }}">← {{ config.extra.back_link_text }}</a> - </nav> -</header> -{% endmacro %} - - -{% macro callout(name) %} -<blockquote class="callout {{ name }} {% if title %}has-title{% else %}no-title{% endif %}"> - {% set icon = load_data(path="icon/" ~ name ~ ".svg") %} - {% if title %} - <p class="title"> - <span class="icon"> - {{ icon | safe }} - </span> - <strong>{{ title }}</strong> - </p> - <div class="content"> - {{ body | markdown | safe }} - </div> - {% else %} - <div class="icon"> - {{ icon | safe }} - </div> - <div class="content"> - {{ body | markdown | safe }} - </div> - {% endif %} -</blockquote> -{% endmacro %} diff --git a/src/themes/serene/templates/post.html b/src/themes/serene/templates/post.html index 24fe0cd..07f9782 100644 --- a/src/themes/serene/templates/post.html +++ b/src/themes/serene/templates/post.html @@ -1,15 +1,13 @@ -{% import "macros/prose.html" as macros %} {% extends "_base.html" %} {% block page %}post{% endblock page %} {% block lang -%} -{%- set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") -%} -{%- set section_md_path = blog_section_path ~ "/_index.md" -%} -{%- set section = get_section(path=section_md_path, metadata_only=true) -%} +{%- set section = get_section(path=page.ancestors | last, metadata_only=true) -%} {%- if page.extra.lang %}{{page.extra.lang}}{% elif section.extra.lang %}{{section.extra.lang}}{% else %}{{page.lang}}{% endif -%} {%- endblock lang %} {% block title %}{{ page.title }}{% endblock title %} {% block desc %} + {%- set section = get_section(path=page.ancestors | last, metadata_only=true) -%} {% if page.summary %} {% set desc = page.summary %} {% elif page.description %} @@ -19,18 +17,30 @@ {% else %} {% set desc = config.description %} {% endif %} - <meta name="description" content="{{ desc }}"> + {%- if page.extra.lang %}{% set og_lang = page.extra.lang %}{% elif section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = page.lang %}{% endif -%} + {{ <seo config + title={page.title} + desc={desc} + url={page.permalink} + page_type="article" + image={page.extra?.og_image or ""} + colocated_path={page.colocated_path or ""} + published={page.date or ""} + updated={page.updated or ""} + tags={page.taxonomies?.tags or []} + lang={og_lang} /> }} {% endblock desc %} {% block head %} -{% if config.markdown.highlight_theme == "css" %} -<link id="hl" rel="stylesheet" type="text/css" href="/hl-{% if config.extra.force_theme == "dark" %}dark{% else %}light{% endif %}.css" /> +{% if config.markdown.highlighting.style == "class" %} +<link id="hl" rel="stylesheet" type="text/css" href="/giallo-{% if color_scheme == "dark" %}dark{% else %}light{% endif %}.css" /> {% endif %} -{% if page.extra.math %} -<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous"> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/copy-tex.min.js" integrity="sha384-HORx6nWi8j5/mYA+y57/9/CZc5z8HnEw4WUZWy5yOn9ToKBv1l58vJaufFAn9Zzi" crossorigin="anonymous"></script> +{% set enable_math = page.extra.math if page.extra.math is defined else (section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false)) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> <script> document.addEventListener("DOMContentLoaded", function () { renderMathInElement(document.body, { @@ -51,7 +61,7 @@ <div id="wrapper"> <div id="blank"></div> <aside> - {% if page.extra.toc is defined %}{% set show_toc = page.extra.toc %}{% else %}{% set show_toc = section.extra.toc %}{% endif %} + {% set show_toc = page.extra.toc if page.extra.toc is defined else (section.extra.toc if section.extra.toc is defined else (config.extra.toc if config.extra.toc is defined else false)) %} {% if show_toc and page.toc %} <nav> <ul> @@ -80,10 +90,10 @@ {% endif %} </aside> <main> - {{ macros::back_link(path = get_url(path=section.path)) }} + {{ <back_link path={get_url(path=section.path)} config /> }} <div> - {% if page.extra.copy is defined %}{% set allow_copy = page.extra.copy %}{% else %}{% set allow_copy = section.extra.copy %}{% endif %} + {% set allow_copy = page.extra.code_copy if page.extra.code_copy is defined else (section.extra.code_copy if section.extra.code_copy is defined else (config.extra.code_copy if config.extra.code_copy is defined else false)) %} {% if allow_copy %} {% set copy_icon = load_data(path="icon/copy.svg") %} {% set check_icon = load_data(path="icon/check.svg") %} @@ -91,31 +101,33 @@ {% endif %} <article class="prose"> <h1>{{ page.title }}</h1> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} <div id="post-info"> <div id="date"> - <span id="publish">{{ page.date | date(format=section.extra.date_format) }}</span> + <span id="publish">{{ page.date | date(format=date_format) }}</span> {% if page.updated and page.updated != page.date -%} - <span>Updated on <span id="updated">{{ page.updated | date(format=section.extra.date_format) }}</span></span> + <span>Updated on <span id="updated">{{ page.updated | date(format=date_format) }}</span></span> {% endif -%} </div> {% if page.taxonomies.tags is defined %} <div id="tags"> {% for tag in page.taxonomies.tags -%} - {% set tag_slugify = tag | slugify -%} - <a class="instant" href="{{ config.base_url ~ '/tags/' ~ tag_slugify | safe }}"><span>#</span>{{ tag }}</a> + <a class="instant" href="{{ get_taxonomy_url(kind="tags", term=tag) }}"><span>#</span>{{ tag }}</a> {%- endfor %} </div> {% endif %} </div> - {% if page.extra.outdate_alert is defined %}{% set show_outdate_alert = page.extra.outdate_alert %}{% else %}{% set show_outdate_alert = section.extra.outdate_alert %}{% endif %} - {% if page.extra.outdate_alert_days is defined %}{% set outdate_alert_days = page.extra.outdate_alert_days %}{% else %}{% set outdate_alert_days = section.extra.outdate_alert_days %}{% endif %} + {% set show_outdated_alert = page.extra.outdated_alert if page.extra.outdated_alert is defined else (section.extra.outdated_alert if section.extra.outdated_alert is defined else (config.extra.outdated_alert if config.extra.outdated_alert is defined else false)) %} + {% set outdated_alert_days = page.extra.outdated_alert_days if page.extra.outdated_alert_days is defined else (section.extra.outdated_alert_days if section.extra.outdated_alert_days is defined else (config.extra.outdated_alert_days if config.extra.outdated_alert_days is defined else 120)) %} - {% if show_outdate_alert -%} - <blockquote id="outdate_alert" class="callout caution hidden" data-days="{{ outdate_alert_days }}" - data-alert-text-before="{{ section.extra.outdate_alert_text_before }}" - data-alert-text-after="{{ section.extra.outdate_alert_text_after }}"> + {% if show_outdated_alert -%} + {% set outdated_alert_text_before = section.extra.outdated_alert_text_before if section.extra.outdated_alert_text_before is defined else (config.extra.outdated_alert_text_before if config.extra.outdated_alert_text_before is defined else "") %} + {% set outdated_alert_text_after = section.extra.outdated_alert_text_after if section.extra.outdated_alert_text_after is defined else (config.extra.outdated_alert_text_after if config.extra.outdated_alert_text_after is defined else "") %} + <blockquote id="outdate_alert" class="markdown-alert-caution hidden" data-days="{{ outdated_alert_days }}" + data-alert-text-before="{{ outdated_alert_text_before }}" + data-alert-text-after="{{ outdated_alert_text_after }}"> <div class="content"></div> </blockquote> {% endif %} @@ -123,12 +135,12 @@ {{ page.content | safe }} </article> - {% if page.extra.reaction is defined %}{% set show_reaction = page.extra.reaction %}{% else %}{% set show_reaction = config.extra.reaction %}{% endif %} + {% set show_reaction = page.extra.reaction if page.extra.reaction is defined else (section.extra.reaction if section.extra.reaction is defined else (config.extra.reaction if config.extra.reaction is defined else false)) %} {% if show_reaction %} <div class="reaction {{ config.extra.reaction_align }}" data-endpoint="{{ config.extra.reaction_endpoint }}"></div> {% endif %} - {% if page.extra.comment is defined %}{% set show_comment = page.extra.comment %}{% else %}{% set show_comment = section.extra.comment %}{% endif %} + {% set show_comment = page.extra.comment if page.extra.comment is defined else (section.extra.comment if section.extra.comment is defined else (config.extra.comment if config.extra.comment is defined else false)) %} {% if show_comment %} <div class="giscus"></div> {% include "_giscus_script.html" %} @@ -142,9 +154,10 @@ {% block script %} <script src="/js/lightense.min.js"></script> -{% if page.extra.mermaid %} +{% set enable_mermaid = page.extra.mermaid if page.extra.mermaid is defined else (section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false)) %} +{% if enable_mermaid %} <script type="module"> - import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {% endif %} diff --git a/src/themes/serene/templates/blog.html b/src/themes/serene/templates/posts.html index 2519d28..9b3dec1 100644 --- a/src/themes/serene/templates/blog.html +++ b/src/themes/serene/templates/posts.html @@ -1,4 +1,3 @@ -{% import "macros/prose.html" as macros %} {% extends "_base.html" %} {% block page %}blog{% endblock page%} @@ -10,26 +9,35 @@ {% else %} {% set desc = config.description %} {% endif %} - <meta name="description" content="{{ desc }}"> + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={desc} + url={section.permalink} + lang={og_lang} /> }} {% endblock desc %} {% block content %} <div id="wrapper"> - {{ macros::back_link(path = get_url(path="/")) }} + {{ <back_link path={get_url(path="/")} config /> }} {% include "_section_title.html" %} <main class="layout-list"> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} {% if section.extra.categorized %} - {% for category,posts in section.pages | sort(attribute="taxonomies.categories.0") | group_by(attribute="taxonomies.categories.0") %} + {% set categorized_posts = [post for post in section.pages if post.taxonomies?.categories] %} + {% set categories = [post.taxonomies.categories[0] for post in categorized_posts] | unique | sort %} + {% for category in categories %} + {% set posts = [post for post in categorized_posts if post.taxonomies.categories[0] == category] %} {% set category_name = category %} - {% if category is matching("^__[0-9]{2}__") %} - {% set category_name = category | split(pat="") | slice(start=7) | join(sep="") %} + {% if category is matching(pat="^__[0-9]{2}__") %} + {% set category_name = category[6:] %} {% endif %} <h2 class="category">{{ category_name }}</h2> <div class="post-list categorized"> {% for post in posts %} - <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink | safe }}"> + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> <span>{{ post.title }}</span> - <span class="date">{{ post.date | date(format=section.extra.date_format) }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> </a> {% endfor %} </div> @@ -37,9 +45,9 @@ {% else %} <div class="post-list"> {% for post in section.pages %} - <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink | safe }}"> + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> <span>{{ post.title }}</span> - <span class="date">{{ post.date | date(format=section.extra.date_format) }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> </a> {% endfor %} </div> diff --git a/src/themes/serene/templates/prose.html b/src/themes/serene/templates/prose.html index 661c408..114ff16 100644 --- a/src/themes/serene/templates/prose.html +++ b/src/themes/serene/templates/prose.html @@ -1,4 +1,3 @@ -{% import "macros/prose.html" as macros %} {% extends "_base.html" %} {% block page %}prose-page{% endblock page %} @@ -12,18 +11,24 @@ {% else %} {% set desc = config.description %} {% endif %} - <meta name="description" content="{{ desc }}"> + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={desc} + url={section.permalink} + lang={og_lang} /> }} {% endblock desc %} {% block head %} -{% if config.markdown.highlight_theme == "css" %} -<link id="hl" rel="stylesheet" type="text/css" href="/hl-{% if config.extra.force_theme == "dark" %}dark{% else %}light{% endif %}.css" /> +{% if config.markdown.highlighting.style == "class" %} +<link id="hl" rel="stylesheet" type="text/css" href="/giallo-{% if color_scheme == "dark" %}dark{% else %}light{% endif %}.css" /> {% endif %} -{% if section.extra.math %} -<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous"> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script> -<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/copy-tex.min.js" integrity="sha384-HORx6nWi8j5/mYA+y57/9/CZc5z8HnEw4WUZWy5yOn9ToKBv1l58vJaufFAn9Zzi" crossorigin="anonymous"></script> +{% set enable_math = section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> <script> document.addEventListener("DOMContentLoaded", function () { renderMathInElement(document.body, { @@ -43,10 +48,11 @@ {% block content %} <div id="wrapper"> <main> - {{ macros::back_link(path = get_url(path="/")) }} + {{ <back_link path={get_url(path="/")} config /> }} {% include "_section_title.html" %} <div> - {% if section.extra.copy %} + {% set allow_copy = section.extra.code_copy if section.extra.code_copy is defined else (config.extra.code_copy if config.extra.code_copy is defined else false) %} + {% if allow_copy %} {% set copy_icon = load_data(path="icon/copy.svg") %} {% set check_icon = load_data(path="icon/check.svg") %} <div id="copy-cfg" style="display: none;" data-copy-icon="{{ copy_icon }}" data-check-icon="{{ check_icon }}"></div> @@ -55,12 +61,13 @@ {{ section.content | safe }} </article> - {% if section.extra.reaction is defined %}{% set show_reaction = section.extra.reaction %}{% else %}{% set show_reaction = config.extra.reaction %}{% endif %} + {% set show_reaction = section.extra.reaction if section.extra.reaction is defined else (config.extra.reaction if config.extra.reaction is defined else false) %} {% if show_reaction %} <div class="reaction {{ config.extra.reaction_align }}" data-endpoint="{{ config.extra.reaction_endpoint }}"></div> {% endif %} - {% if section.extra.comment %} + {% set show_comment = section.extra.comment if section.extra.comment is defined else (config.extra.comment if config.extra.comment is defined else false) %} + {% if show_comment %} <div class="giscus"></div> {% include "_giscus_script.html" %} {% endif %} @@ -73,9 +80,10 @@ {% block script %} <script src="/js/lightense.min.js"></script> -{% if section.extra.mermaid %} +{% set enable_mermaid = section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false) %} +{% if enable_mermaid %} <script type="module"> - import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {% endif %} diff --git a/src/themes/serene/templates/shortcodes/caution.html b/src/themes/serene/templates/shortcodes/caution.html deleted file mode 100644 index 7d1797f..0000000 --- a/src/themes/serene/templates/shortcodes/caution.html +++ /dev/null @@ -1,2 +0,0 @@ -{% import "macros/prose.html" as macros %} -{{ macros::callout(name="caution") }} diff --git a/src/themes/serene/templates/shortcodes/collection.html b/src/themes/serene/templates/shortcodes/collection.html deleted file mode 100644 index c217a0c..0000000 --- a/src/themes/serene/templates/shortcodes/collection.html +++ /dev/null @@ -1,23 +0,0 @@ -{% import 'macros/collection.html' as marcos -%} - -{% set data = load_data(path="content" ~ section.path ~ file, format="toml") %} - -<section class="collection-wrapper"> -{% for item in data.collection %} - {% if item.type == "card" %} - {{ marcos::card(item=item) }} - {% elif item.type == "card_simple" %} - {{ marcos::card_simple(item=item) }} - {% elif item.type == "entry" %} - {{ marcos::entry(item=item) }} - {% elif item.type == "box" %} - {{ marcos::box(item=item) }} - {% elif item.type == "art" %} - {{ marcos::art(item=item) }} - {% elif item.type == "art_simple" %} - {{ marcos::art_simple(item=item) }} - {% elif item.type == "br" %} - <br /> - {% endif %} -{% endfor %} -</section> diff --git a/src/themes/serene/templates/shortcodes/detail.html b/src/themes/serene/templates/shortcodes/detail.html deleted file mode 100644 index 0d34a74..0000000 --- a/src/themes/serene/templates/shortcodes/detail.html +++ /dev/null @@ -1,4 +0,0 @@ -<details {% if default_open %}open{% endif %}> - <summary><span>{{ title }}</span></summary> - {{ body | markdown | safe }} -</details> diff --git a/src/themes/serene/templates/shortcodes/figure.html b/src/themes/serene/templates/shortcodes/figure.html deleted file mode 100644 index 8a4123f..0000000 --- a/src/themes/serene/templates/shortcodes/figure.html +++ /dev/null @@ -1,8 +0,0 @@ -<figure> - <img src="{{ src }}" {% if alt %} alt="{{ alt }}"{% endif %}> - {% if via %} - <figcaption><a href="{{via}}">via</a></figcaption> - {% else %} - <figcaption>{{ caption }}</figcaption> - {% endif %} -</figure>
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/important.html b/src/themes/serene/templates/shortcodes/important.html deleted file mode 100644 index 451c141..0000000 --- a/src/themes/serene/templates/shortcodes/important.html +++ /dev/null @@ -1,2 +0,0 @@ -{% import "macros/prose.html" as macros %} -{{ macros::callout(name="important") }} diff --git a/src/themes/serene/templates/shortcodes/mermaid.html b/src/themes/serene/templates/shortcodes/mermaid.html deleted file mode 100644 index 3d3725d..0000000 --- a/src/themes/serene/templates/shortcodes/mermaid.html +++ /dev/null @@ -1,3 +0,0 @@ -<pre class="mermaid"> - {{ body }} -</pre>
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/note.html b/src/themes/serene/templates/shortcodes/note.html deleted file mode 100644 index b16657b..0000000 --- a/src/themes/serene/templates/shortcodes/note.html +++ /dev/null @@ -1,2 +0,0 @@ -{% import "macros/prose.html" as macros %} -{{ macros::callout(name="note") }} diff --git a/src/themes/serene/templates/shortcodes/quote.html b/src/themes/serene/templates/shortcodes/quote.html deleted file mode 100644 index c7e3c37..0000000 --- a/src/themes/serene/templates/shortcodes/quote.html +++ /dev/null @@ -1,10 +0,0 @@ -<blockquote class="quote"> - {% set icon = load_data(path="icon/quote.svg") %} - <div class="icon" style="display: none;">{{ icon | safe }}</div> - <div class="content">{{ body | markdown | safe }}</div> - {% if cite %} - <div class="from"> - {{ "— " ~ cite | markdown | safe }} - </div> - {% endif %} -</blockquote>
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/tip.html b/src/themes/serene/templates/shortcodes/tip.html deleted file mode 100644 index 8904437..0000000 --- a/src/themes/serene/templates/shortcodes/tip.html +++ /dev/null @@ -1,2 +0,0 @@ -{% import "macros/prose.html" as macros %} -{{ macros::callout(name="tip") }} diff --git a/src/themes/serene/templates/shortcodes/warning.html b/src/themes/serene/templates/shortcodes/warning.html deleted file mode 100644 index 0964b15..0000000 --- a/src/themes/serene/templates/shortcodes/warning.html +++ /dev/null @@ -1,2 +0,0 @@ -{% import "macros/prose.html" as macros %} -{{ macros::callout(name="warning") }} diff --git a/src/themes/serene/templates/tags/list.html b/src/themes/serene/templates/tags/list.html index bde1123..e2d48d7 100644 --- a/src/themes/serene/templates/tags/list.html +++ b/src/themes/serene/templates/tags/list.html @@ -1,26 +1,32 @@ -{% import "macros/prose.html" as macros %} {% extends "_base.html" %} {% block page %}tag-list{% endblock page%} {% block lang -%} -{% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} +{% set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") %} {% set section_md_path = blog_section_path ~ "/_index.md"%} {% set section = get_section(path=section_md_path, metadata_only=true) %} {%- if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ lang }}{% endif -%} {%- endblock lang %} {% block title %}Tags{% endblock title %} {% block desc %} - <meta name="description" content="Tags"> + {%- set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") -%} + {%- set section = get_section(path=blog_section_path ~ "/_index.md", metadata_only=true) -%} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = lang %}{% endif -%} + {{ <seo config + title="Tags" + desc="Tags" + url={current_url or ""} + lang={og_lang} /> }} {% endblock desc %} {% block content %} <div id="wrapper"> - {{ macros::back_link(path = get_url(path="/")) }} + {{ <back_link path={get_url(path="/")} config /> }} <main> <h1 class="title">Tags</h1> <div class="tags"> {% for tag in terms -%} - <a class="instant" href="{{ tag.permalink | safe }}"># {{ tag.name | lower }}</a> + <a class="instant" href="{{ tag.permalink }}"># {{ tag.name | lower }}</a> {% endfor %} </div> </main> diff --git a/src/themes/serene/templates/tags/single.html b/src/themes/serene/templates/tags/single.html index 1ce059a..ea9f360 100644 --- a/src/themes/serene/templates/tags/single.html +++ b/src/themes/serene/templates/tags/single.html @@ -1,30 +1,38 @@ -{% import "macros/prose.html" as macros %} {% extends "_base.html" %} {% block page %}tag-single{% endblock page %} {% block lang -%} -{% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} +{% set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") %} {% set section_md_path = blog_section_path ~ "/_index.md"%} {% set section = get_section(path=section_md_path, metadata_only=true) %} {%- if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ lang }}{% endif -%} {%- endblock lang %} {% block title %}{{section.title}}{% endblock title %} {% block desc %} - <meta name="description" content="{{ term.name }}"> + {%- set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") -%} + {%- set section = get_section(path=blog_section_path ~ "/_index.md", metadata_only=true) -%} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={term.name} + url={term.permalink} + lang={og_lang} /> }} {% endblock desc %} {% block content %} <div id="wrapper"> - {{ macros::back_link(path = get_url(path="/tags")) }} + {% set tags_taxonomy = get_taxonomy(kind="tags") %} + {{ <back_link path={tags_taxonomy.permalink} config /> }} <main class="layout-list"> <div class="title"> <span># {{ term.name }}</span> </div> <div class="post-list"> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} {% for post in term.pages %} - <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink | safe }}"> + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> <span>{{ post.title }}</span> - <span class="date">{{ post.date | date(format=section.extra.date_format) }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> </a> {% endfor %} </div> diff --git a/src/themes/serene/theme.toml b/src/themes/serene/theme.toml index 30efe2e..2d2b84a 100644 --- a/src/themes/serene/theme.toml +++ b/src/themes/serene/theme.toml @@ -1,8 +1,8 @@ name = "serene" -description = "A spiffy blog theme for zola" +description = "A minimal blog theme for zola, well crafted" license = "MIT" homepage = "https://github.com/isunjn/serene" -min_version = "0.20.0" +min_version = "0.23.4" demo = "https://serene-demo.pages.dev" [author] diff --git a/src/themes/serene/zola.toml b/src/themes/serene/zola.toml new file mode 100644 index 0000000..1c3a8e3 --- /dev/null +++ b/src/themes/serene/zola.toml @@ -0,0 +1,75 @@ +base_url = "https://serene-demo.pages.dev" +title = "Serene" +description = "" +default_language = "en" +# theme = "serene" +output_dir = "public" +compile_sass = true +minify_html = false +build_search_index = false +generate_feeds = false +feed_filenames = ["feed.xml"] +taxonomies = [{ name = "tags" }, { name = "categories" }] + +[markdown] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false +github_alerts = true + +[markdown.highlighting] +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= +[extra] + +nav = [ + { name = "posts", path = "/posts" }, + { name = "collections", path = "/collections" }, + { name = "about", path = "/about" }, +] + +name = "Serene" +handle = "serene" +bio = "A minimal blog theme for Zola, well crafted" +# avatar = "img/avatar.webp" +links = [ + { name = "GitHub", icon = "github", url = "https://github.com/isunjn/serene" }, + { name = "Email", icon = "email", url = "mailto:<your-email-address>" }, +] +blog_section_path = "/posts" + +back_link_text = "Back" +color_scheme = "auto" # "auto" | "light" | "dark" + +# og_image = "https://github.com/isunjn/serene/blob/main/screenshot.png?raw=true" + +footer_copyright = "© 2025 Serene Theme" +footer_credits = true + +not_found_error_text = "404 Not Found" +not_found_recover_text = "« back to home »" + +date_format = "%b %-d, %Y" +toc = true +code_copy = true +comment = false +math = false +mermaid = false +outdated_alert = false +outdated_alert_days = 120 +outdated_alert_text_before = "This article was last updated " +outdated_alert_text_after = " days ago and may be out of date." + +reaction = false +reaction_align = "right" +reaction_endpoint = "https://example.com/api/reaction" diff --git a/src/themes/serene/zola.toml.example b/src/themes/serene/zola.toml.example new file mode 100644 index 0000000..02f00fd --- /dev/null +++ b/src/themes/serene/zola.toml.example @@ -0,0 +1,80 @@ +base_url = "https://example.com" +title = "xxxx" +description = "xxxx xxxx xxxx" +default_language = "en" +theme = "serene" +output_dir = "public" +compile_sass = true +minify_html = false # Keep this false, as it may cause issues with some styles +build_search_index = false # Keep this false, search is temporarily unsupported +generate_feeds = false # Whether to generate a feed file in root, read docs for more info about rss feed +feed_filenames = ["feed.xml"] # "feed.xml" | "atom.xml" | "rss.xml", read docs for more info +taxonomies = [{ name = "tags" }, { name = "categories" }] + +[markdown] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false +github_alerts = true + +[markdown.highlighting] +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= + +[extra] + +nav = [ + { name = "posts", path = "/posts" }, + # { name = "tags", path = "/tags" }, + # { name = "github", path = "https://github.com/<your-username>" }, +] + +# Your info on the home page, if you don't want to display handle/bio/avatar, simply comment out that line +name = "Jhon Wick" +handle = "jhonwick" +bio = "dog person, killer" +avatar = "img/avatar.webp" +links = [ + { name = "GitHub", icon = "github", url = "https://github.com/<your-username>" }, + { name = "Email", icon = "email", url = "mailto:<your-email-address>" }, + { name = "Twitter", icon = "twitter", url = "https://twitter.com/<your-username>" }, + { name = "Mastodon", icon = "mastodon", url = "https://mastodon.social/<your-username>", rel_me = true }, +] +blog_section_path = "/posts" # Path of the main blog section, used by home page's recent posts and tags pages + +back_link_text = "Back" # Text of the back button +color_scheme = "auto" # "auto" | "light" | "dark" + +# og_image = "img/og.png" # The default image for social media sharing (Open Graph), a path in static folder or a full URL + +footer_copyright = "© 2025 <your-name>" +footer_credits = true # Whether to show "Built with zola and serene" in footer + +not_found_error_text = "404 Not Found" +not_found_recover_text = "« back to home »" + +# Site-wide default values of display options, can be overridden in a section's `_index.md`, and again in a post's front-matter +date_format = "%b %-d, %Y" +toc = true # show table-of-contents +code_copy = true # show copy button in code block +comment = false # enable comment +math = false # enable formula rendering with KaTeX +mermaid = false # enable chart rendering with Mermaid +outdated_alert = false # show outdate alert after certain days +outdated_alert_days = 120 +outdated_alert_text_before = "This article was last updated " +outdated_alert_text_after = " days ago and may be out of date." + +reaction = false # Whether to enable anonymous emoji reactions (Note: You need to set up a working api endpoint to enable this feature) +reaction_align = "right" # "left" | "center" | "right" +reaction_endpoint = "https://example.com/api/reaction" |
