@jant/core 0.6.11 → 0.6.13
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 +47 -30
- package/bin/commands/migrate-text-attachments.js +1 -3
- package/bin/commands/site/export.js +1 -3
- package/bin/commands/telegram/register-webhooks.js +9 -3
- package/bin/lib/site-snapshot.js +1 -5
- package/dist/app-CWJFPjuR.js +6 -0
- package/dist/{app-CpmficmQ.js → app-HnzmsaqU.js} +721 -473
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-Dd9U383b.js → client-3_OaxhTs.js} +1 -1
- package/dist/client/_assets/client-DCrcHVJl.css +2 -0
- package/dist/client/_assets/{client-auth-DkpSdIDz.js → client-auth-TsYq90xm.js} +625 -561
- package/dist/{export-Ba7NJImL.js → export-Dfbq9aot.js} +12 -15
- package/dist/{github-sync-Cb4_6_i7.js → github-sync--PSoE-Ne.js} +1 -1
- package/dist/{github-sync-BD4w2m8-.js → github-sync-BGTLx8Mf.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/__tests__/export-service.test.ts +1 -2
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +0 -1
- package/src/__tests__/import-site-command.test.ts +76 -2
- package/src/__tests__/vite-dev-plugins.test.ts +34 -0
- package/src/client/__tests__/compose-launch.test.ts +50 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +74 -18
- package/src/client/__tests__/site-header-nav.test.ts +36 -2
- package/src/client/components/__tests__/compose-title-from-heading.test.ts +57 -0
- package/src/client/components/__tests__/jant-command-palette.test.ts +63 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +33 -0
- package/src/client/components/__tests__/jant-compose-editor-rehost-notice.test.ts +20 -1
- package/src/client/components/__tests__/jant-compose-editor.test.ts +94 -2
- package/src/client/components/__tests__/jant-nav-manager.test.ts +181 -20
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +6 -0
- package/src/client/components/__tests__/jant-settings-general.test.ts +72 -0
- package/src/client/components/compose-title-from-heading.ts +66 -0
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/inline-image-issues.ts +17 -0
- package/src/client/components/jant-command-palette.ts +67 -2
- package/src/client/components/jant-compose-dialog.ts +10 -2
- package/src/client/components/jant-compose-editor.ts +78 -1
- package/src/client/components/jant-compose-fullscreen.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +382 -152
- package/src/client/components/jant-settings-general.ts +56 -0
- package/src/client/components/nav-manager-types.ts +14 -10
- package/src/client/components/settings-types.ts +30 -0
- package/src/client/compose-launch.ts +30 -0
- package/src/client/compose-shortcuts.ts +46 -3
- package/src/client/site-header-nav.d.ts +1 -0
- package/src/client/site-header-nav.js +105 -47
- package/src/client/tiptap/__tests__/block-insertion.test.ts +35 -0
- package/src/client/tiptap/__tests__/image-input-rules.test.ts +117 -0
- package/src/client/tiptap/__tests__/rehost-images.test.ts +66 -13
- package/src/client/tiptap/create-editor.ts +7 -0
- package/src/client/tiptap/extensions.ts +7 -2
- package/src/client/tiptap/image-input-rules.ts +48 -0
- package/src/client/tiptap/image-node.ts +189 -7
- package/src/client/tiptap/rehost-images.ts +5 -1
- package/src/client-auth.ts +0 -1
- package/src/i18n/__tests__/fallback.test.ts +24 -0
- package/src/i18n/locales/public/en.po +20 -4
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +22 -6
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +22 -6
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +77 -20
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +85 -28
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +79 -22
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/feed.test.ts +96 -10
- package/src/lib/__tests__/navigation.test.ts +4 -37
- package/src/lib/__tests__/resolve-config.test.ts +6 -1
- package/src/lib/__tests__/view.test.ts +6 -30
- package/src/lib/feed.ts +63 -3
- package/src/lib/github-sync-site-config.ts +0 -2
- package/src/lib/hugo-markdown.ts +1 -1
- package/src/lib/navigation.ts +5 -22
- package/src/lib/post-display.ts +14 -8
- package/src/lib/resolve-config.ts +9 -2
- package/src/lib/site-header-fragment.tsx +37 -0
- package/src/lib/view.ts +2 -7
- package/src/node/__tests__/cli-site-snapshot.test.ts +45 -45
- package/src/preset.css +60 -18
- package/src/routes/api/__tests__/nav-items.test.ts +26 -0
- package/src/routes/api/export.ts +0 -2
- package/src/routes/api/internal/__tests__/uploads.test.ts +9 -13
- package/src/routes/api/internal/sites.ts +5 -5
- package/src/routes/api/nav-items.ts +25 -4
- package/src/routes/dash/settings.tsx +48 -3
- package/src/routes/feed/__tests__/feed.test.ts +42 -0
- package/src/routes/feed/__tests__/sitemap.test.ts +2 -13
- package/src/routes/feed/feed.ts +1 -0
- package/src/routes/feed/sitemap.ts +2 -6
- package/src/routes/pages/__tests__/draft-about-edit.test.ts +65 -0
- package/src/routes/pages/archive.tsx +1 -0
- package/src/routes/pages/collection.tsx +1 -0
- package/src/routes/pages/featured.tsx +0 -4
- package/src/routes/pages/home.tsx +4 -51
- package/src/routes/pages/latest.tsx +2 -51
- package/src/routes/pages/page.tsx +44 -12
- package/src/services/__tests__/about-page.test.ts +116 -0
- package/src/services/__tests__/github-app-installations.test.ts +10 -10
- package/src/services/__tests__/navigation.test.ts +231 -1
- package/src/services/__tests__/post-timeline.test.ts +175 -2
- package/src/services/__tests__/post.test.ts +33 -0
- package/src/services/__tests__/settings.test.ts +5 -20
- package/src/services/about-page.ts +143 -0
- package/src/services/export-theme/assets/client-site.js +10 -10
- package/src/services/export-theme/layouts/_default/rss.xml +7 -16
- package/src/services/export-theme/layouts/index.html +26 -39
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +25 -1
- package/src/services/export-theme/styles/main.css +46 -14
- package/src/services/export.ts +5 -10
- package/src/services/github-app-installations.ts +1 -3
- package/src/services/index.ts +27 -17
- package/src/services/navigation.ts +186 -1
- package/src/services/post.ts +103 -63
- package/src/services/settings.ts +0 -10
- package/src/services/site-admin.ts +0 -2
- package/src/services/upload-session.ts +4 -18
- package/src/styles/components.css +20 -0
- package/src/styles/tokens.css +28 -10
- package/src/styles/ui.css +349 -13
- package/src/types/bindings.ts +0 -1
- package/src/types/config.ts +3 -7
- package/src/types/views.ts +13 -0
- package/src/ui/compose/ComposeDialog.tsx +28 -0
- package/src/ui/dash/appearance/NavigationContent.tsx +58 -1
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +19 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/GeneralContent.tsx +44 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +3 -3
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +8 -0
- package/src/ui/feed/__tests__/timeline-cards.test.ts +162 -1
- package/src/ui/layouts/SiteLayout.tsx +249 -216
- package/dist/app-DqKkZenB.js +0 -6
- package/dist/client/_assets/client-BhHHVvSY.css +0 -2
- package/src/client/nav-manager-bridge.ts +0 -54
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-
|
|
1
|
+
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-Dfbq9aot.js";
|
|
2
2
|
import { r as getInstallationToken } from "./github-app-BbklkFmU.js";
|
|
3
3
|
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BgSiE71w.js";
|
|
4
4
|
//#region src/lib/markdown-to-tiptap.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import "./export-
|
|
3
|
-
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-
|
|
2
|
+
import "./export-Dfbq9aot.js";
|
|
3
|
+
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync--PSoE-Ne.js";
|
|
4
4
|
export { classifyRepoForSync, createGitHubSyncService };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { y as url_exports } from "./url-BMYO-Zlt.js";
|
|
2
|
-
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, j as MAX_PINNED_POSTS, k as FORMATS, m as defaultFeedRenderer, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-
|
|
3
|
-
import { S as time_exports, a as markdown_exports } from "./export-
|
|
2
|
+
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, j as MAX_PINNED_POSTS, k as FORMATS, m as defaultFeedRenderer, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-HnzmsaqU.js";
|
|
3
|
+
import { S as time_exports, a as markdown_exports } from "./export-Dfbq9aot.js";
|
|
4
4
|
import "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync-
|
|
5
|
+
import "./github-sync--PSoE-Ne.js";
|
|
6
6
|
export { FORMATS, MAX_MEDIA_ATTACHMENTS, MAX_PINNED_POSTS, MEDIA_KINDS, NAV_ITEM_TYPES, SORT_ORDERS, STATUSES, TEXT_ATTACHMENT_CONTENT_FORMATS, createApp, createMediaContext, defaultFeedRenderer, markdown_exports as markdown, time_exports as time, toArchiveGroups, toArchiveGroupsWithMedia, toMediaView, toNavItemView, toNavItemViews, toPostView, toPostViews, toSearchResultView, url_exports as url };
|
package/dist/node.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as getWebhookUrl, d as BUILTIN_FONT_THEMES, f as getCjkSerifCssVariables, g as pgSchemaBundle, h as createStorageDriver, i as createSiteService, l as setMyCommands, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getFontThemeCssVariables, r as createNodeRequestRuntime, s as resolveConfig, t as createApp, u as setWebhook, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-
|
|
3
|
-
import { t as createExportService } from "./export-
|
|
2
|
+
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as getWebhookUrl, d as BUILTIN_FONT_THEMES, f as getCjkSerifCssVariables, g as pgSchemaBundle, h as createStorageDriver, i as createSiteService, l as setMyCommands, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getFontThemeCssVariables, r as createNodeRequestRuntime, s as resolveConfig, t as createApp, u as setWebhook, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-HnzmsaqU.js";
|
|
3
|
+
import { t as createExportService } from "./export-Dfbq9aot.js";
|
|
4
4
|
import { C as shouldTrustProxy, S as getTelegramWebhookSecret, b as getSiteResolutionMode, d as getHostedControlPlaneBaseUrl, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as getTelegramBotPool, y as getPort } from "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync-
|
|
5
|
+
import "./github-sync--PSoE-Ne.js";
|
|
6
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
7
7
|
import { serve } from "@hono/node-server";
|
|
8
8
|
import Database from "better-sqlite3";
|
|
@@ -529,7 +529,7 @@ async function createNodeRequestHandler(options) {
|
|
|
529
529
|
async function start(env = process.env, app) {
|
|
530
530
|
const handler = await createNodeRequestHandler({
|
|
531
531
|
env,
|
|
532
|
-
app: async () => app ?? (await import("./app-
|
|
532
|
+
app: async () => app ?? (await import("./app-CWJFPjuR.js")).createApp()
|
|
533
533
|
});
|
|
534
534
|
const hostname = resolveHost(env);
|
|
535
535
|
const port = resolvePort(env);
|
package/package.json
CHANGED
|
@@ -281,7 +281,6 @@ describe("createExportService (Hugo)", () => {
|
|
|
281
281
|
makeSiteConfig({
|
|
282
282
|
siteName: "My Site",
|
|
283
283
|
siteUrl: "https://my.example",
|
|
284
|
-
homeDefaultView: "featured",
|
|
285
284
|
}),
|
|
286
285
|
);
|
|
287
286
|
const files = filesToMap(await service.generateHugoFiles());
|
|
@@ -292,7 +291,7 @@ describe("createExportService (Hugo)", () => {
|
|
|
292
291
|
expect(toml).toContain('theme = "jant"');
|
|
293
292
|
expect(toml).toMatch(/\[permalinks\][\s\S]*post = "\/:slug\/"/);
|
|
294
293
|
expect(toml).toContain("[params]");
|
|
295
|
-
expect(toml).toContain(
|
|
294
|
+
expect(toml).not.toContain("home_default_view");
|
|
296
295
|
});
|
|
297
296
|
|
|
298
297
|
it("configures hugo.toml for Atom RSS output with per-section opt-in", async () => {
|
|
@@ -123,6 +123,9 @@ export function createTestApp(options: TestAppOptions = {}) {
|
|
|
123
123
|
c.set("appConfig", resolveConfig(c.env, allSettings));
|
|
124
124
|
c.set("storage", options.storage ?? null);
|
|
125
125
|
c.set("rateLimiter", rateLimiter);
|
|
126
|
+
const publicRequestUrl = c.req.url;
|
|
127
|
+
c.set("publicRequestUrl", publicRequestUrl);
|
|
128
|
+
c.set("publicPath", new URL(publicRequestUrl).pathname);
|
|
126
129
|
|
|
127
130
|
// i18n (English default for tests)
|
|
128
131
|
const i18n = createI18n("en");
|
|
@@ -88,7 +88,6 @@ export function makeSiteConfig(over: Partial<SiteConfig> = {}): SiteConfig {
|
|
|
88
88
|
siteDescription: "Export fixture",
|
|
89
89
|
siteLanguage: "en",
|
|
90
90
|
showJantBrandingOnHome: true,
|
|
91
|
-
homeDefaultView: "latest",
|
|
92
91
|
mainRssFeed: "latest",
|
|
93
92
|
siteFooter: "",
|
|
94
93
|
showHeaderAvatar: false,
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
uploadMediaList,
|
|
25
25
|
normalizeTextAttachmentSpec,
|
|
26
26
|
isAbsoluteImportUrl,
|
|
27
|
+
shouldImportReplyQuietly,
|
|
27
28
|
} = __test__;
|
|
28
29
|
|
|
29
30
|
async function writeFileTree(
|
|
@@ -97,7 +98,6 @@ describe("Hugo import CLI helpers", () => {
|
|
|
97
98
|
'site_name = "Example Site"',
|
|
98
99
|
'site_description = "A description"',
|
|
99
100
|
'site_language = "en"',
|
|
100
|
-
'home_default_view = "featured"',
|
|
101
101
|
"show_jant_branding_on_home = true",
|
|
102
102
|
"show_header_avatar = false",
|
|
103
103
|
"noindex = false",
|
|
@@ -135,7 +135,7 @@ describe("Hugo import CLI helpers", () => {
|
|
|
135
135
|
expect(siteConfig.title).toBe("Example Site");
|
|
136
136
|
expect(siteConfig.base_url).toBe("https://example.com/");
|
|
137
137
|
expect(siteConfig.extra.jant.theme_id).toBe("paper");
|
|
138
|
-
expect(siteConfig.extra.jant
|
|
138
|
+
expect(siteConfig.extra.jant).not.toHaveProperty("home_default_view");
|
|
139
139
|
expect(siteConfig.extra.jant.nav_exported).toBe(true);
|
|
140
140
|
expect(siteConfig.extra.jant.nav).toHaveLength(1);
|
|
141
141
|
expect(siteConfig.extra.jant.collections_directory_exported).toBe(true);
|
|
@@ -368,6 +368,35 @@ describe("Hugo import CLI helpers", () => {
|
|
|
368
368
|
});
|
|
369
369
|
});
|
|
370
370
|
|
|
371
|
+
it("buildPostPayloadFromBundle can mark imported replies as quiet", () => {
|
|
372
|
+
const bundle = {
|
|
373
|
+
slug: "reply-a",
|
|
374
|
+
frontMatter: {
|
|
375
|
+
id: "pst_reply",
|
|
376
|
+
title: "Reply A",
|
|
377
|
+
date: "2026-04-01T01:00:00Z",
|
|
378
|
+
slug: "reply-a",
|
|
379
|
+
type: "post",
|
|
380
|
+
format: "note",
|
|
381
|
+
status: "published",
|
|
382
|
+
visibility: "public",
|
|
383
|
+
},
|
|
384
|
+
body: "Reply body",
|
|
385
|
+
};
|
|
386
|
+
const data = buildPostPayloadFromBundle(bundle, {
|
|
387
|
+
bodyMarkdown: "Reply body",
|
|
388
|
+
attachments: [],
|
|
389
|
+
memberships: { entries: [], ids: [] },
|
|
390
|
+
replyToId: "pst_root",
|
|
391
|
+
quietReply: true,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(data).toMatchObject({
|
|
395
|
+
replyToId: "pst_root",
|
|
396
|
+
quietReply: true,
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
371
400
|
it("buildPostPayloadFromBundle preserves every canonical visibility value", () => {
|
|
372
401
|
const base = {
|
|
373
402
|
slug: "hello",
|
|
@@ -406,6 +435,51 @@ describe("Hugo import CLI helpers", () => {
|
|
|
406
435
|
expect(fallback.visibility).toBeUndefined();
|
|
407
436
|
});
|
|
408
437
|
|
|
438
|
+
it("shouldImportReplyQuietly preserves exported thread activity cutoffs", () => {
|
|
439
|
+
const rootFrontMatter = {
|
|
440
|
+
date: "2026-04-01T00:00:00Z",
|
|
441
|
+
last_activity_at: "2026-04-01T01:00:00Z",
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
expect(
|
|
445
|
+
shouldImportReplyQuietly(rootFrontMatter, {
|
|
446
|
+
date: "2026-04-01T00:30:00Z",
|
|
447
|
+
status: "published",
|
|
448
|
+
}),
|
|
449
|
+
).toBe(false);
|
|
450
|
+
expect(
|
|
451
|
+
shouldImportReplyQuietly(rootFrontMatter, {
|
|
452
|
+
date: "2026-04-01T01:00:00Z",
|
|
453
|
+
status: "published",
|
|
454
|
+
}),
|
|
455
|
+
).toBe(false);
|
|
456
|
+
expect(
|
|
457
|
+
shouldImportReplyQuietly(rootFrontMatter, {
|
|
458
|
+
date: "2026-04-01T02:00:00Z",
|
|
459
|
+
status: "published",
|
|
460
|
+
}),
|
|
461
|
+
).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("shouldImportReplyQuietly falls back to root date for all-quiet threads", () => {
|
|
465
|
+
const rootFrontMatter = {
|
|
466
|
+
date: "2026-04-01T00:00:00Z",
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
expect(
|
|
470
|
+
shouldImportReplyQuietly(rootFrontMatter, {
|
|
471
|
+
date: "2026-04-01T01:00:00Z",
|
|
472
|
+
status: "published",
|
|
473
|
+
}),
|
|
474
|
+
).toBe(true);
|
|
475
|
+
expect(
|
|
476
|
+
shouldImportReplyQuietly(rootFrontMatter, {
|
|
477
|
+
date: "2026-04-01T01:00:00Z",
|
|
478
|
+
status: "draft",
|
|
479
|
+
}),
|
|
480
|
+
).toBe(false);
|
|
481
|
+
});
|
|
482
|
+
|
|
409
483
|
it("getRootAliasPathsForImport prefers root_aliases and strips reply slugs", () => {
|
|
410
484
|
// Reply slug paths are normalized (no trailing slash) to match the
|
|
411
485
|
// return format of normalizeImportAliasPath.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { shouldRunLinguiBuildForFile } from "../../vite.dev-plugins.js";
|
|
3
|
+
|
|
4
|
+
describe("vite dev plugins", () => {
|
|
5
|
+
it("does not run Lingui build for generated i18n output", () => {
|
|
6
|
+
expect(
|
|
7
|
+
shouldRunLinguiBuildForFile(
|
|
8
|
+
"/repo/packages/core/src/i18n/coverage.generated.ts",
|
|
9
|
+
),
|
|
10
|
+
).toBe(false);
|
|
11
|
+
expect(
|
|
12
|
+
shouldRunLinguiBuildForFile(
|
|
13
|
+
"\\repo\\packages\\core\\src\\i18n\\coverage.generated.ts",
|
|
14
|
+
),
|
|
15
|
+
).toBe(false);
|
|
16
|
+
expect(
|
|
17
|
+
shouldRunLinguiBuildForFile(
|
|
18
|
+
"/repo/packages/core/src/i18n/locales/settings/en.ts",
|
|
19
|
+
),
|
|
20
|
+
).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("runs Lingui build for source TypeScript files", () => {
|
|
24
|
+
expect(
|
|
25
|
+
shouldRunLinguiBuildForFile("/repo/packages/core/src/ui/pages/Home.tsx"),
|
|
26
|
+
).toBe(true);
|
|
27
|
+
expect(
|
|
28
|
+
shouldRunLinguiBuildForFile("/repo/packages/core/src/routes/posts.ts"),
|
|
29
|
+
).toBe(true);
|
|
30
|
+
expect(
|
|
31
|
+
shouldRunLinguiBuildForFile("/repo/packages/core/src/styles/ui.css"),
|
|
32
|
+
).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getReplyRefreshTarget,
|
|
6
|
+
getReplyTargetArticle,
|
|
7
|
+
} from "../compose-launch.js";
|
|
5
8
|
|
|
6
9
|
function getArticle(): HTMLElement {
|
|
7
10
|
const article = document.querySelector<HTMLElement>("article[data-post]");
|
|
@@ -9,6 +12,14 @@ function getArticle(): HTMLElement {
|
|
|
9
12
|
return article;
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
function getArticleByPostId(postId: string): HTMLElement {
|
|
16
|
+
const article = document.querySelector<HTMLElement>(
|
|
17
|
+
`article[data-post-id="${postId}"]`,
|
|
18
|
+
);
|
|
19
|
+
if (!article) throw new Error(`expected article for ${postId}`);
|
|
20
|
+
return article;
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
describe("getReplyRefreshTarget", () => {
|
|
13
24
|
afterEach(() => {
|
|
14
25
|
document.body.innerHTML = "";
|
|
@@ -118,3 +129,41 @@ describe("getReplyRefreshTarget", () => {
|
|
|
118
129
|
expect(getReplyRefreshTarget(getArticle())).toBeNull();
|
|
119
130
|
});
|
|
120
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("getReplyTargetArticle", () => {
|
|
134
|
+
afterEach(() => {
|
|
135
|
+
document.body.innerHTML = "";
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("targets the latest thread detail post when the current page is the root", () => {
|
|
139
|
+
document.body.innerHTML = `
|
|
140
|
+
<div data-post-view data-post-view-id="pst_root">
|
|
141
|
+
<div class="thread-group thread-group-detail" data-page="post">
|
|
142
|
+
<div class="thread-item thread-detail-item" data-post-current>
|
|
143
|
+
<article data-post data-post-id="pst_root" data-thread-root-id="pst_root"></article>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="thread-item thread-detail-item">
|
|
146
|
+
<article data-post data-post-id="pst_reply_1" data-thread-root-id="pst_root"></article>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="thread-item thread-detail-item">
|
|
149
|
+
<article data-post data-post-id="pst_reply_2" data-thread-root-id="pst_root"></article>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
expect(getReplyTargetArticle(document)?.dataset.postId).toBe("pst_reply_2");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("keeps using the current article outside thread detail pages", () => {
|
|
159
|
+
document.body.innerHTML = `
|
|
160
|
+
<div data-post-view data-post-view-id="pst_single">
|
|
161
|
+
<article data-post data-post-id="pst_single"></article>
|
|
162
|
+
</div>
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
expect(getReplyTargetArticle(document)).toBe(
|
|
166
|
+
getArticleByPostId("pst_single"),
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { __testOnly as composeDiscoveryTestOnly } from "../compose-discovery.js";
|
|
5
|
-
import "../compose-shortcuts.js";
|
|
5
|
+
import { __testOnly as composeShortcutsTestOnly } from "../compose-shortcuts.js";
|
|
6
6
|
|
|
7
7
|
type ComposeHarness = HTMLElement & {
|
|
8
8
|
openNew: (options?: unknown) => Promise<void>;
|
|
@@ -44,6 +44,7 @@ function renderThreadDetailPage() {
|
|
|
44
44
|
threadGroup.className = "thread-group thread-group-detail";
|
|
45
45
|
|
|
46
46
|
const currentItem = document.createElement("div");
|
|
47
|
+
currentItem.className = "thread-item thread-detail-item";
|
|
47
48
|
currentItem.dataset.postCurrent = "";
|
|
48
49
|
|
|
49
50
|
const currentArticle = document.createElement("article");
|
|
@@ -58,6 +59,7 @@ function renderThreadDetailPage() {
|
|
|
58
59
|
`;
|
|
59
60
|
|
|
60
61
|
const hoveredItem = document.createElement("div");
|
|
62
|
+
hoveredItem.className = "thread-item thread-detail-item";
|
|
61
63
|
|
|
62
64
|
const hoveredArticle = document.createElement("article");
|
|
63
65
|
hoveredArticle.dataset.post = "";
|
|
@@ -70,14 +72,30 @@ function renderThreadDetailPage() {
|
|
|
70
72
|
<div data-post-body>Hovered body</div>
|
|
71
73
|
`;
|
|
72
74
|
|
|
75
|
+
const latestItem = document.createElement("div");
|
|
76
|
+
latestItem.className = "thread-item thread-detail-item";
|
|
77
|
+
|
|
78
|
+
const latestArticle = document.createElement("article");
|
|
79
|
+
latestArticle.dataset.post = "";
|
|
80
|
+
latestArticle.dataset.postId = "post-latest";
|
|
81
|
+
latestArticle.dataset.threadRootId = "thread-root";
|
|
82
|
+
latestArticle.dataset.format = "note";
|
|
83
|
+
latestArticle.innerHTML = `
|
|
84
|
+
<div data-post-meta>meta</div>
|
|
85
|
+
<time class="dt-published">Mar 21</time>
|
|
86
|
+
<div data-post-body>Latest body</div>
|
|
87
|
+
`;
|
|
88
|
+
|
|
73
89
|
currentItem.appendChild(currentArticle);
|
|
74
90
|
hoveredItem.appendChild(hoveredArticle);
|
|
91
|
+
latestItem.appendChild(latestArticle);
|
|
75
92
|
threadGroup.appendChild(currentItem);
|
|
76
93
|
threadGroup.appendChild(hoveredItem);
|
|
94
|
+
threadGroup.appendChild(latestItem);
|
|
77
95
|
postView.appendChild(threadGroup);
|
|
78
96
|
document.body.appendChild(postView);
|
|
79
97
|
|
|
80
|
-
return { currentArticle, hoveredArticle };
|
|
98
|
+
return { currentArticle, hoveredArticle, latestArticle };
|
|
81
99
|
}
|
|
82
100
|
|
|
83
101
|
describe("compose shortcuts", () => {
|
|
@@ -130,7 +148,7 @@ describe("compose shortcuts", () => {
|
|
|
130
148
|
expect(composeEl.openNew).not.toHaveBeenCalled();
|
|
131
149
|
});
|
|
132
150
|
|
|
133
|
-
it("opens a reply composer for the
|
|
151
|
+
it("opens a reply composer for the latest thread post on detail pages with r", () => {
|
|
134
152
|
const composeEl = createComposeHarness();
|
|
135
153
|
|
|
136
154
|
const postView = document.createElement("div");
|
|
@@ -138,22 +156,45 @@ describe("compose shortcuts", () => {
|
|
|
138
156
|
postView.dataset.postViewId = "post-current";
|
|
139
157
|
|
|
140
158
|
const current = document.createElement("div");
|
|
159
|
+
current.className = "thread-item thread-detail-item";
|
|
141
160
|
current.dataset.postCurrent = "";
|
|
142
161
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
const currentArticle = document.createElement("article");
|
|
163
|
+
currentArticle.dataset.post = "";
|
|
164
|
+
currentArticle.dataset.postId = "post-current";
|
|
165
|
+
currentArticle.dataset.threadRootId = "thread-root";
|
|
166
|
+
currentArticle.dataset.format = "quote";
|
|
167
|
+
currentArticle.innerHTML = `
|
|
149
168
|
<div data-post-meta>meta</div>
|
|
150
169
|
<div class="post-status-badges">badges</div>
|
|
151
170
|
<time class="dt-published">Mar 19</time>
|
|
152
|
-
<div data-post-body>
|
|
171
|
+
<div data-post-body>Current body</div>
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
const latest = document.createElement("div");
|
|
175
|
+
latest.className = "thread-item thread-detail-item";
|
|
176
|
+
|
|
177
|
+
const latestArticle = document.createElement("article");
|
|
178
|
+
latestArticle.dataset.post = "";
|
|
179
|
+
latestArticle.dataset.postId = "post-latest";
|
|
180
|
+
latestArticle.dataset.threadRootId = "thread-root";
|
|
181
|
+
latestArticle.dataset.format = "quote";
|
|
182
|
+
latestArticle.innerHTML = `
|
|
183
|
+
<div data-post-meta>meta</div>
|
|
184
|
+
<div class="post-status-badges">badges</div>
|
|
185
|
+
<time class="dt-published">Mar 21</time>
|
|
186
|
+
<div data-post-body>Latest reply body</div>
|
|
153
187
|
`;
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
const threadGroup = document.createElement("div");
|
|
190
|
+
threadGroup.dataset.page = "post";
|
|
191
|
+
threadGroup.className = "thread-group thread-group-detail";
|
|
192
|
+
|
|
193
|
+
current.appendChild(currentArticle);
|
|
194
|
+
latest.appendChild(latestArticle);
|
|
195
|
+
threadGroup.appendChild(current);
|
|
196
|
+
threadGroup.appendChild(latest);
|
|
197
|
+
postView.appendChild(threadGroup);
|
|
157
198
|
document.body.appendChild(postView);
|
|
158
199
|
|
|
159
200
|
const event = dispatchShortcut(document, "r");
|
|
@@ -165,13 +206,13 @@ describe("compose shortcuts", () => {
|
|
|
165
206
|
composeEl.openReply,
|
|
166
207
|
).mock.calls[0] ?? [null, null, null, null];
|
|
167
208
|
|
|
168
|
-
expect(postId).toBe("post-
|
|
209
|
+
expect(postId).toBe("post-latest");
|
|
169
210
|
expect(threadRootId).toBe("thread-root");
|
|
170
211
|
expect(refreshTarget).toEqual({ kind: "post-view", id: "post-current" });
|
|
171
212
|
expect(vi.mocked(composeEl.openReply).mock.calls[0]?.[4]).toBeUndefined();
|
|
172
|
-
expect(replyData).toMatchObject({ dateText: "Mar
|
|
213
|
+
expect(replyData).toMatchObject({ dateText: "Mar 21" });
|
|
173
214
|
expect((replyData as { contentHtml: string }).contentHtml).toContain(
|
|
174
|
-
"
|
|
215
|
+
"Latest reply body",
|
|
175
216
|
);
|
|
176
217
|
expect((replyData as { contentHtml: string }).contentHtml).not.toContain(
|
|
177
218
|
"meta",
|
|
@@ -181,7 +222,7 @@ describe("compose shortcuts", () => {
|
|
|
181
222
|
);
|
|
182
223
|
});
|
|
183
224
|
|
|
184
|
-
it("
|
|
225
|
+
it("targets the latest thread post for reply shortcuts even when an older post is hovered", () => {
|
|
185
226
|
const composeEl = createComposeHarness();
|
|
186
227
|
const { hoveredArticle } = renderThreadDetailPage();
|
|
187
228
|
const originalQuerySelector = document.querySelector.bind(document);
|
|
@@ -199,8 +240,8 @@ describe("compose shortcuts", () => {
|
|
|
199
240
|
|
|
200
241
|
expect(event.defaultPrevented).toBe(true);
|
|
201
242
|
expect(composeEl.openReply).toHaveBeenCalledWith(
|
|
202
|
-
"post-
|
|
203
|
-
expect.objectContaining({ dateText: "Mar
|
|
243
|
+
"post-latest",
|
|
244
|
+
expect.objectContaining({ dateText: "Mar 21" }),
|
|
204
245
|
"thread-root",
|
|
205
246
|
{ kind: "post-view", id: "post-current" },
|
|
206
247
|
);
|
|
@@ -228,6 +269,21 @@ describe("compose shortcuts", () => {
|
|
|
228
269
|
expect(composeEl.openEdit).toHaveBeenCalledWith("post-hovered");
|
|
229
270
|
});
|
|
230
271
|
|
|
272
|
+
it("opens the current post editor from the edit query parameter", async () => {
|
|
273
|
+
const composeEl = createComposeHarness();
|
|
274
|
+
composeEl.openEdit = vi.fn(async () => {});
|
|
275
|
+
document.body.setAttribute("data-authenticated", "true");
|
|
276
|
+
renderThreadDetailPage();
|
|
277
|
+
globalThis.history.pushState({}, "", "/about?edit=1");
|
|
278
|
+
|
|
279
|
+
composeShortcutsTestOnly.openEditFromQueryParam();
|
|
280
|
+
await Promise.resolve();
|
|
281
|
+
|
|
282
|
+
expect(composeEl.openEdit).toHaveBeenCalledWith("post-current");
|
|
283
|
+
expect(window.location.pathname).toBe("/about");
|
|
284
|
+
expect(window.location.search).toBe("");
|
|
285
|
+
});
|
|
286
|
+
|
|
231
287
|
it("keeps using the hovered post for reply shortcuts in the timeline", () => {
|
|
232
288
|
const composeEl = createComposeHarness();
|
|
233
289
|
const article = document.createElement("article");
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
5
5
|
type SiteHeaderInit = typeof import("../site-header-nav.js").initSiteHeaderNav;
|
|
6
|
+
type SiteHeaderDestroy =
|
|
7
|
+
typeof import("../site-header-nav.js").destroySiteHeaderNav;
|
|
6
8
|
|
|
7
9
|
function createDrawerDOM(): HTMLElement {
|
|
8
10
|
const root = document.createElement("div");
|
|
@@ -50,13 +52,15 @@ function createMoreDropdownDOM(): HTMLElement {
|
|
|
50
52
|
|
|
51
53
|
describe("site header more dropdown", () => {
|
|
52
54
|
let initSiteHeaderNav: SiteHeaderInit;
|
|
55
|
+
let destroySiteHeaderNav: SiteHeaderDestroy;
|
|
53
56
|
let root: HTMLElement;
|
|
54
57
|
|
|
55
58
|
beforeEach(async () => {
|
|
56
59
|
document.body.innerHTML = "";
|
|
57
60
|
vi.resetModules();
|
|
58
61
|
root = createMoreDropdownDOM();
|
|
59
|
-
({ initSiteHeaderNav } =
|
|
62
|
+
({ initSiteHeaderNav, destroySiteHeaderNav } =
|
|
63
|
+
await import("../site-header-nav.js"));
|
|
60
64
|
initSiteHeaderNav(root);
|
|
61
65
|
});
|
|
62
66
|
|
|
@@ -130,10 +134,26 @@ describe("site header more dropdown", () => {
|
|
|
130
134
|
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
131
135
|
expect(popover.getAttribute("aria-hidden")).toBe("true");
|
|
132
136
|
});
|
|
137
|
+
|
|
138
|
+
it("removes dropdown listeners when destroyed", () => {
|
|
139
|
+
const trigger = root.querySelector(
|
|
140
|
+
".site-header-more-btn",
|
|
141
|
+
) as HTMLButtonElement;
|
|
142
|
+
const popover = root.querySelector(
|
|
143
|
+
".site-header-more-popover",
|
|
144
|
+
) as HTMLElement;
|
|
145
|
+
|
|
146
|
+
destroySiteHeaderNav(root);
|
|
147
|
+
trigger.click();
|
|
148
|
+
|
|
149
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
150
|
+
expect(popover.getAttribute("aria-hidden")).toBe("true");
|
|
151
|
+
});
|
|
133
152
|
});
|
|
134
153
|
|
|
135
154
|
describe("site header nav drawer", () => {
|
|
136
155
|
let initSiteHeaderNav: SiteHeaderInit;
|
|
156
|
+
let destroySiteHeaderNav: SiteHeaderDestroy;
|
|
137
157
|
let root: HTMLElement;
|
|
138
158
|
|
|
139
159
|
beforeEach(async () => {
|
|
@@ -141,7 +161,8 @@ describe("site header nav drawer", () => {
|
|
|
141
161
|
document.documentElement.classList.remove("drawer-open");
|
|
142
162
|
vi.resetModules();
|
|
143
163
|
root = createDrawerDOM();
|
|
144
|
-
({ initSiteHeaderNav } =
|
|
164
|
+
({ initSiteHeaderNav, destroySiteHeaderNav } =
|
|
165
|
+
await import("../site-header-nav.js"));
|
|
145
166
|
initSiteHeaderNav(root);
|
|
146
167
|
});
|
|
147
168
|
|
|
@@ -247,4 +268,17 @@ describe("site header nav drawer", () => {
|
|
|
247
268
|
expect(drawer.getAttribute("aria-hidden")).toBe("true");
|
|
248
269
|
expect(hamburger.getAttribute("aria-expanded")).toBe("false");
|
|
249
270
|
});
|
|
271
|
+
|
|
272
|
+
it("removes drawer listeners when destroyed", () => {
|
|
273
|
+
const hamburger = root.querySelector(
|
|
274
|
+
".site-header-hamburger",
|
|
275
|
+
) as HTMLButtonElement;
|
|
276
|
+
const drawer = root.querySelector("#site-nav-drawer") as HTMLElement;
|
|
277
|
+
|
|
278
|
+
destroySiteHeaderNav(root);
|
|
279
|
+
hamburger.click();
|
|
280
|
+
|
|
281
|
+
expect(drawer.getAttribute("aria-hidden")).toBe("true");
|
|
282
|
+
expect(hamburger.getAttribute("aria-expanded")).toBe("false");
|
|
283
|
+
});
|
|
250
284
|
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { JSONContent } from "@tiptap/core";
|
|
3
|
+
|
|
4
|
+
import { promoteLeadingH1Title } from "../compose-title-from-heading.js";
|
|
5
|
+
|
|
6
|
+
function doc(...content: JSONContent[]): JSONContent {
|
|
7
|
+
return { type: "doc", content };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function paragraph(text = ""): JSONContent {
|
|
11
|
+
return text
|
|
12
|
+
? { type: "paragraph", content: [{ type: "text", text }] }
|
|
13
|
+
: { type: "paragraph" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function heading(level: number, text = ""): JSONContent {
|
|
17
|
+
return text
|
|
18
|
+
? {
|
|
19
|
+
type: "heading",
|
|
20
|
+
attrs: { level },
|
|
21
|
+
content: [{ type: "text", text }],
|
|
22
|
+
}
|
|
23
|
+
: { type: "heading", attrs: { level } };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("promoteLeadingH1Title", () => {
|
|
27
|
+
it("extracts the first non-empty H1 and removes it from the body", () => {
|
|
28
|
+
const result = promoteLeadingH1Title(
|
|
29
|
+
doc(paragraph(), heading(1, " My Title "), paragraph("Body")),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(result?.title).toBe("My Title");
|
|
33
|
+
expect(result?.headingIndex).toBe(1);
|
|
34
|
+
expect(result?.bodyJson).toEqual(doc(paragraph("Body")));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns null when the first non-empty block is not an H1", () => {
|
|
38
|
+
const result = promoteLeadingH1Title(
|
|
39
|
+
doc(paragraph("Intro"), heading(1, "Title")),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(result).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns null for empty H1 blocks", () => {
|
|
46
|
+
const result = promoteLeadingH1Title(doc(heading(1), paragraph("Body")));
|
|
47
|
+
|
|
48
|
+
expect(result).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns a null body when the promoted H1 is the only content", () => {
|
|
52
|
+
const result = promoteLeadingH1Title(doc(heading(1, "Only title")));
|
|
53
|
+
|
|
54
|
+
expect(result?.title).toBe("Only title");
|
|
55
|
+
expect(result?.bodyJson).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
});
|