@jant/core 0.6.8 → 0.6.9
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-C-jxWmAV.js} +12324 -12157
- package/dist/app-DqHzOwL5.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-C6peCkkD.css → client-CGf2m3qp.css} +1 -1
- package/dist/client/_assets/{client-CXnEhyyv.js → client-DWy1LEEk.js} +1 -1
- package/dist/client/_assets/{client-auth-CSItbyU8.js → client-auth-Blg-a5Ep.js} +180 -162
- package/dist/{export-Be082J0n.js → export-C2DIB7mm.js} +2 -2
- package/dist/{github-sync-_kPWM4m9.js → github-sync-7XQ5ZM6z.js} +2 -2
- package/dist/{github-sync-D1Cw8mOY.js → github-sync-BEFCfLKK.js} +1 -1
- 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 +5 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +55 -8
- package/src/client/components/jant-compose-dialog.ts +12 -0
- package/src/client/components/jant-settings-general.ts +56 -18
- package/src/client/components/settings-types.ts +11 -0
- package/src/client/settings-bridge.ts +3 -0
- package/src/client/tiptap/__tests__/mark-exit.test.ts +99 -0
- package/src/client/tiptap/bubble-menu.ts +37 -4
- 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 +25 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +25 -10
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +25 -10
- 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/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 +15 -2
- 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/settings.ts +49 -15
- package/src/services/upload-session.ts +13 -3
- package/src/styles/tokens.css +6 -4
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +13 -0
- package/src/ui/dash/settings/GeneralContent.tsx +38 -4
- package/src/ui/layouts/BaseLayout.tsx +1 -0
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +13 -0
- package/dist/app-DaxS_Cz-.js +0 -6
|
@@ -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 --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
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import "./export-
|
|
3
|
-
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-
|
|
2
|
+
import "./export-C2DIB7mm.js";
|
|
3
|
+
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-BEFCfLKK.js";
|
|
4
4
|
export { classifyRepoForSync, createGitHubSyncService };
|
|
@@ -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-C2DIB7mm.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
|
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-C-jxWmAV.js";
|
|
3
|
+
import { T as time_exports, a as markdown_exports } from "./export-C2DIB7mm.js";
|
|
4
4
|
import "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync-
|
|
5
|
+
import "./github-sync-BEFCfLKK.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-C-jxWmAV.js";
|
|
3
|
+
import { t as createExportService } from "./export-C2DIB7mm.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-BEFCfLKK.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-DqHzOwL5.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.",
|
|
@@ -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.",
|
|
@@ -127,10 +131,17 @@ const cjkFonts: SettingsCjkFont[] = [
|
|
|
127
131
|
{ value: "zh-Hans", label: "简体中文 (Simplified Chinese)" },
|
|
128
132
|
];
|
|
129
133
|
|
|
134
|
+
const dashboardLanguages: SettingsDashboardLanguage[] = [
|
|
135
|
+
{ value: "en", label: "English" },
|
|
136
|
+
{ value: "zh-Hans", label: "简体中文" },
|
|
137
|
+
{ value: "zh-Hant", label: "繁體中文" },
|
|
138
|
+
];
|
|
139
|
+
|
|
130
140
|
const initialData = {
|
|
131
141
|
siteName: "My Blog",
|
|
132
142
|
siteDescription: "A test blog",
|
|
133
143
|
siteLanguage: "en",
|
|
144
|
+
dashboardLanguage: "en",
|
|
134
145
|
cjkSerifFont: "off",
|
|
135
146
|
timeZone: "UTC",
|
|
136
147
|
mainRssFeed: "featured",
|
|
@@ -161,6 +172,7 @@ async function createElement(
|
|
|
161
172
|
el.labels = labels;
|
|
162
173
|
el.timezones = timezones;
|
|
163
174
|
el.cjkFonts = cjkFonts;
|
|
175
|
+
el.dashboardLanguages = dashboardLanguages;
|
|
164
176
|
el.siteNameFallback = "Fallback Name";
|
|
165
177
|
el.siteDescriptionFallback = "Fallback Description";
|
|
166
178
|
el.mainFeedUrl = "/feed";
|
|
@@ -239,10 +251,11 @@ describe("JantSettingsGeneral", () => {
|
|
|
239
251
|
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
240
252
|
|
|
241
253
|
const options = el.querySelectorAll<HTMLButtonElement>('[role="option"]');
|
|
254
|
+
// The content-language picker lists the full BCP 47 catalog so any public
|
|
255
|
+
// content language is reachable. Coverage / raw tags are not shown here.
|
|
242
256
|
expect(options.length).toBeGreaterThanOrEqual(20);
|
|
243
|
-
// Each option carries the universal "translated" coverage suffix.
|
|
244
257
|
for (const option of options) {
|
|
245
|
-
expect(option.textContent).toMatch(/% translated/);
|
|
258
|
+
expect(option.textContent).not.toMatch(/% translated/);
|
|
246
259
|
}
|
|
247
260
|
|
|
248
261
|
const search = requireElement(
|
|
@@ -258,7 +271,7 @@ describe("JantSettingsGeneral", () => {
|
|
|
258
271
|
expect(filtered[0]?.textContent).toMatch(/Suomi|Finnish/);
|
|
259
272
|
});
|
|
260
273
|
|
|
261
|
-
it("selects a non-catalog
|
|
274
|
+
it("selects a non-catalog content language and shows its native name", async () => {
|
|
262
275
|
const el = await createElement();
|
|
263
276
|
const trigger = requireElement(
|
|
264
277
|
el.querySelector<HTMLButtonElement>(
|
|
@@ -285,9 +298,43 @@ describe("JantSettingsGeneral", () => {
|
|
|
285
298
|
|
|
286
299
|
// Picker closes after selection.
|
|
287
300
|
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
288
|
-
// Trigger
|
|
289
|
-
|
|
290
|
-
expect(trigger.textContent).toMatch(/
|
|
301
|
+
// Trigger shows the selected language's native name only — no raw BCP 47
|
|
302
|
+
// tag, no coverage metric.
|
|
303
|
+
expect(trigger.textContent).toMatch(/suomi|finnish/i);
|
|
304
|
+
expect(trigger.textContent).not.toMatch(/% translated/);
|
|
305
|
+
expect(trigger.textContent).not.toMatch(/\bfi\b/);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("renders dashboard language options and saves the selection", async () => {
|
|
309
|
+
const el = await createElement();
|
|
310
|
+
const select = requireElement(
|
|
311
|
+
el.querySelector(
|
|
312
|
+
'select[aria-labelledby="dashboard-language-label"]',
|
|
313
|
+
) as globalThis.HTMLSelectElement | null,
|
|
314
|
+
"expected dashboard language select",
|
|
315
|
+
);
|
|
316
|
+
const values = Array.from(select.options).map((o) => o.value);
|
|
317
|
+
expect(values).toEqual(["en", "zh-Hans", "zh-Hant"]);
|
|
318
|
+
expect(select.value).toBe("en");
|
|
319
|
+
|
|
320
|
+
const saves: SettingsSaveDetail[] = [];
|
|
321
|
+
el.addEventListener("jant:settings-save", (e) => {
|
|
322
|
+
saves.push((e as CustomEvent<SettingsSaveDetail>).detail);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
select.value = "zh-Hant";
|
|
326
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
327
|
+
await el.updateComplete;
|
|
328
|
+
|
|
329
|
+
const saveButton = requireElement(
|
|
330
|
+
findSaveButtonByHeading(el, labels.languageAndTime),
|
|
331
|
+
"expected language & time save button",
|
|
332
|
+
);
|
|
333
|
+
saveButton.click();
|
|
334
|
+
|
|
335
|
+
expect(saves).toHaveLength(1);
|
|
336
|
+
expect(saves[0]?.endpoint).toBe("/settings/general/language-time");
|
|
337
|
+
expect(saves[0]?.data.dashboardLanguage).toBe("zh-Hant");
|
|
291
338
|
});
|
|
292
339
|
|
|
293
340
|
it("renders CJK font options", async () => {
|
|
@@ -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,6 +38,7 @@ 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" },
|
|
@@ -52,6 +54,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
52
54
|
|
|
53
55
|
// Language, CJK & time group
|
|
54
56
|
_siteLanguage: { state: true },
|
|
57
|
+
_dashboardLanguage: { state: true },
|
|
55
58
|
_localeOpen: { state: true },
|
|
56
59
|
_localeQuery: { state: true },
|
|
57
60
|
_cjkSerifFont: { state: true },
|
|
@@ -80,6 +83,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
80
83
|
declare labels: SettingsLabels;
|
|
81
84
|
declare timezones: SettingsTimezone[];
|
|
82
85
|
declare cjkFonts: SettingsCjkFont[];
|
|
86
|
+
declare dashboardLanguages: SettingsDashboardLanguage[];
|
|
83
87
|
declare siteNameFallback: string;
|
|
84
88
|
declare siteDescriptionFallback: string;
|
|
85
89
|
declare demoMode: boolean;
|
|
@@ -101,6 +105,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
101
105
|
|
|
102
106
|
// Language, CJK & time
|
|
103
107
|
declare _siteLanguage: string;
|
|
108
|
+
/** Admin dashboard UI locale (one of the translated catalog locales). */
|
|
109
|
+
declare _dashboardLanguage: string;
|
|
104
110
|
/** Whether the locale combobox dropdown is currently open. */
|
|
105
111
|
declare _localeOpen: boolean;
|
|
106
112
|
/** Search query inside the locale combobox. */
|
|
@@ -109,6 +115,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
109
115
|
declare _timeZone: string;
|
|
110
116
|
declare _origLocale: {
|
|
111
117
|
siteLanguage: string;
|
|
118
|
+
dashboardLanguage: string;
|
|
112
119
|
cjkSerifFont: string;
|
|
113
120
|
timeZone: string;
|
|
114
121
|
};
|
|
@@ -145,6 +152,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
145
152
|
this.labels = {} as SettingsLabels;
|
|
146
153
|
this.timezones = [];
|
|
147
154
|
this.cjkFonts = [];
|
|
155
|
+
this.dashboardLanguages = [];
|
|
148
156
|
this.siteNameFallback = "";
|
|
149
157
|
this.siteDescriptionFallback = "";
|
|
150
158
|
this.demoMode = false;
|
|
@@ -164,12 +172,14 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
164
172
|
this._siteLoading = false;
|
|
165
173
|
|
|
166
174
|
this._siteLanguage = "en";
|
|
175
|
+
this._dashboardLanguage = "en";
|
|
167
176
|
this._localeOpen = false;
|
|
168
177
|
this._localeQuery = "";
|
|
169
178
|
this._cjkSerifFont = "off";
|
|
170
179
|
this._timeZone = "UTC";
|
|
171
180
|
this._origLocale = {
|
|
172
181
|
siteLanguage: "en",
|
|
182
|
+
dashboardLanguage: "en",
|
|
173
183
|
cjkSerifFont: "off",
|
|
174
184
|
timeZone: "UTC",
|
|
175
185
|
};
|
|
@@ -213,10 +223,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
213
223
|
this._siteFooter = data.siteFooter;
|
|
214
224
|
|
|
215
225
|
this._siteLanguage = data.siteLanguage;
|
|
226
|
+
this._dashboardLanguage = data.dashboardLanguage;
|
|
216
227
|
this._cjkSerifFont = data.cjkSerifFont;
|
|
217
228
|
this._timeZone = data.timeZone;
|
|
218
229
|
this._origLocale = {
|
|
219
230
|
siteLanguage: data.siteLanguage,
|
|
231
|
+
dashboardLanguage: data.dashboardLanguage,
|
|
220
232
|
cjkSerifFont: data.cjkSerifFont,
|
|
221
233
|
timeZone: data.timeZone,
|
|
222
234
|
};
|
|
@@ -255,6 +267,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
255
267
|
} else if (section === "language-time") {
|
|
256
268
|
this._origLocale = {
|
|
257
269
|
siteLanguage: this._siteLanguage,
|
|
270
|
+
dashboardLanguage: this._dashboardLanguage,
|
|
258
271
|
cjkSerifFont: this._cjkSerifFont,
|
|
259
272
|
timeZone: this._timeZone,
|
|
260
273
|
};
|
|
@@ -381,6 +394,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
381
394
|
private _syncLocaleDirty() {
|
|
382
395
|
this._localeDirty =
|
|
383
396
|
this._siteLanguage !== this._origLocale.siteLanguage ||
|
|
397
|
+
this._dashboardLanguage !== this._origLocale.dashboardLanguage ||
|
|
384
398
|
this._cjkSerifFont !== this._origLocale.cjkSerifFont ||
|
|
385
399
|
this._timeZone !== this._origLocale.timeZone;
|
|
386
400
|
}
|
|
@@ -395,6 +409,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
395
409
|
endpoint: "/settings/general/language-time",
|
|
396
410
|
data: {
|
|
397
411
|
siteLanguage: this._siteLanguage,
|
|
412
|
+
dashboardLanguage: this._dashboardLanguage,
|
|
398
413
|
cjkSerifFont: this._cjkSerifFont,
|
|
399
414
|
timeZone: this._timeZone,
|
|
400
415
|
},
|
|
@@ -465,28 +480,22 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
465
480
|
const noMatches = this.labels.siteLanguageNoMatches || "No matches.";
|
|
466
481
|
|
|
467
482
|
return html`
|
|
468
|
-
<div class="relative" data-locale-picker>
|
|
483
|
+
<div class="relative w-fit max-w-full" data-locale-picker>
|
|
469
484
|
<button
|
|
470
485
|
type="button"
|
|
471
|
-
class="
|
|
486
|
+
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
487
|
aria-expanded=${this._localeOpen ? "true" : "false"}
|
|
473
488
|
aria-haspopup="listbox"
|
|
474
489
|
aria-labelledby="site-language-label"
|
|
475
490
|
@click=${this._toggleLocalePicker}
|
|
476
491
|
>
|
|
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>
|
|
492
|
+
<span class="min-w-0 truncate">${current.native}</span>
|
|
484
493
|
</button>
|
|
485
494
|
|
|
486
495
|
${this._localeOpen
|
|
487
496
|
? html`
|
|
488
497
|
<div
|
|
489
|
-
class="absolute left-0
|
|
498
|
+
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
499
|
>
|
|
491
500
|
<div class="border-b p-2">
|
|
492
501
|
<input
|
|
@@ -518,21 +527,16 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
518
527
|
? "true"
|
|
519
528
|
: "false"}
|
|
520
529
|
class=${[
|
|
521
|
-
"flex w-full
|
|
530
|
+
"flex w-full flex-col px-3 py-2 text-left text-sm hover:bg-accent",
|
|
522
531
|
entry.tag === this._siteLanguage
|
|
523
532
|
? "bg-accent/60"
|
|
524
533
|
: "",
|
|
525
534
|
].join(" ")}
|
|
526
535
|
@click=${() => this._selectLocale(entry.tag)}
|
|
527
536
|
>
|
|
528
|
-
<span
|
|
529
|
-
<span>${entry.native}</span>
|
|
530
|
-
<span class="text-xs text-muted-foreground">
|
|
531
|
-
${entry.tag} · ${entry.english}
|
|
532
|
-
</span>
|
|
533
|
-
</span>
|
|
537
|
+
<span>${entry.native}</span>
|
|
534
538
|
<span class="text-xs text-muted-foreground">
|
|
535
|
-
${
|
|
539
|
+
${entry.english}
|
|
536
540
|
</span>
|
|
537
541
|
</button>
|
|
538
542
|
`,
|
|
@@ -819,6 +823,40 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
819
823
|
<p class="text-sm text-muted-foreground mt-1">
|
|
820
824
|
${this.labels.siteLanguageHelp}
|
|
821
825
|
</p>
|
|
826
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
827
|
+
${this.labels.contentLanguagePreview}
|
|
828
|
+
<code class="rounded bg-muted px-1.5 py-0.5 text-xs"
|
|
829
|
+
>${`<html lang="${this._siteLanguage || "en"}">`}</code
|
|
830
|
+
>
|
|
831
|
+
</p>
|
|
832
|
+
</div>
|
|
833
|
+
|
|
834
|
+
<div class="field">
|
|
835
|
+
<label id="dashboard-language-label" class="label"
|
|
836
|
+
>${this.labels.dashboardLanguage}</label
|
|
837
|
+
>
|
|
838
|
+
<select
|
|
839
|
+
class="select"
|
|
840
|
+
aria-labelledby="dashboard-language-label"
|
|
841
|
+
@change=${(e: Event) => {
|
|
842
|
+
this._dashboardLanguage = (e.target as HTMLSelectElement).value;
|
|
843
|
+
this._syncLocaleDirty();
|
|
844
|
+
}}
|
|
845
|
+
>
|
|
846
|
+
${this.dashboardLanguages.map(
|
|
847
|
+
(lang) => html`
|
|
848
|
+
<option
|
|
849
|
+
value=${lang.value}
|
|
850
|
+
?selected=${this._dashboardLanguage === lang.value}
|
|
851
|
+
>
|
|
852
|
+
${lang.label}
|
|
853
|
+
</option>
|
|
854
|
+
`,
|
|
855
|
+
)}
|
|
856
|
+
</select>
|
|
857
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
858
|
+
${this.labels.dashboardLanguageHelp}
|
|
859
|
+
</p>
|
|
822
860
|
</div>
|
|
823
861
|
|
|
824
862
|
<div class="field">
|
|
@@ -47,6 +47,10 @@ export interface SettingsLabels {
|
|
|
47
47
|
siteLanguageSearchPlaceholder: string;
|
|
48
48
|
/** Empty-state message when the search filters out every option. */
|
|
49
49
|
siteLanguageNoMatches: string;
|
|
50
|
+
/** Lead text before the live `<html lang>` preview. */
|
|
51
|
+
contentLanguagePreview: string;
|
|
52
|
+
dashboardLanguage: string;
|
|
53
|
+
dashboardLanguageHelp: string;
|
|
50
54
|
cjkFont: string;
|
|
51
55
|
cjkFontHelp: string;
|
|
52
56
|
timeZone: string;
|
|
@@ -75,10 +79,17 @@ export interface SettingsCjkFont {
|
|
|
75
79
|
label: string;
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
/** Dashboard UI language option for the select dropdown */
|
|
83
|
+
export interface SettingsDashboardLanguage {
|
|
84
|
+
value: string;
|
|
85
|
+
label: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
export interface SettingsInitialData {
|
|
79
89
|
siteName: string;
|
|
80
90
|
siteDescription: string;
|
|
81
91
|
siteLanguage: string;
|
|
92
|
+
dashboardLanguage: string;
|
|
82
93
|
cjkSerifFont: string;
|
|
83
94
|
mainRssFeed: string;
|
|
84
95
|
timeZone: string;
|
|
@@ -20,6 +20,8 @@ function parseSettingsInitialData(data: unknown): SettingsInitialData | null {
|
|
|
20
20
|
const siteName = getJsonString(data, "siteName");
|
|
21
21
|
const siteDescription = getJsonString(data, "siteDescription");
|
|
22
22
|
const siteLanguage = getJsonString(data, "siteLanguage");
|
|
23
|
+
// Tolerate older payloads without the key: empty = follow content language.
|
|
24
|
+
const dashboardLanguage = getJsonString(data, "dashboardLanguage") ?? "";
|
|
23
25
|
const cjkSerifFont = getJsonString(data, "cjkSerifFont");
|
|
24
26
|
const mainRssFeed = getJsonString(data, "mainRssFeed");
|
|
25
27
|
const timeZone = getJsonString(data, "timeZone");
|
|
@@ -45,6 +47,7 @@ function parseSettingsInitialData(data: unknown): SettingsInitialData | null {
|
|
|
45
47
|
siteName,
|
|
46
48
|
siteDescription,
|
|
47
49
|
siteLanguage,
|
|
50
|
+
dashboardLanguage,
|
|
48
51
|
cjkSerifFont,
|
|
49
52
|
mainRssFeed,
|
|
50
53
|
timeZone,
|