@jant/core 0.3.43 → 0.3.45
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/dist/{app-GbfwoeDJ.js → app-C-L7wL6o.js} +485 -452
- package/dist/app-Hvqe7Ks_.js +5 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-DDs6NzB3.css +2 -0
- package/dist/client/_assets/{client-auth-CXILhW1b.js → client-auth-Dcon89Av.js} +30 -11
- package/dist/client/_assets/{client-D95FNDg5.js → client-dSfWfMe9.js} +7 -7
- package/dist/{github-sync-7y_nTXx1.js → github-sync-CQ1x271f.js} +3 -0
- package/dist/index.js +4 -87
- package/dist/node.js +3 -3
- package/package.json +1 -1
- package/src/client/components/jant-compose-dialog.ts +87 -9
- package/src/client/components/jant-compose-editor.ts +5 -1
- package/src/client/components/jant-post-menu.ts +23 -5
- package/src/client/compose-bridge.ts +2 -1
- package/src/client/toast.ts +29 -2
- package/src/client/upload-session.ts +1 -1
- package/src/db/migrations/0019_bored_magus.sql +2 -0
- package/src/db/migrations/0020_free_zaladane.sql +1 -0
- package/src/db/migrations/meta/0019_snapshot.json +2238 -0
- package/src/db/migrations/meta/0020_snapshot.json +2129 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
- package/src/db/migrations/pg/0018_red_warlock.sql +1 -0
- package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
- package/src/db/migrations/pg/meta/0018_snapshot.json +2739 -0
- package/src/db/migrations/pg/meta/_journal.json +14 -0
- package/src/db/pg/schema.ts +4 -30
- package/src/db/schema.ts +4 -39
- package/src/i18n/locales/public/en.po +10 -5
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +10 -5
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +10 -5
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/index.ts +0 -3
- package/src/lib/__tests__/resolve-config.test.ts +4 -4
- package/src/lib/__tests__/startup-config.test.ts +27 -2
- package/src/lib/constants.ts +1 -0
- package/src/lib/github-sync-trigger.ts +7 -51
- package/src/lib/icons.ts +37 -0
- package/src/lib/startup-config.ts +53 -6
- package/src/routes/api/github-sync.tsx +36 -14
- package/src/routes/api/internal/sites.ts +1 -0
- package/src/routes/pages/home.tsx +2 -0
- package/src/routes/pages/latest.tsx +2 -0
- package/src/runtime/__tests__/readiness.test.ts +34 -0
- package/src/runtime/readiness.ts +8 -4
- package/src/services/__tests__/collection.test.ts +13 -11
- package/src/services/__tests__/site-admin.test.ts +85 -0
- package/src/services/github-sync.ts +6 -0
- package/src/services/site-admin.ts +66 -1
- package/src/styles/components.css +14 -0
- package/src/styles/ui.css +109 -0
- package/src/types/bindings.ts +0 -2
- package/src/types/config.ts +1 -1
- package/src/types/props.ts +2 -0
- package/src/ui/__tests__/font-themes.test.ts +2 -2
- package/src/ui/dash/settings/SettingsRootContent.tsx +17 -17
- package/src/ui/feed/LinkCard.tsx +3 -20
- package/src/ui/feed/LinkPreview.tsx +5 -19
- package/src/ui/feed/PostStatusBadges.tsx +4 -38
- package/src/ui/font-themes.ts +17 -17
- package/src/ui/layouts/BaseLayout.tsx +14 -29
- package/src/ui/pages/HomePage.tsx +21 -5
- package/src/ui/shared/DecorativeQuoteMark.tsx +2 -13
- package/src/ui/shared/Icon.tsx +60 -0
- package/src/ui/shared/IconSprite.tsx +57 -0
- package/src/ui/shared/PostFooter.tsx +6 -62
- package/src/ui/shared/custom-icons.ts +132 -0
- package/src/ui/shared/icon-collector.ts +37 -0
- package/dist/app-Ctl0T0zO.js +0 -5
- package/dist/client/_assets/client-C_kImWZj.css +0 -2
- package/src/lib/github-sync-queue-handler.ts +0 -69
- package/src/lib/github-sync-worker.ts +0 -72
- package/src/lib/job-queue-cf.ts +0 -18
- package/src/lib/job-queue-db.ts +0 -149
- package/src/lib/job-queue.ts +0 -35
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { a as getSitePathPrefix, c as normalizePath, d as sanitizeUrl, f as slugify, g as toPublicPath, h as toPublicHref, i as getSiteOrigin, m as toAbsoluteSiteUrl, n as extractDisplayDomain, o as isFullUrl, p as stripSitePathPrefix, r as extractDomain, s as isSafeInternalRedirect, t as buildSiteUrl, u as normalizeSiteUrl, v as __exportAll } from "./url-umUptr5z.js";
|
|
2
|
-
import { A as JANT_BRAND_PACK_FILENAME, B as getJantLogoFills, C as formatTime, D as toISOString, F as getJantBrandPackHref, G as arrayBufferToBase64, H as getJantPositiveLogoPngHref, I as getJantBundledAsset, K as base64ToUint8Array, L as getJantIconFilename, M as JANT_REPO_URL, N as getDefaultJantAppleTouchIconBytes, O as HOME_BRANDING_LINK_LABEL, P as getDefaultJantFaviconIcoBytes, R as getJantIconHref, S as formatRelativeTime, T as now, U as JANT_LOGO_PATH_DATA, V as getJantLogoHref, W as JANT_LOGO_VIEW_BOX, _ as getImageUrl, a as tiptapJsonToMarkdown, b as formatDate, c as render, d as extractSummary, f as extractSummaryHtml, g as escapeHtml, h as trimTiptapBody, i as createExportService, j as JANT_POSITIVE_LOGO_PNG_FILENAME, k as HOME_BRANDING_PREFIX, l as toPlainText, m as renderTiptapJson, n as createGitHubSyncService, o as markdownToTiptapJson, p as renderTiptapDocument, u as extractBodyText, v as getMediaUrl, w as formatYearMonth, x as formatRelativeAge, y as getPublicUrlForProvider, z as getJantLogoFilename } from "./github-sync-
|
|
2
|
+
import { A as JANT_BRAND_PACK_FILENAME, B as getJantLogoFills, C as formatTime, D as toISOString, F as getJantBrandPackHref, G as arrayBufferToBase64, H as getJantPositiveLogoPngHref, I as getJantBundledAsset, K as base64ToUint8Array, L as getJantIconFilename, M as JANT_REPO_URL, N as getDefaultJantAppleTouchIconBytes, O as HOME_BRANDING_LINK_LABEL, P as getDefaultJantFaviconIcoBytes, R as getJantIconHref, S as formatRelativeTime, T as now, U as JANT_LOGO_PATH_DATA, V as getJantLogoHref, W as JANT_LOGO_VIEW_BOX, _ as getImageUrl, a as tiptapJsonToMarkdown, b as formatDate, c as render, d as extractSummary, f as extractSummaryHtml, g as escapeHtml, h as trimTiptapBody, i as createExportService, j as JANT_POSITIVE_LOGO_PNG_FILENAME, k as HOME_BRANDING_PREFIX, l as toPlainText, m as renderTiptapJson, n as createGitHubSyncService, o as markdownToTiptapJson, p as renderTiptapDocument, u as extractBodyText, v as getMediaUrl, w as formatYearMonth, x as formatRelativeAge, y as getPublicUrlForProvider, z as getJantLogoFilename } from "./github-sync-CQ1x271f.js";
|
|
3
3
|
import { C as coalesceDisplayText, S as shouldUseSecureCookies, _ as getInternalAdminToken, a as getConfiguredSingleSiteUrl, b as getSiteResolutionMode, c as getDevApiToken, d as getHostedControlPlaneBaseUrl, f as getHostedControlPlaneDomainCheckSecret, g as getHostedControlPlaneSsoSecret, h as getHostedControlPlaneProviderLabel$1, i as getConfiguredSingleSitePathPrefix, l as getEnvString, m as getHostedControlPlaneInternalToken, n as getAuthSecret, o as getConfiguredStorageDriver, p as getHostedControlPlaneInternalBaseUrl, r as getConfiguredSingleSiteOrigin, s as getCorsOrigins, u as getGitHubAppConfig, v as getLocalStoragePath } from "./env-CgaH9Mut.js";
|
|
4
4
|
import { a as listInstallationReposPage, n as getInstallation, o as searchInstallationRepos, t as buildInstallUrl } from "./github-app-WeadXMb8.js";
|
|
5
5
|
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BkRWnqMx.js";
|
|
6
6
|
import { I18n } from "@lingui/core";
|
|
7
|
+
import * as lucideIcons from "lucide-static";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { fromString, typeidUnboxed } from "typeid-js";
|
|
9
10
|
import { decode } from "blurhash";
|
|
@@ -13,7 +14,6 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
|
13
14
|
import { drizzle as drizzle$1 } from "drizzle-orm/d1";
|
|
14
15
|
import { check, foreignKey, index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
15
16
|
import { boolean, check as check$1, customType, foreignKey as foreignKey$1, index as index$1, integer as integer$1, pgTable, primaryKey as primaryKey$1, text as text$1, timestamp, unique, uniqueIndex as uniqueIndex$1 } from "drizzle-orm/pg-core";
|
|
16
|
-
import * as lucideIcons from "lucide-static";
|
|
17
17
|
import { APIError, betterAuth } from "better-auth";
|
|
18
18
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
19
19
|
import { verifyPassword } from "better-auth/crypto";
|
|
@@ -1848,7 +1848,7 @@ var Hono = class extends Hono$1 {
|
|
|
1848
1848
|
}
|
|
1849
1849
|
//#endregion
|
|
1850
1850
|
//#region src/i18n/locales/public/en.ts
|
|
1851
|
-
var messages$3 = JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"WpXcBJ\":[\"Nothing here yet.\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}");
|
|
1851
|
+
var messages$3 = JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q/uoSA\":[\"Quiet here for now.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"nfU386\":[\"Sign in if this is your space.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}");
|
|
1852
1852
|
//#endregion
|
|
1853
1853
|
//#region src/i18n/locales/settings/en.ts
|
|
1854
1854
|
var messages$2 = JSON.parse("{\"+9JI/F\":[\"Connecting will sync your site onto \",[\"repo\"],\"'s default branch on top of its existing history. Existing files outside Jant's managed paths are kept. This can't be undone.\"],\"+AXdXp\":[\"Label and URL are required\"],\"+K0AvT\":[\"Disconnect\"],\"+zy2Nq\":[\"Type\"],\"/3H2/s\":[\"This hosted site signs in through \",[\"providerLabel\"],\". Manage password and hosted access there.\"],\"/JnyjR\":[\"Toggle built-in navigation items. Their order controls what shows in the header and which feed the homepage opens first.\"],\"0OGSSc\":[\"Avatar display updated.\"],\"0UzCUX\":[\"Update the password you use to sign in\"],\"10UtuM\":[\"CJK Font\"],\"14BEca\":[\"Read why\"],\"1F6Mzc\":[\"No navigation items yet. Add links or enable system items below.\"],\"1njn7W\":[\"Light\"],\"2B7t+s\":[\"Sessions and password\"],\"2DoBvq\":[\"Feeds\"],\"2FYpfJ\":[\"More\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2PTjMB\":[\"I want to delete \",[\"siteName\"]],\"2cFU6q\":[\"Site Footer\"],\"2oWZo7\":[\"Last commit\"],\"2uuy4H\":[\"Connected via Personal Access Token\"],\"35x8eZ\":[\"Showing \",[\"shown\"],\" of \",[\"total\"]],\"3Cw1AI\":[\"Add Collection\"],\"3VrybB\":[\"Redirect\"],\"3Yvsaz\":[\"302 (Temporary)\"],\"3n0zbB\":[\"Session management is off in demo mode. Use the shared demo session instead.\"],\"3sYJi5\":[\"Download a Hugo-compatible archive — host it statically or move to another Jant.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"49Bsal\":[\"Feed settings updated.\"],\"4Jge8E\":[\"Active Sessions\"],\"4KIa+q\":[\"Export downloaded.\"],\"4cEClj\":[\"Sessions\"],\"4zGJ5E\":[\"Delete Account Permanently\"],\"5QlUIt\":[\"Empty repository. Ready to connect.\"],\"5VQnR3\":[\"Use these when you want a feed URL that never changes.\"],\"5dpcN1\":[\"type to search all\"],\"69OXZB\":[\"Delete Hosted Site\"],\"6ArdBh\":[\"Uses featured posts for /feed.\"],\"6DjeBT\":[\"Demo sites always stay hidden from search engines.\"],\"6FFB7q\":[\"Uses the latest public posts for /feed.\"],\"6K1Vef\":[\"Delete this blog permanently? This cannot be undone.\"],\"6NpNLc\":[\"This repository has existing content.\"],\"6V3Ea3\":[\"Copied\"],\"746NHh\":[\"this blog\"],\"7811AW\":[\"This repository is already backing up this site.\"],\"7FaY4u\":[\"Usage\"],\"7G9YLi\":[\"Allow search engines to index my site\"],\"7MZxzw\":[\"Password changed.\"],\"7vhWI8\":[\"New Password\"],\"7z05Pf\":[\"Open the hosted site controls in \",[\"providerLabel\"],\" to cancel billing or permanently delete this site.\"],\"81nFIS\":[\"Passwords don't match. Make sure both fields are identical.\"],\"87a/t/\":[\"Label\"],\"89Upyo\":[\"That theme isn't available. Pick another one.\"],\"8BfEpW\":[\"Hosted account\"],\"8N/Mcp\":[\"Archive filter parameters (e.g. format=note&view=list)\"],\"8U2Z7f\":[\"New Custom URL\"],\"8ZsakT\":[\"Password\"],\"9+vGLh\":[\"Custom CSS\"],\"9As8Nu\":[\"Create one on GitHub\"],\"9Lsvt5\":[\"Signed in \",[\"date\"]],\"9T7Cwm\":[\"Redirects, vanity paths, and URL control\"],\"9aUyym\":[\"See where you're signed in and revoke old sessions\"],\"A1taO8\":[\"Search\"],\"AeXO77\":[\"Account\"],\"AnY+O9\":[\"Show \\\"Build with Jant\\\" at the bottom of the home page\"],\"ApZDMk\":[\"This is used for your favicon and apple-touch-icon. For best results, upload a square PNG with a solid background at least 512x512 pixels.\"],\"B495Gs\":[\"Archive\"],\"B4ESok\":[\"API reference\"],\"CTAEes\":[\"Select a repository\"],\"CjZZgz\":[\"This repository already has commits\"],\"DCKkhU\":[\"Current Password\"],\"DKKKeF\":[\"Manage password and hosted access in \",[\"providerLabel\"]],\"ECIBO2\":[\"Controls the language of the dashboard and settings. Public pages stay in English.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"Enslfm\":[\"Destination\"],\"F7FKwe\":[\"Anything you paste here has full access to your visitors' browsers. Only use code from sources you trust.\"],\"FkMol5\":[\"Featured\"],\"G/1oP+\":[\"Remove the webhook and stop syncing. Your repository content will not be deleted.\"],\"G0qJsQ\":[\"Security token missing. Refresh the page and try again.\"],\"G39wnK\":[\"Back up and sync content with a GitHub repository\"],\"GMMWcy\":[\"Name, metadata, language, and search defaults\"],\"GXsAby\":[\"Revoke\"],\"GxkJXS\":[\"Uploading...\"],\"GzKzUa\":[\"Demo limits\"],\"HKH+W+\":[\"Data\"],\"Hp1l6f\":[\"Current\"],\"HxlY7t\":[\"Changing this updates what subscribers get from /feed.\"],\"HxuOlm\":[\"Site Header\"],\"I6gXOa\":[\"Path\"],\"ID38tA\":[\"Account deletion is off in demo mode. The shared demo resets separately.\"],\"IF9tPu\":[\"When to use site export, database backups, and recovery drills.\"],\"IW5PBo\":[\"Copy Token\"],\"IagCbF\":[\"URL\"],\"IreQBq\":[\"Repository\"],\"J6bLeg\":[\"Add a custom link to any URL\"],\"JL7LF5\":[\"available CSS variables, data attributes, and examples.\"],\"JTviaO\":[\"Manage sign-in security, exports, and irreversible actions.\"],\"JcD7qf\":[\"More actions\"],\"JjX0OO\":[\"Copy your token now — it won't be shown again.\"],\"JrFTcr\":[\"Connecting…\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"KDw4GX\":[\"Try again\"],\"KSgo21\":[\"Pick a repository\"],\"KVVYBh\":[\"Add collection to navigation\"],\"KiJn9B\":[\"Note\"],\"L3DEwT\":[\"Remove this avatar? Your favicon and header icon will go back to the default.\"],\"L4t4/q\":[\"March 14\"],\"LdyooL\":[\"link\"],\"M/D8PK\":[\"+ Install on another account\"],\"M/haSd\":[\"Always show the light version of the theme.\"],\"M2kIWU\":[\"Font theme\"],\"M6CbAU\":[\"Toggle edit panel\"],\"Me5t5H\":[\"Connect a GitHub repository to automatically back up your posts as Markdown files. Edits on GitHub sync back to your site.\"],\"MtENL9\":[\"Tune how your site looks, reads, and runs.\"],\"N/8NPV\":[\"Before deleting, download a site export. You won't be able to recover this account after deletion.\"],\"N7UNHY\":[\"Featured feed\"],\"NHnUHF\":[\"Favicon and the profile mark in your header\"],\"NU2Fqi\":[\"Save CSS\"],\"Nldjdr\":[\"No custom URLs yet. Create one to add redirects or custom paths for posts.\"],\"O7rgs6\":[\"Header RSS points to your \",[\"feed\"],\" feed (/feed). Change what /feed returns in General.\"],\"OSJXFg\":[\"Applies to your entire site, including admin pages. Pick a palette, then choose whether it follows the system or stays fixed.\"],\"PEUV5I\":[\"Code injection updated.\"],\"PZ7HJ8\":[\"Blog Avatar\"],\"Pwqkdw\":[\"Loading…\"],\"PxJ9W6\":[\"Generate Token\"],\"Q/6Y+2\":[\"Needs Contents (read/write) and Webhooks (read/write) on the target repository.\"],\"Q30z/l\":[\"Remove this collection from navigation? The collection itself won't be deleted.\"],\"Q99OtV\":[\"Pin a collection to your navigation bar. An asterisk (*) appears next to collections updated in the last 48 hours.\"],\"QZmz0H\":[\"Built-in links\"],\"Qnrzvb\":[\"Active Tokens\"],\"R6Z4LE\":[\"Download failed. Please try again.\"],\"R9Khdg\":[\"Auto\"],\"RxsRD6\":[\"Time Zone\"],\"SJmfuf\":[\"Site Name\"],\"SKZhW9\":[\"Token name\"],\"SVQQPe\":[\"Couldn't connect. Check the error and try again.\"],\"TpF3v+\":[\"Injected before </head>. Use for analytics, custom meta tags, and styles that must load early.\"],\"Tz0i8g\":[\"Settings\"],\"UFK415\":[\"Site-wide HTML for analytics and widgets\"],\"Uj/btJ\":[\"Display avatar in my site header\"],\"UsODUn\":[\"Select an account\"],\"UxKoFf\":[\"Navigation\"],\"V+bhUy\":[\"Install GitHub App\"],\"V4WsyL\":[\"Add Link\"],\"V5pZwT\":[\"Search settings updated.\"],\"VXUPla\":[\"Connect with GitHub App\"],\"VhMDMg\":[\"Change Password\"],\"Vn3jYy\":[\"Navigation items\"],\"VoZYGU\":[\"This will permanently delete all your data — posts, media, collections, settings, and your account. Your blog will be reset to its initial setup state. This cannot be undone.\"],\"Weq9zb\":[\"General\"],\"Wi9i06\":[\"Follow each visitor's system preference.\"],\"Wx1M8N\":[\"Install the GitHub App to grant access without managing personal tokens. Permissions are scoped per repository and revocable from GitHub.\"],\"X+8FMk\":[\"Current password doesn't match. Try again.\"],\"X1G9eY\":[\"Navigation Preview\"],\"X9Hujr\":[\"Manual Push\"],\"XtBJV8\":[\"Checking repository…\"],\"Xtc16w\":[\"Refresh repository list\"],\"Y/F35r\":[\"Create a post with curl:\"],\"YF6zHf\":[\"Site settings updated.\"],\"YdG2RF\":[\"Export Site\"],\"YwhjRx\":[\"Manage Account\"],\"ZDY7Fy\":[\"Syncing…\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZS/CBL\":[\"Delete this navigation link? Visitors won't see it in your site header anymore.\"],\"ZhhOwV\":[\"Quote\"],\"ZiooJI\":[\"API Tokens\"],\"Zm7Qb0\":[\"Backup & Restore Guide\"],\"ZmUkwN\":[\"Add custom link to navigation\"],\"a14mj8\":[\"Unknown device\"],\"a3LDKx\":[\"Security\"],\"aAIQg2\":[\"Appearance\"],\"aFkzVF\":[\"The slug of the target post or collection\"],\"alKG0+\":[\"Font Theme\"],\"anibOb\":[\"About this blog\"],\"any7NR\":[\"Theming guide\"],\"b+/jO6\":[\"301 (Permanent)\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bHYIks\":[\"Sign Out\"],\"bmrL08\":[\"Demo mode hides sessions, password changes, and account deletion. Export still works.\"],\"c3MN2z\":[\"all available endpoints and request formats.\"],\"cSDy01\":[\"Custom CSS updated.\"],\"clzoNp\":[\"Always show the dark version of the theme.\"],\"cnGeoo\":[\"Delete\"],\"d3FRkY\":[\"Could not copy. Try again.\"],\"d5oGUo\":[\"Create a new repository on GitHub\"],\"dEgA5A\":[\"Cancel\"],\"dTXUY+\":[\"Confirm account deletion\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dk7TCH\":[\"Permanently delete all data and reset the blog\"],\"drodVV\":[\"No collections yet. Create one first, then add it to your navigation.\"],\"dsWkIw\":[\"Disconnect from GitHub? The webhook will be removed. Your repository content will not be deleted.\"],\"e/tSI5\":[\"Navigation order updated.\"],\"ePK91l\":[\"Edit\"],\"ebQKK7\":[\"Site\"],\"egK+Yy\":[\"Bearer tokens for scripts and automation\"],\"ehj/zN\":[\"Redirect Type\"],\"eneWvv\":[\"Draft\"],\"erTMh7\":[\"Last synced\"],\"f+m8jj\":[\"Feed URL copied.\"],\"f8fH8W\":[\"Design\"],\"fWYqkz\":[\"Code Injection\"],\"gOWiTY\":[\"Load a serif font optimized for Chinese, Japanese, or Korean content.\"],\"gZ5owP\":[\"Search repositories\"],\"gbqbh6\":[\"Safe to leave this page — syncing continues in the background.\"],\"gkFvVN\":[\"Injected before </body>. Use for chat widgets and scripts that should not block page load.\"],\"gtQsRO\":[\"Create Custom URL\"],\"hBO/y4\":[\"Security token expired. Refresh the page and try again.\"],\"hGmyDl\":[\"Tokens let you access the API from scripts, shortcuts, and other tools without signing in.\"],\"hIHkRy\":[\"Connected via GitHub App\"],\"hdSi1b\":[\"Type \",[\"repo\"],\" to confirm\"],\"he3ygx\":[\"Copy\"],\"i0qMbr\":[\"Home\"],\"iEUzMn\":[\"system\"],\"iSLIjg\":[\"Connect\"],\"iVOMRi\":[\"Home settings updated.\"],\"icB4Cv\":[\"Drag links here to show them under the More menu\"],\"iiDXZc\":[\"Displayed at the bottom of all posts and pages.\"],\"j4VrG6\":[\"Download Export ZIP\"],\"j5nQL2\":[\"e.g. iOS Shortcuts\"],\"jUV7CU\":[\"Upload Avatar\"],\"jVUmOK\":[\"Markdown supported\"],\"jgBjXJ\":[\"Revoke this token? Any scripts using it will stop working.\"],\"jpctdh\":[\"View\"],\"k1ifdL\":[\"Processing...\"],\"kMXclu\":[\"Download a site export\"],\"kNiQp6\":[\"Pinned\"],\"kRhzWq\":[\"GitHub Sync\"],\"kVQs7s\":[\"Fine-grained styling overrides\"],\"ke1gWS\":[\"Custom URLs\"],\"kfcRb0\":[\"Avatar\"],\"kxDZ2i\":[\"This code runs on every page of your site.\"],\"l2Op2p\":[\"Query Parameters\"],\"lLW3vJ\":[\"Target Slug\"],\"lYHJih\":[\"Revoke this session? That device will need to sign in again.\"],\"mLOk1i\":[\"Push all posts to GitHub right now instead of waiting for the next automatic sync.\"],\"mSNmrX\":[\"List posts:\"],\"nK07ni\":[\"Choose a typographic direction for your site. Each theme changes both the font pairing and the reading rhythm.\"],\"nbfdhU\":[\"Integrations\"],\"o/vNDE\":[\"lets you override any theme variable.\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"We'll prefill the name \",[\"name\"],\". The list refreshes on return.\"],\"oKOOsY\":[\"Color Theme\"],\"oL535e\":[\"Not synced yet\"],\"oNA4If\":[\"All collections are already in your navigation.\"],\"pZq3aX\":[\"Upload failed. Please try again.\"],\"pgTIrt\":[\"Choose the GitHub account and repository to sync with this site.\"],\"psoxDF\":[\"That font theme isn't available. Pick another one.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"r5EW6f\":[\"This repository is already backing up another Jant site (\",[\"host\"],\"). Pick a different repository.\"],\"rEspiY\":[\"Navigation placement updated.\"],\"rFmBG3\":[\"Color theme\"],\"rlonmB\":[\"Couldn't delete. Try again in a moment.\"],\"satWc6\":[\"Main RSS feed\"],\"sgr2wQ\":[\"collection\"],\"sqxcaY\":[\"Created \",[\"date\"]],\"sxkWRg\":[\"Advanced\"],\"t/YqKh\":[\"Remove\"],\"t3hvHq\":[\"Sync Now\"],\"tfDRzk\":[\"Save\"],\"tvgAq5\":[\"No accounts authorized yet\"],\"u1VTd3\":[\"Palette, surface tone, and overall mood\"],\"u3wRF+\":[\"Published\"],\"u6KOjV\":[\"Want more control?\"],\"udPwLB\":[\"Header\"],\"ui6aMF\":[\"These devices are currently signed in to your account. Revoke any session you don't recognize.\"],\"vBEKwo\":[\"Manage this site's active sessions here. Password and hosted access are managed through \",[\"providerLabel\"],\".\"],\"vRldcl\":[\"Typography choices and reading texture\"],\"vSYKYI\":[\"Main feed\"],\"vTuib7\":[\"This controls what /feed returns.\"],\"vXIe7J\":[\"Language\"],\"vmQmHx\":[\"Add custom CSS to override any styles. Use data attributes like [data-page], [data-post], [data-format] to target specific elements.\"],\"vzX5FB\":[\"Delete Account\"],\"w8Rv8T\":[\"Label is required\"],\"wL3cK8\":[\"Latest\"],\"wPmHHc\":[\"Quiet surfaces let writing lead.\"],\"wW6NCp\":[\"Last error\"],\"wc+17X\":[\"/* Your custom CSS here */\"],\"wuLtXn\":[\"No active sessions right now. Signed-in devices show up here.\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xHt036\":[\"Personal Access Token\"],\"xbN8dp\":[\"Soft color should still carry a clear reading rhythm.\"],\"y28hnO\":[\"Post\"],\"y8Md/V\":[\"Language and time updated.\"],\"yNCqOt\":[\"Latest feed\"],\"yQ3kNF\":[\"Type the following phrase to confirm:\"],\"ydq1k2\":[\"Pick an account first\"],\"yjjCV8\":[\"Fixed feed URLs\"],\"yjkELF\":[\"Confirm New Password\"],\"yzF66j\":[\"Link\"],\"z6wakA\":[\"A short intro shown on your home page.\"],\"zEizrk\":[\"Last used \",[\"date\"]],\"zSURJW\":[\"No repositories match.\"],\"zXH2jX\":[\"Language & Time\"],\"zlcDd2\":[\"Delete this custom URL? Visitors using it won't be redirected anymore.\"],\"zwBp5t\":[\"Private\"],\"zxRN6H\":[\"Header links, home feed, and overflow menu\"]}");
|
|
@@ -3352,15 +3352,289 @@ function normalizeThemeColorForMeta(color) {
|
|
|
3352
3352
|
* internal paths (e.g. `/_assets/client-HASH.js`) embedded by the Worker build
|
|
3353
3353
|
* from the Vite client manifest. Used only in production (IS_VITE_DEV=false).
|
|
3354
3354
|
*/ var IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
|
|
3355
|
-
var CORE_VERSION = "0.3.
|
|
3356
|
-
var CLIENT_JS_FILE = "/_assets/client-
|
|
3357
|
-
var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-
|
|
3358
|
-
var CLIENT_CSS_FILE = "/_assets/client-
|
|
3355
|
+
var CORE_VERSION = "0.3.45-63de2a28e1b65691";
|
|
3356
|
+
var CLIENT_JS_FILE = "/_assets/client-dSfWfMe9.js";
|
|
3357
|
+
var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-Dcon89Av.js";
|
|
3358
|
+
var CLIENT_CSS_FILE = "/_assets/client-DDs6NzB3.css";
|
|
3359
3359
|
var CLIENT_CJK_CSS_FILE = "/_assets/client-cjk-B7Z0snDu.css";
|
|
3360
3360
|
var CLIENT_CJK_TC_CSS_FILE = "/_assets/client-cjk-tc-BesJYrb2.css";
|
|
3361
3361
|
var CLIENT_CJK_JP_CSS_FILE = "/_assets/client-cjk-jp-DZwrTzQC.css";
|
|
3362
3362
|
var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
3363
3363
|
//#endregion
|
|
3364
|
+
//#region src/ui/shared/icon-collector.ts
|
|
3365
|
+
/**
|
|
3366
|
+
* Request-scoped icon collector for SSR SVG sprite pattern.
|
|
3367
|
+
*
|
|
3368
|
+
* The <Icon> component registers each used icon name here during render.
|
|
3369
|
+
* At the end of <body>, <IconSprite> reads the collected set and emits a
|
|
3370
|
+
* single <svg><symbol>...</symbol></svg> block so every <use href="#icon-x">
|
|
3371
|
+
* reference in the page resolves to a definition.
|
|
3372
|
+
*
|
|
3373
|
+
* Mirrors the I18nProvider pattern in i18n/context.tsx: Hono JSX renders
|
|
3374
|
+
* synchronously per request, so a module-level singleton is safe.
|
|
3375
|
+
*/ var currentCollector = null;
|
|
3376
|
+
/**
|
|
3377
|
+
* Start a new collection scope for the current render pass.
|
|
3378
|
+
* Call at the top of the root layout before children render.
|
|
3379
|
+
*/ function resetIconCollector() {
|
|
3380
|
+
currentCollector = /* @__PURE__ */ new Set();
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Register an icon as used during this render.
|
|
3384
|
+
* Safe to call even when no collector is active (no-op).
|
|
3385
|
+
*/ function collectIcon(name) {
|
|
3386
|
+
currentCollector?.add(name);
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Get the icon names collected so far during this render.
|
|
3390
|
+
* Returns an empty set if no collection scope is active.
|
|
3391
|
+
*/ function getCollectedIcons() {
|
|
3392
|
+
return currentCollector ?? /* @__PURE__ */ new Set();
|
|
3393
|
+
}
|
|
3394
|
+
//#endregion
|
|
3395
|
+
//#region src/lib/featured-icons.ts
|
|
3396
|
+
/**
|
|
3397
|
+
* Shared icon definitions for featured post affordances.
|
|
3398
|
+
*
|
|
3399
|
+
* These paths are reused across Hono JSX, Lit, and exported static markup so
|
|
3400
|
+
* "Featured" keeps one visual language everywhere.
|
|
3401
|
+
*/ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
|
|
3402
|
+
var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
3403
|
+
/**
|
|
3404
|
+
* Build inline SVG markup for the shared featured sparkle icon.
|
|
3405
|
+
*
|
|
3406
|
+
* @param options - Render options for the sparkle icon.
|
|
3407
|
+
* @returns SVG markup string for inline insertion.
|
|
3408
|
+
* @example
|
|
3409
|
+
* getFeaturedIconSvg({ off: true, className: "icon-fine" });
|
|
3410
|
+
*/ function getFeaturedIconSvg(options = {}) {
|
|
3411
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
|
|
3412
|
+
}
|
|
3413
|
+
//#endregion
|
|
3414
|
+
//#region src/lib/decorative-quote-mark.ts
|
|
3415
|
+
var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
|
|
3416
|
+
var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
|
|
3417
|
+
DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
|
|
3418
|
+
//#endregion
|
|
3419
|
+
//#region src/ui/shared/custom-icons.ts
|
|
3420
|
+
/**
|
|
3421
|
+
* Custom (non-lucide) SVG symbol definitions used by the icon sprite.
|
|
3422
|
+
*
|
|
3423
|
+
* Icons here fall into three groups:
|
|
3424
|
+
* 1. Jant-specific paths (decorative quote mark, featured sparkle).
|
|
3425
|
+
* 2. Lucide-equivalent paths the UI uses with non-default stroke widths
|
|
3426
|
+
* or sizes that don't match the stock lucide symbol (we keep them as
|
|
3427
|
+
* custom symbols to preserve exact visual fidelity during refactor).
|
|
3428
|
+
* 3. Fixed-color SVGs (video play overlay) that don't use currentColor.
|
|
3429
|
+
*
|
|
3430
|
+
* Each entry provides everything needed to render a <symbol> element:
|
|
3431
|
+
* <symbol id="icon-${name}" viewBox={viewBox}>{inner}</symbol>
|
|
3432
|
+
* Consumers of <Icon name="..."> pass `size` / `className` on the outer
|
|
3433
|
+
* <svg><use/></svg>; `<symbol>` children inherit the outer attributes.
|
|
3434
|
+
*/ var STROKE_THIN = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.35\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3435
|
+
var STROKE_POST_BADGE = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3436
|
+
var CUSTOM_SYMBOLS = {
|
|
3437
|
+
"featured-sparkle": {
|
|
3438
|
+
viewBox: "0 0 24 24",
|
|
3439
|
+
inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" />`
|
|
3440
|
+
},
|
|
3441
|
+
"featured-sparkle-off": {
|
|
3442
|
+
viewBox: "0 0 24 24",
|
|
3443
|
+
inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" /><path ${STROKE_THIN} d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />`
|
|
3444
|
+
},
|
|
3445
|
+
"decorative-quote": {
|
|
3446
|
+
viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
|
|
3447
|
+
inner: DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("")
|
|
3448
|
+
},
|
|
3449
|
+
"post-collection-lock": {
|
|
3450
|
+
viewBox: "0 0 16 16",
|
|
3451
|
+
inner: `<rect ${STROKE_THIN} x="3" y="5.05" width="10" height="8.15" rx="2.2" /><path ${STROKE_THIN} d="M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" />`
|
|
3452
|
+
},
|
|
3453
|
+
"post-menu-dots": {
|
|
3454
|
+
viewBox: "0 0 24 24",
|
|
3455
|
+
inner: `<circle cx="5" cy="12" r="1.75" fill="currentColor" /><circle cx="12" cy="12" r="1.75" fill="currentColor" /><circle cx="19" cy="12" r="1.75" fill="currentColor" />`
|
|
3456
|
+
},
|
|
3457
|
+
"post-external-link": {
|
|
3458
|
+
viewBox: "0 0 24 24",
|
|
3459
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 17 17 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M9 7h8v8" />`
|
|
3460
|
+
},
|
|
3461
|
+
"post-reply": {
|
|
3462
|
+
viewBox: "0 0 24 24",
|
|
3463
|
+
inner: `<polyline fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="9 17 4 12 9 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M20 18v-2a4 4 0 0 0-4-4H4" />`
|
|
3464
|
+
},
|
|
3465
|
+
"link-domain": {
|
|
3466
|
+
viewBox: "0 0 24 24",
|
|
3467
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />`
|
|
3468
|
+
},
|
|
3469
|
+
"link-preview-play": {
|
|
3470
|
+
viewBox: "0 0 68 48",
|
|
3471
|
+
inner: "<path class=\"link-preview-play-bg\" fill=\"rgba(0,0,0,.65)\" d=\"M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z\" /><path fill=\"#fff\" d=\"M45 24L27 14v20\" />"
|
|
3472
|
+
},
|
|
3473
|
+
"link-preview-badge-play": {
|
|
3474
|
+
viewBox: "0 0 16 16",
|
|
3475
|
+
inner: "<path fill=\"currentColor\" d=\"M5.5 3.5v9l7-4.5z\" />"
|
|
3476
|
+
},
|
|
3477
|
+
"toast-success": {
|
|
3478
|
+
viewBox: "0 0 24 24",
|
|
3479
|
+
inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m9 12 2 2 4-4" />`
|
|
3480
|
+
},
|
|
3481
|
+
"toast-error": {
|
|
3482
|
+
viewBox: "0 0 24 24",
|
|
3483
|
+
inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m15 9-6 6M9 9l6 6" />`
|
|
3484
|
+
},
|
|
3485
|
+
"toast-close": {
|
|
3486
|
+
viewBox: "0 0 24 24",
|
|
3487
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M18 6 6 18M6 6l12 12" />`
|
|
3488
|
+
},
|
|
3489
|
+
"post-status-pin": {
|
|
3490
|
+
viewBox: "0 0 24 24",
|
|
3491
|
+
inner: `<line ${STROKE_POST_BADGE} x1="12" x2="12" y1="17" y2="22" /><path ${STROKE_POST_BADGE} d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" />`
|
|
3492
|
+
},
|
|
3493
|
+
"post-status-private": {
|
|
3494
|
+
viewBox: "0 0 24 24",
|
|
3495
|
+
inner: `<path ${STROKE_POST_BADGE} d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path ${STROKE_POST_BADGE} d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path ${STROKE_POST_BADGE} d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path ${STROKE_POST_BADGE} d="m2 2 20 20" />`
|
|
3496
|
+
}
|
|
3497
|
+
};
|
|
3498
|
+
function getCustomSymbol(name) {
|
|
3499
|
+
return CUSTOM_SYMBOLS[name] ?? null;
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Return the viewBox for an icon's outer <svg> wrapper.
|
|
3503
|
+
*
|
|
3504
|
+
* This must match the <symbol>'s viewBox so the browser computes the correct
|
|
3505
|
+
* intrinsic aspect ratio. Without this, outer <svg> with `height: auto` in
|
|
3506
|
+
* CSS falls back to the 300×150 replaced-element default instead of the
|
|
3507
|
+
* icon's real aspect ratio.
|
|
3508
|
+
*
|
|
3509
|
+
* Falls back to lucide's "0 0 24 24" for lucide-sourced icons.
|
|
3510
|
+
*/ function getIconViewBox(name) {
|
|
3511
|
+
return CUSTOM_SYMBOLS[name]?.viewBox ?? "0 0 24 24";
|
|
3512
|
+
}
|
|
3513
|
+
//#endregion
|
|
3514
|
+
//#region src/ui/shared/Icon.tsx
|
|
3515
|
+
/**
|
|
3516
|
+
* <Icon> — sprite-based SVG icon for SSR pages.
|
|
3517
|
+
*
|
|
3518
|
+
* Renders a lightweight <svg><use href="#icon-${name}"/></svg> stub and
|
|
3519
|
+
* registers the icon name with the request-scoped collector so the final
|
|
3520
|
+
* sprite (rendered by <IconSprite>) contains exactly the icons used on
|
|
3521
|
+
* this page.
|
|
3522
|
+
*
|
|
3523
|
+
* Name can refer to any lucide-static icon (kebab-case) or one of the
|
|
3524
|
+
* custom symbols defined in `custom-icons.ts`. Unknown names render an
|
|
3525
|
+
* empty <svg> — the same failure mode as the previous getIconSvg() path.
|
|
3526
|
+
*
|
|
3527
|
+
* Size: outer <svg> width/height in pixels. Defaults to 24 (lucide default).
|
|
3528
|
+
* Pass `class` to add CSS classes, e.g. for sizing via stylesheet instead
|
|
3529
|
+
* of inline width/height.
|
|
3530
|
+
*/ var Icon$1 = ({ name, size, class: cls, "aria-label": ariaLabel, "aria-hidden": ariaHidden }) => {
|
|
3531
|
+
collectIcon(name);
|
|
3532
|
+
const hidden = ariaHidden ?? (ariaLabel ? void 0 : true);
|
|
3533
|
+
return /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3534
|
+
viewBox: getIconViewBox(name),
|
|
3535
|
+
...size !== void 0 ? {
|
|
3536
|
+
width: size,
|
|
3537
|
+
height: size
|
|
3538
|
+
} : {},
|
|
3539
|
+
...cls ? { class: cls } : {},
|
|
3540
|
+
...ariaLabel ? {
|
|
3541
|
+
"aria-label": ariaLabel,
|
|
3542
|
+
role: "img"
|
|
3543
|
+
} : {},
|
|
3544
|
+
...hidden ? { "aria-hidden": "true" } : {},
|
|
3545
|
+
children: /* @__PURE__ */ jsxDEV$1("use", { href: `#icon-${name}` })
|
|
3546
|
+
});
|
|
3547
|
+
};
|
|
3548
|
+
//#endregion
|
|
3549
|
+
//#region src/lib/icons.ts
|
|
3550
|
+
/**
|
|
3551
|
+
* Shared icon utilities.
|
|
3552
|
+
*
|
|
3553
|
+
* Provides a small wrapper around lucide-static so server-rendered UI can fetch
|
|
3554
|
+
* SVG markup by kebab-case icon name.
|
|
3555
|
+
*/
|
|
3556
|
+
/**
|
|
3557
|
+
* Convert a kebab-case icon name to PascalCase for lucide-static lookup.
|
|
3558
|
+
*
|
|
3559
|
+
* @param name - Kebab-case icon name such as "book-open"
|
|
3560
|
+
* @returns PascalCase name such as "BookOpen"
|
|
3561
|
+
*/ function toPascalCase(name) {
|
|
3562
|
+
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* Get SVG markup for a Lucide icon by kebab-case name.
|
|
3566
|
+
*
|
|
3567
|
+
* @param name - Kebab-case icon name
|
|
3568
|
+
* @returns SVG string or null when the icon is unknown
|
|
3569
|
+
*
|
|
3570
|
+
* @example
|
|
3571
|
+
* ```ts
|
|
3572
|
+
* getIconSvg("book-open");
|
|
3573
|
+
* ```
|
|
3574
|
+
*/ function getIconSvg(name) {
|
|
3575
|
+
const svg = lucideIcons[toPascalCase(name)];
|
|
3576
|
+
return typeof svg === "string" ? svg : null;
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Get the inner SVG contents for a Lucide icon (the path children only,
|
|
3580
|
+
* without the outer <svg> wrapper). Used by the icon sprite to build
|
|
3581
|
+
* <symbol> definitions.
|
|
3582
|
+
*
|
|
3583
|
+
* @param name - Kebab-case icon name
|
|
3584
|
+
* @returns Inner SVG markup (e.g. "<path ... />"), or null when unknown
|
|
3585
|
+
*
|
|
3586
|
+
* @example
|
|
3587
|
+
* ```ts
|
|
3588
|
+
* getIconInnerSvg("book-open");
|
|
3589
|
+
* // -> "<path d=\"...\"/><path d=\"...\"/>"
|
|
3590
|
+
* ```
|
|
3591
|
+
*/ function getIconInnerSvg(name) {
|
|
3592
|
+
const svg = getIconSvg(name);
|
|
3593
|
+
if (!svg) return null;
|
|
3594
|
+
const openTagEnd = svg.indexOf(">");
|
|
3595
|
+
const closeTagStart = svg.lastIndexOf("</svg>");
|
|
3596
|
+
if (openTagEnd < 0 || closeTagStart < 0) return null;
|
|
3597
|
+
return svg.slice(openTagEnd + 1, closeTagStart);
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Default stroke/fill attributes inherited by <symbol> children when a
|
|
3601
|
+
* lucide icon is referenced via <use>. These mirror the attributes lucide
|
|
3602
|
+
* normally sets on the outer <svg> so the currentColor-based theming keeps
|
|
3603
|
+
* working through <use>.
|
|
3604
|
+
*/ var LUCIDE_SYMBOL_ATTRS = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3605
|
+
var LUCIDE_VIEWBOX = "0 0 24 24";
|
|
3606
|
+
//#endregion
|
|
3607
|
+
//#region src/ui/shared/IconSprite.tsx
|
|
3608
|
+
/**
|
|
3609
|
+
* <IconSprite> — emits the SVG symbol definitions used by this render.
|
|
3610
|
+
*
|
|
3611
|
+
* Must be rendered AFTER all <Icon> usages in the document (e.g. at the
|
|
3612
|
+
* end of <body>) so the collector has the full set of icon names. Hono
|
|
3613
|
+
* JSX stringifies synchronously in document order, so children declared
|
|
3614
|
+
* earlier in the tree are evaluated before this component.
|
|
3615
|
+
*
|
|
3616
|
+
* <use href="#icon-x"> anywhere in the document resolves correctly even
|
|
3617
|
+
* when the <symbol> definition comes after the reference, since browsers
|
|
3618
|
+
* wire up the references after the full document is parsed.
|
|
3619
|
+
*/ function buildSymbol(name) {
|
|
3620
|
+
const custom = getCustomSymbol(name);
|
|
3621
|
+
if (custom) return `<symbol id="icon-${name}" viewBox="${custom.viewBox}">${custom.inner}</symbol>`;
|
|
3622
|
+
const inner = getIconInnerSvg(name);
|
|
3623
|
+
if (inner === null) return null;
|
|
3624
|
+
return `<symbol id="icon-${name}" viewBox="${LUCIDE_VIEWBOX}" ${LUCIDE_SYMBOL_ATTRS}>${inner}</symbol>`;
|
|
3625
|
+
}
|
|
3626
|
+
var IconSprite = () => {
|
|
3627
|
+
const symbols = Array.from(getCollectedIcons()).sort().map(buildSymbol).filter((s) => s !== null).join("");
|
|
3628
|
+
if (!symbols) return null;
|
|
3629
|
+
return /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3630
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3631
|
+
style: "display:none",
|
|
3632
|
+
"aria-hidden": "true",
|
|
3633
|
+
"data-icon-sprite": true,
|
|
3634
|
+
children: raw(symbols)
|
|
3635
|
+
});
|
|
3636
|
+
};
|
|
3637
|
+
//#endregion
|
|
3364
3638
|
//#region src/ui/layouts/BaseLayout.tsx
|
|
3365
3639
|
/**
|
|
3366
3640
|
* Base HTML Layout
|
|
@@ -3371,6 +3645,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3371
3645
|
* In dev mode (Vite), serves assets via Vite's dev server.
|
|
3372
3646
|
* In production, serves pre-built assets with content-hashed filenames.
|
|
3373
3647
|
*/ var BaseLayout = ({ title, description, lang, c, toast, faviconHref, appleTouchHref, faviconUrl, faviconVersion, socialImageUrl, canonicalHref, noindex, isAuthenticated = false, clientBundle, children }) => {
|
|
3648
|
+
resetIconCollector();
|
|
3374
3649
|
const resolvedLang = lang ?? (c ? c.get("lang") : "en");
|
|
3375
3650
|
const appConfig = c ? c.get("appConfig") : void 0;
|
|
3376
3651
|
const resolvedSocialImagePath = socialImageUrl ?? faviconUrl ?? appConfig?.siteAvatarUrl ?? getJantIconHref("socialImage", appConfig?.sitePathPrefix || "");
|
|
@@ -3398,7 +3673,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3398
3673
|
const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
|
|
3399
3674
|
const cjkStylesheetPath = cjkSerifFont === "zh-Hans" ? IS_VITE_DEV ? assetPath("/src/style-cjk.css") : toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath) : cjkSerifFont === "zh-Hant" ? IS_VITE_DEV ? assetPath("/src/style-cjk-tc.css") : toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath) : cjkSerifFont === "ja" ? IS_VITE_DEV ? assetPath("/src/style-cjk-jp.css") : toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath) : cjkSerifFont === "ko" ? IS_VITE_DEV ? assetPath("/src/style-cjk-kr.css") : toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath) : null;
|
|
3400
3675
|
const clientScriptPath = IS_VITE_DEV ? resolvedClientBundle === "full" ? assetPath("/src/client-auth.ts") : assetPath("/src/client.ts") : toPublicAssetPath(resolvedClientBundle === "full" ? CLIENT_AUTH_JS_FILE : CLIENT_JS_FILE, assetBasePath);
|
|
3401
|
-
const faviconAssetVersion = resolvedFaviconVersion || "0.3.
|
|
3676
|
+
const faviconAssetVersion = resolvedFaviconVersion || "0.3.45-63de2a28e1b65691";
|
|
3402
3677
|
const resolvedFaviconHref = faviconHref ?? (faviconAssetVersion ? toPublicPath(`/favicon.ico?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/favicon.ico", sitePathPrefix));
|
|
3403
3678
|
const resolvedAppleTouchHref = appleTouchHref ?? (faviconAssetVersion ? toPublicPath(`/apple-touch-icon.png?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/apple-touch-icon.png", sitePathPrefix));
|
|
3404
3679
|
const socialImageHref = resolvedSocialImagePath && (isFullUrl(resolvedSocialImagePath) || resolvedSocialImagePath.startsWith("//") ? resolvedSocialImagePath : toAbsoluteSiteUrl(resolvedSocialImagePath, appConfig?.siteUrl || "", sitePathPrefix));
|
|
@@ -3559,46 +3834,18 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3559
3834
|
class: `toast ${toast.type === "error" ? "toast-error" : "toast-success"}`,
|
|
3560
3835
|
"data-init": "el.closest('[popover]').showPopover(); history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)",
|
|
3561
3836
|
children: [
|
|
3562
|
-
toast.type === "error" ? /* @__PURE__ */ jsxDEV$1("
|
|
3563
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3564
|
-
fill: "none",
|
|
3565
|
-
viewBox: "0 0 24 24",
|
|
3566
|
-
"stroke-width": "2",
|
|
3567
|
-
stroke: "currentColor",
|
|
3568
|
-
children: [/* @__PURE__ */ jsxDEV$1("circle", {
|
|
3569
|
-
cx: "12",
|
|
3570
|
-
cy: "12",
|
|
3571
|
-
r: "10"
|
|
3572
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "m15 9-6 6M9 9l6 6" })]
|
|
3573
|
-
}) : /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3574
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3575
|
-
fill: "none",
|
|
3576
|
-
viewBox: "0 0 24 24",
|
|
3577
|
-
"stroke-width": "2",
|
|
3578
|
-
stroke: "currentColor",
|
|
3579
|
-
children: [/* @__PURE__ */ jsxDEV$1("circle", {
|
|
3580
|
-
cx: "12",
|
|
3581
|
-
cy: "12",
|
|
3582
|
-
r: "10"
|
|
3583
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "m9 12 2 2 4-4" })]
|
|
3584
|
-
}),
|
|
3837
|
+
toast.type === "error" ? /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-error" }) : /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-success" }),
|
|
3585
3838
|
/* @__PURE__ */ jsxDEV$1("span", { children: toast.message }),
|
|
3586
3839
|
/* @__PURE__ */ jsxDEV$1("button", {
|
|
3587
3840
|
class: "toast-close",
|
|
3588
3841
|
"data-on:click": "el.closest('.toast').classList.add('toast-out'); el.closest('.toast').addEventListener('animationend', () => el.closest('.toast').remove())",
|
|
3589
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
3590
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3591
|
-
fill: "none",
|
|
3592
|
-
viewBox: "0 0 24 24",
|
|
3593
|
-
"stroke-width": "2",
|
|
3594
|
-
stroke: "currentColor",
|
|
3595
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
3596
|
-
})
|
|
3842
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-close" })
|
|
3597
3843
|
})
|
|
3598
3844
|
]
|
|
3599
3845
|
})
|
|
3600
3846
|
}),
|
|
3601
|
-
customBodyEndHtml && raw(customBodyEndHtml)
|
|
3847
|
+
customBodyEndHtml && raw(customBodyEndHtml),
|
|
3848
|
+
/* @__PURE__ */ jsxDEV$1(IconSprite, {})
|
|
3602
3849
|
]
|
|
3603
3850
|
})]
|
|
3604
3851
|
})] });
|
|
@@ -3908,7 +4155,7 @@ var STORAGE_DRIVERS = [
|
|
|
3908
4155
|
envKeys: ["DEFAULT_THEME"]
|
|
3909
4156
|
},
|
|
3910
4157
|
DEFAULT_FONT_THEME: {
|
|
3911
|
-
defaultValue: "
|
|
4158
|
+
defaultValue: "classic",
|
|
3912
4159
|
envOnly: true,
|
|
3913
4160
|
envKeys: ["DEFAULT_FONT_THEME"]
|
|
3914
4161
|
},
|
|
@@ -4374,6 +4621,7 @@ function createTypeIdSchema(prefix) {
|
|
|
4374
4621
|
"reset",
|
|
4375
4622
|
"collections",
|
|
4376
4623
|
"compose",
|
|
4624
|
+
"new",
|
|
4377
4625
|
"static",
|
|
4378
4626
|
"assets",
|
|
4379
4627
|
"_assets",
|
|
@@ -9054,25 +9302,6 @@ var MediaGallery = ({ attachments, postPermalink }) => {
|
|
|
9054
9302
|
});
|
|
9055
9303
|
};
|
|
9056
9304
|
//#endregion
|
|
9057
|
-
//#region src/lib/featured-icons.ts
|
|
9058
|
-
/**
|
|
9059
|
-
* Shared icon definitions for featured post affordances.
|
|
9060
|
-
*
|
|
9061
|
-
* These paths are reused across Hono JSX, Lit, and exported static markup so
|
|
9062
|
-
* "Featured" keeps one visual language everywhere.
|
|
9063
|
-
*/ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
|
|
9064
|
-
var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
9065
|
-
/**
|
|
9066
|
-
* Build inline SVG markup for the shared featured sparkle icon.
|
|
9067
|
-
*
|
|
9068
|
-
* @param options - Render options for the sparkle icon.
|
|
9069
|
-
* @returns SVG markup string for inline insertion.
|
|
9070
|
-
* @example
|
|
9071
|
-
* getFeaturedIconSvg({ off: true, className: "icon-fine" });
|
|
9072
|
-
*/ function getFeaturedIconSvg(options = {}) {
|
|
9073
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
|
|
9074
|
-
}
|
|
9075
|
-
//#endregion
|
|
9076
9305
|
//#region src/ui/shared/PostFooter.tsx
|
|
9077
9306
|
/**
|
|
9078
9307
|
* Post Footer
|
|
@@ -9099,22 +9328,7 @@ var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
|
9099
9328
|
children: [showIcon && /* @__PURE__ */ jsxDEV$1("span", {
|
|
9100
9329
|
class: "post-collection-primary-icon",
|
|
9101
9330
|
"aria-hidden": "true",
|
|
9102
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9103
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9104
|
-
viewBox: "0 0 16 16",
|
|
9105
|
-
fill: "none",
|
|
9106
|
-
stroke: "currentColor",
|
|
9107
|
-
"stroke-width": "1.35",
|
|
9108
|
-
"stroke-linecap": "round",
|
|
9109
|
-
"stroke-linejoin": "round",
|
|
9110
|
-
children: [/* @__PURE__ */ jsxDEV$1("rect", {
|
|
9111
|
-
x: "3",
|
|
9112
|
-
y: "5.05",
|
|
9113
|
-
width: "10",
|
|
9114
|
-
height: "8.15",
|
|
9115
|
-
rx: "2.2"
|
|
9116
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" })]
|
|
9117
|
-
})
|
|
9331
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-collection-lock" })
|
|
9118
9332
|
}), /* @__PURE__ */ jsxDEV$1("span", {
|
|
9119
9333
|
class: "post-collection-tag-text",
|
|
9120
9334
|
children: first.title
|
|
@@ -9185,29 +9399,9 @@ var PostMenuTriggerButton = ({ className = "post-menu-trigger" }) => {
|
|
|
9185
9399
|
"aria-label": i18n._({ id: "JcD7qf" }),
|
|
9186
9400
|
"aria-expanded": "false",
|
|
9187
9401
|
"data-post-menu-trigger": true,
|
|
9188
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
height: "15",
|
|
9192
|
-
viewBox: "0 0 24 24",
|
|
9193
|
-
fill: "currentColor",
|
|
9194
|
-
children: [
|
|
9195
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9196
|
-
cx: "5",
|
|
9197
|
-
cy: "12",
|
|
9198
|
-
r: "1.75"
|
|
9199
|
-
}),
|
|
9200
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9201
|
-
cx: "12",
|
|
9202
|
-
cy: "12",
|
|
9203
|
-
r: "1.75"
|
|
9204
|
-
}),
|
|
9205
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9206
|
-
cx: "19",
|
|
9207
|
-
cy: "12",
|
|
9208
|
-
r: "1.75"
|
|
9209
|
-
})
|
|
9210
|
-
]
|
|
9402
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9403
|
+
name: "post-menu-dots",
|
|
9404
|
+
size: 15
|
|
9211
9405
|
})
|
|
9212
9406
|
});
|
|
9213
9407
|
};
|
|
@@ -9235,17 +9429,7 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9235
9429
|
"aria-label": featuredLabel,
|
|
9236
9430
|
"data-tooltip": featuredLabel,
|
|
9237
9431
|
"data-align": "center",
|
|
9238
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9239
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9240
|
-
viewBox: "0 0 24 24",
|
|
9241
|
-
fill: "none",
|
|
9242
|
-
stroke: "currentColor",
|
|
9243
|
-
"stroke-width": "1.35",
|
|
9244
|
-
"stroke-linecap": "round",
|
|
9245
|
-
"stroke-linejoin": "round",
|
|
9246
|
-
"aria-hidden": "true",
|
|
9247
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: FEATURED_SPARKLE_PATH })
|
|
9248
|
-
})
|
|
9432
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "featured-sparkle" })
|
|
9249
9433
|
}),
|
|
9250
9434
|
showTimestamp && /* @__PURE__ */ jsxDEV$1(PostPublishedLink, {
|
|
9251
9435
|
post,
|
|
@@ -9257,16 +9441,7 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9257
9441
|
target: "_blank",
|
|
9258
9442
|
rel: "noopener noreferrer",
|
|
9259
9443
|
"aria-label": i18n._({ id: "9dr9Nh" }),
|
|
9260
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9261
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9262
|
-
viewBox: "0 0 24 24",
|
|
9263
|
-
fill: "none",
|
|
9264
|
-
stroke: "currentColor",
|
|
9265
|
-
"stroke-width": "2",
|
|
9266
|
-
"stroke-linecap": "round",
|
|
9267
|
-
"stroke-linejoin": "round",
|
|
9268
|
-
children: [/* @__PURE__ */ jsxDEV$1("path", { d: "M7 17 17 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M9 7h8v8" })]
|
|
9269
|
-
})
|
|
9444
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-external-link" })
|
|
9270
9445
|
}),
|
|
9271
9446
|
/* @__PURE__ */ jsxDEV$1(CompactCollectionTags, {
|
|
9272
9447
|
collections: post.collections,
|
|
@@ -9281,17 +9456,9 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9281
9456
|
class: "reply-trigger",
|
|
9282
9457
|
"aria-label": i18n._({ id: "ImOQa9" }),
|
|
9283
9458
|
"data-reply-trigger": true,
|
|
9284
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
height: "14",
|
|
9288
|
-
viewBox: "0 0 24 24",
|
|
9289
|
-
fill: "none",
|
|
9290
|
-
stroke: "currentColor",
|
|
9291
|
-
"stroke-width": "2",
|
|
9292
|
-
"stroke-linecap": "round",
|
|
9293
|
-
"stroke-linejoin": "round",
|
|
9294
|
-
children: [/* @__PURE__ */ jsxDEV$1("polyline", { points: "9 17 4 12 9 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })]
|
|
9459
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9460
|
+
name: "post-reply",
|
|
9461
|
+
size: 14
|
|
9295
9462
|
})
|
|
9296
9463
|
}), /* @__PURE__ */ jsxDEV$1(PostMenuTriggerButton, {})]
|
|
9297
9464
|
})]
|
|
@@ -9313,57 +9480,15 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9313
9480
|
children: [
|
|
9314
9481
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9315
9482
|
class: "post-status-badge post-status-pinned",
|
|
9316
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9317
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9318
|
-
viewBox: "0 0 24 24",
|
|
9319
|
-
fill: "none",
|
|
9320
|
-
stroke: "currentColor",
|
|
9321
|
-
"stroke-width": "1.75",
|
|
9322
|
-
"stroke-linecap": "round",
|
|
9323
|
-
"stroke-linejoin": "round",
|
|
9324
|
-
children: [/* @__PURE__ */ jsxDEV$1("line", {
|
|
9325
|
-
x1: "12",
|
|
9326
|
-
x2: "12",
|
|
9327
|
-
y1: "17",
|
|
9328
|
-
y2: "22"
|
|
9329
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
|
|
9330
|
-
}), "Pinned"]
|
|
9483
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
|
|
9331
9484
|
}),
|
|
9332
9485
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9333
9486
|
class: "post-status-badge post-status-pinned-in-collection",
|
|
9334
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9335
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9336
|
-
viewBox: "0 0 24 24",
|
|
9337
|
-
fill: "none",
|
|
9338
|
-
stroke: "currentColor",
|
|
9339
|
-
"stroke-width": "1.75",
|
|
9340
|
-
"stroke-linecap": "round",
|
|
9341
|
-
"stroke-linejoin": "round",
|
|
9342
|
-
children: [/* @__PURE__ */ jsxDEV$1("line", {
|
|
9343
|
-
x1: "12",
|
|
9344
|
-
x2: "12",
|
|
9345
|
-
y1: "17",
|
|
9346
|
-
y2: "22"
|
|
9347
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
|
|
9348
|
-
}), "Pinned"]
|
|
9487
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
|
|
9349
9488
|
}),
|
|
9350
9489
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9351
9490
|
class: "post-status-badge post-status-private",
|
|
9352
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9353
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9354
|
-
viewBox: "0 0 24 24",
|
|
9355
|
-
fill: "none",
|
|
9356
|
-
stroke: "currentColor",
|
|
9357
|
-
"stroke-width": "1.75",
|
|
9358
|
-
"stroke-linecap": "round",
|
|
9359
|
-
"stroke-linejoin": "round",
|
|
9360
|
-
children: [
|
|
9361
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
|
|
9362
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
|
|
9363
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
|
|
9364
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "m2 2 20 20" })
|
|
9365
|
-
]
|
|
9366
|
-
}), "Private"]
|
|
9491
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-private" }), "Private"]
|
|
9367
9492
|
})
|
|
9368
9493
|
]
|
|
9369
9494
|
});
|
|
@@ -9497,29 +9622,17 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9497
9622
|
isVideo && /* @__PURE__ */ jsxDEV$1("div", {
|
|
9498
9623
|
class: "link-preview-play",
|
|
9499
9624
|
"aria-hidden": "true",
|
|
9500
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9504
|
-
children: [/* @__PURE__ */ jsxDEV$1("path", {
|
|
9505
|
-
class: "link-preview-play-bg",
|
|
9506
|
-
d: "M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z",
|
|
9507
|
-
fill: "rgba(0,0,0,.65)"
|
|
9508
|
-
}), /* @__PURE__ */ jsxDEV$1("path", {
|
|
9509
|
-
d: "M45 24L27 14v20",
|
|
9510
|
-
fill: "#fff"
|
|
9511
|
-
})]
|
|
9625
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9626
|
+
name: "link-preview-play",
|
|
9627
|
+
class: "link-preview-play-icon"
|
|
9512
9628
|
})
|
|
9513
9629
|
}),
|
|
9514
9630
|
providerLabel && /* @__PURE__ */ jsxDEV$1("span", {
|
|
9515
9631
|
class: "link-preview-badge",
|
|
9516
9632
|
"aria-hidden": "true",
|
|
9517
|
-
children: [isVideo && /* @__PURE__ */ jsxDEV$1(
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9521
|
-
fill: "currentColor",
|
|
9522
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M5.5 3.5v9l7-4.5z" })
|
|
9633
|
+
children: [isVideo && /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9634
|
+
name: "link-preview-badge-play",
|
|
9635
|
+
class: "link-preview-badge-icon"
|
|
9523
9636
|
}), providerLabel]
|
|
9524
9637
|
})
|
|
9525
9638
|
]
|
|
@@ -9546,25 +9659,15 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9546
9659
|
class: "feed-link-domain",
|
|
9547
9660
|
target: "_blank",
|
|
9548
9661
|
rel: "noopener noreferrer",
|
|
9549
|
-
children: [/* @__PURE__ */ jsxDEV$1(
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
fill: "none",
|
|
9553
|
-
viewBox: "0 0 24 24",
|
|
9554
|
-
"stroke-width": "2",
|
|
9555
|
-
stroke: "currentColor",
|
|
9556
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
|
|
9662
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9663
|
+
name: "link-domain",
|
|
9664
|
+
class: "feed-link-domain-icon"
|
|
9557
9665
|
}), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
|
|
9558
9666
|
}) : /* @__PURE__ */ jsxDEV$1("div", {
|
|
9559
9667
|
class: "feed-link-domain",
|
|
9560
|
-
children: [/* @__PURE__ */ jsxDEV$1(
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
fill: "none",
|
|
9564
|
-
viewBox: "0 0 24 24",
|
|
9565
|
-
"stroke-width": "2",
|
|
9566
|
-
stroke: "currentColor",
|
|
9567
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
|
|
9668
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9669
|
+
name: "link-domain",
|
|
9670
|
+
class: "feed-link-domain-icon"
|
|
9568
9671
|
}), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
|
|
9569
9672
|
}));
|
|
9570
9673
|
const previewEl = !isCompact && post.previewImageUrl && /* @__PURE__ */ jsxDEV$1(LinkPreview, {
|
|
@@ -9645,11 +9748,6 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9645
9748
|
});
|
|
9646
9749
|
};
|
|
9647
9750
|
//#endregion
|
|
9648
|
-
//#region src/lib/decorative-quote-mark.ts
|
|
9649
|
-
var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
|
|
9650
|
-
var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
|
|
9651
|
-
DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
|
|
9652
|
-
//#endregion
|
|
9653
9751
|
//#region src/ui/shared/DecorativeQuoteMark.tsx
|
|
9654
9752
|
/**
|
|
9655
9753
|
* Decorative double-quote mark rendered as SVG so the shape stays consistent
|
|
@@ -9658,15 +9756,7 @@ DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}"
|
|
|
9658
9756
|
class: `decorative-quote-mark${cls ? ` ${cls}` : ""}`,
|
|
9659
9757
|
"data-direction": direction,
|
|
9660
9758
|
"aria-hidden": "true",
|
|
9661
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9662
|
-
viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
|
|
9663
|
-
role: "presentation",
|
|
9664
|
-
focusable: "false",
|
|
9665
|
-
children: DECORATIVE_QUOTE_MARK_PATHS.map((path) => /* @__PURE__ */ jsxDEV$1("path", {
|
|
9666
|
-
fill: "currentColor",
|
|
9667
|
-
d: path
|
|
9668
|
-
}))
|
|
9669
|
-
})
|
|
9759
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "decorative-quote" })
|
|
9670
9760
|
});
|
|
9671
9761
|
//#endregion
|
|
9672
9762
|
//#region src/ui/feed/QuoteCard.tsx
|
|
@@ -10060,7 +10150,7 @@ var PaginatedPageHeader = ({ title, currentPage = 1, totalPages, description, ic
|
|
|
10060
10150
|
* Home Page
|
|
10061
10151
|
*
|
|
10062
10152
|
* Timeline feed with per-type card components and thread previews.
|
|
10063
|
-
*/ var HomePage = ({ items, baseUrl, currentPage, totalPages }) => {
|
|
10153
|
+
*/ var HomePage = ({ items, baseUrl, currentPage, totalPages, isAuthenticated, signinUrl }) => {
|
|
10064
10154
|
const { i18n } = useLingui();
|
|
10065
10155
|
return /* @__PURE__ */ jsxDEV$1("div", {
|
|
10066
10156
|
"data-page": "home",
|
|
@@ -10079,8 +10169,12 @@ var PaginatedPageHeader = ({ title, currentPage = 1, totalPages, description, ic
|
|
|
10079
10169
|
class: "flex flex-col",
|
|
10080
10170
|
children: /* @__PURE__ */ jsxDEV$1("p", {
|
|
10081
10171
|
id: "empty-timeline",
|
|
10082
|
-
class: "py-
|
|
10083
|
-
children: i18n._({ id: "
|
|
10172
|
+
class: "py-8 text-muted-foreground",
|
|
10173
|
+
children: [i18n._({ id: "Q/uoSA" }), !isAuthenticated && /* @__PURE__ */ jsxDEV$1(Fragment$1, { children: [" ", /* @__PURE__ */ jsxDEV$1("a", {
|
|
10174
|
+
href: signinUrl,
|
|
10175
|
+
class: "underline-offset-2 hover:underline",
|
|
10176
|
+
children: i18n._({ id: "nfU386" })
|
|
10177
|
+
})] })]
|
|
10084
10178
|
})
|
|
10085
10179
|
})
|
|
10086
10180
|
})
|
|
@@ -10169,7 +10263,9 @@ homeRoutes.get("/", async (c) => {
|
|
|
10169
10263
|
items,
|
|
10170
10264
|
baseUrl: toPublicPath("/", navData.sitePathPrefix),
|
|
10171
10265
|
currentPage,
|
|
10172
|
-
totalPages
|
|
10266
|
+
totalPages,
|
|
10267
|
+
isAuthenticated,
|
|
10268
|
+
signinUrl: `${toPublicPath("/signin", navData.sitePathPrefix)}?redirect=${encodeURIComponent(toPublicPath("/", navData.sitePathPrefix))}`
|
|
10173
10269
|
})
|
|
10174
10270
|
});
|
|
10175
10271
|
});
|
|
@@ -10370,7 +10466,6 @@ function buildPostMeta(post, siteName) {
|
|
|
10370
10466
|
siteDomains: () => siteDomains$1,
|
|
10371
10467
|
siteMembers: () => siteMembers$1,
|
|
10372
10468
|
sites: () => sites$1,
|
|
10373
|
-
syncJobs: () => syncJobs$1,
|
|
10374
10469
|
uploadSessions: () => uploadSessions$1,
|
|
10375
10470
|
user: () => user$1,
|
|
10376
10471
|
verification: () => verification$1
|
|
@@ -10427,9 +10522,14 @@ var sites$1 = sqliteTable("site", {
|
|
|
10427
10522
|
id: text("id").primaryKey(),
|
|
10428
10523
|
key: text("key").notNull(),
|
|
10429
10524
|
status: text("status", { enum: SITE_STATUSES$1 }).notNull().default("active"),
|
|
10525
|
+
provisioningIdempotencyKey: text("provisioning_idempotency_key"),
|
|
10430
10526
|
createdAt: integer("created_at").notNull(),
|
|
10431
10527
|
updatedAt: integer("updated_at").notNull()
|
|
10432
|
-
}, (table) => [
|
|
10528
|
+
}, (table) => [
|
|
10529
|
+
uniqueIndex("uq_site_key").on(table.key),
|
|
10530
|
+
uniqueIndex("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
|
|
10531
|
+
check("chk_site_status", sql`${table.status} IN (${sqlTextEnum$1(SITE_STATUSES$1)})`)
|
|
10532
|
+
]);
|
|
10433
10533
|
var siteDomains$1 = sqliteTable("site_domain", {
|
|
10434
10534
|
id: text("id").primaryKey(),
|
|
10435
10535
|
siteId: text("site_id").notNull().references(() => sites$1.id, { onDelete: "cascade" }),
|
|
@@ -10768,28 +10868,6 @@ var account$1 = sqliteTable("account", {
|
|
|
10768
10868
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
10769
10869
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
10770
10870
|
});
|
|
10771
|
-
var SYNC_JOB_STATUSES = [
|
|
10772
|
-
"pending",
|
|
10773
|
-
"processing",
|
|
10774
|
-
"completed",
|
|
10775
|
-
"failed"
|
|
10776
|
-
];
|
|
10777
|
-
var syncJobs$1 = sqliteTable("sync_job", {
|
|
10778
|
-
id: text("id").primaryKey(),
|
|
10779
|
-
siteId: text("site_id").notNull().references(() => sites$1.id, { onDelete: "cascade" }),
|
|
10780
|
-
kind: text("kind").notNull(),
|
|
10781
|
-
payload: text("payload").notNull(),
|
|
10782
|
-
status: text("status", { enum: SYNC_JOB_STATUSES }).notNull().default("pending"),
|
|
10783
|
-
attempts: integer("attempts").notNull().default(0),
|
|
10784
|
-
maxAttempts: integer("max_attempts").notNull().default(3),
|
|
10785
|
-
createdAt: integer("created_at").notNull(),
|
|
10786
|
-
updatedAt: integer("updated_at").notNull(),
|
|
10787
|
-
lockedUntil: integer("locked_until")
|
|
10788
|
-
}, (table) => [
|
|
10789
|
-
index("idx_sync_job_status_created").on(table.status, table.createdAt),
|
|
10790
|
-
index("idx_sync_job_site_id").on(table.siteId),
|
|
10791
|
-
check("chk_sync_job_status", sql`${table.status} IN (${sqlTextEnum$1(SYNC_JOB_STATUSES)})`)
|
|
10792
|
-
]);
|
|
10793
10871
|
var verification$1 = sqliteTable("verification", {
|
|
10794
10872
|
id: text("id").primaryKey(),
|
|
10795
10873
|
identifier: text("identifier").notNull(),
|
|
@@ -10935,7 +11013,6 @@ function isNodeSqliteDatabase(db) {
|
|
|
10935
11013
|
siteDomains: () => siteDomains,
|
|
10936
11014
|
siteMembers: () => siteMembers,
|
|
10937
11015
|
sites: () => sites,
|
|
10938
|
-
syncJobs: () => syncJobs,
|
|
10939
11016
|
uploadSessions: () => uploadSessions,
|
|
10940
11017
|
user: () => user,
|
|
10941
11018
|
verification: () => verification
|
|
@@ -10995,9 +11072,14 @@ var sites = pgTable("site", {
|
|
|
10995
11072
|
id: text$1("id").primaryKey(),
|
|
10996
11073
|
key: text$1("key").notNull(),
|
|
10997
11074
|
status: text$1("status", { enum: SITE_STATUSES }).notNull().default("active"),
|
|
11075
|
+
provisioningIdempotencyKey: text$1("provisioning_idempotency_key"),
|
|
10998
11076
|
createdAt: integer$1("created_at").notNull(),
|
|
10999
11077
|
updatedAt: integer$1("updated_at").notNull()
|
|
11000
|
-
}, (table) => [
|
|
11078
|
+
}, (table) => [
|
|
11079
|
+
uniqueIndex$1("uq_site_key").on(table.key),
|
|
11080
|
+
uniqueIndex$1("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
|
|
11081
|
+
check$1("chk_site_status", sql`${table.status} IN (${sqlTextEnum(SITE_STATUSES)})`)
|
|
11082
|
+
]);
|
|
11001
11083
|
var siteDomains = pgTable("site_domain", {
|
|
11002
11084
|
id: text$1("id").primaryKey(),
|
|
11003
11085
|
siteId: text$1("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
@@ -11370,23 +11452,6 @@ var account = pgTable("account", {
|
|
|
11370
11452
|
mode: "date"
|
|
11371
11453
|
}).notNull()
|
|
11372
11454
|
});
|
|
11373
|
-
var syncJobs = pgTable("sync_job", {
|
|
11374
|
-
id: text$1("id").primaryKey(),
|
|
11375
|
-
siteId: text$1("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
11376
|
-
kind: text$1("kind").notNull(),
|
|
11377
|
-
payload: text$1("payload").notNull(),
|
|
11378
|
-
status: text$1("status", { enum: [
|
|
11379
|
-
"pending",
|
|
11380
|
-
"processing",
|
|
11381
|
-
"completed",
|
|
11382
|
-
"failed"
|
|
11383
|
-
] }).notNull().default("pending"),
|
|
11384
|
-
attempts: integer$1("attempts").notNull().default(0),
|
|
11385
|
-
maxAttempts: integer$1("max_attempts").notNull().default(3),
|
|
11386
|
-
createdAt: integer$1("created_at").notNull(),
|
|
11387
|
-
updatedAt: integer$1("updated_at").notNull(),
|
|
11388
|
-
lockedUntil: integer$1("locked_until")
|
|
11389
|
-
}, (table) => [index$1("idx_sync_job_status_created").on(table.status, table.createdAt), index$1("idx_sync_job_site_id").on(table.siteId)]);
|
|
11390
11455
|
var verification = pgTable("verification", {
|
|
11391
11456
|
id: text$1("id").primaryKey(),
|
|
11392
11457
|
identifier: text$1("identifier").notNull(),
|
|
@@ -11848,36 +11913,6 @@ function createMediaService(db, siteId, databaseSchema = sqliteSchemaBundle, dat
|
|
|
11848
11913
|
};
|
|
11849
11914
|
}
|
|
11850
11915
|
//#endregion
|
|
11851
|
-
//#region src/lib/icons.ts
|
|
11852
|
-
/**
|
|
11853
|
-
* Shared icon utilities.
|
|
11854
|
-
*
|
|
11855
|
-
* Provides a small wrapper around lucide-static so server-rendered UI can fetch
|
|
11856
|
-
* SVG markup by kebab-case icon name.
|
|
11857
|
-
*/
|
|
11858
|
-
/**
|
|
11859
|
-
* Convert a kebab-case icon name to PascalCase for lucide-static lookup.
|
|
11860
|
-
*
|
|
11861
|
-
* @param name - Kebab-case icon name such as "book-open"
|
|
11862
|
-
* @returns PascalCase name such as "BookOpen"
|
|
11863
|
-
*/ function toPascalCase(name) {
|
|
11864
|
-
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
11865
|
-
}
|
|
11866
|
-
/**
|
|
11867
|
-
* Get SVG markup for a Lucide icon by kebab-case name.
|
|
11868
|
-
*
|
|
11869
|
-
* @param name - Kebab-case icon name
|
|
11870
|
-
* @returns SVG string or null when the icon is unknown
|
|
11871
|
-
*
|
|
11872
|
-
* @example
|
|
11873
|
-
* ```ts
|
|
11874
|
-
* getIconSvg("book-open");
|
|
11875
|
-
* ```
|
|
11876
|
-
*/ function getIconSvg(name) {
|
|
11877
|
-
const svg = lucideIcons[toPascalCase(name)];
|
|
11878
|
-
return typeof svg === "string" ? svg : null;
|
|
11879
|
-
}
|
|
11880
|
-
//#endregion
|
|
11881
11916
|
//#region src/ui/pages/ArchivePage.tsx
|
|
11882
11917
|
/**
|
|
11883
11918
|
* Archive Page
|
|
@@ -14328,7 +14363,9 @@ latestRoutes.get("/", async (c) => {
|
|
|
14328
14363
|
items,
|
|
14329
14364
|
baseUrl: toPublicPath("/latest", navData.sitePathPrefix),
|
|
14330
14365
|
currentPage,
|
|
14331
|
-
totalPages
|
|
14366
|
+
totalPages,
|
|
14367
|
+
isAuthenticated: navData.isAuthenticated,
|
|
14368
|
+
signinUrl: `${toPublicPath("/signin", navData.sitePathPrefix)}?redirect=${encodeURIComponent(toPublicPath("/latest", navData.sitePathPrefix))}`
|
|
14332
14369
|
})
|
|
14333
14370
|
});
|
|
14334
14371
|
});
|
|
@@ -15724,22 +15761,6 @@ var CJK_SERIF_FALLBACK_VAR = "var(--font-cjk-serif-fallback)";
|
|
|
15724
15761
|
}
|
|
15725
15762
|
}
|
|
15726
15763
|
var BUILTIN_FONT_THEMES = [
|
|
15727
|
-
{
|
|
15728
|
-
id: "tufte",
|
|
15729
|
-
name: {
|
|
15730
|
-
id: "Tufte",
|
|
15731
|
-
message: "Tufte",
|
|
15732
|
-
comment: "@context: Font theme name"
|
|
15733
|
-
},
|
|
15734
|
-
headingFontFamily: TUFTE_SERIF,
|
|
15735
|
-
bodyFontFamily: TUFTE_SERIF,
|
|
15736
|
-
cssVariables: {},
|
|
15737
|
-
description: {
|
|
15738
|
-
id: "Palatino-based old-style serif matching Tufte CSS proportions",
|
|
15739
|
-
message: "Palatino-based old-style serif matching Tufte CSS proportions",
|
|
15740
|
-
comment: "@context: Font theme description"
|
|
15741
|
-
}
|
|
15742
|
-
},
|
|
15743
15764
|
{
|
|
15744
15765
|
id: "classic",
|
|
15745
15766
|
name: {
|
|
@@ -15756,6 +15777,22 @@ var BUILTIN_FONT_THEMES = [
|
|
|
15756
15777
|
comment: "@context: Font theme description"
|
|
15757
15778
|
}
|
|
15758
15779
|
},
|
|
15780
|
+
{
|
|
15781
|
+
id: "tufte",
|
|
15782
|
+
name: {
|
|
15783
|
+
id: "Tufte",
|
|
15784
|
+
message: "Tufte",
|
|
15785
|
+
comment: "@context: Font theme name"
|
|
15786
|
+
},
|
|
15787
|
+
headingFontFamily: TUFTE_SERIF,
|
|
15788
|
+
bodyFontFamily: TUFTE_SERIF,
|
|
15789
|
+
cssVariables: {},
|
|
15790
|
+
description: {
|
|
15791
|
+
id: "Palatino-based old-style serif matching Tufte CSS proportions",
|
|
15792
|
+
message: "Palatino-based old-style serif matching Tufte CSS proportions",
|
|
15793
|
+
comment: "@context: Font theme description"
|
|
15794
|
+
}
|
|
15795
|
+
},
|
|
15759
15796
|
{
|
|
15760
15797
|
id: "system-sans",
|
|
15761
15798
|
name: {
|
|
@@ -17775,13 +17812,19 @@ function SettingsRootContent({ sitePathPrefix = "", demoMode = false }) {
|
|
|
17775
17812
|
}),
|
|
17776
17813
|
/* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
|
|
17777
17814
|
title: i18n._({ id: "ebQKK7" }),
|
|
17778
|
-
children: /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17815
|
+
children: [/* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17779
17816
|
href: toPublicPath("/settings/general", sitePathPrefix),
|
|
17780
17817
|
icon: ICONS$1.settings,
|
|
17781
17818
|
tone: "subtle",
|
|
17782
17819
|
name: i18n._({ id: "Weq9zb" }),
|
|
17783
17820
|
description: i18n._({ id: "GMMWcy" })
|
|
17784
|
-
})
|
|
17821
|
+
}), /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17822
|
+
href: toPublicPath("/settings/custom-urls", sitePathPrefix),
|
|
17823
|
+
icon: ICONS$1.arrowRightLeft,
|
|
17824
|
+
tone: "subtle",
|
|
17825
|
+
name: i18n._({ id: "ke1gWS" }),
|
|
17826
|
+
description: i18n._({ id: "9T7Cwm" })
|
|
17827
|
+
})]
|
|
17785
17828
|
}),
|
|
17786
17829
|
/* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
|
|
17787
17830
|
title: i18n._({ id: "aAIQg2" }),
|
|
@@ -17832,29 +17875,19 @@ function SettingsRootContent({ sitePathPrefix = "", demoMode = false }) {
|
|
|
17832
17875
|
}),
|
|
17833
17876
|
/* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
|
|
17834
17877
|
title: i18n._({ id: "sxkWRg" }),
|
|
17835
|
-
children: [
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
description: i18n._({ id: "UFK415" })
|
|
17849
|
-
}),
|
|
17850
|
-
/* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17851
|
-
href: toPublicPath("/settings/api-tokens", sitePathPrefix),
|
|
17852
|
-
icon: ICONS$1.key,
|
|
17853
|
-
tone: "subtle",
|
|
17854
|
-
name: i18n._({ id: "ZiooJI" }),
|
|
17855
|
-
description: i18n._({ id: "egK+Yy" })
|
|
17856
|
-
})
|
|
17857
|
-
]
|
|
17878
|
+
children: [/* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17879
|
+
href: toPublicPath("/settings/code-injection", sitePathPrefix),
|
|
17880
|
+
icon: ICONS$1.terminal,
|
|
17881
|
+
tone: "subtle",
|
|
17882
|
+
name: i18n._({ id: "fWYqkz" }),
|
|
17883
|
+
description: i18n._({ id: "UFK415" })
|
|
17884
|
+
}), /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
|
|
17885
|
+
href: toPublicPath("/settings/api-tokens", sitePathPrefix),
|
|
17886
|
+
icon: ICONS$1.key,
|
|
17887
|
+
tone: "subtle",
|
|
17888
|
+
name: i18n._({ id: "ZiooJI" }),
|
|
17889
|
+
description: i18n._({ id: "egK+Yy" })
|
|
17890
|
+
})]
|
|
17858
17891
|
}),
|
|
17859
17892
|
/* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
|
|
17860
17893
|
title: i18n._({ id: "AeXO77" }),
|
|
@@ -19948,31 +19981,6 @@ async function syncHostedControlPlaneSiteAvatar(input) {
|
|
|
19948
19981
|
});
|
|
19949
19982
|
}
|
|
19950
19983
|
//#endregion
|
|
19951
|
-
//#region src/lib/job-queue.ts
|
|
19952
|
-
/**
|
|
19953
|
-
* Job Queue abstraction.
|
|
19954
|
-
*
|
|
19955
|
-
* Provides a unified interface for enqueueing background jobs across
|
|
19956
|
-
* Cloudflare Workers (CF Queues) and Node/Postgres (DB-backed polling).
|
|
19957
|
-
*/
|
|
19958
|
-
/**
|
|
19959
|
-
* A no-op queue that silently drops jobs.
|
|
19960
|
-
* Used as a fallback when neither CF Queue nor DB queue is configured.
|
|
19961
|
-
*/ var noopQueue = { async enqueue() {} };
|
|
19962
|
-
//#endregion
|
|
19963
|
-
//#region src/lib/job-queue-cf.ts
|
|
19964
|
-
/**
|
|
19965
|
-
* Cloudflare Queue adapter for the job queue.
|
|
19966
|
-
*/ /**
|
|
19967
|
-
* Create a job queue backed by a Cloudflare Queue binding.
|
|
19968
|
-
*
|
|
19969
|
-
* @param queue - The CF Queue binding (e.g. `env.GITHUB_SYNC_QUEUE`)
|
|
19970
|
-
*/ function createCfJobQueue(queue) {
|
|
19971
|
-
return { async enqueue(payload) {
|
|
19972
|
-
await queue.send(payload);
|
|
19973
|
-
} };
|
|
19974
|
-
}
|
|
19975
|
-
//#endregion
|
|
19976
19984
|
//#region src/lib/github-sync-site-config.ts
|
|
19977
19985
|
/**
|
|
19978
19986
|
* Build the SiteConfig the GitHub Sync service needs from a request
|
|
@@ -20020,33 +20028,6 @@ async function syncHostedControlPlaneSiteAvatar(input) {
|
|
|
20020
20028
|
rssFeedLimit: appConfig.rssFeedLimit
|
|
20021
20029
|
};
|
|
20022
20030
|
}
|
|
20023
|
-
//#endregion
|
|
20024
|
-
//#region src/lib/github-sync-trigger.ts
|
|
20025
|
-
/**
|
|
20026
|
-
* GitHub Sync Trigger
|
|
20027
|
-
*
|
|
20028
|
-
* Two dispatch paths:
|
|
20029
|
-
*
|
|
20030
|
-
* - `triggerGitHubSync` (queue-based): the original design, kept for
|
|
20031
|
-
* future use when a Cloudflare Queue binding is actually wired up.
|
|
20032
|
-
* Falls back to a no-op queue today, which silently drops jobs.
|
|
20033
|
-
* - `triggerGitHubSyncInline` (inline): runs pushFullSync in the
|
|
20034
|
-
* current worker invocation via `c.executionCtx.waitUntil`. Works
|
|
20035
|
-
* uniformly on Workers and Node, no queue binding required. This
|
|
20036
|
-
* is what every caller uses today.
|
|
20037
|
-
*
|
|
20038
|
-
* Both paths debounce through a PENDING flag. When a new trigger
|
|
20039
|
-
* arrives while a sync is running, the inline runner records it via
|
|
20040
|
-
* DIRTY; the running sync re-runs once more after completion so the
|
|
20041
|
-
* new edits land.
|
|
20042
|
-
*/
|
|
20043
|
-
/**
|
|
20044
|
-
* Resolve the appropriate job queue from the environment.
|
|
20045
|
-
* Returns the CF Queue adapter if available, otherwise noop.
|
|
20046
|
-
*/ function resolveJobQueue(env) {
|
|
20047
|
-
if (env.GITHUB_SYNC_QUEUE) return createCfJobQueue(env.GITHUB_SYNC_QUEUE);
|
|
20048
|
-
return noopQueue;
|
|
20049
|
-
}
|
|
20050
20031
|
/**
|
|
20051
20032
|
* Returns the effective "sync in progress" state.
|
|
20052
20033
|
*
|
|
@@ -20118,7 +20099,7 @@ async function syncHostedControlPlaneSiteAvatar(input) {
|
|
|
20118
20099
|
return;
|
|
20119
20100
|
}
|
|
20120
20101
|
await markSyncPending(settings);
|
|
20121
|
-
const { createGitHubSyncService } = await import("./github-sync-
|
|
20102
|
+
const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
20122
20103
|
const { getGitHubAppConfig } = await import("./env-CgaH9Mut.js").then((n) => n.t);
|
|
20123
20104
|
const run = runBackgroundSync(settings, createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
|
|
20124
20105
|
storage: c.var.storage,
|
|
@@ -20905,7 +20886,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
|
|
|
20905
20886
|
await c.var.services.settings.set("GITHUB_SYNC_AUTH_MODE", "pat");
|
|
20906
20887
|
await c.var.services.settings.set("GITHUB_SYNC_APP_INSTALLATION_ID", "");
|
|
20907
20888
|
await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
|
|
20908
|
-
const { createGitHubSyncService } = await import("./github-sync-
|
|
20889
|
+
const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
20909
20890
|
const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
|
|
20910
20891
|
storage: c.var.storage,
|
|
20911
20892
|
githubApp: getGitHubAppConfig(c.env)
|
|
@@ -20924,7 +20905,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
|
|
|
20924
20905
|
return dsRedirect(publicPath(c, "/settings/github-sync"));
|
|
20925
20906
|
});
|
|
20926
20907
|
settingsRoutes.post("/github-sync/push", async (c) => {
|
|
20927
|
-
const { createGitHubSyncService } = await import("./github-sync-
|
|
20908
|
+
const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
20928
20909
|
const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
|
|
20929
20910
|
storage: c.var.storage,
|
|
20930
20911
|
githubApp: getGitHubAppConfig(c.env)
|
|
@@ -20944,7 +20925,7 @@ settingsRoutes.post("/github-sync/push", async (c) => {
|
|
|
20944
20925
|
});
|
|
20945
20926
|
});
|
|
20946
20927
|
settingsRoutes.post("/github-sync/disconnect", async (c) => {
|
|
20947
|
-
const { createGitHubSyncService } = await import("./github-sync-
|
|
20928
|
+
const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
20948
20929
|
await createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), { githubApp: getGitHubAppConfig(c.env) }).teardownWebhook();
|
|
20949
20930
|
return dsRedirect(publicPath(c, "/settings/github-sync"));
|
|
20950
20931
|
});
|
|
@@ -21135,7 +21116,7 @@ function buildRepoPickerLabels(c) {
|
|
|
21135
21116
|
const { parseRepoSlug, createGitHubClient } = await import("./github-api-BkRWnqMx.js").then((n) => n.n);
|
|
21136
21117
|
const parsed = parseRepoSlug(repo);
|
|
21137
21118
|
if (!parsed) return wantsJson ? c.json({ error: "Invalid repository format." }, 400) : c.text("Invalid repository format.", 400);
|
|
21138
|
-
const { classifyRepoForSync } = await import("./github-sync-
|
|
21119
|
+
const { classifyRepoForSync } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
21139
21120
|
const ghClient = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
|
|
21140
21121
|
let classification;
|
|
21141
21122
|
try {
|
|
@@ -21165,7 +21146,7 @@ function buildRepoPickerLabels(c) {
|
|
|
21165
21146
|
await c.var.services.settings.set("GITHUB_SYNC_REPO", repo);
|
|
21166
21147
|
await c.var.services.settings.set("GITHUB_SYNC_TOKEN", "");
|
|
21167
21148
|
await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
|
|
21168
|
-
const { createGitHubSyncService } = await import("./github-sync-
|
|
21149
|
+
const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
21169
21150
|
const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
|
|
21170
21151
|
storage: c.var.storage,
|
|
21171
21152
|
githubApp: app
|
|
@@ -21281,7 +21262,7 @@ function requireGitHubApp(c) {
|
|
|
21281
21262
|
const { parseRepoSlug, createGitHubClient } = await import("./github-api-BkRWnqMx.js").then((n) => n.n);
|
|
21282
21263
|
const parsed = parseRepoSlug(repo);
|
|
21283
21264
|
if (!parsed) return c.json({ error: "Invalid repository format." }, 400);
|
|
21284
|
-
const { classifyRepoForSync } = await import("./github-sync-
|
|
21265
|
+
const { classifyRepoForSync } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
|
|
21285
21266
|
const client = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
|
|
21286
21267
|
try {
|
|
21287
21268
|
const classification = await classifyRepoForSync(client, parsed.owner, parsed.repo, c.var.currentSite.id);
|
|
@@ -24639,7 +24620,8 @@ var CreateManagedSiteSchema = z.object({
|
|
|
24639
24620
|
primaryHost: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Primary host must be a valid hostname."),
|
|
24640
24621
|
siteName: z.string().trim().min(1).max(120),
|
|
24641
24622
|
siteLanguage: z.string().trim().max(35).optional(),
|
|
24642
|
-
timeZone: z.string().trim().max(100).optional()
|
|
24623
|
+
timeZone: z.string().trim().max(100).optional(),
|
|
24624
|
+
idempotencyKey: z.string().trim().min(1).max(128).optional()
|
|
24643
24625
|
});
|
|
24644
24626
|
var ManagedSiteDomainSchema = z.object({
|
|
24645
24627
|
host: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Domain host must be a valid hostname."),
|
|
@@ -24739,6 +24721,38 @@ internalSitesRoutes.delete("/:siteId/domains/:domainId", requireInternalAdminApi
|
|
|
24739
24721
|
})) });
|
|
24740
24722
|
});
|
|
24741
24723
|
//#endregion
|
|
24724
|
+
//#region src/middleware/config.ts
|
|
24725
|
+
/**
|
|
24726
|
+
* Config Middleware
|
|
24727
|
+
*
|
|
24728
|
+
* Loads settings from DB, resolves app config and theme.
|
|
24729
|
+
* Apply only to route groups that need config/theme data —
|
|
24730
|
+
* skip for /healthz, /media/*, /favicon.ico, /api/auth/*, etc.
|
|
24731
|
+
*/
|
|
24732
|
+
/**
|
|
24733
|
+
* Middleware that loads settings, resolves app config, and builds theme CSS.
|
|
24734
|
+
*
|
|
24735
|
+
* Sets `allSettings`, `appConfig`, and `themeStyle` on the Hono context.
|
|
24736
|
+
*/ function withConfig() {
|
|
24737
|
+
return async (c, next) => {
|
|
24738
|
+
const allSettings = await c.var.services.settings.getAll();
|
|
24739
|
+
c.set("allSettings", allSettings);
|
|
24740
|
+
const publicRequestOrigin = new URL(c.var.publicRequestUrl).origin;
|
|
24741
|
+
const siteUrlOverride = getSiteResolutionMode(c.env) === "host-based" ? `${publicRequestOrigin}${c.var.currentSiteDomain?.pathPrefix ?? ""}` : getConfiguredSingleSiteUrl(c.env) || `${publicRequestOrigin}${getConfiguredSingleSitePathPrefix(c.env)}`;
|
|
24742
|
+
const appConfig = resolveConfig(c.env, allSettings, { siteUrl: siteUrlOverride });
|
|
24743
|
+
c.set("appConfig", appConfig);
|
|
24744
|
+
const activeTheme = resolveBuiltinTheme(appConfig.themeId);
|
|
24745
|
+
const fontTheme = BUILTIN_FONT_THEMES.find((f) => f.id === appConfig.fontThemeId);
|
|
24746
|
+
const fontOverrides = {
|
|
24747
|
+
...getCjkSerifCssVariables(appConfig.cjkSerifFont),
|
|
24748
|
+
...fontTheme ? getFontThemeCssVariables(fontTheme) : {}
|
|
24749
|
+
};
|
|
24750
|
+
const themeStyle = buildThemeStyle(activeTheme, appConfig.themeMode, fontOverrides);
|
|
24751
|
+
c.set("themeStyle", themeStyle);
|
|
24752
|
+
await next();
|
|
24753
|
+
};
|
|
24754
|
+
}
|
|
24755
|
+
//#endregion
|
|
24742
24756
|
//#region src/lib/webhook-signature.ts
|
|
24743
24757
|
/**
|
|
24744
24758
|
* GitHub webhook HMAC-SHA256 signature verification.
|
|
@@ -24794,7 +24808,7 @@ function timingSafeEqual(a, b) {
|
|
|
24794
24808
|
* Webhook receiver (HMAC-verified, no session auth) and admin endpoints
|
|
24795
24809
|
* (session/token auth) for managing GitHub Sync configuration.
|
|
24796
24810
|
*/ var githubSyncWebhookRoutes = new Hono();
|
|
24797
|
-
githubSyncWebhookRoutes.post("/webhook", async (c) => {
|
|
24811
|
+
githubSyncWebhookRoutes.post("/webhook", withConfig(), async (c) => {
|
|
24798
24812
|
const secret = getGitHubAppConfig(c.env)?.webhookSecret ?? await c.var.services.settings.get("GITHUB_SYNC_WEBHOOK_SECRET");
|
|
24799
24813
|
if (!secret) return c.json({ error: "GitHub Sync not configured" }, 404);
|
|
24800
24814
|
const signature = c.req.header("X-Hub-Signature-256") ?? "";
|
|
@@ -24809,16 +24823,23 @@ githubSyncWebhookRoutes.post("/webhook", async (c) => {
|
|
|
24809
24823
|
ok: true,
|
|
24810
24824
|
skipped: "jant-sync commits"
|
|
24811
24825
|
});
|
|
24812
|
-
|
|
24813
|
-
|
|
24814
|
-
|
|
24815
|
-
data: {
|
|
24816
|
-
ref: payload.ref,
|
|
24817
|
-
before: payload.before,
|
|
24818
|
-
after: payload.after,
|
|
24819
|
-
commits: payload.commits
|
|
24820
|
-
}
|
|
24826
|
+
const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
|
|
24827
|
+
storage: c.var.storage,
|
|
24828
|
+
githubApp: getGitHubAppConfig(c.env)
|
|
24821
24829
|
});
|
|
24830
|
+
const settings = c.var.services.settings;
|
|
24831
|
+
const run = (async () => {
|
|
24832
|
+
try {
|
|
24833
|
+
await syncService.handleWebhookPush(payload);
|
|
24834
|
+
await settings.set("GITHUB_SYNC_LAST_ERROR", "");
|
|
24835
|
+
} catch (err) {
|
|
24836
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24837
|
+
await settings.set("GITHUB_SYNC_LAST_ERROR", message);
|
|
24838
|
+
}
|
|
24839
|
+
})();
|
|
24840
|
+
try {
|
|
24841
|
+
c.executionCtx?.waitUntil(run);
|
|
24842
|
+
} catch {}
|
|
24822
24843
|
return c.json({
|
|
24823
24844
|
ok: true,
|
|
24824
24845
|
queued: true
|
|
@@ -25778,6 +25799,20 @@ manifestRoutes.get("/manifest.webmanifest", (c) => {
|
|
|
25778
25799
|
//#endregion
|
|
25779
25800
|
//#region src/lib/startup-config.ts
|
|
25780
25801
|
var HOSTED_SHARED_SECRET_MIN_LENGTH = 32;
|
|
25802
|
+
var AUTH_SECRET_GENERATION_HINT = "Generate one with `openssl rand -base64 32`.";
|
|
25803
|
+
var AUTH_SECRET_PLACEHOLDER_MARKER = "replace-me";
|
|
25804
|
+
function getAuthSecretIssueKind(env) {
|
|
25805
|
+
const secret = getAuthSecret(env);
|
|
25806
|
+
if (!secret) return "missing";
|
|
25807
|
+
if (secret.toLowerCase().includes(AUTH_SECRET_PLACEHOLDER_MARKER)) return "placeholder";
|
|
25808
|
+
if (secret.length < 32) return "too-short";
|
|
25809
|
+
return null;
|
|
25810
|
+
}
|
|
25811
|
+
function getAuthSecretReadinessError(kind) {
|
|
25812
|
+
if (kind === "placeholder") return `AUTH_SECRET still uses the placeholder value from .env.example. ${AUTH_SECRET_GENERATION_HINT}`;
|
|
25813
|
+
if (kind === "too-short") return `AUTH_SECRET must be at least 32 characters before Jant can accept traffic. ${AUTH_SECRET_GENERATION_HINT}`;
|
|
25814
|
+
return "AUTH_SECRET must be set before Jant can accept traffic.";
|
|
25815
|
+
}
|
|
25781
25816
|
function renderConfigurationErrorPage(input) {
|
|
25782
25817
|
return `<!DOCTYPE html>
|
|
25783
25818
|
<html lang="en">
|
|
@@ -25796,11 +25831,20 @@ ${input.bodyHtml}
|
|
|
25796
25831
|
</body>
|
|
25797
25832
|
</html>`;
|
|
25798
25833
|
}
|
|
25799
|
-
function getAuthSecretErrorHtml() {
|
|
25834
|
+
function getAuthSecretErrorHtml(kind) {
|
|
25835
|
+
const runtimeInstructions = `<p>Set <code>AUTH_SECRET=...</code> in the environment used to start Jant. Generate one with <code>openssl rand -base64 32</code>.</p>
|
|
25836
|
+
<p><strong>Cloudflare Workers:</strong> add <code>AUTH_SECRET</code> as a Worker secret in the dashboard under Variables and Secrets, or run <code>wrangler secret put AUTH_SECRET</code>.</p>`;
|
|
25800
25837
|
return renderConfigurationErrorPage({
|
|
25801
|
-
title:
|
|
25802
|
-
|
|
25803
|
-
|
|
25838
|
+
title: {
|
|
25839
|
+
missing: "AUTH_SECRET is not set",
|
|
25840
|
+
placeholder: "AUTH_SECRET is still the placeholder from .env.example",
|
|
25841
|
+
"too-short": `AUTH_SECRET is too short (must be at least 32 characters)`
|
|
25842
|
+
}[kind],
|
|
25843
|
+
bodyHtml: `${{
|
|
25844
|
+
missing: `<p>Jant needs a 32+ character auth secret to sign sessions.</p>`,
|
|
25845
|
+
placeholder: `<p>The current <code>AUTH_SECRET</code> still contains the <code>replace-me</code> placeholder from <code>.env.example</code>. This value is publicly known and unsafe to use; replace it with a real secret before serving traffic.</p>`,
|
|
25846
|
+
"too-short": `<p>Jant needs an auth secret of at least 32 characters to sign sessions. The current value is too short.</p>`
|
|
25847
|
+
}[kind]}${runtimeInstructions}`,
|
|
25804
25848
|
docsHref: "https://github.com/jant-me/jant/blob/main/docs/configuration.md#required"
|
|
25805
25849
|
});
|
|
25806
25850
|
}
|
|
@@ -25887,7 +25931,8 @@ function getRuntimeConfigurationErrorPage(message) {
|
|
|
25887
25931
|
* getStartupConfigurationErrorPage({ AUTH_SECRET: "secret" }) // null
|
|
25888
25932
|
* ```
|
|
25889
25933
|
*/ function getStartupConfigurationErrorPage(env) {
|
|
25890
|
-
|
|
25934
|
+
const authSecretIssue = getAuthSecretIssueKind(env);
|
|
25935
|
+
if (authSecretIssue) return getAuthSecretErrorHtml(authSecretIssue);
|
|
25891
25936
|
const hostBasedIssues = collectHostBasedStartupConfigurationIssues(env);
|
|
25892
25937
|
if (hostBasedIssues.length > 0) return getHostBasedConfigurationErrorHtml(hostBasedIssues);
|
|
25893
25938
|
return null;
|
|
@@ -25938,38 +25983,6 @@ function getRuntimeConfigurationErrorPage(message) {
|
|
|
25938
25983
|
throw err;
|
|
25939
25984
|
};
|
|
25940
25985
|
//#endregion
|
|
25941
|
-
//#region src/middleware/config.ts
|
|
25942
|
-
/**
|
|
25943
|
-
* Config Middleware
|
|
25944
|
-
*
|
|
25945
|
-
* Loads settings from DB, resolves app config and theme.
|
|
25946
|
-
* Apply only to route groups that need config/theme data —
|
|
25947
|
-
* skip for /healthz, /media/*, /favicon.ico, /api/auth/*, etc.
|
|
25948
|
-
*/
|
|
25949
|
-
/**
|
|
25950
|
-
* Middleware that loads settings, resolves app config, and builds theme CSS.
|
|
25951
|
-
*
|
|
25952
|
-
* Sets `allSettings`, `appConfig`, and `themeStyle` on the Hono context.
|
|
25953
|
-
*/ function withConfig() {
|
|
25954
|
-
return async (c, next) => {
|
|
25955
|
-
const allSettings = await c.var.services.settings.getAll();
|
|
25956
|
-
c.set("allSettings", allSettings);
|
|
25957
|
-
const publicRequestOrigin = new URL(c.var.publicRequestUrl).origin;
|
|
25958
|
-
const siteUrlOverride = getSiteResolutionMode(c.env) === "host-based" ? `${publicRequestOrigin}${c.var.currentSiteDomain?.pathPrefix ?? ""}` : getConfiguredSingleSiteUrl(c.env) || `${publicRequestOrigin}${getConfiguredSingleSitePathPrefix(c.env)}`;
|
|
25959
|
-
const appConfig = resolveConfig(c.env, allSettings, { siteUrl: siteUrlOverride });
|
|
25960
|
-
c.set("appConfig", appConfig);
|
|
25961
|
-
const activeTheme = resolveBuiltinTheme(appConfig.themeId);
|
|
25962
|
-
const fontTheme = BUILTIN_FONT_THEMES.find((f) => f.id === appConfig.fontThemeId);
|
|
25963
|
-
const fontOverrides = {
|
|
25964
|
-
...getCjkSerifCssVariables(appConfig.cjkSerifFont),
|
|
25965
|
-
...fontTheme ? getFontThemeCssVariables(fontTheme) : {}
|
|
25966
|
-
};
|
|
25967
|
-
const themeStyle = buildThemeStyle(activeTheme, appConfig.themeMode, fontOverrides);
|
|
25968
|
-
c.set("themeStyle", themeStyle);
|
|
25969
|
-
await next();
|
|
25970
|
-
};
|
|
25971
|
-
}
|
|
25972
|
-
//#endregion
|
|
25973
25986
|
//#region ../../node_modules/.pnpm/hono@4.11.9/node_modules/hono/dist/middleware/secure-headers/secure-headers.js
|
|
25974
25987
|
var HEADERS_MAP = {
|
|
25975
25988
|
crossOriginEmbedderPolicy: ["Cross-Origin-Embedder-Policy", "require-corp"],
|
|
@@ -30366,10 +30379,28 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30366
30379
|
const pathPrefix = domain.pathPrefix ?? "";
|
|
30367
30380
|
return `${protocol}//${domain.host}${pathPrefix}`;
|
|
30368
30381
|
}
|
|
30382
|
+
async function loadByIdempotencyKey(targetDb, idempotencyKey) {
|
|
30383
|
+
const siteRow = (await targetDb.select().from(sites).where(eq(sites.provisioningIdempotencyKey, idempotencyKey)).limit(1))[0];
|
|
30384
|
+
if (!siteRow) return null;
|
|
30385
|
+
const domainRow = (await targetDb.select().from(siteDomains).where(and(eq(siteDomains.siteId, siteRow.id), eq(siteDomains.kind, "primary"))).limit(1))[0];
|
|
30386
|
+
if (!domainRow) return null;
|
|
30387
|
+
return {
|
|
30388
|
+
site: toSite(siteRow),
|
|
30389
|
+
domain: toSiteDomain(domainRow)
|
|
30390
|
+
};
|
|
30391
|
+
}
|
|
30369
30392
|
async function createWithDatabase(targetDb, input) {
|
|
30370
30393
|
const siteKey = input.key.trim();
|
|
30371
30394
|
const primaryHost = input.primaryHost.trim().toLowerCase();
|
|
30372
30395
|
const siteName = input.siteName.trim();
|
|
30396
|
+
const idempotencyKey = input.idempotencyKey?.trim() || null;
|
|
30397
|
+
if (idempotencyKey) {
|
|
30398
|
+
const existing = await loadByIdempotencyKey(targetDb, idempotencyKey);
|
|
30399
|
+
if (existing) {
|
|
30400
|
+
if (existing.site.key !== siteKey || existing.domain.host !== primaryHost) throw new ConflictError("Idempotency key was reused with a different site key or primary host.");
|
|
30401
|
+
return existing;
|
|
30402
|
+
}
|
|
30403
|
+
}
|
|
30373
30404
|
if ((await targetDb.select({ id: sites.id }).from(sites).where(eq(sites.key, siteKey)).limit(1))[0]) throw new ConflictError("Site key is already in use.");
|
|
30374
30405
|
if ((await targetDb.select({ id: siteDomains.id }).from(siteDomains).where(eq(siteDomains.host, primaryHost)).limit(1))[0]) throw new ConflictError("Primary host is already in use.");
|
|
30375
30406
|
const timestamp = now();
|
|
@@ -30379,6 +30410,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30379
30410
|
id: siteId,
|
|
30380
30411
|
key: siteKey,
|
|
30381
30412
|
status: "active",
|
|
30413
|
+
provisioningIdempotencyKey: idempotencyKey,
|
|
30382
30414
|
createdAt: timestamp,
|
|
30383
30415
|
updatedAt: timestamp
|
|
30384
30416
|
}).returning())[0];
|
|
@@ -31484,7 +31516,8 @@ async function createRequestRuntime(env, publicRequestUrl) {
|
|
|
31484
31516
|
//#region src/runtime/readiness.ts
|
|
31485
31517
|
function getStartupConfigurationReadiness(env) {
|
|
31486
31518
|
const errors = [];
|
|
31487
|
-
|
|
31519
|
+
const authSecretIssue = getAuthSecretIssueKind(env);
|
|
31520
|
+
if (authSecretIssue) errors.push(getAuthSecretReadinessError(authSecretIssue));
|
|
31488
31521
|
for (const issue of getHostBasedStartupConfigurationIssues(env)) errors.push(`${issue.variable}: ${issue.message}`);
|
|
31489
31522
|
return errors.length > 0 ? {
|
|
31490
31523
|
ok: false,
|
|
@@ -31859,4 +31892,4 @@ async function servePublicStorage(c) {
|
|
|
31859
31892
|
return app;
|
|
31860
31893
|
}
|
|
31861
31894
|
//#endregion
|
|
31862
|
-
export {
|
|
31895
|
+
export { SORT_ORDERS as A, toPostViews as C, MAX_PINNED_POSTS as D, MAX_MEDIA_ATTACHMENTS as E, getPublicAssetBasePath as F, isAssetPath as I, TEXT_ATTACHMENT_CONTENT_FORMATS as M, buildThemeStyle as N, MEDIA_KINDS as O, BUILTIN_COLOR_THEMES as P, toPostView as S, FORMATS$2 as T, toArchiveGroups as _, resolveDatabaseDialect as a, toNavItemView as b, BUILTIN_FONT_THEMES as c, defaultFeedRenderer as d, pgSchemaBundle as f, createMediaContext as g, schema_exports$1 as h, createSiteService as i, STATUSES$2 as j, NAV_ITEM_TYPES$2 as k, getCjkSerifCssVariables as l, createNodeDatabase as m, createNodeCliRuntime as n, getHostBasedStartupConfigurationIssues as o, sqliteSchemaBundle as p, createNodeRequestRuntime as r, resolveConfig as s, createApp as t, getFontThemeCssVariables as u, toArchiveGroupsWithMedia as v, toSearchResultView as w, toNavItemViews as x, toMediaView as y };
|