@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
|
@@ -106,4 +106,11 @@ describe("ArchivePage", () => {
|
|
|
106
106
|
expect(html).toContain("archive-chip-active");
|
|
107
107
|
expect(html).toContain("Single posts");
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it("renders the feed button only when a feed href is provided", () => {
|
|
111
|
+
expect(renderArchivePage()).not.toContain('class="feed-link"');
|
|
112
|
+
expect(renderArchivePage({ feedHref: "/archive/feed" })).toContain(
|
|
113
|
+
'class="feed-link"',
|
|
114
|
+
);
|
|
115
|
+
});
|
|
109
116
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { renderToString } from "hono/jsx/dom/server";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { I18nProvider } from "../../../i18n/context.js";
|
|
5
|
+
import { createI18n } from "../../../i18n/i18n.js";
|
|
6
|
+
import type { PostView } from "../../../types.js";
|
|
7
|
+
import { PostPage } from "../PostPage.js";
|
|
8
|
+
|
|
9
|
+
function createPostView(overrides: Partial<PostView> = {}): PostView {
|
|
10
|
+
return {
|
|
11
|
+
id: "pst_root",
|
|
12
|
+
permalink: "/root",
|
|
13
|
+
slug: "root",
|
|
14
|
+
bodyHtml: "<p>Post body</p>",
|
|
15
|
+
format: "note",
|
|
16
|
+
status: "published",
|
|
17
|
+
visibility: "public",
|
|
18
|
+
pinned: false,
|
|
19
|
+
featured: false,
|
|
20
|
+
publishedAt: "2026-07-18T00:00:00.000Z",
|
|
21
|
+
publishedAtFormatted: "Jul 18, 2026",
|
|
22
|
+
publishedAtTime: "08:00",
|
|
23
|
+
publishedAtRelative: "now",
|
|
24
|
+
updatedAt: "2026-07-18T00:00:00.000Z",
|
|
25
|
+
media: [],
|
|
26
|
+
collections: [],
|
|
27
|
+
isLastInThread: false,
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderPostPage(post: PostView, threadPosts: PostView[]): string {
|
|
33
|
+
const i18n = createI18n("en");
|
|
34
|
+
const c = {
|
|
35
|
+
get(key: string) {
|
|
36
|
+
if (key === "i18n") return i18n;
|
|
37
|
+
return undefined;
|
|
38
|
+
},
|
|
39
|
+
} as unknown as Context;
|
|
40
|
+
|
|
41
|
+
I18nProvider({ c, children: "" });
|
|
42
|
+
return renderToString(PostPage({ post, threadPosts }));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getArticleTag(html: string, postId: string): string {
|
|
46
|
+
const article = (html.match(/<article\b[^>]*>/g) ?? []).find((tag) =>
|
|
47
|
+
tag.includes(`data-post-id="${postId}"`),
|
|
48
|
+
);
|
|
49
|
+
if (!article) throw new Error(`Missing article for ${postId}`);
|
|
50
|
+
return article;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("PostPage", () => {
|
|
54
|
+
it("preserves Root-versus-Child action context in detail mode", () => {
|
|
55
|
+
const root = createPostView();
|
|
56
|
+
const child = createPostView({
|
|
57
|
+
id: "pst_child",
|
|
58
|
+
permalink: "/child",
|
|
59
|
+
slug: "child",
|
|
60
|
+
format: "link",
|
|
61
|
+
title: "Child link",
|
|
62
|
+
url: "https://example.com/child",
|
|
63
|
+
replyToId: root.id,
|
|
64
|
+
threadRootId: root.id,
|
|
65
|
+
});
|
|
66
|
+
const grandchild = createPostView({
|
|
67
|
+
id: "pst_grandchild",
|
|
68
|
+
permalink: "/grandchild",
|
|
69
|
+
slug: "grandchild",
|
|
70
|
+
format: "quote",
|
|
71
|
+
quoteText: "Child quote",
|
|
72
|
+
replyToId: child.id,
|
|
73
|
+
threadRootId: root.id,
|
|
74
|
+
isLastInThread: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const html = renderPostPage(child, [root, child, grandchild]);
|
|
78
|
+
const rootArticle = getArticleTag(html, root.id);
|
|
79
|
+
const childArticle = getArticleTag(html, child.id);
|
|
80
|
+
const grandchildArticle = getArticleTag(html, grandchild.id);
|
|
81
|
+
|
|
82
|
+
expect(rootArticle).not.toContain("data-post-reply");
|
|
83
|
+
expect(childArticle).toContain('data-post-reply=""');
|
|
84
|
+
expect(childArticle).toContain(`data-thread-root-id="${root.id}"`);
|
|
85
|
+
expect(grandchildArticle).toContain('data-post-reply=""');
|
|
86
|
+
expect(grandchildArticle).toContain(`data-thread-root-id="${root.id}"`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -124,7 +124,7 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
124
124
|
i18n._(
|
|
125
125
|
msg({
|
|
126
126
|
message:
|
|
127
|
-
"No collections yet. Start one to organize
|
|
127
|
+
"No collections yet. Start one to organize threads by topic.",
|
|
128
128
|
comment: "@context: Empty state message on collections page",
|
|
129
129
|
}),
|
|
130
130
|
)}
|
|
@@ -231,7 +231,9 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
231
231
|
<div
|
|
232
232
|
class="collection-directory-description prose"
|
|
233
233
|
dangerouslySetInnerHTML={{
|
|
234
|
-
__html: renderMarkdown(item.description
|
|
234
|
+
__html: renderMarkdown(item.description, {
|
|
235
|
+
namespace: item.id,
|
|
236
|
+
}),
|
|
235
237
|
}}
|
|
236
238
|
/>
|
|
237
239
|
) : (
|
|
@@ -279,24 +281,26 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
|
|
|
279
281
|
<div
|
|
280
282
|
class="collection-directory-description prose"
|
|
281
283
|
dangerouslySetInnerHTML={{
|
|
282
|
-
__html: renderMarkdown(collection.description
|
|
284
|
+
__html: renderMarkdown(collection.description, {
|
|
285
|
+
namespace: collection.id,
|
|
286
|
+
}),
|
|
283
287
|
}}
|
|
284
288
|
/>
|
|
285
289
|
)}
|
|
286
290
|
<p class="collection-directory-summary">
|
|
287
291
|
<span class="collection-directory-meta">
|
|
288
|
-
{collection.
|
|
289
|
-
{collection.
|
|
292
|
+
{collection.threadCount}{" "}
|
|
293
|
+
{collection.threadCount === 1
|
|
290
294
|
? i18n._(
|
|
291
295
|
msg({
|
|
292
|
-
message: "
|
|
293
|
-
comment: "@context: Singular
|
|
296
|
+
message: "thread",
|
|
297
|
+
comment: "@context: Singular thread count label",
|
|
294
298
|
}),
|
|
295
299
|
)
|
|
296
300
|
: i18n._(
|
|
297
301
|
msg({
|
|
298
|
-
message: "
|
|
299
|
-
comment: "@context: Plural
|
|
302
|
+
message: "threads",
|
|
303
|
+
comment: "@context: Plural thread count label",
|
|
300
304
|
}),
|
|
301
305
|
)}
|
|
302
306
|
</span>
|
|
@@ -15,12 +15,14 @@ const escapeJson = (data: unknown) =>
|
|
|
15
15
|
|
|
16
16
|
export interface CollectionsManagerProps {
|
|
17
17
|
items: CollectionDirectoryItem[];
|
|
18
|
+
navigationCollectionIds?: string[];
|
|
18
19
|
sitePathPrefix?: string;
|
|
19
20
|
siteOrigin?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
23
24
|
items,
|
|
25
|
+
navigationCollectionIds = [],
|
|
24
26
|
sitePathPrefix = "",
|
|
25
27
|
siteOrigin = "",
|
|
26
28
|
}) => {
|
|
@@ -102,21 +104,21 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
102
104
|
comment: "@context: Tooltip for divider delete button",
|
|
103
105
|
}),
|
|
104
106
|
),
|
|
105
|
-
|
|
107
|
+
threadSingular: i18n._(
|
|
106
108
|
msg({
|
|
107
|
-
message: "
|
|
108
|
-
comment: "@context: Singular
|
|
109
|
+
message: "thread",
|
|
110
|
+
comment: "@context: Singular thread count label",
|
|
109
111
|
}),
|
|
110
112
|
),
|
|
111
|
-
|
|
113
|
+
threadPlural: i18n._(
|
|
112
114
|
msg({
|
|
113
|
-
message: "
|
|
114
|
-
comment: "@context: Plural
|
|
115
|
+
message: "threads",
|
|
116
|
+
comment: "@context: Plural thread count label",
|
|
115
117
|
}),
|
|
116
118
|
),
|
|
117
119
|
emptyState: i18n._(
|
|
118
120
|
msg({
|
|
119
|
-
message: "No collections yet. Start one to organize
|
|
121
|
+
message: "No collections yet. Start one to organize threads by topic.",
|
|
120
122
|
comment: "@context: Empty state message on collections page",
|
|
121
123
|
}),
|
|
122
124
|
),
|
|
@@ -126,6 +128,13 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
126
128
|
comment: "@context: Toast after reordering collections",
|
|
127
129
|
}),
|
|
128
130
|
),
|
|
131
|
+
notNow: i18n._(
|
|
132
|
+
msg({
|
|
133
|
+
message: "Not now",
|
|
134
|
+
comment:
|
|
135
|
+
"@context: Action to dismiss the post-create Collection navigation prompt",
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
129
138
|
...mutationLabels,
|
|
130
139
|
};
|
|
131
140
|
|
|
@@ -312,6 +321,7 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
|
|
|
312
321
|
<jant-collections-manager
|
|
313
322
|
items={escapeJson(items)}
|
|
314
323
|
labels={escapeJson(labels)}
|
|
324
|
+
navigation-collection-ids={escapeJson(navigationCollectionIds)}
|
|
315
325
|
>
|
|
316
326
|
<CollectionDirectory
|
|
317
327
|
items={items}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { msg } from "@lingui/core/macro";
|
|
2
|
+
import type { FC } from "hono/jsx";
|
|
3
|
+
import { useLingui } from "../../i18n/context.js";
|
|
4
|
+
|
|
5
|
+
/** Persistent page chrome that distinguishes an authenticated draft preview. */
|
|
6
|
+
export const DraftPreviewBar: FC = () => {
|
|
7
|
+
const { i18n } = useLingui();
|
|
8
|
+
const label = i18n._(
|
|
9
|
+
msg({
|
|
10
|
+
message: "Draft preview",
|
|
11
|
+
comment: "@context: Status label above a draft preview page",
|
|
12
|
+
}),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<aside class="draft-preview-bar" aria-label={label} data-preview-status>
|
|
17
|
+
<div class="draft-preview-bar-inner">
|
|
18
|
+
<span class="draft-preview-bar-label">
|
|
19
|
+
<span class="draft-preview-bar-dot" aria-hidden="true" />
|
|
20
|
+
{label}
|
|
21
|
+
</span>
|
|
22
|
+
<span class="draft-preview-bar-description">
|
|
23
|
+
{i18n._(
|
|
24
|
+
msg({
|
|
25
|
+
message: "This post isn’t published.",
|
|
26
|
+
comment: "@context: Explanation in the draft preview status bar",
|
|
27
|
+
}),
|
|
28
|
+
)}
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
</aside>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -2,7 +2,7 @@ import type { FC } from "hono/jsx";
|
|
|
2
2
|
import {
|
|
3
3
|
HOME_BRANDING_LINK_LABEL,
|
|
4
4
|
HOME_BRANDING_PREFIX,
|
|
5
|
-
|
|
5
|
+
JANT_HOME_URL,
|
|
6
6
|
} from "../../lib/jant-branding.js";
|
|
7
7
|
|
|
8
8
|
export const HomePageBranding: FC = () => {
|
|
@@ -10,7 +10,7 @@ export const HomePageBranding: FC = () => {
|
|
|
10
10
|
<footer class="home-branding-credit">
|
|
11
11
|
{HOME_BRANDING_PREFIX}{" "}
|
|
12
12
|
<a
|
|
13
|
-
href={
|
|
13
|
+
href={JANT_HOME_URL}
|
|
14
14
|
target="_blank"
|
|
15
15
|
rel="noopener noreferrer"
|
|
16
16
|
class="home-branding-link"
|
|
@@ -186,6 +186,8 @@ export const PostFooter: FC<PostFooterProps> = ({ post, detail, display }) => {
|
|
|
186
186
|
const showReply = !hideReply && post.isLastInThread;
|
|
187
187
|
const showCollectionSeparator =
|
|
188
188
|
showTimestamp || !!safeExternalUrl || post.featured;
|
|
189
|
+
const visibleCollections =
|
|
190
|
+
post.replyToId || post.threadRootId ? [] : post.collections;
|
|
189
191
|
|
|
190
192
|
return (
|
|
191
193
|
<footer
|
|
@@ -224,7 +226,7 @@ export const PostFooter: FC<PostFooterProps> = ({ post, detail, display }) => {
|
|
|
224
226
|
</a>
|
|
225
227
|
)}
|
|
226
228
|
<CompactCollectionTags
|
|
227
|
-
collections={
|
|
229
|
+
collections={visibleCollections}
|
|
228
230
|
showSeparator={showCollectionSeparator}
|
|
229
231
|
showIcon={detail}
|
|
230
232
|
/>
|
|
@@ -7,7 +7,7 @@ describe("HomePageBranding", () => {
|
|
|
7
7
|
const html = renderToString(HomePageBranding({}));
|
|
8
8
|
|
|
9
9
|
expect(html).toContain("Build with");
|
|
10
|
-
expect(html).toContain("https://
|
|
10
|
+
expect(html).toContain('href="https://jant.me"');
|
|
11
11
|
expect(html).toContain(">Jant</span>");
|
|
12
12
|
expect(html).not.toContain("<svg");
|
|
13
13
|
});
|
|
@@ -104,6 +104,7 @@ describe("MediaGallery", () => {
|
|
|
104
104
|
|
|
105
105
|
expect(html).toContain("data-feed-short-video");
|
|
106
106
|
expect(html).toContain('data-video-src="/media/clip.mp4"');
|
|
107
|
+
expect(html).toContain(""id":"media-3"");
|
|
107
108
|
expect(html).toContain("data-feed-video-mute-toggle");
|
|
108
109
|
expect(html).not.toContain("media-video-play-overlay");
|
|
109
110
|
expect(html).not.toContain("media-short-video-progress");
|
|
@@ -139,10 +139,10 @@ describe("system nav labels", () => {
|
|
|
139
139
|
|
|
140
140
|
it("translates system nav descriptions", () => {
|
|
141
141
|
expect(getSystemNavDescription("latest", i18n)).toBe(
|
|
142
|
-
"translated:Link to your latest posts.
|
|
142
|
+
"translated:Link to your latest posts. Your homepage shows this feed.",
|
|
143
143
|
);
|
|
144
144
|
expect(getSystemNavDescription("featured", i18n)).toBe(
|
|
145
|
-
"translated:Link to
|
|
145
|
+
"translated:Link to posts you've marked as featured.",
|
|
146
146
|
);
|
|
147
147
|
expect(getSystemNavDescription("archive", i18n)).toBe(
|
|
148
148
|
"translated:Link to the post archive",
|
|
@@ -106,6 +106,21 @@ describe("PostFooter", () => {
|
|
|
106
106
|
expect(html.match(/href="\/studio"/g)).toHaveLength(1);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
it("shows collection tags only on the thread root", () => {
|
|
110
|
+
const html = renderPostFooter(
|
|
111
|
+
createPostView({
|
|
112
|
+
id: "post-child",
|
|
113
|
+
replyToId: "post-root",
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(html).not.toContain('class="post-collection-tags"');
|
|
118
|
+
expect(html).not.toContain('href="/notes"');
|
|
119
|
+
expect(html).not.toContain("data-collection-popover-trigger");
|
|
120
|
+
expect(html).toContain('aria-label="More actions"');
|
|
121
|
+
expect(html).toContain("data-post-menu-trigger");
|
|
122
|
+
});
|
|
123
|
+
|
|
109
124
|
it("renders the featured icon before the timestamp for client-side toggles", () => {
|
|
110
125
|
const html = renderPostFooter(
|
|
111
126
|
createPostView({
|
|
@@ -53,7 +53,7 @@ const collectionFormMessages = {
|
|
|
53
53
|
}),
|
|
54
54
|
createdLabel: msg({
|
|
55
55
|
message: "Collection created.",
|
|
56
|
-
comment: "@context:
|
|
56
|
+
comment: "@context: Confirmation shown after creating a collection",
|
|
57
57
|
}),
|
|
58
58
|
descriptionLabel: msg({
|
|
59
59
|
message: "Description (optional)",
|
|
@@ -94,6 +94,30 @@ const collectionMutationMessages = {
|
|
|
94
94
|
message: "Edit",
|
|
95
95
|
comment: "@context: Per-collection edit action",
|
|
96
96
|
}),
|
|
97
|
+
addToNavigation: msg({
|
|
98
|
+
message: "Add to Navigation",
|
|
99
|
+
comment: "@context: Action that adds a Collection to site navigation",
|
|
100
|
+
}),
|
|
101
|
+
addingToNavigation: msg({
|
|
102
|
+
message: "Adding…",
|
|
103
|
+
comment:
|
|
104
|
+
"@context: Loading label while adding a Collection to site navigation",
|
|
105
|
+
}),
|
|
106
|
+
addedToNavigation: msg({
|
|
107
|
+
message: "Collection added to navigation.",
|
|
108
|
+
comment:
|
|
109
|
+
"@context: Confirmation after adding a Collection to site navigation",
|
|
110
|
+
}),
|
|
111
|
+
editNavigation: msg({
|
|
112
|
+
message: "Edit Navigation",
|
|
113
|
+
comment:
|
|
114
|
+
"@context: Action that opens Navigation settings for a Collection already in navigation",
|
|
115
|
+
}),
|
|
116
|
+
addToNavigationFailed: msg({
|
|
117
|
+
message: "Couldn't add this collection to navigation. Try again.",
|
|
118
|
+
comment:
|
|
119
|
+
"@context: Error after a Collection could not be added to site navigation",
|
|
120
|
+
}),
|
|
97
121
|
moreActions: msg({
|
|
98
122
|
message: "More actions",
|
|
99
123
|
comment: "@context: Aria-label for collections page more button",
|
|
@@ -112,7 +136,7 @@ const collectionMutationMessages = {
|
|
|
112
136
|
}),
|
|
113
137
|
confirmDelete: msg({
|
|
114
138
|
message:
|
|
115
|
-
"Delete this collection permanently?
|
|
139
|
+
"Delete this collection permanently? Threads inside won't be removed.",
|
|
116
140
|
comment: "@context: Confirm dialog for deleting a collection",
|
|
117
141
|
}),
|
|
118
142
|
cancel: msg({
|
|
@@ -210,6 +234,13 @@ export const getCollectionFormLabels = (i18n: Translator) => ({
|
|
|
210
234
|
|
|
211
235
|
export const getCollectionMutationLabels = (i18n: Translator) => ({
|
|
212
236
|
edit: i18n._(collectionMutationMessages.edit),
|
|
237
|
+
addToNavigation: i18n._(collectionMutationMessages.addToNavigation),
|
|
238
|
+
addingToNavigation: i18n._(collectionMutationMessages.addingToNavigation),
|
|
239
|
+
addedToNavigation: i18n._(collectionMutationMessages.addedToNavigation),
|
|
240
|
+
editNavigation: i18n._(collectionMutationMessages.editNavigation),
|
|
241
|
+
addToNavigationFailed: i18n._(
|
|
242
|
+
collectionMutationMessages.addToNavigationFailed,
|
|
243
|
+
),
|
|
213
244
|
moreActions: i18n._(collectionMutationMessages.moreActions),
|
|
214
245
|
label: i18n._(collectionMutationMessages.label),
|
|
215
246
|
url: i18n._(collectionMutationMessages.url),
|
|
@@ -81,13 +81,11 @@ const SYSTEM_NAV_TITLES: Partial<Record<SystemNavKey, MessageDescriptor>> = {
|
|
|
81
81
|
|
|
82
82
|
const SYSTEM_NAV_DESCRIPTIONS: Record<SystemNavKey, MessageDescriptor> = {
|
|
83
83
|
latest: msg({
|
|
84
|
-
message:
|
|
85
|
-
"Link to your latest posts. If it comes before Featured, the homepage opens here.",
|
|
84
|
+
message: "Link to your latest posts. Your homepage shows this feed.",
|
|
86
85
|
comment: "@context: Description for the latest system navigation toggle",
|
|
87
86
|
}),
|
|
88
87
|
featured: msg({
|
|
89
|
-
message:
|
|
90
|
-
"Link to your featured posts. If it comes before Latest, the homepage opens here.",
|
|
88
|
+
message: "Link to posts you've marked as featured.",
|
|
91
89
|
comment: "@context: Description for the featured system navigation toggle",
|
|
92
90
|
}),
|
|
93
91
|
rss: msg({
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PostView } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
type PostArticleAttributeSource = Pick<
|
|
4
|
+
PostView,
|
|
5
|
+
| "id"
|
|
6
|
+
| "slug"
|
|
7
|
+
| "format"
|
|
8
|
+
| "visibility"
|
|
9
|
+
| "replyToId"
|
|
10
|
+
| "threadRootId"
|
|
11
|
+
| "pinned"
|
|
12
|
+
| "pinnedInCollection"
|
|
13
|
+
| "featured"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Builds the shared data-attribute contract for interactive Post articles.
|
|
18
|
+
* Child identity is content-derived and therefore stays stable across feed,
|
|
19
|
+
* detail, search, and partial-render modes.
|
|
20
|
+
*
|
|
21
|
+
* @param post - Render-ready Post identity and action state
|
|
22
|
+
* @returns Data attributes consumed by menus, shortcuts, themes, and scripts
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <article {...getPostArticleAttributes(post)}>...</article>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function getPostArticleAttributes(post: PostArticleAttributeSource) {
|
|
30
|
+
const isChildPost = Boolean(post.replyToId || post.threadRootId);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
"data-post": "",
|
|
34
|
+
"data-format": post.format,
|
|
35
|
+
"data-post-id": post.id,
|
|
36
|
+
"data-post-slug": post.slug,
|
|
37
|
+
"data-thread-root-id": post.threadRootId ?? post.id,
|
|
38
|
+
...(post.pinned ? { "data-post-pinned": "" } : {}),
|
|
39
|
+
...(post.pinnedInCollection
|
|
40
|
+
? { "data-post-pinned-in-collection": "" }
|
|
41
|
+
: {}),
|
|
42
|
+
...(post.featured ? { "data-post-featured": "" } : {}),
|
|
43
|
+
"data-post-visibility": post.visibility,
|
|
44
|
+
...(isChildPost ? { "data-post-reply": "" } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|