@jant/core 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/import-site.js +81 -13
- package/bin/commands/migrate.js +55 -0
- package/bin/commands/posts/rebuild-html.js +184 -0
- package/bin/commands/site/snapshot/import.js +14 -10
- package/bin/lib/d1-query.js +23 -5
- package/bin/lib/migration-runner.js +29 -7
- package/bin/lib/node-database.js +32 -0
- package/bin/lib/site-snapshot.js +347 -46
- package/bin/lib/sql-export.js +1 -1
- package/bin/lib/thread-collection-migration.js +326 -0
- package/dist/app-BxCOR3Uc.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +5 -5
- package/package.json +11 -10
- package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
- package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
- package/src/__tests__/export-hugo-build.test.ts +63 -4
- package/src/__tests__/export-import-roundtrip.test.ts +6 -4
- package/src/__tests__/export-service.test.ts +142 -9
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +2 -0
- package/src/__tests__/import-site-command.test.ts +65 -0
- package/src/__tests__/vite-config.test.ts +45 -0
- package/src/app.tsx +16 -3
- package/src/client/__tests__/collection-created-notice.test.ts +36 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
- package/src/client/__tests__/collection-page-actions.test.ts +71 -2
- package/src/client/__tests__/compose-bridge.test.ts +64 -0
- package/src/client/__tests__/feed-video-player.test.ts +16 -1
- package/src/client/__tests__/footnote-rail.test.ts +472 -0
- package/src/client/__tests__/hydrate-partial.test.ts +3 -0
- package/src/client/__tests__/toast.test.ts +18 -1
- package/src/client/collection-created-notice.ts +66 -0
- package/src/client/collection-form-bridge.ts +17 -0
- package/src/client/collection-navigation.ts +44 -0
- package/src/client/collection-page-actions.ts +57 -1
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
- package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
- package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
- package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
- package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-manager-types.ts +9 -3
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +2 -1
- package/src/client/components/jant-compose-dialog.ts +85 -21
- package/src/client/components/jant-compose-editor.ts +13 -7
- package/src/client/components/jant-compose-fullscreen.ts +24 -4
- package/src/client/components/jant-config-editor.ts +722 -0
- package/src/client/components/jant-media-lightbox.ts +100 -18
- package/src/client/components/jant-nav-manager.ts +1283 -54
- package/src/client/components/jant-post-form.ts +3 -3
- package/src/client/components/jant-post-menu.ts +86 -74
- package/src/client/components/nav-manager-types.ts +51 -2
- package/src/client/components/post-form-types.ts +1 -1
- package/src/client/compose-bridge.ts +4 -1
- package/src/client/feed-video-player.ts +62 -2
- package/src/client/footnote-rail.ts +478 -0
- package/src/client/hydrate-partial.ts +2 -0
- package/src/client/media-lightbox-events.ts +63 -0
- package/src/client/site-header-fragment.ts +38 -0
- package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
- package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
- package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
- package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
- package/src/client/tiptap/bubble-menu.ts +88 -10
- package/src/client/tiptap/continuous-lists.ts +102 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +14 -5
- package/src/client/tiptap/markdown-clipboard.ts +608 -11
- package/src/client/tiptap/slash-commands.ts +8 -1
- package/src/client/tiptap/structural-keymap.ts +211 -0
- package/src/client/tiptap/tab-indent.ts +8 -22
- package/src/client/tiptap/table-control-labels.ts +30 -0
- package/src/client/tiptap/table-controls.ts +537 -0
- package/src/client/tiptap/table-size-picker.ts +254 -0
- package/src/client/toast.ts +8 -6
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/d1-query.test.ts +43 -1
- package/src/db/__tests__/migration-runner.test.ts +63 -0
- package/src/db/__tests__/migrations.test.ts +469 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/migrations/0027_old_blue_blade.sql +39 -0
- package/src/db/migrations/0028_superb_post.sql +2 -0
- package/src/db/migrations/0029_boring_magma.sql +1 -0
- package/src/db/migrations/0030_typical_vivisector.sql +50 -0
- package/src/db/migrations/meta/0027_snapshot.json +2511 -0
- package/src/db/migrations/meta/0028_snapshot.json +2511 -0
- package/src/db/migrations/meta/0029_snapshot.json +2519 -0
- package/src/db/migrations/meta/0030_snapshot.json +2541 -0
- package/src/db/migrations/meta/_journal.json +28 -0
- package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
- package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
- package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
- package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
- package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
- package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
- package/src/db/migrations/pg/meta/_journal.json +28 -0
- package/src/db/pg/schema.ts +28 -20
- package/src/db/rehearsal-fixtures/demo-current.json +39 -4
- package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
- package/src/db/schema.ts +28 -20
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +133 -30
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +128 -25
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +128 -25
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +513 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +515 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +515 -12
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/api-settings.test.ts +299 -0
- package/src/lib/__tests__/constants.test.ts +5 -0
- package/src/lib/__tests__/feed-policy.test.ts +25 -0
- package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
- package/src/lib/__tests__/markdown.test.ts +8 -6
- package/src/lib/__tests__/navigation.test.ts +59 -2
- package/src/lib/__tests__/post-body-html.test.ts +176 -0
- package/src/lib/__tests__/post-display.test.ts +64 -0
- package/src/lib/__tests__/resolve-config.test.ts +113 -1
- package/src/lib/__tests__/summary.test.ts +82 -0
- package/src/lib/__tests__/timeline.test.ts +109 -70
- package/src/lib/__tests__/timezones.test.ts +31 -3
- package/src/lib/__tests__/tiptap-render.test.ts +268 -9
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
- package/src/lib/__tests__/view.test.ts +53 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
- package/src/lib/api-settings.ts +239 -12
- package/src/lib/collection-sort.ts +7 -5
- package/src/lib/constants.ts +2 -0
- package/src/lib/feed-path.ts +25 -0
- package/src/lib/feed-policy.ts +64 -0
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +10 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/markdown-manager.ts +188 -5
- package/src/lib/markdown.ts +10 -3
- package/src/lib/navigation.ts +16 -3
- package/src/lib/post-body-html.ts +131 -0
- package/src/lib/post-display.ts +7 -1
- package/src/lib/render.tsx +9 -2
- package/src/lib/resolve-config.ts +100 -16
- package/src/lib/schemas.ts +133 -1
- package/src/lib/settings-paths.ts +5 -0
- package/src/lib/site-skill.ts +78 -0
- package/src/lib/summary.ts +16 -5
- package/src/lib/timeline.ts +98 -122
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +355 -81
- package/src/lib/view.ts +30 -20
- package/src/lib/worker-response-cache.ts +11 -18
- package/src/middleware/__tests__/public-content-access.test.ts +91 -0
- package/src/middleware/config.ts +2 -2
- package/src/middleware/public-content-access.ts +87 -0
- package/src/node/__tests__/cli-export.test.ts +14 -0
- package/src/node/__tests__/cli-migrate.test.ts +6 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
- package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
- package/src/node/index.ts +2 -1
- package/src/preset.css +16 -2
- package/src/routes/__tests__/compose.test.ts +51 -0
- package/src/routes/__tests__/site-skill.test.ts +112 -0
- package/src/routes/api/__tests__/collections.test.ts +91 -19
- package/src/routes/api/__tests__/mcp.test.ts +65 -1
- package/src/routes/api/__tests__/nav-items.test.ts +91 -0
- package/src/routes/api/__tests__/public-access.test.ts +81 -0
- package/src/routes/api/__tests__/settings.test.ts +217 -0
- package/src/routes/api/collections.ts +22 -26
- package/src/routes/api/export.ts +2 -0
- package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
- package/src/routes/api/internal/post-body-html.ts +43 -0
- package/src/routes/api/internal/sites.ts +24 -0
- package/src/routes/api/nav-items.ts +24 -1
- package/src/routes/api/posts.ts +2 -2
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +208 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +41 -24
- package/src/routes/api/search.ts +3 -0
- package/src/routes/api/settings.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +7 -4
- package/src/routes/compose.tsx +2 -1
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
- package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
- package/src/routes/pages/__tests__/collections.test.ts +9 -9
- package/src/routes/pages/__tests__/preview.test.ts +174 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +27 -7
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +83 -11
- package/src/routes/site-skill.ts +18 -0
- package/src/services/__tests__/auth.test.ts +7 -5
- package/src/services/__tests__/collection.test.ts +159 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +109 -30
- package/src/services/__tests__/post.test.ts +567 -5
- package/src/services/__tests__/search.test.ts +48 -1
- package/src/services/__tests__/settings.test.ts +8 -0
- package/src/services/__tests__/site-admin.test.ts +121 -1
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +3 -2
- package/src/services/collection.ts +226 -167
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +46 -25
- package/src/services/export-theme/layouts/_default/list.html +18 -4
- package/src/services/export-theme/layouts/_default/rss.xml +8 -12
- package/src/services/export-theme/layouts/featured/list.html +5 -4
- package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
- package/src/services/export-theme/styles/main.css +30 -2
- package/src/services/export.ts +91 -80
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/post.ts +874 -271
- package/src/services/search.ts +8 -2
- package/src/services/settings.ts +2 -10
- package/src/services/site-admin.ts +63 -43
- package/src/style-cjk-jp.css +1 -1
- package/src/style-cjk-kr.css +1 -1
- package/src/style-cjk-tc.css +1 -1
- package/src/style-cjk.css +1 -1
- package/src/styles/components.css +540 -0
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +14 -9
- package/src/styles/ui.css +829 -29
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +243 -12
- package/src/types/constants.ts +22 -1
- package/src/types/entities.ts +9 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +8 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +16 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +92 -2
- package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
- package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
- package/src/ui/dash/settings/GeneralContent.tsx +15 -5
- package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
- package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
- package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
- package/src/ui/feed/LinkCard.tsx +2 -12
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/QuoteCard.tsx +2 -12
- package/src/ui/feed/ThreadPreview.tsx +26 -34
- package/src/ui/feed/TimelineFeed.tsx +2 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
- package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +22 -8
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/pages/CollectionPage.tsx +101 -22
- package/src/ui/pages/CollectionsPage.tsx +3 -1
- package/src/ui/pages/PostPage.tsx +39 -12
- package/src/ui/pages/SearchPage.tsx +6 -14
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
- package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
- package/src/ui/shared/CollectionDirectory.tsx +13 -9
- package/src/ui/shared/CollectionsManager.tsx +17 -7
- package/src/ui/shared/DraftPreviewBar.tsx +33 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +3 -1
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
- package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
- package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
- package/src/ui/shared/collection-management-labels.ts +33 -2
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +46 -0
- package/dist/app-DZDlmh7C.js +0 -6
- package/dist/client/_assets/client-Cevfpm8H.css +0 -2
- package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
- package/src/client/tiptap/wrapping-input-rules.ts +0 -102
|
@@ -42,15 +42,19 @@ function resolve(
|
|
|
42
42
|
const envKeys = "envKeys" in field ? field.envKeys : undefined;
|
|
43
43
|
|
|
44
44
|
// User-configurable: DB > ENV > Default
|
|
45
|
-
if (!field.envOnly) {
|
|
46
|
-
|
|
47
|
-
if (dbValue) return dbValue;
|
|
45
|
+
if (!field.envOnly && Object.hasOwn(allSettings, key)) {
|
|
46
|
+
return allSettings[key] ?? "";
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
// ENV > Default
|
|
51
50
|
const envValue = getEnvString(env, ...(envKeys ?? []));
|
|
52
51
|
if (envValue) return envValue;
|
|
53
52
|
|
|
53
|
+
if (field.defaultValue) return field.defaultValue;
|
|
54
|
+
if ("fallbackKey" in field && field.fallbackKey) {
|
|
55
|
+
return resolve(field.fallbackKey, allSettings, env);
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
return field.defaultValue;
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -70,12 +74,73 @@ function resolveFallback(key: string, env: Bindings): string {
|
|
|
70
74
|
const envValue = getEnvString(env, ...(envKeys ?? []));
|
|
71
75
|
if (envValue) return envValue;
|
|
72
76
|
|
|
77
|
+
if (field.defaultValue) return field.defaultValue;
|
|
78
|
+
if ("fallbackKey" in field && field.fallbackKey) {
|
|
79
|
+
return resolveFallback(field.fallbackKey, env);
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
return field.defaultValue;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
type RuntimeNumberConfigKey =
|
|
86
|
+
| "PAGE_SIZE"
|
|
87
|
+
| "SEARCH_PAGE_SIZE"
|
|
88
|
+
| "ARCHIVE_PAGE_SIZE"
|
|
89
|
+
| "SUMMARY_MAX_PARAGRAPHS"
|
|
90
|
+
| "SUMMARY_MAX_CHARS"
|
|
91
|
+
| "RSS_FEED_LIMIT"
|
|
92
|
+
| "RSS_PUBLISH_DELAY_SECONDS";
|
|
93
|
+
|
|
94
|
+
function parseConfigInt(
|
|
95
|
+
key: RuntimeNumberConfigKey,
|
|
96
|
+
value: string,
|
|
97
|
+
fallback: number,
|
|
98
|
+
): number {
|
|
99
|
+
const normalized = value.trim();
|
|
100
|
+
const parsed = Number(normalized);
|
|
101
|
+
const definition = CONFIG_FIELDS[key].editor;
|
|
102
|
+
return normalized.length > 0 &&
|
|
103
|
+
Number.isInteger(parsed) &&
|
|
104
|
+
(definition.min === undefined || parsed >= definition.min) &&
|
|
105
|
+
(definition.max === undefined || parsed <= definition.max)
|
|
106
|
+
? parsed
|
|
107
|
+
: fallback;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the runtime limits used for automatic post summaries.
|
|
112
|
+
*
|
|
113
|
+
* Internal maintenance routes run before the full config middleware, so they
|
|
114
|
+
* use this focused resolver instead of assuming `c.var.appConfig` exists.
|
|
115
|
+
*
|
|
116
|
+
* @param env - Runtime bindings.
|
|
117
|
+
* @param allSettings - DB settings map when available.
|
|
118
|
+
* @returns Positive summary extraction limits with configured defaults.
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* resolveSummaryConfig({ SUMMARY_MAX_CHARS: "240" }).maxChars;
|
|
122
|
+
* // 240
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function resolveSummaryConfig(
|
|
126
|
+
env: Bindings,
|
|
127
|
+
allSettings: Record<string, string> = {},
|
|
128
|
+
): {
|
|
129
|
+
maxParagraphs: number;
|
|
130
|
+
maxChars: number;
|
|
131
|
+
} {
|
|
132
|
+
return {
|
|
133
|
+
maxParagraphs: parseConfigInt(
|
|
134
|
+
"SUMMARY_MAX_PARAGRAPHS",
|
|
135
|
+
resolve("SUMMARY_MAX_PARAGRAPHS", allSettings, env),
|
|
136
|
+
5,
|
|
137
|
+
),
|
|
138
|
+
maxChars: parseConfigInt(
|
|
139
|
+
"SUMMARY_MAX_CHARS",
|
|
140
|
+
resolve("SUMMARY_MAX_CHARS", allSettings, env),
|
|
141
|
+
500,
|
|
142
|
+
),
|
|
143
|
+
};
|
|
79
144
|
}
|
|
80
145
|
|
|
81
146
|
function parseFeedKind(value: string, fallback: FeedKind): FeedKind {
|
|
@@ -102,12 +167,19 @@ export function resolveConfig(
|
|
|
102
167
|
allSettings: Record<string, string>,
|
|
103
168
|
options?: { siteUrl?: string },
|
|
104
169
|
): AppConfig {
|
|
105
|
-
const
|
|
170
|
+
const summaryConfig = resolveSummaryConfig(env, allSettings);
|
|
171
|
+
const pageSize = parseConfigInt(
|
|
172
|
+
"PAGE_SIZE",
|
|
173
|
+
resolve("PAGE_SIZE", allSettings, env),
|
|
174
|
+
50,
|
|
175
|
+
);
|
|
106
176
|
const searchPageSize = parseConfigInt(
|
|
177
|
+
"SEARCH_PAGE_SIZE",
|
|
107
178
|
resolve("SEARCH_PAGE_SIZE", allSettings, env),
|
|
108
179
|
pageSize,
|
|
109
180
|
);
|
|
110
181
|
const archivePageSize = parseConfigInt(
|
|
182
|
+
"ARCHIVE_PAGE_SIZE",
|
|
111
183
|
resolve("ARCHIVE_PAGE_SIZE", allSettings, env),
|
|
112
184
|
pageSize,
|
|
113
185
|
);
|
|
@@ -137,9 +209,12 @@ export function resolveConfig(
|
|
|
137
209
|
}
|
|
138
210
|
|
|
139
211
|
// Description is "explicit" when set in DB or ENV (not just the default)
|
|
212
|
+
const hasDbDescription = Object.hasOwn(allSettings, "SITE_DESCRIPTION");
|
|
140
213
|
const dbDescription = allSettings["SITE_DESCRIPTION"];
|
|
141
214
|
const envDescription = getEnvString(env, "SITE_DESCRIPTION");
|
|
142
|
-
const siteDescriptionExplicit =
|
|
215
|
+
const siteDescriptionExplicit = hasDbDescription
|
|
216
|
+
? !!dbDescription
|
|
217
|
+
: !!envDescription;
|
|
143
218
|
|
|
144
219
|
return {
|
|
145
220
|
// Site identity (DB > ENV > Default)
|
|
@@ -158,6 +233,9 @@ export function resolveConfig(
|
|
|
158
233
|
showJantBrandingOnHome:
|
|
159
234
|
resolve("SHOW_JANT_BRANDING_ON_HOME", allSettings, env) === "true",
|
|
160
235
|
noindex: demoMode || resolve("NOINDEX", allSettings, env) === "true",
|
|
236
|
+
publicApiEnabled:
|
|
237
|
+
resolve("PUBLIC_API_ENABLED", allSettings, env) === "true",
|
|
238
|
+
rssFeedsEnabled: resolve("RSS_FEEDS_ENABLED", allSettings, env) === "true",
|
|
161
239
|
|
|
162
240
|
// Infrastructure (ENV only)
|
|
163
241
|
siteUrl,
|
|
@@ -185,21 +263,27 @@ export function resolveConfig(
|
|
|
185
263
|
parseInt(getEnvString(env, "UPLOAD_MAX_FILE_SIZE_MB") ?? "1024", 10) ||
|
|
186
264
|
1024,
|
|
187
265
|
|
|
188
|
-
// Summary extraction (ENV
|
|
189
|
-
summaryMaxParagraphs:
|
|
190
|
-
|
|
191
|
-
summaryMaxChars:
|
|
192
|
-
parseInt(getEnvString(env, "SUMMARY_MAX_CHARS") ?? "500", 10) || 500,
|
|
266
|
+
// Summary extraction (DB > ENV > Default)
|
|
267
|
+
summaryMaxParagraphs: summaryConfig.maxParagraphs,
|
|
268
|
+
summaryMaxChars: summaryConfig.maxChars,
|
|
193
269
|
|
|
194
270
|
// Slug (ENV only)
|
|
195
271
|
slugIdLength: parseInt(getEnvString(env, "SLUG_ID_LENGTH") ?? "5", 10) || 5,
|
|
196
272
|
|
|
197
|
-
// Pagination/
|
|
273
|
+
// Pagination/feed (DB > ENV > Default)
|
|
198
274
|
pageSize,
|
|
199
275
|
searchPageSize,
|
|
200
276
|
archivePageSize,
|
|
201
|
-
rssFeedLimit:
|
|
202
|
-
|
|
277
|
+
rssFeedLimit: parseConfigInt(
|
|
278
|
+
"RSS_FEED_LIMIT",
|
|
279
|
+
resolve("RSS_FEED_LIMIT", allSettings, env),
|
|
280
|
+
50,
|
|
281
|
+
),
|
|
282
|
+
rssPublishDelaySeconds: parseConfigInt(
|
|
283
|
+
"RSS_PUBLISH_DELAY_SECONDS",
|
|
284
|
+
resolve("RSS_PUBLISH_DELAY_SECONDS", allSettings, env),
|
|
285
|
+
300,
|
|
286
|
+
),
|
|
203
287
|
|
|
204
288
|
// Demo (ENV only)
|
|
205
289
|
demoEmail: getEnvString(env, "DEMO_EMAIL") || "",
|
package/src/lib/schemas.ts
CHANGED
|
@@ -26,13 +26,22 @@ import {
|
|
|
26
26
|
MAX_SITE_DESCRIPTION_LENGTH,
|
|
27
27
|
MAX_SITE_FOOTER_LENGTH,
|
|
28
28
|
TEXT_ATTACHMENT_CONTENT_FORMATS,
|
|
29
|
+
CONFIG_FIELDS,
|
|
30
|
+
type ConfigEditorDefinition,
|
|
29
31
|
type ConfigKey,
|
|
30
32
|
} from "../types.js";
|
|
33
|
+
import { isCjkSerifFont } from "../i18n/detect.js";
|
|
34
|
+
import {
|
|
35
|
+
isLocale,
|
|
36
|
+
isValidContentLanguage,
|
|
37
|
+
normalizeContentLanguage,
|
|
38
|
+
} from "../i18n/locales.js";
|
|
31
39
|
import { ValidationError } from "./errors.js";
|
|
32
40
|
import { createTypeIdSchema, ID_PREFIX } from "./ids.js";
|
|
33
41
|
import { normalizeSlug } from "./slug-format.js";
|
|
34
42
|
import { isReservedPath } from "./constants.js";
|
|
35
43
|
import { sanitizeUrl, normalizePath } from "./url.js";
|
|
44
|
+
import { isSupportedTimeZone, normalizeTimeZone } from "./timezones.js";
|
|
36
45
|
|
|
37
46
|
// =============================================================================
|
|
38
47
|
// Shared Transforms
|
|
@@ -557,6 +566,12 @@ export const CreateNavItemSchema = z.discriminatedUnion("type", [
|
|
|
557
566
|
label: sanitizeText(100).pipe(z.string().min(1)).optional(),
|
|
558
567
|
placement: z.enum(["header", "more"]).optional(),
|
|
559
568
|
}),
|
|
569
|
+
z.object({
|
|
570
|
+
type: z.literal("page"),
|
|
571
|
+
postId: PostIdSchema,
|
|
572
|
+
label: sanitizeText(100).pipe(z.string().min(1)).optional(),
|
|
573
|
+
placement: z.enum(["header", "more"]).optional(),
|
|
574
|
+
}),
|
|
560
575
|
]);
|
|
561
576
|
|
|
562
577
|
/**
|
|
@@ -684,12 +699,129 @@ const EDITABLE_SETTING_VALUE_SCHEMAS: Partial<
|
|
|
684
699
|
SITE_FOOTER: SiteFooterSettingSchema,
|
|
685
700
|
};
|
|
686
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Normalize a raw string according to a Config Editor control definition.
|
|
704
|
+
*
|
|
705
|
+
* @param definition - Typed editor metadata from the config registry
|
|
706
|
+
* @param value - Raw string received at the HTTP/service boundary
|
|
707
|
+
* @returns Canonical string suitable for the settings key-value store
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* normalizeConfigEditorDefinitionValue({ type: "boolean" }, "true");
|
|
711
|
+
* // "true"
|
|
712
|
+
* ```
|
|
713
|
+
*/
|
|
714
|
+
export function normalizeConfigEditorDefinitionValue(
|
|
715
|
+
definition: ConfigEditorDefinition,
|
|
716
|
+
value: string,
|
|
717
|
+
): string {
|
|
718
|
+
switch (definition.type) {
|
|
719
|
+
case "boolean": {
|
|
720
|
+
const normalized = value.trim().toLowerCase();
|
|
721
|
+
if (normalized !== "true" && normalized !== "false") {
|
|
722
|
+
throw new ValidationError("Choose true or false.");
|
|
723
|
+
}
|
|
724
|
+
return normalized;
|
|
725
|
+
}
|
|
726
|
+
case "string": {
|
|
727
|
+
const normalized = value.trim();
|
|
728
|
+
if (
|
|
729
|
+
definition.maxLength !== undefined &&
|
|
730
|
+
normalized.length > definition.maxLength
|
|
731
|
+
) {
|
|
732
|
+
throw new ValidationError(
|
|
733
|
+
`Keep this value at ${definition.maxLength} characters or fewer.`,
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
return normalized;
|
|
737
|
+
}
|
|
738
|
+
case "number": {
|
|
739
|
+
const normalized = value.trim();
|
|
740
|
+
const parsed = Number(normalized);
|
|
741
|
+
if (!normalized || !Number.isFinite(parsed)) {
|
|
742
|
+
throw new ValidationError("Enter a valid number.");
|
|
743
|
+
}
|
|
744
|
+
if (definition.min !== undefined && parsed < definition.min) {
|
|
745
|
+
throw new ValidationError(
|
|
746
|
+
`Enter a number greater than or equal to ${definition.min}.`,
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (definition.max !== undefined && parsed > definition.max) {
|
|
750
|
+
throw new ValidationError(
|
|
751
|
+
`Enter a number less than or equal to ${definition.max}.`,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
if (definition.step !== undefined && definition.step > 0) {
|
|
755
|
+
const steps = parsed / definition.step;
|
|
756
|
+
const tolerance = Number.EPSILON * Math.max(1, Math.abs(steps)) * 4;
|
|
757
|
+
if (Math.abs(steps - Math.round(steps)) > tolerance) {
|
|
758
|
+
throw new ValidationError(
|
|
759
|
+
`Enter a number in steps of ${definition.step}.`,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return String(parsed);
|
|
764
|
+
}
|
|
765
|
+
case "enum": {
|
|
766
|
+
if (definition.options && !definition.options.includes(value)) {
|
|
767
|
+
throw new ValidationError("Choose one of the available options.");
|
|
768
|
+
}
|
|
769
|
+
return value;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
687
774
|
export function normalizeEditableSettingValue(
|
|
688
775
|
key: ConfigKey,
|
|
689
776
|
value: string,
|
|
690
777
|
): string {
|
|
778
|
+
const field = CONFIG_FIELDS[key];
|
|
779
|
+
if (!("editor" in field)) {
|
|
780
|
+
return value;
|
|
781
|
+
}
|
|
782
|
+
|
|
691
783
|
const schema = EDITABLE_SETTING_VALUE_SCHEMAS[key];
|
|
692
|
-
|
|
784
|
+
let normalized = schema
|
|
785
|
+
? parseValidated(schema, value)
|
|
786
|
+
: normalizeConfigEditorDefinitionValue(field.editor, value);
|
|
787
|
+
|
|
788
|
+
switch (key) {
|
|
789
|
+
case "SITE_NAME":
|
|
790
|
+
if (!normalized) {
|
|
791
|
+
throw new ValidationError(
|
|
792
|
+
"Site name can't be empty. Enter a name or reset this setting.",
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
case "SITE_LANGUAGE":
|
|
797
|
+
if (!isValidContentLanguage(normalized)) {
|
|
798
|
+
throw new ValidationError(
|
|
799
|
+
"Enter a valid BCP 47 language tag, such as en, fi, or zh-Hans.",
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
normalized = normalizeContentLanguage(normalized);
|
|
803
|
+
break;
|
|
804
|
+
case "DASHBOARD_LANGUAGE":
|
|
805
|
+
if (normalized && !isLocale(normalized)) {
|
|
806
|
+
throw new ValidationError(
|
|
807
|
+
"Choose a dashboard language Jant is translated into.",
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
break;
|
|
811
|
+
case "CJK_SERIF_FONT":
|
|
812
|
+
if (!isCjkSerifFont(normalized)) {
|
|
813
|
+
throw new ValidationError("Choose an available CJK font fallback.");
|
|
814
|
+
}
|
|
815
|
+
break;
|
|
816
|
+
case "TIME_ZONE":
|
|
817
|
+
if (!isSupportedTimeZone(normalized)) {
|
|
818
|
+
throw new ValidationError("Choose a valid time zone.");
|
|
819
|
+
}
|
|
820
|
+
normalized = normalizeTimeZone(normalized);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return normalized;
|
|
693
825
|
}
|
|
694
826
|
|
|
695
827
|
/**
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import SITE_SKILL_TEMPLATE from "../../../../docs/skill.md?raw";
|
|
2
|
+
import { normalizeSiteUrl } from "./url.js";
|
|
3
|
+
|
|
4
|
+
const INTRODUCTION =
|
|
5
|
+
"This page is written for AI assistants working with one Jant site. Use it when the user asks you to read, publish, edit, organize, search, or migrate site content through Jant's HTTP API or MCP interface.";
|
|
6
|
+
const GENERIC_SITE_URL = "https://example.com";
|
|
7
|
+
const GENERIC_SITE_CONFIRMATION = `- The Jant site URL (e.g. \`${GENERIC_SITE_URL}\`).`;
|
|
8
|
+
const GENERIC_SITE_EXPORT = `export JANT_SITE=${GENERIC_SITE_URL}\n`;
|
|
9
|
+
const GENERIC_SITE_MARKERS = [
|
|
10
|
+
GENERIC_SITE_CONFIRMATION,
|
|
11
|
+
GENERIC_SITE_EXPORT,
|
|
12
|
+
"<site>",
|
|
13
|
+
"$JANT_SITE",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function replaceLiteral(
|
|
17
|
+
content: string,
|
|
18
|
+
search: string,
|
|
19
|
+
replacement: string,
|
|
20
|
+
): string {
|
|
21
|
+
return content.replaceAll(search, () => replacement);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render the Agent skill for one concrete Jant site.
|
|
26
|
+
*
|
|
27
|
+
* @param siteUrl - Resolved public site URL, including any path prefix
|
|
28
|
+
* @returns Markdown with every target-site placeholder bound to `siteUrl`
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* renderSiteSkill("https://example.com/blog");
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function renderSiteSkill(siteUrl: string): string {
|
|
35
|
+
const normalizedSiteUrl = normalizeSiteUrl(siteUrl);
|
|
36
|
+
if (!normalizedSiteUrl) {
|
|
37
|
+
throw new Error("A public site URL is required to render /skill.md");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const targetSiteUrl = normalizedSiteUrl.replace(/\/$/, "");
|
|
41
|
+
if (!SITE_SKILL_TEMPLATE.includes(INTRODUCTION)) {
|
|
42
|
+
throw new Error("The site skill template introduction could not be found");
|
|
43
|
+
}
|
|
44
|
+
const missingMarker = GENERIC_SITE_MARKERS.find(
|
|
45
|
+
(marker) => !SITE_SKILL_TEMPLATE.includes(marker),
|
|
46
|
+
);
|
|
47
|
+
if (missingMarker) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`The site skill template target marker could not be found: ${missingMarker}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let content = SITE_SKILL_TEMPLATE;
|
|
54
|
+
content = replaceLiteral(content, GENERIC_SITE_EXPORT, "");
|
|
55
|
+
content = replaceLiteral(
|
|
56
|
+
content,
|
|
57
|
+
GENERIC_SITE_CONFIRMATION,
|
|
58
|
+
`- That the target Jant site is \`${targetSiteUrl}\`.`,
|
|
59
|
+
);
|
|
60
|
+
content = replaceLiteral(content, "<site>", targetSiteUrl);
|
|
61
|
+
content = replaceLiteral(content, "$JANT_SITE", targetSiteUrl);
|
|
62
|
+
content = replaceLiteral(
|
|
63
|
+
content,
|
|
64
|
+
INTRODUCTION,
|
|
65
|
+
`${INTRODUCTION}\n\nThis site-scoped copy is bound to <${targetSiteUrl}>. Send every API request in this guide to this exact base URL. Do not substitute another target without the user's confirmation.`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const unresolvedMarker = ["<site>", "$JANT_SITE"].find((marker) =>
|
|
69
|
+
content.includes(marker),
|
|
70
|
+
);
|
|
71
|
+
if (unresolvedMarker) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`The site skill template contains an unresolved target marker: ${unresolvedMarker}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return content;
|
|
78
|
+
}
|
package/src/lib/summary.ts
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { JSONContent } from "@tiptap/core";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
renderTiptapDocument,
|
|
11
|
+
type TiptapRenderOptions,
|
|
12
|
+
} from "./tiptap-render.js";
|
|
10
13
|
|
|
11
14
|
interface TiptapNode {
|
|
12
15
|
type: string;
|
|
@@ -220,6 +223,7 @@ export function extractSummary(
|
|
|
220
223
|
* included and `hasMore` is `false`. Avoids a "read more" that reveals only a
|
|
221
224
|
* sliver of text. Explicit `moreBreak` markers reflect author intent and are
|
|
222
225
|
* never subject to this tolerance.
|
|
226
|
+
* @param renderOptions - Renderer options such as the post footnote namespace
|
|
223
227
|
* @returns HTML summary, whether content was truncated, and the index in
|
|
224
228
|
* `doc.content` where the content after the summary boundary begins, or null.
|
|
225
229
|
* `breakAtIndex` lets callers align the summary with the full-body rendering
|
|
@@ -236,6 +240,7 @@ export function extractSummaryHtml(
|
|
|
236
240
|
maxBlocks: number = 5,
|
|
237
241
|
maxChars: number = 500,
|
|
238
242
|
minHiddenChars: number = 0,
|
|
243
|
+
renderOptions: TiptapRenderOptions = {},
|
|
239
244
|
): { html: string; hasMore: boolean; breakAtIndex: number } | null {
|
|
240
245
|
let doc: TiptapNode;
|
|
241
246
|
try {
|
|
@@ -260,10 +265,13 @@ export function extractSummaryHtml(
|
|
|
260
265
|
if (selected.length === 0) return null;
|
|
261
266
|
const subDoc: JSONContent = {
|
|
262
267
|
type: "doc",
|
|
263
|
-
content:
|
|
268
|
+
content: [
|
|
269
|
+
...selected,
|
|
270
|
+
...nodes.filter((node) => node.type === "footnoteDefinition"),
|
|
271
|
+
] as JSONContent[],
|
|
264
272
|
};
|
|
265
273
|
return {
|
|
266
|
-
html: renderTiptapDocument(subDoc),
|
|
274
|
+
html: renderTiptapDocument(subDoc, renderOptions),
|
|
267
275
|
hasMore: true,
|
|
268
276
|
// Anchor goes in place of the moreBreak marker, so the marker itself
|
|
269
277
|
// is NOT part of the pre-anchor body. It remains in the post-anchor
|
|
@@ -329,10 +337,13 @@ export function extractSummaryHtml(
|
|
|
329
337
|
|
|
330
338
|
const subDoc: JSONContent = {
|
|
331
339
|
type: "doc",
|
|
332
|
-
content:
|
|
340
|
+
content: [
|
|
341
|
+
...selected,
|
|
342
|
+
...nodes.filter((node) => node.type === "footnoteDefinition"),
|
|
343
|
+
] as JSONContent[],
|
|
333
344
|
};
|
|
334
345
|
return {
|
|
335
|
-
html: renderTiptapDocument(subDoc),
|
|
346
|
+
html: renderTiptapDocument(subDoc, renderOptions),
|
|
336
347
|
hasMore,
|
|
337
348
|
breakAtIndex: lastSelectedIdx + 1,
|
|
338
349
|
};
|