@jant/core 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/uploads/cleanup.js +1 -0
- package/dist/{app-9P4rVCe2.js → app-CGHkOdme.js} +3450 -3121
- package/dist/app-D24n0DoH.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-CXnEhyyv.js → client-DYrWuaIk.js} +1 -1
- package/dist/client/_assets/{client-auth-CSItbyU8.js → client-auth-B5Re0uCd.js} +187 -167
- package/dist/client/_assets/client-xWDl78yi.css +2 -0
- package/dist/{export-Be082J0n.js → export-DY1v5Iqu.js} +2 -2
- package/dist/{github-sync-D1Cw8mOY.js → github-sync-2_T7nbOv.js} +1 -1
- package/dist/{github-sync-_kPWM4m9.js → github-sync-LefaslGJ.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +8 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +64 -12
- package/src/client/components/jant-compose-dialog.ts +12 -0
- package/src/client/components/jant-settings-general.ts +74 -21
- package/src/client/components/settings-types.ts +13 -0
- package/src/client/settings-bridge.ts +3 -0
- package/src/client/tiptap/__tests__/link-toolbar.test.ts +41 -0
- package/src/client/tiptap/__tests__/mark-exit.test.ts +99 -0
- package/src/client/tiptap/bubble-menu.ts +37 -4
- package/src/client/tiptap/link-toolbar.ts +63 -1
- package/src/db/migrations/0026_absent_rhodey.sql +14 -0
- package/src/db/migrations/meta/0026_snapshot.json +2511 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0024_high_violations.sql +14 -0
- package/src/db/migrations/pg/meta/0024_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +36 -0
- package/src/db/schema.ts +36 -0
- package/src/i18n/__tests__/middleware.test.ts +46 -0
- package/src/i18n/locales/settings/en.po +282 -27
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +282 -27
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +282 -27
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +17 -8
- package/src/i18n/supported-locales.ts +5 -4
- package/src/lib/__tests__/feed.test.ts +5 -1
- package/src/lib/feed.ts +6 -3
- package/src/lib/ids.ts +1 -0
- package/src/lib/resolve-config.ts +1 -0
- package/src/lib/upload.ts +14 -0
- package/src/routes/api/__tests__/settings.test.ts +1 -4
- package/src/routes/api/__tests__/upload.test.ts +2 -0
- package/src/routes/api/internal/__tests__/uploads.test.ts +19 -1
- package/src/routes/api/settings.ts +2 -1
- package/src/routes/auth/__tests__/setup.test.ts +14 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +35 -17
- package/src/routes/dash/settings.tsx +22 -4
- package/src/routes/feed/__tests__/feed.test.ts +58 -19
- package/src/routes/feed/feed.ts +37 -28
- package/src/routes/pages/featured.tsx +17 -0
- package/src/routes/pages/latest.tsx +25 -0
- package/src/services/__tests__/media.test.ts +191 -30
- package/src/services/__tests__/settings.test.ts +55 -0
- package/src/services/bootstrap.ts +7 -0
- package/src/services/export-theme/layouts/_default/baseof.html +2 -1
- package/src/services/media.ts +169 -42
- package/src/services/post.ts +1 -1
- package/src/services/settings.ts +49 -15
- package/src/services/upload-session.ts +13 -3
- package/src/styles/tokens.css +21 -4
- package/src/styles/ui.css +44 -1
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +13 -0
- package/src/ui/__tests__/color-themes.test.ts +2 -2
- package/src/ui/color-themes.ts +32 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +264 -29
- package/src/ui/dash/settings/GeneralContent.tsx +54 -4
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +3 -2
- package/src/ui/layouts/BaseLayout.tsx +3 -2
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +17 -4
- package/dist/app-DaxS_Cz-.js +0 -6
- package/dist/client/_assets/client-C6peCkkD.css +0 -2
|
@@ -3295,7 +3295,7 @@ function serializeMarkdownDocument(doc) {
|
|
|
3295
3295
|
}
|
|
3296
3296
|
//#endregion
|
|
3297
3297
|
//#region src/styles/tokens.css?raw
|
|
3298
|
-
var tokens_default = "/**\n * Design Tokens\n *\n * CSS custom properties for all visual aspects of the UI.\n * These are the stable customization API — override in custom CSS\n * to change typography, layout, surfaces, and element sizing.\n */\n\n:root {\n /* Typography — Font families */\n --font-cjk-serif-fallback:\n \"Songti SC\", STSong, SimSun, \"Songti TC\", PMingLiU, MingLiU,\n \"Noto Serif SC\", \"Noto Serif CJK SC\", \"Noto Serif TC\", \"Noto Serif CJK TC\";\n --font-body:\n system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n \"Helvetica Neue\", Helvetica, Arial, \"PingFang TC\", \"PingFang SC\",\n \"Hiragino Sans CNS\", \"Hiragino Sans GB\", \"Microsoft JhengHei\",\n \"Microsoft YaHei\", \"Noto Sans CJK TC\", \"Noto Sans CJK SC\", sans-serif;\n --font-heading:\n \"New York Small\", \"New York\", \"Iowan Old Style\", Charter,\n \"Bitstream Charter\", \"Source Serif 4\", Cambria, \"Sitka Text\", Georgia,\n var(--font-cjk-serif-fallback), ui-serif, serif;\n --font-site-title:\n \"New York Small\", \"New York\", \"Iowan Old Style\", Charter,\n \"Bitstream Charter\", \"Source Serif 4\", Cambria, \"Sitka Text\", Georgia,\n var(--font-cjk-serif-fallback), ui-serif, serif;\n --font-serif:\n var(--font-cjk-serif-fallback), ui-serif, \"New York Small\", \"New York\",\n \"Iowan Old Style\", Charter, Georgia, \"Times New Roman\", Times, serif;\n --font-ui:\n system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n \"Helvetica Neue\", Helvetica, Arial, \"PingFang TC\", \"PingFang SC\",\n \"Hiragino Sans CNS\", \"Hiragino Sans GB\", \"Microsoft JhengHei\",\n \"Microsoft YaHei\", \"Noto Sans CJK TC\", \"Noto Sans CJK SC\", sans-serif;\n --font-mono:\n ui-monospace, Menlo, Monaco, Consolas, \"Cascadia Code\", \"Courier New\",\n monospace;\n /*\n * Blockquote font family.\n *\n * Defaults to `inherit` so blockquotes follow the body font in sans-only\n * themes (Clean, Friendly, Bold) and serif-only themes (Tufte, Bookish).\n * Mixed themes that want a distinct blockquote voice — e.g. Classic, which\n * pairs a sans body with serif headings — override this token in their\n * `cssVariables` to point at a serif stack. This also restores a type-level\n * distinction for CJK, where italic is disabled.\n */\n --font-blockquote: inherit;\n\n /* Typography — Font weights */\n --fw-light: 300;\n --fw-regular: 400;\n --fw-medium: 500;\n --fw-semibold: 600;\n --fw-bold: 700;\n --fw-extrabold: 800;\n /*\n * Unified type scale.\n *\n * Every font-size in the system — reading content AND UI controls —\n * must reference one of these tokens. No raw rem/px values elsewhere.\n *\n * --type-display 2.7rem (40.5px) — page titles, h1\n * --type-title 2.1rem (31.5px) — h2, feed card titles\n * --type-subtitle 1.8rem (27px) — h3\n * --type-body 1.4rem (21px) — running text, form inputs\n * --type-secondary 1.1rem (16.5px) — meta, nav, sidenotes, UI labels\n * --type-base 1.0rem (15px) — code, UI buttons, controls\n * --type-sm 0.9rem (13.5px) — code blocks, small UI text\n * --type-xs 0.8rem (12px) — captions, descriptions, chips\n * --type-2xs 0.65rem (9.75px) — tiny labels, file sizes, badges\n */\n --type-display: 2.7rem;\n --type-title: 2.1rem;\n --type-subtitle: 1.8rem;\n --type-body: 1.4rem;\n --type-secondary: 1.1rem;\n --type-base: 1rem;\n --type-sm: 0.9rem;\n --type-xs: 0.8rem;\n --type-2xs: 0.65rem;\n\n /* Content reading tokens — desktop maps to core scale,\n mobile overrides below cap sizes for small screens.\n --type-content-scale uniformly scales all content sizes\n without affecting UI controls. */\n --type-content-scale: 0.78;\n --type-content-display: calc(var(--type-display) * var(--type-content-scale));\n --type-content-title: calc(var(--type-title) * var(--type-content-scale));\n --type-content-subtitle: calc(\n var(--type-subtitle) * var(--type-content-scale)\n );\n --type-content-body: calc(var(--type-body) * var(--type-content-scale));\n\n /* Semantic aliases */\n --type-code: var(--type-base);\n --type-code-block: var(--type-sm);\n --type-body-size: var(--type-content-body);\n --type-body-tracking: 0;\n --type-ui-title: var(--type-secondary);\n --type-ui-control: var(--type-base);\n --type-ui-meta: var(--type-base);\n --type-ui-hint: var(--type-sm);\n --type-ui-caption: var(--type-xs);\n --type-ui-micro: var(--type-2xs);\n --type-ui-input: var(--type-secondary);\n --type-thread-context: var(--type-base);\n --type-thread-context-title: var(--type-secondary);\n --type-thread-context-meta: var(--type-sm);\n --feed-note-title-size: var(--type-content-title);\n --feed-note-title-leading: var(--type-heading-leading);\n --type-body-leading: 1.5;\n --type-display-leading: 1.15;\n --type-heading-leading: 1.15;\n --type-heading-weight: var(--fw-regular);\n --type-heading-tracking: 0;\n --type-display-weight: var(--fw-regular);\n --type-display-tracking: 0;\n --type-label-weight: var(--fw-medium);\n --type-label-tracking: 0.08em;\n\n /* Layout — Tufte proportional model */\n --content-max-width: 42rem;\n --form-max-width: 42rem;\n /* compose dialogs + feed content cap */\n --layout-body-max-width: 1088px;\n --layout-content-width: 55%;\n --layout-sidenote-width: 50%;\n --layout-sidenote-margin: -60%;\n --site-padding: 1.5rem;\n --content-gap: 1rem;\n --space-xl: 2rem;\n --space-2xl: 4rem;\n /*\n * Home timeline vertical rhythm — the single source of truth for the gaps\n * between stacked sections on the home page:\n *\n * site description → separator → first post → divider → post → ...\n *\n * where \"separator\" is the description hairline (logged out) or the compose\n * prompt (logged in). Every one of those gaps derives from this token, and\n * the individual pieces (compose prompt, description hairline, post cards)\n * carry no rhythm margin of their own — the structural wrappers\n * (.site-home-header, hr.feed-divider) own the spacing. Retune the whole\n * feed cadence by changing this one value.\n *\n * Note: per-format card padding (.feed-quote-post, thread previews) is the\n * card's own internal spacing and is intentionally NOT part of this rhythm.\n *\n * Kept as a free-standing value (not pinned to the --space-* scale) so the\n * feed cadence can be tuned independently.\n */\n --site-feed-rhythm: 4.4rem;\n /*\n * The post footer's action row (reply / menu buttons) is a taller tap\n * target than its visible content, leaving a few px of dead space below\n * the last visible element of every post (~5.9px logged out from the\n * footer min-height, ~6.8px logged in from the menu button). hr.feed-divider\n * subtracts this from its top margin so the VISIBLE gap between posts equals\n * --site-feed-rhythm — the same as the header spacing. Re-measure if the\n * footer button size or meta font changes.\n */\n --post-footer-overshoot: 6px;\n /*\n * The site intro (description) block is deliberately tighter than\n * --site-feed-rhythm so it reads as a compact header unit rather than a\n * full feed section. -top: gap from the header nav down to the intro.\n * -bottom: gap from the intro down to its separator — the hairline (logged\n * out) or the compose prompt text (logged in). Same values in both states.\n */\n --site-intro-gap-top: 24px;\n --site-intro-gap-bottom: 36px;\n\n /* Sidebar layout (admin + site sidebar pages) */\n --sidebar-width: 12rem;\n --sidebar-gap: 2rem;\n\n /* Surfaces */\n --card-bg: var(--card);\n --card-radius: 0;\n --card-padding: 1rem;\n --card-border-width: 0;\n --card-shadow: none;\n\n /* Elements */\n --avatar-size: 28px;\n --avatar-radius: 50%;\n --media-radius: 0.5rem;\n\n /* Icons */\n --icon-stroke: 2;\n --icon-stroke-fine: 1.5;\n\n /* Derived color tokens (from BaseCoat variables) */\n --site-accent: var(--primary);\n --site-accent-text: var(--primary-foreground);\n --site-column-outline: var(--border);\n --site-border-light: color-mix(\n in srgb,\n var(--site-column-outline) 52%,\n transparent\n );\n --site-threadline: var(--border);\n --site-page-bg: var(--background);\n --site-elevated-bg: var(--background);\n --site-nav-hover-bg: var(--accent);\n --site-text-primary: var(--foreground);\n --site-text-secondary: var(--muted-foreground);\n --site-reading-title: color-mix(\n in oklch,\n var(--site-text-primary) 81%,\n black\n );\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 86%,\n black\n );\n --site-reading-body: color-mix(in oklch, var(--site-text-primary) 90%, black);\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 95%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 72%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 88%,\n var(--site-text-primary)\n );\n --site-content-link: inherit;\n --site-content-link-hover: var(--site-text-primary);\n --site-content-link-underline: color-mix(\n in srgb,\n var(--site-text-secondary) 58%,\n transparent\n );\n --site-reading-link: var(--site-reading-body);\n --site-reading-link-hover: var(--site-reading-heading);\n --site-reading-link-underline: color-mix(\n in srgb,\n var(--site-reading-meta) 58%,\n transparent\n );\n --site-text-placeholder: oklch(from var(--muted-foreground) l c h / 0.5);\n --site-media-outline: var(--border);\n --site-divider: var(--border);\n --site-feed-card-bg: color-mix(\n in srgb,\n var(--site-elevated-bg) 88%,\n var(--site-nav-hover-bg)\n );\n --site-feed-card-border: color-mix(\n in srgb,\n var(--site-divider) 78%,\n transparent\n );\n --site-feed-card-shadow: color-mix(\n in srgb,\n var(--site-text-primary) 12%,\n transparent\n );\n --site-feed-divider-color: color-mix(\n in srgb,\n var(--site-text-secondary) 30%,\n transparent\n );\n --site-feed-link-tint: color-mix(in srgb, var(--site-accent) 7%, transparent);\n --site-feed-quote-tint: color-mix(\n in srgb,\n var(--site-accent) 10%,\n transparent\n );\n --site-blockquote-rail: color-mix(\n in srgb,\n var(--site-accent) 22%,\n var(--site-divider)\n );\n --site-blockquote-bg: color-mix(\n in srgb,\n var(--site-feed-quote-tint) 62%,\n var(--site-page-bg)\n );\n --site-blockquote-text: color-mix(\n in oklch,\n var(--site-text-primary) 92%,\n black\n );\n --site-summary-blockquote-bg: color-mix(\n in srgb,\n var(--site-feed-quote-tint) 42%,\n var(--site-page-bg)\n );\n --site-reading-blockquote-rail: color-mix(\n in srgb,\n var(--site-reading-link) 18%,\n var(--site-reading-meta)\n );\n --site-reading-blockquote-bg: color-mix(\n in srgb,\n var(--site-accent) 6%,\n var(--site-page-bg)\n );\n --site-thread-context-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 58%,\n transparent\n );\n --site-thread-context-border: color-mix(\n in srgb,\n var(--site-divider) 74%,\n transparent\n );\n --site-thread-gap-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 42%,\n transparent\n );\n --site-thread-item-spacing: 32px;\n --site-thread-context-max-height: 160px;\n --site-thread-dot-ring: color-mix(\n in srgb,\n var(--site-accent) 16%,\n transparent\n );\n --compose-paper-bg: var(--site-page-bg);\n --compose-control-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 72%,\n var(--compose-paper-bg)\n );\n --compose-control-bg-strong: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 88%,\n var(--compose-paper-bg)\n );\n --compose-control-border: color-mix(\n in srgb,\n var(--site-column-outline) 68%,\n transparent\n );\n --compose-blockquote-rail: color-mix(\n in srgb,\n var(--site-accent) 24%,\n var(--compose-control-border)\n );\n --compose-blockquote-bg: color-mix(\n in srgb,\n var(--compose-control-bg) 56%,\n var(--compose-paper-bg)\n );\n --compose-blockquote-bg-focus: color-mix(\n in srgb,\n var(--compose-control-bg-strong) 70%,\n var(--compose-paper-bg)\n );\n --compose-blockquote-text: color-mix(\n in srgb,\n var(--site-text-primary) 88%,\n var(--site-text-secondary)\n );\n --compose-floating-bg: color-mix(\n in srgb,\n var(--compose-paper-bg) 94%,\n var(--site-nav-hover-bg) 6%\n );\n\n /* Search highlight */\n --search-mark-bg: oklch(0.92 0.14 90 / 0.55);\n --search-mark-color: oklch(0.35 0.09 70);\n\n /* Admin */\n --dash-bg: oklch(0.97 0.005 80);\n --dash-card-radius: 10px;\n}\n\n@media (max-width: 760px) {\n :root {\n --site-padding: 1.875rem;\n --layout-content-width: 100%;\n }\n}\n\n/*\n * Dark-mode reading color overrides.\n *\n * These rules must beat the active color theme's light-mode block, which\n * uses `:root:root { ... }` (specificity 0,0,2,0) with no media query and\n * therefore applies in both light and dark modes. Most themes do not\n * redefine `--site-reading-*` in their dark block, so without higher\n * specificity here the light reading-body color would leak into dark mode\n * (resulting in near-invisible body text on a dark background).\n *\n * We repeat `:root:root` to reach specificity 0,0,3,0, which outranks the\n * theme's light `:root:root` (0,0,2,0). The theme's dark blocks use\n * `:root:root[data-theme-mode=\"dark\"]` or `:root:root:not([data-theme-mode=\"light\"])`\n * (also 0,0,3,0), so a theme that explicitly defines dark reading colors\n * still wins via source order.\n */\n@media (prefers-color-scheme: dark) {\n :root:root:not([data-theme-mode=\"light\"]) {\n --site-reading-title: var(--site-text-primary);\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 94%,\n var(--site-text-secondary)\n );\n --site-reading-body: color-mix(\n in oklch,\n var(--site-text-primary) 96%,\n black\n );\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 98%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 92%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 96%,\n var(--site-text-primary)\n );\n --site-reading-link: var(--site-reading-body);\n --search-mark-bg: oklch(0.45 0.1 85 / 0.5);\n --search-mark-color: oklch(0.92 0.08 90);\n --dash-bg: oklch(0.2 0.005 80);\n }\n}\n\n:root:root[data-theme-mode=\"dark\"] {\n --site-reading-title: var(--site-text-primary);\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 94%,\n var(--site-text-secondary)\n );\n --site-reading-body: color-mix(in oklch, var(--site-text-primary) 96%, black);\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 98%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 92%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 96%,\n var(--site-text-primary)\n );\n --site-reading-link: var(--site-reading-body);\n --search-mark-bg: oklch(0.45 0.1 85 / 0.5);\n --search-mark-color: oklch(0.92 0.08 90);\n --dash-bg: oklch(0.2 0.005 80);\n}\n\n@media (max-width: 760px), (hover: none) and (pointer: coarse) {\n :root {\n /* Content layer — tighter scale for mobile reading\n Ratio ≈ 1.9 : 1.4 : 1.15 : 1 (display:title:subtitle:body)\n 13px base → 31.9 / 24 / 19.5 / 16.9 */\n --type-content-display: calc(2.45rem * var(--type-content-scale));\n --type-content-title: calc(1.85rem * var(--type-content-scale));\n --type-content-subtitle: calc(1.5rem * var(--type-content-scale));\n --type-content-body: calc(1.3rem * var(--type-content-scale));\n\n /* UI layer — pixel floors for touch targets */\n --type-ui-title: max(16px, var(--type-secondary));\n --type-ui-control: max(15px, var(--type-base));\n --type-ui-meta: max(15px, var(--type-secondary));\n --type-ui-hint: max(13px, var(--type-sm));\n --type-ui-caption: max(13px, var(--type-xs));\n --type-ui-micro: max(12px, var(--type-2xs));\n --type-ui-input: max(16px, var(--type-secondary));\n --type-thread-context: max(15px, var(--type-base));\n --type-thread-context-title: max(16px, var(--type-secondary));\n --type-thread-context-meta: max(14px, var(--type-sm));\n }\n}\n";
|
|
3298
|
+
var tokens_default = "/**\n * Design Tokens\n *\n * CSS custom properties for all visual aspects of the UI.\n * These are the stable customization API — override in custom CSS\n * to change typography, layout, surfaces, and element sizing.\n */\n\n:root {\n /* Typography — Font families */\n --font-cjk-serif-fallback:\n \"Songti SC\", STSong, SimSun, \"Songti TC\", PMingLiU, MingLiU,\n \"Noto Serif SC\", \"Noto Serif CJK SC\", \"Noto Serif TC\", \"Noto Serif CJK TC\";\n --font-body:\n system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n \"Helvetica Neue\", Helvetica, Arial, \"PingFang TC\", \"PingFang SC\",\n \"Hiragino Sans CNS\", \"Hiragino Sans GB\", \"Microsoft JhengHei\",\n \"Microsoft YaHei\", \"Noto Sans CJK TC\", \"Noto Sans CJK SC\", sans-serif;\n --font-heading:\n \"New York Small\", \"New York\", \"Iowan Old Style\", Charter,\n \"Bitstream Charter\", \"Source Serif 4\", Cambria, \"Sitka Text\", Georgia,\n var(--font-cjk-serif-fallback), ui-serif, serif;\n --font-site-title:\n \"New York Small\", \"New York\", \"Iowan Old Style\", Charter,\n \"Bitstream Charter\", \"Source Serif 4\", Cambria, \"Sitka Text\", Georgia,\n var(--font-cjk-serif-fallback), ui-serif, serif;\n --font-serif:\n var(--font-cjk-serif-fallback), ui-serif, \"New York Small\", \"New York\",\n \"Iowan Old Style\", Charter, Georgia, \"Times New Roman\", Times, serif;\n --font-ui:\n system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n \"Helvetica Neue\", Helvetica, Arial, \"PingFang TC\", \"PingFang SC\",\n \"Hiragino Sans CNS\", \"Hiragino Sans GB\", \"Microsoft JhengHei\",\n \"Microsoft YaHei\", \"Noto Sans CJK TC\", \"Noto Sans CJK SC\", sans-serif;\n --font-mono:\n ui-monospace, Menlo, Monaco, Consolas, \"Cascadia Code\", \"Courier New\",\n monospace;\n /*\n * Blockquote font family.\n *\n * Defaults to `inherit` so blockquotes follow the body font in sans-only\n * themes (Clean, Friendly, Bold) and serif-only themes (Tufte, Bookish).\n * Mixed themes that want a distinct blockquote voice — e.g. Classic, which\n * pairs a sans body with serif headings — override this token in their\n * `cssVariables` to point at a serif stack. This also restores a type-level\n * distinction for CJK, where italic is disabled.\n */\n --font-blockquote: inherit;\n\n /* Typography — Font weights */\n --fw-light: 300;\n --fw-regular: 400;\n --fw-medium: 500;\n --fw-semibold: 600;\n --fw-bold: 700;\n --fw-extrabold: 800;\n /*\n * Unified type scale.\n *\n * Every font-size in the system — reading content AND UI controls —\n * must reference one of these tokens. No raw rem/px values elsewhere.\n *\n * --type-display 2.7rem (40.5px) — page titles, h1\n * --type-title 2.1rem (31.5px) — h2, feed card titles\n * --type-subtitle 1.8rem (27px) — h3\n * --type-body 1.4rem (21px) — running text, form inputs\n * --type-secondary 1.1rem (16.5px) — meta, nav, sidenotes, UI labels\n * --type-base 1.0rem (15px) — code, UI buttons, controls\n * --type-sm 0.9rem (13.5px) — code blocks, small UI text\n * --type-xs 0.8rem (12px) — captions, descriptions, chips\n * --type-2xs 0.65rem (9.75px) — tiny labels, file sizes, badges\n */\n --type-display: 2.7rem;\n --type-title: 2.1rem;\n --type-subtitle: 1.8rem;\n --type-body: 1.4rem;\n --type-secondary: 1.1rem;\n --type-base: 1rem;\n --type-sm: 0.9rem;\n --type-xs: 0.8rem;\n --type-2xs: 0.65rem;\n\n /* Content reading tokens — desktop maps to core scale,\n mobile overrides below cap sizes for small screens.\n --type-content-scale uniformly scales all content sizes\n without affecting UI controls. */\n --type-content-scale: 0.78;\n --type-content-display: calc(var(--type-display) * var(--type-content-scale));\n --type-content-title: calc(var(--type-title) * var(--type-content-scale));\n --type-content-subtitle: calc(\n var(--type-subtitle) * var(--type-content-scale)\n );\n --type-content-body: calc(var(--type-body) * var(--type-content-scale));\n\n /* Semantic aliases */\n --type-code: var(--type-base);\n --type-code-block: var(--type-sm);\n --type-body-size: var(--type-content-body);\n --type-body-tracking: 0;\n --type-ui-title: var(--type-secondary);\n --type-ui-control: var(--type-base);\n --type-ui-meta: var(--type-base);\n --type-ui-hint: var(--type-sm);\n --type-ui-caption: var(--type-xs);\n --type-ui-micro: var(--type-2xs);\n --type-ui-input: var(--type-secondary);\n --type-thread-context: var(--type-base);\n --type-thread-context-title: var(--type-secondary);\n --type-thread-context-meta: var(--type-sm);\n --feed-note-title-size: var(--type-content-title);\n --feed-note-title-leading: var(--type-heading-leading);\n --type-body-leading: 1.5;\n --type-display-leading: 1.15;\n --type-heading-leading: 1.15;\n --type-heading-weight: var(--fw-regular);\n --type-heading-tracking: 0;\n --type-display-weight: var(--fw-regular);\n --type-display-tracking: 0;\n --type-label-weight: var(--fw-medium);\n --type-label-tracking: 0.08em;\n\n /* Layout — Tufte proportional model */\n --content-max-width: 42rem;\n --form-max-width: 42rem;\n /* compose dialogs + feed content cap */\n --layout-body-max-width: 1088px;\n --layout-content-width: 55%;\n --layout-sidenote-width: 50%;\n --layout-sidenote-margin: -60%;\n --site-padding: 1.5rem;\n --content-gap: 1rem;\n --space-xl: 2rem;\n --space-2xl: 4rem;\n /*\n * Home timeline vertical rhythm — the single source of truth for the gaps\n * between stacked sections on the home page:\n *\n * site description → separator → first post → divider → post → ...\n *\n * where \"separator\" is the description hairline (logged out) or the compose\n * prompt (logged in). Every one of those gaps derives from this token, and\n * the individual pieces (compose prompt, description hairline, post cards)\n * carry no rhythm margin of their own — the structural wrappers\n * (.site-home-header, hr.feed-divider) own the spacing. Retune the whole\n * feed cadence by changing this one value.\n *\n * Note: per-format card padding (.feed-quote-post, thread previews) is the\n * card's own internal spacing and is intentionally NOT part of this rhythm.\n *\n * Kept as a free-standing value (not pinned to the --space-* scale) so the\n * feed cadence can be tuned independently.\n */\n --site-feed-rhythm: 4.4rem;\n /*\n * The post footer's action row (reply / menu buttons) is a taller tap\n * target than its visible content, leaving a few px of dead space below\n * the last visible element of every post (~5.9px logged out from the\n * footer min-height, ~6.8px logged in from the menu button). hr.feed-divider\n * subtracts this from its top margin so the VISIBLE gap between posts equals\n * --site-feed-rhythm — the same as the header spacing. Re-measure if the\n * footer button size or meta font changes.\n */\n --post-footer-overshoot: 6px;\n /*\n * The site intro (description) block is deliberately tighter than\n * --site-feed-rhythm so it reads as a compact header unit rather than a\n * full feed section. -top: gap from the header nav down to the intro.\n * -bottom: gap from the intro down to its separator — the hairline (logged\n * out) or the compose prompt text (logged in). Same values in both states.\n */\n --site-intro-gap-top: 24px;\n --site-intro-gap-bottom: 36px;\n\n /* Sidebar layout (admin + site sidebar pages) */\n --sidebar-width: 12rem;\n --sidebar-gap: 2rem;\n\n /* Surfaces */\n --card-bg: var(--card);\n --card-radius: 0;\n --card-padding: 1rem;\n --card-border-width: 0;\n --card-shadow: none;\n\n /* Elements */\n --avatar-size: 28px;\n --avatar-radius: 50%;\n --media-radius: 0.5rem;\n\n /* Icons */\n --icon-stroke: 2;\n --icon-stroke-fine: 1.5;\n\n /* Derived color tokens (from BaseCoat variables) */\n --site-accent: var(--primary);\n --site-accent-text: var(--primary-foreground);\n --site-column-outline: var(--border);\n --site-border-light: color-mix(\n in srgb,\n var(--site-column-outline) 52%,\n transparent\n );\n --site-threadline: var(--border);\n --site-page-bg: var(--background);\n --site-elevated-bg: var(--background);\n --site-nav-hover-bg: var(--accent);\n --site-text-primary: var(--foreground);\n --site-text-secondary: var(--muted-foreground);\n --site-reading-title: color-mix(\n in oklch,\n var(--site-text-primary) 81%,\n black\n );\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 86%,\n black\n );\n --site-reading-body: color-mix(in oklch, var(--site-text-primary) 90%, black);\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 95%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 72%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 88%,\n var(--site-text-primary)\n );\n --site-content-link: inherit;\n --site-content-link-hover: var(--site-text-primary);\n --site-content-link-underline: color-mix(\n in srgb,\n var(--site-text-secondary) 58%,\n transparent\n );\n --site-reading-link: var(--site-reading-body);\n --site-reading-link-hover: var(--site-reading-heading);\n --site-reading-link-underline: color-mix(\n in srgb,\n var(--site-reading-meta) 58%,\n transparent\n );\n --site-text-placeholder: oklch(from var(--muted-foreground) l c h / 0.5);\n --site-media-outline: var(--border);\n --site-divider: var(--border);\n --site-feed-card-bg: color-mix(\n in srgb,\n var(--site-elevated-bg) 88%,\n var(--site-nav-hover-bg)\n );\n --site-feed-card-border: color-mix(\n in srgb,\n var(--site-divider) 78%,\n transparent\n );\n --site-feed-card-shadow: color-mix(\n in srgb,\n var(--site-text-primary) 12%,\n transparent\n );\n --site-feed-divider-color: color-mix(\n in srgb,\n var(--site-text-secondary) 30%,\n transparent\n );\n --site-feed-link-tint: color-mix(in srgb, var(--site-accent) 7%, transparent);\n --site-feed-quote-tint: color-mix(\n in srgb,\n var(--site-accent) 10%,\n transparent\n );\n --site-blockquote-rail: color-mix(\n in srgb,\n var(--site-accent) 22%,\n var(--site-divider)\n );\n --site-blockquote-bg: color-mix(\n in srgb,\n var(--site-feed-quote-tint) 62%,\n var(--site-page-bg)\n );\n --site-blockquote-text: color-mix(\n in oklch,\n var(--site-text-primary) 92%,\n black\n );\n --site-summary-blockquote-bg: color-mix(\n in srgb,\n var(--site-feed-quote-tint) 42%,\n var(--site-page-bg)\n );\n --site-reading-blockquote-rail: color-mix(\n in srgb,\n var(--site-reading-link) 18%,\n var(--site-reading-meta)\n );\n --site-reading-blockquote-bg: color-mix(\n in srgb,\n var(--site-accent) 6%,\n var(--site-page-bg)\n );\n --site-thread-context-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 58%,\n transparent\n );\n --site-thread-context-border: color-mix(\n in srgb,\n var(--site-divider) 74%,\n transparent\n );\n --site-thread-gap-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 42%,\n transparent\n );\n --site-thread-item-spacing: 32px;\n --site-thread-context-max-height: 160px;\n --site-thread-dot-ring: color-mix(\n in srgb,\n var(--site-accent) 16%,\n transparent\n );\n --compose-paper-bg: var(--site-page-bg);\n --compose-control-bg: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 72%,\n var(--compose-paper-bg)\n );\n --compose-control-bg-strong: color-mix(\n in srgb,\n var(--site-nav-hover-bg) 88%,\n var(--compose-paper-bg)\n );\n --compose-control-border: color-mix(\n in srgb,\n var(--site-column-outline) 68%,\n transparent\n );\n --compose-blockquote-rail: color-mix(\n in srgb,\n var(--site-accent) 24%,\n var(--compose-control-border)\n );\n --compose-blockquote-bg: color-mix(\n in srgb,\n var(--compose-control-bg) 56%,\n var(--compose-paper-bg)\n );\n --compose-blockquote-bg-focus: color-mix(\n in srgb,\n var(--compose-control-bg-strong) 70%,\n var(--compose-paper-bg)\n );\n --compose-blockquote-text: color-mix(\n in srgb,\n var(--site-text-primary) 88%,\n var(--site-text-secondary)\n );\n --compose-floating-bg: color-mix(\n in srgb,\n var(--compose-paper-bg) 94%,\n var(--site-nav-hover-bg) 6%\n );\n\n /* Search highlight */\n --search-mark-bg: oklch(0.92 0.14 90 / 0.55);\n --search-mark-color: oklch(0.35 0.09 70);\n\n /* Admin */\n --dash-bg: oklch(0.97 0.005 80);\n --dash-card-radius: 10px;\n}\n\n@media (max-width: 760px) {\n :root {\n --site-padding: 1.875rem;\n }\n}\n\n/* Tufte two-column → single-column collapse.\n *\n * Below this width the post column is already narrowed to `min(100%, 35rem)`\n * (preset.css) and there is no room for a 45% sidenote gutter, so all content\n * goes full-width. Single images (MediaGallery getSingleVisualWidth) and link\n * previews (.link-preview) read this token directly, so they collapse here too.\n *\n * Keep this breakpoint in sync with the post-column collapse (preset.css /\n * ui.css feed-divider, both `max-width: 1024px`) and the sidenote float→inline\n * collapse (ui.css). They are one layout switch; do not let them drift apart. */\n@media (max-width: 1024px) {\n :root {\n --layout-content-width: 100%;\n }\n}\n\n/*\n * Dark-mode reading color overrides.\n *\n * These rules must beat the active color theme's light-mode block, which\n * uses `:root:root { ... }` (specificity 0,0,2,0) with no media query and\n * therefore applies in both light and dark modes. Most themes do not\n * redefine `--site-reading-*` in their dark block, so without higher\n * specificity here the light reading-body color would leak into dark mode\n * (resulting in near-invisible body text on a dark background).\n *\n * We repeat `:root:root` to reach specificity 0,0,3,0, which outranks the\n * theme's light `:root:root` (0,0,2,0). The theme's dark blocks use\n * `:root:root[data-theme-mode=\"dark\"]` or `:root:root:not([data-theme-mode=\"light\"])`\n * (also 0,0,3,0), so a theme that explicitly defines dark reading colors\n * still wins via source order.\n */\n@media (prefers-color-scheme: dark) {\n :root:root:not([data-theme-mode=\"light\"]) {\n --site-reading-title: var(--site-text-primary);\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 94%,\n var(--site-text-secondary)\n );\n --site-reading-body: color-mix(\n in oklch,\n var(--site-text-primary) 96%,\n black\n );\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 98%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 92%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 96%,\n var(--site-text-primary)\n );\n --site-reading-link: var(--site-reading-body);\n --search-mark-bg: oklch(0.45 0.1 85 / 0.5);\n --search-mark-color: oklch(0.92 0.08 90);\n --dash-bg: oklch(0.2 0.005 80);\n }\n}\n\n:root:root[data-theme-mode=\"dark\"] {\n --site-reading-title: var(--site-text-primary);\n --site-reading-heading: color-mix(\n in oklch,\n var(--site-text-primary) 94%,\n var(--site-text-secondary)\n );\n --site-reading-body: color-mix(in oklch, var(--site-text-primary) 96%, black);\n --site-reading-quote: color-mix(\n in oklch,\n var(--site-text-primary) 98%,\n black\n );\n --site-reading-meta: color-mix(\n in srgb,\n var(--site-text-secondary) 92%,\n var(--site-text-primary)\n );\n --site-reading-caption: color-mix(\n in srgb,\n var(--site-text-secondary) 96%,\n var(--site-text-primary)\n );\n --site-reading-link: var(--site-reading-body);\n --search-mark-bg: oklch(0.45 0.1 85 / 0.5);\n --search-mark-color: oklch(0.92 0.08 90);\n --dash-bg: oklch(0.2 0.005 80);\n}\n\n@media (max-width: 760px), (hover: none) and (pointer: coarse) {\n :root {\n /* Content layer — tighter scale for mobile reading.\n Ratio ≈ 1.88 : 1.42 : 1.15 : 1 (display:title:subtitle:body).\n At 15px root × 0.78 content-scale: 28.7 / 21.6 / 17.6 / 15.2px.\n Body is floored to a 16px minimum below (max()) for readability;\n the floor still yields to calc() when the user zooms the root up. */\n --type-content-display: calc(2.45rem * var(--type-content-scale));\n --type-content-title: calc(1.85rem * var(--type-content-scale));\n --type-content-subtitle: calc(1.5rem * var(--type-content-scale));\n --type-content-body: max(16px, calc(1.3rem * var(--type-content-scale)));\n\n /* UI layer — pixel floors for touch targets */\n --type-ui-title: max(16px, var(--type-secondary));\n --type-ui-control: max(15px, var(--type-base));\n --type-ui-meta: max(15px, var(--type-secondary));\n --type-ui-hint: max(13px, var(--type-sm));\n --type-ui-caption: max(13px, var(--type-xs));\n --type-ui-micro: max(12px, var(--type-2xs));\n --type-ui-input: max(16px, var(--type-secondary));\n --type-thread-context: max(15px, var(--type-base));\n --type-thread-context-title: max(16px, var(--type-secondary));\n --type-thread-context-meta: max(14px, var(--type-sm));\n }\n}\n";
|
|
3299
3299
|
//#endregion
|
|
3300
3300
|
//#region src/services/export-theme/theme.toml?raw
|
|
3301
3301
|
var theme_default = "name = \"jant\"\nlicense = \"MIT\"\nlicenselink = \"https://github.com/jant-me/jant/blob/main/LICENSE\"\ndescription = \"Default theme packaged with Jant exports.\"\nhomepage = \"https://jant.so\"\ntags = [\"blog\", \"microblog\", \"minimal\"]\nfeatures = [\"pagination\", \"aliases\"]\nmin_version = \"0.160.1\"\n\n[author]\n name = \"Jant\"\n homepage = \"https://jant.so\"\n";
|
|
@@ -3310,7 +3310,7 @@ var client_site_default$1 = "var e=null,t=0,n=!1,r=null,i=null,a=0;function o(e)
|
|
|
3310
3310
|
var client_site_default = "@keyframes lightbox-fade-in{0%{opacity:0}to{opacity:1}}@keyframes lightbox-scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.media-lightbox{background:0 0;border:none;outline:none;width:100%;max-width:100%;height:100%;max-height:100%;padding:0}.media-lightbox[open]{animation:.2s both lightbox-fade-in}.media-lightbox::backdrop{background-color:#000}.media-lightbox-content{outline:none;width:100%;height:100dvh;position:relative}.media-lightbox-stage{box-sizing:border-box;justify-content:center;align-items:center;width:100%;height:100%;padding:64px 96px;display:flex;overflow:hidden}.media-lightbox-stage-scroll{overscroll-behavior:contain;scrollbar-gutter:stable both-edges;-webkit-overflow-scrolling:touch;align-items:flex-start;overflow:hidden auto}.media-lightbox-img{object-fit:contain;border-radius:4px;max-width:100%;max-height:100%;animation:.28s cubic-bezier(.22,1,.36,1) both lightbox-scale-in;display:block}.media-lightbox-img-zoomable{cursor:zoom-in}.media-lightbox-img-scroll{width:min(100%,44rem);max-width:none;height:auto;max-height:none;margin:0 auto}.media-lightbox-img-zoomable.media-lightbox-img-scroll{cursor:zoom-out}.media-lightbox-close{z-index:10;-webkit-backdrop-filter:blur(8px);color:#fff;cursor:pointer;background-color:#00000080;border:none;border-radius:50%;justify-content:center;align-items:center;width:40px;height:40px;transition:background-color .15s;display:flex;position:fixed;top:16px;left:16px}.media-lightbox-close:hover{background-color:#000000b3}.media-lightbox-nav{z-index:10;-webkit-backdrop-filter:blur(8px);color:#fff;cursor:pointer;background-color:#00000080;border:none;border-radius:50%;justify-content:center;align-items:center;width:44px;height:44px;transition:background-color .15s;display:flex;position:fixed;top:50%;transform:translateY(-50%)}.media-lightbox-nav:hover{background-color:#000000b3}.media-lightbox-nav-prev{left:16px}.media-lightbox-nav-next{right:16px}.media-lightbox-counter{z-index:10;font-size:var(--type-xs);color:#ffffffb3;font-variant-numeric:tabular-nums;-webkit-user-select:none;user-select:none;position:fixed;top:20px;left:50%;transform:translate(-50%)}.media-lightbox-short-frame{max-width:100%;max-height:100%;display:block;position:relative}.media-lightbox-short-controls{z-index:2;pointer-events:none;height:86px;position:absolute;bottom:0;left:0;right:0}.media-lightbox-short-progress{--media-progress:0%;z-index:1;appearance:none;pointer-events:auto;cursor:pointer;background:0 0;width:auto;height:34px;margin:0;padding:0;display:block;position:absolute;bottom:0;left:16px;right:16px}.media-lightbox-short-progress::-webkit-slider-runnable-track{background:linear-gradient(to right, #ffffffeb 0, #ffffffeb var(--media-progress), #ffffff2e var(--media-progress), #ffffff2e 100%);border-radius:999px;height:2px;transition:height .15s,background .15s}.media-lightbox-short-progress::-webkit-slider-thumb{appearance:none;opacity:.82;background-color:#fff;border:none;border-radius:999px;width:10px;height:10px;margin-top:-4px;transition:opacity .15s,transform .15s;box-shadow:0 1px 4px #00000052}.media-lightbox-short-progress::-moz-range-track{background:#ffffff2e;border:none;border-radius:999px;height:2px}.media-lightbox-short-progress::-moz-range-progress{background:#ffffffeb;border-radius:999px;height:2px}.media-lightbox-short-progress::-moz-range-thumb{opacity:.82;background-color:#fff;border:none;border-radius:999px;width:10px;height:10px;transition:opacity .15s,transform .15s;box-shadow:0 1px 4px #00000052}.media-lightbox-short-controls-portrait .media-lightbox-short-progress{width:calc(100% - 48px);right:auto}.media-lightbox-short-progress:hover::-webkit-slider-runnable-track{height:4px}.media-lightbox-short-progress:focus-visible::-webkit-slider-runnable-track{height:4px}.media-lightbox-short-progress:hover::-moz-range-track{height:4px}.media-lightbox-short-progress:focus-visible::-moz-range-track{height:4px}.media-lightbox-short-progress:hover::-moz-range-progress{height:4px}.media-lightbox-short-progress:focus-visible::-moz-range-progress{height:4px}.media-lightbox-short-progress:hover::-webkit-slider-thumb{opacity:1;transform:scale(1.05)}.media-lightbox-short-progress:focus-visible::-webkit-slider-thumb{opacity:1;transform:scale(1.05)}.media-lightbox-short-progress:hover::-moz-range-thumb{opacity:1;transform:scale(1.05)}.media-lightbox-short-progress:focus-visible::-moz-range-thumb{opacity:1;transform:scale(1.05)}.media-lightbox-short-mute{z-index:2;color:#fff;pointer-events:auto;cursor:pointer;background-color:#777;border:none;border-radius:999px;justify-content:center;align-items:center;width:44px;height:44px;transition:background-color .15s,transform .15s;display:inline-flex;position:absolute;bottom:30px;right:24px}.media-lightbox-short-mute svg{flex-shrink:0;width:16px;height:16px;display:block}.media-lightbox-short-mute:hover{background-color:#686868;transform:scale(1.03)}.media-lightbox-short-mute:focus-visible{outline:none;box-shadow:0 0 0 3px #fff3}@media (max-width:640px){.media-lightbox-stage{padding:48px 16px}.media-lightbox-img{border-radius:0}.media-lightbox-img-scroll{width:100%}.media-lightbox-close{width:36px;height:36px;top:12px;left:12px}.media-lightbox-nav{width:36px;height:36px}.media-lightbox-nav-prev{left:8px}.media-lightbox-nav-next{right:8px}.media-lightbox-short-mute{bottom:26px;right:24px}}.media-visual-frame{border-radius:var(--media-radius,.5rem);background-color:var(--color-muted);display:block;overflow:hidden}.media-visual{background-position:50%;background-repeat:no-repeat;display:block}.media-video-wrap{position:relative}.media-video-link{cursor:pointer;display:block}.media-video-wrap video{object-fit:contain;background-color:var(--color-muted);width:100%;max-height:24rem}.media-video-wrap-short video{background-color:#000}.media-feed-video-mute{z-index:1;color:#fff;cursor:pointer;background-color:#00000080;border:none;border-radius:999px;justify-content:center;align-items:center;width:28px;height:28px;transition:background-color .15s,transform .15s;display:inline-flex;position:absolute;bottom:16px;right:16px}.media-feed-video-mute svg{width:12px;height:12px}.media-feed-video-mute:hover{background-color:#0000009e;transform:scale(1.03)}.media-feed-video-mute:focus-visible{outline:none;box-shadow:0 0 0 3px #fff3}.media-feed-video-icon{transition:opacity .15s,transform .15s;position:absolute}.media-feed-video-mute[data-muted=true] .media-feed-video-icon-muted,.media-feed-video-mute[data-muted=false] .media-feed-video-icon-unmuted{opacity:1;transform:scale(1)}.media-feed-video-mute[data-muted=true] .media-feed-video-icon-unmuted,.media-feed-video-mute[data-muted=false] .media-feed-video-icon-muted{opacity:0;transform:scale(.92)}.media-video-play-overlay{pointer-events:none;justify-content:center;align-items:center;transition:opacity .15s;display:flex;position:absolute;inset:0}.media-video-play-overlay svg{filter:drop-shadow(0 2px 6px #0006);opacity:.85;width:48px;height:48px}.media-gallery-card.media-audio-card{flex-direction:column;display:flex}.media-audio-card .media-audio-el{opacity:0;pointer-events:none;width:0;height:0;position:absolute}.media-audio-card .media-audio-artwork{background:linear-gradient(160deg,#8080801f 0%,#80808008 100%);flex:1;justify-content:center;align-items:center;width:100%;min-height:0;display:flex}.media-audio-card .media-audio-artwork svg{width:32px;height:32px;color:var(--site-text-secondary);opacity:.3}.media-audio-card .media-audio-waveform{cursor:pointer;touch-action:none;width:100%;height:24px;display:none}.media-audio-card.has-waveform .media-audio-waveform{display:block}.media-audio-card.has-waveform .media-audio-range{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.media-audio-card .media-audio-controls{flex-direction:column;flex-shrink:0;padding:0 0 6px;display:flex}.media-audio-card .media-audio-range{appearance:none;cursor:pointer;touch-action:none;background:0 0;width:100%;height:20px;margin:0;padding:0}.media-audio-card .media-audio-range::-webkit-slider-runnable-track{background:#80808026;border-radius:1.5px;height:3px}.media-audio-card .media-audio-range::-moz-range-track{background:#80808026;border:none;border-radius:1.5px;height:3px}.media-audio-card .media-audio-range::-moz-range-progress{background:var(--site-text-primary);border-radius:1.5px;height:3px}.media-audio-card .media-audio-range::-webkit-slider-thumb{-webkit-appearance:none;background:var(--site-text-primary);opacity:0;border:none;border-radius:50%;width:10px;height:10px;margin-top:-3.5px;transition:opacity .15s}.media-audio-card .media-audio-range:hover::-webkit-slider-thumb{opacity:1}.media-audio-card.is-playing .media-audio-range::-webkit-slider-thumb{opacity:1}.media-audio-card .media-audio-range::-moz-range-thumb{background:var(--site-text-primary);opacity:0;border:none;border-radius:50%;width:10px;height:10px;transition:opacity .15s}.media-audio-card .media-audio-range:hover::-moz-range-thumb{opacity:1}.media-audio-card.is-playing .media-audio-range::-moz-range-thumb{opacity:1}.media-audio-card .media-audio-range:focus-visible{outline:2px solid var(--site-text-primary);outline-offset:2px;border-radius:2px}.media-audio-card .media-audio-row{align-items:center;gap:6px;min-width:0;padding:6px 8px 0;display:flex}.media-audio-card .media-audio-info{flex-direction:column;flex:1;gap:1px;min-width:0;display:flex}.media-audio-card .media-audio-title{font-size:var(--type-2xs);font-weight:var(--fw-medium,500);color:var(--site-text-primary);text-overflow:ellipsis;white-space:nowrap;line-height:1.3;overflow:hidden}.media-audio-card .media-audio-time{font-size:var(--type-2xs);color:var(--site-text-secondary);font-variant-numeric:tabular-nums;line-height:1}.media-audio-card .media-audio-play-btn{cursor:pointer;background:var(--site-text-primary);width:28px;height:28px;color:var(--background,#fff);border:none;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;transition:transform .12s;display:flex}.media-audio-card .media-audio-play-btn:hover{transform:scale(1.1)}.media-audio-card .media-audio-play-btn:active{transform:scale(.92)}.media-audio-card .media-audio-play-btn svg{width:14px;height:14px}.media-audio-card .media-audio-icon-play{margin-left:2px}.media-audio-card .media-audio-icon-pause,.media-audio-card.is-playing .media-audio-icon-play{display:none}.media-audio-card.is-playing .media-audio-icon-pause{display:block}.media-gallery-card{color:var(--site-text-primary);border-radius:var(--media-radius,.5rem);background-color:var(--site-nav-hover-bg);border:1px solid var(--site-divider);text-decoration:none;transition:background-color .15s;display:block;overflow:hidden}a.media-gallery-card:hover,button.media-gallery-card:hover{background-color:var(--site-divider)}button.media-gallery-card{cursor:pointer;font:inherit;text-align:inherit}.media-gallery-card-inner{text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:8px;width:100%;height:100%;padding:16px 12px;display:flex}.media-gallery-card-icon{color:var(--site-text-secondary);opacity:.6}.media-gallery-card-summary{font-size:var(--type-xs);color:var(--site-text-secondary);-webkit-line-clamp:2;word-break:break-word;-webkit-box-orient:vertical;line-height:1.4;display:-webkit-box;overflow:hidden}.media-gallery-card-meta{font-size:var(--type-xs);color:var(--site-text-secondary)}.media-lightbox-video{background-color:#000;border-radius:4px;outline:none;max-width:100%;max-height:100%;animation:.28s cubic-bezier(.22,1,.36,1) both lightbox-scale-in}.media-lightbox-video:focus,.media-lightbox-video:focus-visible{outline:none}.media-lightbox-video-short{object-fit:contain;width:100%;max-width:none;height:100%;max-height:none;display:block}@media (max-width:640px){.media-lightbox-video{border-radius:0}}[data-post-media] img{background:0 0}.media-gallery-scroll-wrap{position:relative}.media-gallery-nav{z-index:2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#fff;cursor:pointer;opacity:0;pointer-events:none;background-color:#00000080;border:none;border-radius:50%;justify-content:center;align-items:center;width:34px;height:34px;padding:0;transition:opacity .15s,background-color .15s;display:flex;position:absolute;top:50%;transform:translateY(-50%)}.media-gallery-nav:hover{background-color:#000000b3}.media-gallery-nav svg{width:18px;height:18px;display:block}.media-gallery-nav-prev{left:8px}.media-gallery-nav-next{right:8px}@media (hover:hover) and (pointer:fine){.media-gallery-scroll-wrap.can-scroll-start:hover .media-gallery-nav-prev,.media-gallery-scroll-wrap.can-scroll-end:hover .media-gallery-nav-next{opacity:1;pointer-events:auto}}.media-gallery-scroll-wrap>[data-post-media]:focus-visible{outline:2px solid var(--site-text-primary);outline-offset:2px;border-radius:4px}\n/*$vite$:1*/";
|
|
3311
3311
|
//#endregion
|
|
3312
3312
|
//#region src/services/export-theme/layouts/_default/baseof.html?raw
|
|
3313
|
-
var baseof_default = "<!doctype html>\n{{- $lang := .Site.LanguageCode | default \"en\" -}}\n{{- $themeMode := .Site.Params.theme_mode | default \"auto\" -}}\n<html lang=\"{{ $lang }}\"{{ if ne $themeMode \"auto\" }} data-theme-mode=\"{{ $themeMode }}\"{{ end }}>\n <head>\n {{ partial \"head.html\" . }}\n </head>\n <body>\n <div class=\"site-page\">\n {{ partial \"header.html\" . }}\n <main class=\"site-main\" id=\"main\">\n {{ block \"main\" . }}{{ end }}\n </main>\n {{ partial \"footer.html\" . }}\n </div>\n {{/* Mount the shared media lightbox web component once per page.\n The component installs a document-level click listener on connect\n and intercepts clicks on [data-post-media] a[data-lightbox-index]. */}}\n <jant-media-lightbox></jant-media-lightbox>\n </body>\n</html>\n";
|
|
3313
|
+
var baseof_default = "<!doctype html>\n{{- $lang := .Site.LanguageCode | default \"en\" -}}\n{{- $themeMode := .Site.Params.theme_mode | default \"auto\" -}}\n{{- $themeId := .Site.Params.theme_id -}}\n<html lang=\"{{ $lang }}\"{{ with $themeId }} data-theme=\"{{ . }}\"{{ end }}{{ if ne $themeMode \"auto\" }} data-theme-mode=\"{{ $themeMode }}\"{{ end }}>\n <head>\n {{ partial \"head.html\" . }}\n </head>\n <body>\n <div class=\"site-page\">\n {{ partial \"header.html\" . }}\n <main class=\"site-main\" id=\"main\">\n {{ block \"main\" . }}{{ end }}\n </main>\n {{ partial \"footer.html\" . }}\n </div>\n {{/* Mount the shared media lightbox web component once per page.\n The component installs a document-level click listener on connect\n and intercepts clicks on [data-post-media] a[data-lightbox-index]. */}}\n <jant-media-lightbox></jant-media-lightbox>\n </body>\n</html>\n";
|
|
3314
3314
|
//#endregion
|
|
3315
3315
|
//#region src/services/export-theme/layouts/_default/single.html?raw
|
|
3316
3316
|
var single_default$1 = "{{ define \"main\" }}\n <article class=\"page\">\n {{- with .Title -}}\n <header class=\"page-header\">\n <h1 class=\"page-title\">{{ . }}</h1>\n </header>\n {{- end -}}\n {{- with .Params.summary_text -}}\n <p class=\"page-summary\">{{ . }}</p>\n {{- end -}}\n <div class=\"page-body\">\n {{ .Content }}\n </div>\n </article>\n{{ end }}\n";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-
|
|
1
|
+
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-DY1v5Iqu.js";
|
|
2
2
|
import { r as getInstallationToken } from "./github-app-BbklkFmU.js";
|
|
3
3
|
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BgSiE71w.js";
|
|
4
4
|
//#region src/lib/markdown-to-tiptap.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import "./export-
|
|
3
|
-
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-
|
|
2
|
+
import "./export-DY1v5Iqu.js";
|
|
3
|
+
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-2_T7nbOv.js";
|
|
4
4
|
export { classifyRepoForSync, createGitHubSyncService };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { y as url_exports } from "./url-BMYO-Zlt.js";
|
|
2
|
-
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext,
|
|
3
|
-
import { T as time_exports, a as markdown_exports } from "./export-
|
|
2
|
+
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, j as MAX_PINNED_POSTS, k as FORMATS, m as defaultFeedRenderer, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-CGHkOdme.js";
|
|
3
|
+
import { T as time_exports, a as markdown_exports } from "./export-DY1v5Iqu.js";
|
|
4
4
|
import "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync-
|
|
5
|
+
import "./github-sync-2_T7nbOv.js";
|
|
6
6
|
export { FORMATS, MAX_MEDIA_ATTACHMENTS, MAX_PINNED_POSTS, MEDIA_KINDS, NAV_ITEM_TYPES, SORT_ORDERS, STATUSES, TEXT_ATTACHMENT_CONTENT_FORMATS, createApp, createMediaContext, defaultFeedRenderer, markdown_exports as markdown, time_exports as time, toArchiveGroups, toArchiveGroupsWithMedia, toMediaView, toNavItemView, toNavItemViews, toPostView, toPostViews, toSearchResultView, url_exports as url };
|
package/dist/node.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as
|
|
3
|
-
import { t as createExportService } from "./export-
|
|
2
|
+
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as getWebhookUrl, d as BUILTIN_FONT_THEMES, f as getCjkSerifCssVariables, g as pgSchemaBundle, h as createStorageDriver, i as createSiteService, l as setMyCommands, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getFontThemeCssVariables, r as createNodeRequestRuntime, s as resolveConfig, t as createApp, u as setWebhook, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-CGHkOdme.js";
|
|
3
|
+
import { t as createExportService } from "./export-DY1v5Iqu.js";
|
|
4
4
|
import { C as shouldTrustProxy, S as getTelegramWebhookSecret, b as getSiteResolutionMode, d as getHostedControlPlaneBaseUrl, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as getTelegramBotPool, y as getPort } from "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync-
|
|
5
|
+
import "./github-sync-2_T7nbOv.js";
|
|
6
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
7
7
|
import { serve } from "@hono/node-server";
|
|
8
8
|
import Database from "better-sqlite3";
|
|
@@ -529,7 +529,7 @@ async function createNodeRequestHandler(options) {
|
|
|
529
529
|
async function start(env = process.env, app) {
|
|
530
530
|
const handler = await createNodeRequestHandler({
|
|
531
531
|
env,
|
|
532
|
-
app: async () => app ?? (await import("./app-
|
|
532
|
+
app: async () => app ?? (await import("./app-D24n0DoH.js")).createApp()
|
|
533
533
|
});
|
|
534
534
|
const hostname = resolveHost(env);
|
|
535
535
|
const port = resolvePort(env);
|
package/package.json
CHANGED
|
@@ -38,10 +38,13 @@ const labels: SettingsLabels = {
|
|
|
38
38
|
siteName: "Site Name",
|
|
39
39
|
aboutBlog: "About this blog",
|
|
40
40
|
aboutBlogHelp: "Displayed above your blog posts.",
|
|
41
|
-
siteLanguage: "
|
|
42
|
-
siteLanguageHelp: "
|
|
41
|
+
siteLanguage: "Content language",
|
|
42
|
+
siteLanguageHelp: "The language your posts are written in.",
|
|
43
43
|
siteLanguageSearchPlaceholder: "Search…",
|
|
44
44
|
siteLanguageNoMatches: "No matches.",
|
|
45
|
+
contentLanguagePreview: "Readers and search engines see",
|
|
46
|
+
dashboardLanguage: "Dashboard language",
|
|
47
|
+
dashboardLanguageHelp: "The language this admin dashboard shows in.",
|
|
45
48
|
cjkFont: "CJK Font",
|
|
46
49
|
cjkFontHelp:
|
|
47
50
|
"Load a serif font optimized for Chinese, Japanese, or Korean content.",
|
|
@@ -56,6 +59,9 @@ const labels: SettingsLabels = {
|
|
|
56
59
|
mainFeedUrl: "Main feed",
|
|
57
60
|
latestFeedUrl: "Latest feed",
|
|
58
61
|
featuredFeedUrl: "Featured feed",
|
|
62
|
+
archiveFeedUrl: "Archive feed",
|
|
63
|
+
archiveFeedUrlHelp:
|
|
64
|
+
"Every published post, including ones hidden from Latest.",
|
|
59
65
|
latestFeedOption: "Latest",
|
|
60
66
|
latestFeedOptionDescription: "Uses the latest public posts for /feed.",
|
|
61
67
|
featuredFeedOption: "Featured",
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
SettingsLabels,
|
|
6
6
|
SettingsTimezone,
|
|
7
7
|
SettingsCjkFont,
|
|
8
|
+
SettingsDashboardLanguage,
|
|
8
9
|
SettingsSaveDetail,
|
|
9
10
|
} from "../settings-types.js";
|
|
10
11
|
import { MAX_SITE_NAME_LENGTH } from "../../../types.js";
|
|
@@ -81,10 +82,13 @@ const labels: SettingsLabels = {
|
|
|
81
82
|
siteName: "Site Name",
|
|
82
83
|
aboutBlog: "About this blog",
|
|
83
84
|
aboutBlogHelp: "Displayed above your blog posts.",
|
|
84
|
-
siteLanguage: "
|
|
85
|
-
siteLanguageHelp: "
|
|
85
|
+
siteLanguage: "Content language",
|
|
86
|
+
siteLanguageHelp: "The language your posts are written in.",
|
|
86
87
|
siteLanguageSearchPlaceholder: "Search…",
|
|
87
88
|
siteLanguageNoMatches: "No matches.",
|
|
89
|
+
contentLanguagePreview: "Readers and search engines see",
|
|
90
|
+
dashboardLanguage: "Dashboard language",
|
|
91
|
+
dashboardLanguageHelp: "The language this admin dashboard shows in.",
|
|
88
92
|
cjkFont: "CJK Font",
|
|
89
93
|
cjkFontHelp:
|
|
90
94
|
"Load a serif font optimized for Chinese, Japanese, or Korean content.",
|
|
@@ -99,6 +103,9 @@ const labels: SettingsLabels = {
|
|
|
99
103
|
mainFeedUrl: "Main feed",
|
|
100
104
|
latestFeedUrl: "Latest feed",
|
|
101
105
|
featuredFeedUrl: "Featured feed",
|
|
106
|
+
archiveFeedUrl: "Archive feed",
|
|
107
|
+
archiveFeedUrlHelp:
|
|
108
|
+
"Every published post, including ones hidden from Latest.",
|
|
102
109
|
latestFeedOption: "Latest",
|
|
103
110
|
latestFeedOptionDescription: "Uses the latest public posts for /feed.",
|
|
104
111
|
featuredFeedOption: "Featured",
|
|
@@ -127,10 +134,17 @@ const cjkFonts: SettingsCjkFont[] = [
|
|
|
127
134
|
{ value: "zh-Hans", label: "简体中文 (Simplified Chinese)" },
|
|
128
135
|
];
|
|
129
136
|
|
|
137
|
+
const dashboardLanguages: SettingsDashboardLanguage[] = [
|
|
138
|
+
{ value: "en", label: "English" },
|
|
139
|
+
{ value: "zh-Hans", label: "简体中文" },
|
|
140
|
+
{ value: "zh-Hant", label: "繁體中文" },
|
|
141
|
+
];
|
|
142
|
+
|
|
130
143
|
const initialData = {
|
|
131
144
|
siteName: "My Blog",
|
|
132
145
|
siteDescription: "A test blog",
|
|
133
146
|
siteLanguage: "en",
|
|
147
|
+
dashboardLanguage: "en",
|
|
134
148
|
cjkSerifFont: "off",
|
|
135
149
|
timeZone: "UTC",
|
|
136
150
|
mainRssFeed: "featured",
|
|
@@ -161,11 +175,13 @@ async function createElement(
|
|
|
161
175
|
el.labels = labels;
|
|
162
176
|
el.timezones = timezones;
|
|
163
177
|
el.cjkFonts = cjkFonts;
|
|
178
|
+
el.dashboardLanguages = dashboardLanguages;
|
|
164
179
|
el.siteNameFallback = "Fallback Name";
|
|
165
180
|
el.siteDescriptionFallback = "Fallback Description";
|
|
166
181
|
el.mainFeedUrl = "/feed";
|
|
167
|
-
el.latestFeedUrl = "/feed
|
|
168
|
-
el.featuredFeedUrl = "/feed
|
|
182
|
+
el.latestFeedUrl = "/latest/feed";
|
|
183
|
+
el.featuredFeedUrl = "/featured/feed";
|
|
184
|
+
el.archiveFeedUrl = "/archive/feed";
|
|
169
185
|
el.demoMode = opts.demoMode ?? false;
|
|
170
186
|
document.body.appendChild(el);
|
|
171
187
|
await el.updateComplete;
|
|
@@ -239,10 +255,11 @@ describe("JantSettingsGeneral", () => {
|
|
|
239
255
|
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
240
256
|
|
|
241
257
|
const options = el.querySelectorAll<HTMLButtonElement>('[role="option"]');
|
|
258
|
+
// The content-language picker lists the full BCP 47 catalog so any public
|
|
259
|
+
// content language is reachable. Coverage / raw tags are not shown here.
|
|
242
260
|
expect(options.length).toBeGreaterThanOrEqual(20);
|
|
243
|
-
// Each option carries the universal "translated" coverage suffix.
|
|
244
261
|
for (const option of options) {
|
|
245
|
-
expect(option.textContent).toMatch(/% translated/);
|
|
262
|
+
expect(option.textContent).not.toMatch(/% translated/);
|
|
246
263
|
}
|
|
247
264
|
|
|
248
265
|
const search = requireElement(
|
|
@@ -258,7 +275,7 @@ describe("JantSettingsGeneral", () => {
|
|
|
258
275
|
expect(filtered[0]?.textContent).toMatch(/Suomi|Finnish/);
|
|
259
276
|
});
|
|
260
277
|
|
|
261
|
-
it("selects a non-catalog
|
|
278
|
+
it("selects a non-catalog content language and shows its native name", async () => {
|
|
262
279
|
const el = await createElement();
|
|
263
280
|
const trigger = requireElement(
|
|
264
281
|
el.querySelector<HTMLButtonElement>(
|
|
@@ -285,9 +302,43 @@ describe("JantSettingsGeneral", () => {
|
|
|
285
302
|
|
|
286
303
|
// Picker closes after selection.
|
|
287
304
|
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
288
|
-
// Trigger
|
|
289
|
-
|
|
290
|
-
expect(trigger.textContent).toMatch(/
|
|
305
|
+
// Trigger shows the selected language's native name only — no raw BCP 47
|
|
306
|
+
// tag, no coverage metric.
|
|
307
|
+
expect(trigger.textContent).toMatch(/suomi|finnish/i);
|
|
308
|
+
expect(trigger.textContent).not.toMatch(/% translated/);
|
|
309
|
+
expect(trigger.textContent).not.toMatch(/\bfi\b/);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("renders dashboard language options and saves the selection", async () => {
|
|
313
|
+
const el = await createElement();
|
|
314
|
+
const select = requireElement(
|
|
315
|
+
el.querySelector(
|
|
316
|
+
'select[aria-labelledby="dashboard-language-label"]',
|
|
317
|
+
) as globalThis.HTMLSelectElement | null,
|
|
318
|
+
"expected dashboard language select",
|
|
319
|
+
);
|
|
320
|
+
const values = Array.from(select.options).map((o) => o.value);
|
|
321
|
+
expect(values).toEqual(["en", "zh-Hans", "zh-Hant"]);
|
|
322
|
+
expect(select.value).toBe("en");
|
|
323
|
+
|
|
324
|
+
const saves: SettingsSaveDetail[] = [];
|
|
325
|
+
el.addEventListener("jant:settings-save", (e) => {
|
|
326
|
+
saves.push((e as CustomEvent<SettingsSaveDetail>).detail);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
select.value = "zh-Hant";
|
|
330
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
331
|
+
await el.updateComplete;
|
|
332
|
+
|
|
333
|
+
const saveButton = requireElement(
|
|
334
|
+
findSaveButtonByHeading(el, labels.languageAndTime),
|
|
335
|
+
"expected language & time save button",
|
|
336
|
+
);
|
|
337
|
+
saveButton.click();
|
|
338
|
+
|
|
339
|
+
expect(saves).toHaveLength(1);
|
|
340
|
+
expect(saves[0]?.endpoint).toBe("/settings/general/language-time");
|
|
341
|
+
expect(saves[0]?.data.dashboardLanguage).toBe("zh-Hant");
|
|
291
342
|
});
|
|
292
343
|
|
|
293
344
|
it("renders CJK font options", async () => {
|
|
@@ -323,8 +374,9 @@ describe("JantSettingsGeneral", () => {
|
|
|
323
374
|
expect(el.textContent).toContain(labels.latestFeedOptionDescription);
|
|
324
375
|
expect(Array.from(feedUrlInputs, (input) => input.value)).toEqual([
|
|
325
376
|
"/feed",
|
|
326
|
-
"/feed
|
|
327
|
-
"/feed
|
|
377
|
+
"/latest/feed",
|
|
378
|
+
"/featured/feed",
|
|
379
|
+
"/archive/feed",
|
|
328
380
|
]);
|
|
329
381
|
});
|
|
330
382
|
|
|
@@ -5350,6 +5350,18 @@ export class JantComposeDialog extends LitElement {
|
|
|
5350
5350
|
i === index ? { ...it, format: e.detail.format } : it,
|
|
5351
5351
|
);
|
|
5352
5352
|
this._format = e.detail.format;
|
|
5353
|
+
// Move focus to the new format's input, mirroring the single-post
|
|
5354
|
+
// composer's `_switchFormat`. The editor re-renders its fields only
|
|
5355
|
+
// after both this dialog and the editor itself finish updating, so
|
|
5356
|
+
// wait for both before routing focus to the now-visible control.
|
|
5357
|
+
if (this._shouldAutofocusFormatInput()) {
|
|
5358
|
+
this.updateComplete.then(() => {
|
|
5359
|
+
const editor = this.querySelectorAll<JantComposeEditor>(
|
|
5360
|
+
"jant-compose-editor",
|
|
5361
|
+
)[index];
|
|
5362
|
+
editor?.updateComplete.then(() => editor.focusInput());
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5353
5365
|
}}
|
|
5354
5366
|
@jant:thread-remove=${(e: Event) => {
|
|
5355
5367
|
e.stopPropagation();
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
SettingsLabels,
|
|
21
21
|
SettingsTimezone,
|
|
22
22
|
SettingsCjkFont,
|
|
23
|
+
SettingsDashboardLanguage,
|
|
23
24
|
} from "./settings-types.js";
|
|
24
25
|
import { showToast } from "../toast.js";
|
|
25
26
|
import {
|
|
@@ -37,10 +38,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
37
38
|
type: String,
|
|
38
39
|
attribute: "sitedescription-fallback",
|
|
39
40
|
},
|
|
41
|
+
dashboardLanguages: { type: Array, attribute: "dashboard-languages" },
|
|
40
42
|
demoMode: { type: Boolean, attribute: "demo-mode" },
|
|
41
43
|
mainFeedUrl: { type: String, attribute: "main-feed-url" },
|
|
42
44
|
latestFeedUrl: { type: String, attribute: "latest-feed-url" },
|
|
43
45
|
featuredFeedUrl: { type: String, attribute: "featured-feed-url" },
|
|
46
|
+
archiveFeedUrl: { type: String, attribute: "archive-feed-url" },
|
|
44
47
|
|
|
45
48
|
// Site group
|
|
46
49
|
_siteName: { state: true },
|
|
@@ -52,6 +55,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
52
55
|
|
|
53
56
|
// Language, CJK & time group
|
|
54
57
|
_siteLanguage: { state: true },
|
|
58
|
+
_dashboardLanguage: { state: true },
|
|
55
59
|
_localeOpen: { state: true },
|
|
56
60
|
_localeQuery: { state: true },
|
|
57
61
|
_cjkSerifFont: { state: true },
|
|
@@ -80,12 +84,14 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
80
84
|
declare labels: SettingsLabels;
|
|
81
85
|
declare timezones: SettingsTimezone[];
|
|
82
86
|
declare cjkFonts: SettingsCjkFont[];
|
|
87
|
+
declare dashboardLanguages: SettingsDashboardLanguage[];
|
|
83
88
|
declare siteNameFallback: string;
|
|
84
89
|
declare siteDescriptionFallback: string;
|
|
85
90
|
declare demoMode: boolean;
|
|
86
91
|
declare mainFeedUrl: string;
|
|
87
92
|
declare latestFeedUrl: string;
|
|
88
93
|
declare featuredFeedUrl: string;
|
|
94
|
+
declare archiveFeedUrl: string;
|
|
89
95
|
|
|
90
96
|
// Site
|
|
91
97
|
declare _siteName: string;
|
|
@@ -101,6 +107,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
101
107
|
|
|
102
108
|
// Language, CJK & time
|
|
103
109
|
declare _siteLanguage: string;
|
|
110
|
+
/** Admin dashboard UI locale (one of the translated catalog locales). */
|
|
111
|
+
declare _dashboardLanguage: string;
|
|
104
112
|
/** Whether the locale combobox dropdown is currently open. */
|
|
105
113
|
declare _localeOpen: boolean;
|
|
106
114
|
/** Search query inside the locale combobox. */
|
|
@@ -109,6 +117,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
109
117
|
declare _timeZone: string;
|
|
110
118
|
declare _origLocale: {
|
|
111
119
|
siteLanguage: string;
|
|
120
|
+
dashboardLanguage: string;
|
|
112
121
|
cjkSerifFont: string;
|
|
113
122
|
timeZone: string;
|
|
114
123
|
};
|
|
@@ -145,12 +154,14 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
145
154
|
this.labels = {} as SettingsLabels;
|
|
146
155
|
this.timezones = [];
|
|
147
156
|
this.cjkFonts = [];
|
|
157
|
+
this.dashboardLanguages = [];
|
|
148
158
|
this.siteNameFallback = "";
|
|
149
159
|
this.siteDescriptionFallback = "";
|
|
150
160
|
this.demoMode = false;
|
|
151
161
|
this.mainFeedUrl = "/feed";
|
|
152
|
-
this.latestFeedUrl = "/feed
|
|
153
|
-
this.featuredFeedUrl = "/feed
|
|
162
|
+
this.latestFeedUrl = "/latest/feed";
|
|
163
|
+
this.featuredFeedUrl = "/featured/feed";
|
|
164
|
+
this.archiveFeedUrl = "/archive/feed";
|
|
154
165
|
|
|
155
166
|
this._siteName = "";
|
|
156
167
|
this._siteDescription = "";
|
|
@@ -164,12 +175,14 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
164
175
|
this._siteLoading = false;
|
|
165
176
|
|
|
166
177
|
this._siteLanguage = "en";
|
|
178
|
+
this._dashboardLanguage = "en";
|
|
167
179
|
this._localeOpen = false;
|
|
168
180
|
this._localeQuery = "";
|
|
169
181
|
this._cjkSerifFont = "off";
|
|
170
182
|
this._timeZone = "UTC";
|
|
171
183
|
this._origLocale = {
|
|
172
184
|
siteLanguage: "en",
|
|
185
|
+
dashboardLanguage: "en",
|
|
173
186
|
cjkSerifFont: "off",
|
|
174
187
|
timeZone: "UTC",
|
|
175
188
|
};
|
|
@@ -213,10 +226,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
213
226
|
this._siteFooter = data.siteFooter;
|
|
214
227
|
|
|
215
228
|
this._siteLanguage = data.siteLanguage;
|
|
229
|
+
this._dashboardLanguage = data.dashboardLanguage;
|
|
216
230
|
this._cjkSerifFont = data.cjkSerifFont;
|
|
217
231
|
this._timeZone = data.timeZone;
|
|
218
232
|
this._origLocale = {
|
|
219
233
|
siteLanguage: data.siteLanguage,
|
|
234
|
+
dashboardLanguage: data.dashboardLanguage,
|
|
220
235
|
cjkSerifFont: data.cjkSerifFont,
|
|
221
236
|
timeZone: data.timeZone,
|
|
222
237
|
};
|
|
@@ -255,6 +270,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
255
270
|
} else if (section === "language-time") {
|
|
256
271
|
this._origLocale = {
|
|
257
272
|
siteLanguage: this._siteLanguage,
|
|
273
|
+
dashboardLanguage: this._dashboardLanguage,
|
|
258
274
|
cjkSerifFont: this._cjkSerifFont,
|
|
259
275
|
timeZone: this._timeZone,
|
|
260
276
|
};
|
|
@@ -381,6 +397,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
381
397
|
private _syncLocaleDirty() {
|
|
382
398
|
this._localeDirty =
|
|
383
399
|
this._siteLanguage !== this._origLocale.siteLanguage ||
|
|
400
|
+
this._dashboardLanguage !== this._origLocale.dashboardLanguage ||
|
|
384
401
|
this._cjkSerifFont !== this._origLocale.cjkSerifFont ||
|
|
385
402
|
this._timeZone !== this._origLocale.timeZone;
|
|
386
403
|
}
|
|
@@ -395,6 +412,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
395
412
|
endpoint: "/settings/general/language-time",
|
|
396
413
|
data: {
|
|
397
414
|
siteLanguage: this._siteLanguage,
|
|
415
|
+
dashboardLanguage: this._dashboardLanguage,
|
|
398
416
|
cjkSerifFont: this._cjkSerifFont,
|
|
399
417
|
timeZone: this._timeZone,
|
|
400
418
|
},
|
|
@@ -465,28 +483,22 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
465
483
|
const noMatches = this.labels.siteLanguageNoMatches || "No matches.";
|
|
466
484
|
|
|
467
485
|
return html`
|
|
468
|
-
<div class="relative" data-locale-picker>
|
|
486
|
+
<div class="relative w-fit max-w-full" data-locale-picker>
|
|
469
487
|
<button
|
|
470
488
|
type="button"
|
|
471
|
-
class="
|
|
489
|
+
class="flex h-9 w-full cursor-pointer items-center rounded-md border border-input bg-transparent bg-[image:var(--chevron-down-icon-50)] bg-position-[center_right_0.75rem] bg-size-[1rem] bg-no-repeat py-2 pl-3 pr-9 text-left text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
|
472
490
|
aria-expanded=${this._localeOpen ? "true" : "false"}
|
|
473
491
|
aria-haspopup="listbox"
|
|
474
492
|
aria-labelledby="site-language-label"
|
|
475
493
|
@click=${this._toggleLocalePicker}
|
|
476
494
|
>
|
|
477
|
-
<span class="truncate">
|
|
478
|
-
${current.native}
|
|
479
|
-
<span class="ml-2 text-xs text-muted-foreground">
|
|
480
|
-
${current.tag} · ${Math.round(current.coverage * 100)}% translated
|
|
481
|
-
</span>
|
|
482
|
-
</span>
|
|
483
|
-
<span class="ml-2 text-muted-foreground" aria-hidden="true">▾</span>
|
|
495
|
+
<span class="min-w-0 truncate">${current.native}</span>
|
|
484
496
|
</button>
|
|
485
497
|
|
|
486
498
|
${this._localeOpen
|
|
487
499
|
? html`
|
|
488
500
|
<div
|
|
489
|
-
class="absolute left-0
|
|
501
|
+
class="absolute left-0 top-full z-10 mt-1 w-80 min-w-full max-w-[calc(100vw-2rem)] max-h-72 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md"
|
|
490
502
|
>
|
|
491
503
|
<div class="border-b p-2">
|
|
492
504
|
<input
|
|
@@ -518,21 +530,16 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
518
530
|
? "true"
|
|
519
531
|
: "false"}
|
|
520
532
|
class=${[
|
|
521
|
-
"flex w-full
|
|
533
|
+
"flex w-full flex-col px-3 py-2 text-left text-sm hover:bg-accent",
|
|
522
534
|
entry.tag === this._siteLanguage
|
|
523
535
|
? "bg-accent/60"
|
|
524
536
|
: "",
|
|
525
537
|
].join(" ")}
|
|
526
538
|
@click=${() => this._selectLocale(entry.tag)}
|
|
527
539
|
>
|
|
528
|
-
<span
|
|
529
|
-
<span>${entry.native}</span>
|
|
530
|
-
<span class="text-xs text-muted-foreground">
|
|
531
|
-
${entry.tag} · ${entry.english}
|
|
532
|
-
</span>
|
|
533
|
-
</span>
|
|
540
|
+
<span>${entry.native}</span>
|
|
534
541
|
<span class="text-xs text-muted-foreground">
|
|
535
|
-
${
|
|
542
|
+
${entry.english}
|
|
536
543
|
</span>
|
|
537
544
|
</button>
|
|
538
545
|
`,
|
|
@@ -714,10 +721,17 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
714
721
|
}
|
|
715
722
|
}
|
|
716
723
|
|
|
717
|
-
private _renderFeedInfoRow(
|
|
724
|
+
private _renderFeedInfoRow(
|
|
725
|
+
label: string,
|
|
726
|
+
value: string,
|
|
727
|
+
description?: string,
|
|
728
|
+
) {
|
|
718
729
|
return html`
|
|
719
730
|
<div class="flex min-w-0 flex-col gap-1">
|
|
720
731
|
<p class="text-sm font-medium">${label}</p>
|
|
732
|
+
${description
|
|
733
|
+
? html`<p class="text-sm text-muted-foreground">${description}</p>`
|
|
734
|
+
: ""}
|
|
721
735
|
<div class="relative">
|
|
722
736
|
<input
|
|
723
737
|
type="text"
|
|
@@ -819,6 +833,40 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
819
833
|
<p class="text-sm text-muted-foreground mt-1">
|
|
820
834
|
${this.labels.siteLanguageHelp}
|
|
821
835
|
</p>
|
|
836
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
837
|
+
${this.labels.contentLanguagePreview}
|
|
838
|
+
<code class="rounded bg-muted px-1.5 py-0.5 text-xs"
|
|
839
|
+
>${`<html lang="${this._siteLanguage || "en"}">`}</code
|
|
840
|
+
>
|
|
841
|
+
</p>
|
|
842
|
+
</div>
|
|
843
|
+
|
|
844
|
+
<div class="field">
|
|
845
|
+
<label id="dashboard-language-label" class="label"
|
|
846
|
+
>${this.labels.dashboardLanguage}</label
|
|
847
|
+
>
|
|
848
|
+
<select
|
|
849
|
+
class="select"
|
|
850
|
+
aria-labelledby="dashboard-language-label"
|
|
851
|
+
@change=${(e: Event) => {
|
|
852
|
+
this._dashboardLanguage = (e.target as HTMLSelectElement).value;
|
|
853
|
+
this._syncLocaleDirty();
|
|
854
|
+
}}
|
|
855
|
+
>
|
|
856
|
+
${this.dashboardLanguages.map(
|
|
857
|
+
(lang) => html`
|
|
858
|
+
<option
|
|
859
|
+
value=${lang.value}
|
|
860
|
+
?selected=${this._dashboardLanguage === lang.value}
|
|
861
|
+
>
|
|
862
|
+
${lang.label}
|
|
863
|
+
</option>
|
|
864
|
+
`,
|
|
865
|
+
)}
|
|
866
|
+
</select>
|
|
867
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
868
|
+
${this.labels.dashboardLanguageHelp}
|
|
869
|
+
</p>
|
|
822
870
|
</div>
|
|
823
871
|
|
|
824
872
|
<div class="field">
|
|
@@ -929,6 +977,11 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
929
977
|
this.labels.featuredFeedUrl,
|
|
930
978
|
this.featuredFeedUrl,
|
|
931
979
|
)}
|
|
980
|
+
${this._renderFeedInfoRow(
|
|
981
|
+
this.labels.archiveFeedUrl,
|
|
982
|
+
this.archiveFeedUrl,
|
|
983
|
+
this.labels.archiveFeedUrlHelp,
|
|
984
|
+
)}
|
|
932
985
|
</div>
|
|
933
986
|
</div>
|
|
934
987
|
|