@jant/core 0.6.15 → 0.6.16
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 +7 -0
- package/dist/app-C75Zc8dD.js +6 -0
- package/dist/{app-C-oi_t8W.js → app-C7_4oab9.js} +480 -196
- package/dist/client/.vite/manifest.json +8 -8
- package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
- package/dist/client/_assets/{client-DxY5BFe8.js → client-Bq0vre8Y.js} +3 -3
- package/dist/client/_assets/client-D3VZyG4L.css +2 -0
- package/dist/client/_assets/{client-auth-CakPESv9.js → client-auth-F2rb9WfF.js} +1015 -977
- package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
- package/dist/{export-CsFx6F_M.js → export-Dl5KCiEs.js} +93 -7
- package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
- package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
- package/dist/{github-sync-ppWXN3jb.js → github-sync-Bqg62IvV.js} +3 -3
- package/dist/{github-sync-Cq1pzzOI.js → github-sync-HdK2EgvJ.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/node.js +6 -6
- package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
- package/package.json +1 -1
- package/src/__tests__/mise-config.test.ts +22 -0
- package/src/client/__tests__/compose-bridge.test.ts +252 -2
- package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
- package/src/client/__tests__/toast.test.ts +65 -1
- package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
- package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +2015 -318
- package/src/client/components/__tests__/jant-compose-editor.test.ts +53 -62
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +35 -89
- package/src/client/components/compose-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +47 -3
- package/src/client/components/jant-command-palette.ts +12 -6
- package/src/client/components/jant-compose-dialog.ts +1464 -680
- package/src/client/components/jant-compose-editor.ts +225 -93
- package/src/client/components/jant-compose-fullscreen.ts +3 -5
- package/src/client/components/jant-post-menu.ts +91 -21
- package/src/client/compose-bridge.ts +143 -122
- package/src/client/compose-launch.ts +13 -0
- package/src/client/compose-shortcuts.ts +5 -1
- package/src/client/post-refresh.ts +135 -0
- package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
- package/src/client/tiptap/link-input-rules.ts +4 -4
- package/src/client/toast.ts +130 -38
- package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
- package/src/db/__tests__/thread-activity.test.ts +113 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
- package/src/db/migrations/0031_many_ego.sql +3 -0
- package/src/db/migrations/meta/0031_snapshot.json +2562 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
- package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +18 -0
- package/src/db/schema.ts +20 -0
- package/src/db/thread-activity.ts +101 -0
- package/src/i18n/locales/public/en.po +89 -21
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +88 -20
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +88 -20
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/lib/__tests__/feed.test.ts +168 -0
- package/src/lib/__tests__/markdown.test.ts +16 -0
- package/src/lib/__tests__/slug.test.ts +18 -0
- package/src/lib/__tests__/translit.test.ts +87 -0
- package/src/lib/__tests__/url.test.ts +53 -0
- package/src/lib/feed.ts +113 -11
- package/src/lib/hugo-markdown.ts +5 -0
- package/src/lib/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +6 -1
- package/src/lib/post-display.ts +42 -9
- package/src/lib/timeline.ts +39 -9
- package/src/lib/tiptap-render.ts +4 -2
- package/src/lib/translit.ts +288 -0
- package/src/lib/url.ts +123 -12
- package/src/lib/view.ts +28 -0
- package/src/routes/api/__tests__/palette.test.ts +44 -0
- package/src/routes/api/posts.ts +13 -9
- package/src/routes/api/public/__tests__/posts.test.ts +38 -0
- package/src/routes/api/public/posts.ts +7 -0
- package/src/routes/compose.tsx +6 -2
- package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
- package/src/routes/pages/__tests__/archive-params.test.ts +292 -0
- package/src/routes/pages/__tests__/featured.test.ts +6 -2
- package/src/routes/pages/__tests__/preview.test.ts +3 -1
- package/src/routes/pages/archive.tsx +65 -17
- package/src/routes/pages/page.tsx +5 -2
- package/src/services/__tests__/collection.test.ts +32 -1
- package/src/services/__tests__/post-timeline.test.ts +142 -11
- package/src/services/__tests__/post.test.ts +277 -0
- package/src/services/collection.ts +8 -10
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export.ts +3 -5
- package/src/services/path.ts +4 -1
- package/src/services/post.ts +297 -100
- package/src/services/search.ts +9 -0
- package/src/styles/components.css +0 -14
- package/src/styles/site-media.css +4 -4
- package/src/styles/tokens.css +62 -1
- package/src/styles/ui.css +1363 -1283
- package/src/types/entities.ts +5 -0
- package/src/types/props.ts +12 -0
- package/src/types/views.ts +6 -0
- package/src/ui/compose/ComposeDialog.tsx +17 -17
- package/src/ui/feed/LinkPreview.tsx +2 -7
- package/src/ui/feed/PostStatusBadges.tsx +42 -3
- package/src/ui/pages/ArchivePage.tsx +131 -24
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +63 -0
- package/src/ui/shared/DraftPreviewBar.tsx +25 -9
- package/src/ui/shared/PostFooter.tsx +35 -12
- package/src/ui/shared/custom-icons.ts +5 -0
- package/src/ui/shared/post-article-attributes.ts +6 -0
- package/dist/app-BxCOR3Uc.js +0 -6
- package/dist/client/_assets/client-B_eGKN5p.css +0 -2
|
@@ -8,6 +8,7 @@ type ComposeHarness = HTMLElement & {
|
|
|
8
8
|
clearLocalDraftFromStorage?: () => void;
|
|
9
9
|
clearEditDraftFromStorage?: (postId: string) => void;
|
|
10
10
|
openEdit?: (id: string) => Promise<void>;
|
|
11
|
+
openDraft?: (id: string) => Promise<void>;
|
|
11
12
|
openNew?: (options?: { restoreDraft?: boolean }) => Promise<void>;
|
|
12
13
|
openReply?: (
|
|
13
14
|
id: string,
|
|
@@ -42,9 +43,18 @@ async function flushBridgeWork(times = 4) {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function renderPostView(postId: string) {
|
|
47
|
+
const postView = document.createElement("div");
|
|
48
|
+
postView.dataset.postView = "";
|
|
49
|
+
postView.dataset.postViewId = postId;
|
|
50
|
+
document.body.appendChild(postView);
|
|
51
|
+
return postView;
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
describe("compose bridge", () => {
|
|
46
55
|
beforeEach(() => {
|
|
47
56
|
document.body.innerHTML = "";
|
|
57
|
+
delete document.documentElement.dataset.sitePathPrefix;
|
|
48
58
|
vi.restoreAllMocks();
|
|
49
59
|
globalThis.sessionStorage.clear();
|
|
50
60
|
});
|
|
@@ -517,7 +527,12 @@ describe("compose bridge", () => {
|
|
|
517
527
|
undefined,
|
|
518
528
|
"pst_root",
|
|
519
529
|
{ kind: "timeline-item", id: "pst_root" },
|
|
520
|
-
{
|
|
530
|
+
{
|
|
531
|
+
restoreDraft: true,
|
|
532
|
+
initialFormat: "quote",
|
|
533
|
+
restoreToast: false,
|
|
534
|
+
restoreMedia: [],
|
|
535
|
+
},
|
|
521
536
|
);
|
|
522
537
|
expect(composeEl.openNew).not.toHaveBeenCalled();
|
|
523
538
|
expect(composeEl.clearLocalDraftFromStorage).not.toHaveBeenCalled();
|
|
@@ -716,6 +731,241 @@ describe("compose bridge", () => {
|
|
|
716
731
|
|
|
717
732
|
await flushBridgeWork();
|
|
718
733
|
|
|
719
|
-
expect(fetchSpy).toHaveBeenCalledTimes(
|
|
734
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("keeps an updated draft on its authenticated preview path", async () => {
|
|
738
|
+
const composeEl = document.createElement(
|
|
739
|
+
"jant-compose-dialog",
|
|
740
|
+
) as ComposeHarness;
|
|
741
|
+
composeEl.refreshCollections = vi.fn(async () => true);
|
|
742
|
+
composeEl.pageMode = false;
|
|
743
|
+
document.body.appendChild(composeEl);
|
|
744
|
+
const previewBar = document.createElement("aside");
|
|
745
|
+
previewBar.dataset.previewStatus = "";
|
|
746
|
+
document.body.appendChild(previewBar);
|
|
747
|
+
document.documentElement.dataset.sitePathPrefix = "/blog";
|
|
748
|
+
globalThis.history.replaceState({}, "", "/blog/preview/draft-post");
|
|
749
|
+
|
|
750
|
+
const assignSpy = vi
|
|
751
|
+
.spyOn(globalThis.location, "assign")
|
|
752
|
+
.mockImplementation(() => {});
|
|
753
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => {
|
|
754
|
+
const raw =
|
|
755
|
+
typeof input === "string"
|
|
756
|
+
? input
|
|
757
|
+
: input instanceof URL
|
|
758
|
+
? input.toString()
|
|
759
|
+
: input.url;
|
|
760
|
+
const url = new URL(raw, "http://localhost");
|
|
761
|
+
|
|
762
|
+
expect(url.pathname).toBe("/api/posts/pst_draft");
|
|
763
|
+
expect(init?.method).toBe("PUT");
|
|
764
|
+
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
765
|
+
status: "draft",
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
return new Response(
|
|
769
|
+
JSON.stringify({
|
|
770
|
+
id: "pst_draft",
|
|
771
|
+
slug: "draft-post",
|
|
772
|
+
status: "draft",
|
|
773
|
+
}),
|
|
774
|
+
{
|
|
775
|
+
status: 200,
|
|
776
|
+
headers: { "Content-Type": "application/json" },
|
|
777
|
+
},
|
|
778
|
+
);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
composeEl.dispatchEvent(
|
|
782
|
+
new CustomEvent("jant:compose-submit-deferred", {
|
|
783
|
+
bubbles: true,
|
|
784
|
+
detail: {
|
|
785
|
+
format: "note",
|
|
786
|
+
title: "Draft post",
|
|
787
|
+
body: "Updated draft body",
|
|
788
|
+
url: "",
|
|
789
|
+
quoteText: "",
|
|
790
|
+
quoteAuthor: "",
|
|
791
|
+
slug: "draft-post",
|
|
792
|
+
status: "draft",
|
|
793
|
+
visibility: "public",
|
|
794
|
+
rating: 0,
|
|
795
|
+
collectionIds: [],
|
|
796
|
+
attachments: [],
|
|
797
|
+
pendingAttachments: [],
|
|
798
|
+
editPostId: "pst_draft",
|
|
799
|
+
draftSourceId: "pst_draft",
|
|
800
|
+
},
|
|
801
|
+
}),
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
await flushBridgeWork();
|
|
805
|
+
|
|
806
|
+
expect(assignSpy).toHaveBeenCalledWith("/blog/preview/draft-post");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it("keeps a draft-panel save in place and signals the updated list", async () => {
|
|
810
|
+
const composeEl = document.createElement(
|
|
811
|
+
"jant-compose-dialog",
|
|
812
|
+
) as ComposeHarness;
|
|
813
|
+
const refreshCollections = vi.fn(async () => true);
|
|
814
|
+
composeEl.refreshCollections = refreshCollections;
|
|
815
|
+
composeEl.pageMode = false;
|
|
816
|
+
document.body.appendChild(composeEl);
|
|
817
|
+
|
|
818
|
+
const assignSpy = vi
|
|
819
|
+
.spyOn(globalThis.location, "assign")
|
|
820
|
+
.mockImplementation(() => {});
|
|
821
|
+
const completeSpy = vi.fn();
|
|
822
|
+
document.addEventListener("jant:compose-submit-complete", completeSpy, {
|
|
823
|
+
once: true,
|
|
824
|
+
});
|
|
825
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
826
|
+
new Response(
|
|
827
|
+
JSON.stringify({
|
|
828
|
+
id: "pst_draft",
|
|
829
|
+
slug: "draft-post",
|
|
830
|
+
status: "draft",
|
|
831
|
+
}),
|
|
832
|
+
{
|
|
833
|
+
status: 200,
|
|
834
|
+
headers: { "Content-Type": "application/json" },
|
|
835
|
+
},
|
|
836
|
+
),
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
composeEl.dispatchEvent(
|
|
840
|
+
new CustomEvent("jant:compose-submit-deferred", {
|
|
841
|
+
bubbles: true,
|
|
842
|
+
detail: {
|
|
843
|
+
format: "note",
|
|
844
|
+
title: "Draft post",
|
|
845
|
+
body: "Updated draft body",
|
|
846
|
+
url: "",
|
|
847
|
+
quoteText: "",
|
|
848
|
+
quoteAuthor: "",
|
|
849
|
+
slug: "draft-post",
|
|
850
|
+
status: "draft",
|
|
851
|
+
visibility: "public",
|
|
852
|
+
rating: 0,
|
|
853
|
+
collectionIds: [],
|
|
854
|
+
attachments: [],
|
|
855
|
+
pendingAttachments: [],
|
|
856
|
+
editPostId: "pst_draft",
|
|
857
|
+
draftSourceId: "pst_draft",
|
|
858
|
+
},
|
|
859
|
+
}),
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
await flushBridgeWork();
|
|
863
|
+
|
|
864
|
+
expect(assignSpy).not.toHaveBeenCalled();
|
|
865
|
+
expect(refreshCollections).toHaveBeenCalled();
|
|
866
|
+
expect(completeSpy).toHaveBeenCalledOnce();
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it("reopens a failed server draft through the draft loader", async () => {
|
|
870
|
+
const composeEl = document.createElement(
|
|
871
|
+
"jant-compose-dialog",
|
|
872
|
+
) as ComposeHarness;
|
|
873
|
+
composeEl.refreshCollections = vi.fn(async () => true);
|
|
874
|
+
composeEl.pageMode = false;
|
|
875
|
+
composeEl.openDraft = vi.fn(async () => {});
|
|
876
|
+
composeEl.openEdit = vi.fn(async () => {});
|
|
877
|
+
document.body.appendChild(composeEl);
|
|
878
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
879
|
+
new Response(JSON.stringify({ error: "Could not save draft." }), {
|
|
880
|
+
status: 500,
|
|
881
|
+
headers: { "Content-Type": "application/json" },
|
|
882
|
+
}),
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
composeEl.dispatchEvent(
|
|
886
|
+
new CustomEvent("jant:compose-submit-deferred", {
|
|
887
|
+
bubbles: true,
|
|
888
|
+
detail: {
|
|
889
|
+
format: "note",
|
|
890
|
+
title: "Draft post",
|
|
891
|
+
body: "Updated draft body",
|
|
892
|
+
url: "",
|
|
893
|
+
quoteText: "",
|
|
894
|
+
quoteAuthor: "",
|
|
895
|
+
status: "draft",
|
|
896
|
+
visibility: "public",
|
|
897
|
+
rating: 0,
|
|
898
|
+
collectionIds: [],
|
|
899
|
+
attachments: [],
|
|
900
|
+
pendingAttachments: [],
|
|
901
|
+
editPostId: "pst_draft",
|
|
902
|
+
draftSourceId: "pst_draft",
|
|
903
|
+
},
|
|
904
|
+
}),
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
await flushBridgeWork();
|
|
908
|
+
|
|
909
|
+
expect(composeEl.openDraft).toHaveBeenCalledWith("pst_draft");
|
|
910
|
+
expect(composeEl.openEdit).not.toHaveBeenCalled();
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it("sends a published draft to its public permalink", async () => {
|
|
914
|
+
const composeEl = document.createElement(
|
|
915
|
+
"jant-compose-dialog",
|
|
916
|
+
) as ComposeHarness;
|
|
917
|
+
composeEl.refreshCollections = vi.fn(async () => true);
|
|
918
|
+
composeEl.pageMode = false;
|
|
919
|
+
document.body.appendChild(composeEl);
|
|
920
|
+
renderPostView("pst_draft");
|
|
921
|
+
const previewBar = document.createElement("aside");
|
|
922
|
+
previewBar.dataset.previewStatus = "";
|
|
923
|
+
document.body.appendChild(previewBar);
|
|
924
|
+
document.documentElement.dataset.sitePathPrefix = "/blog";
|
|
925
|
+
globalThis.history.replaceState({}, "", "/blog/preview/draft-post");
|
|
926
|
+
|
|
927
|
+
const assignSpy = vi
|
|
928
|
+
.spyOn(globalThis.location, "assign")
|
|
929
|
+
.mockImplementation(() => {});
|
|
930
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
931
|
+
new Response(
|
|
932
|
+
JSON.stringify({
|
|
933
|
+
id: "pst_draft",
|
|
934
|
+
slug: "published-post",
|
|
935
|
+
status: "published",
|
|
936
|
+
}),
|
|
937
|
+
{
|
|
938
|
+
status: 200,
|
|
939
|
+
headers: { "Content-Type": "application/json" },
|
|
940
|
+
},
|
|
941
|
+
),
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
composeEl.dispatchEvent(
|
|
945
|
+
new CustomEvent("jant:compose-submit-deferred", {
|
|
946
|
+
bubbles: true,
|
|
947
|
+
detail: {
|
|
948
|
+
format: "note",
|
|
949
|
+
title: "Published post",
|
|
950
|
+
body: "Ready to publish",
|
|
951
|
+
url: "",
|
|
952
|
+
quoteText: "",
|
|
953
|
+
quoteAuthor: "",
|
|
954
|
+
slug: "published-post",
|
|
955
|
+
status: "published",
|
|
956
|
+
visibility: "public",
|
|
957
|
+
rating: 0,
|
|
958
|
+
collectionIds: [],
|
|
959
|
+
attachments: [],
|
|
960
|
+
pendingAttachments: [],
|
|
961
|
+
editPostId: "pst_draft",
|
|
962
|
+
draftSourceId: "pst_draft",
|
|
963
|
+
},
|
|
964
|
+
}),
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
await flushBridgeWork();
|
|
968
|
+
|
|
969
|
+
expect(assignSpy).toHaveBeenCalledWith("/blog/published-post");
|
|
720
970
|
});
|
|
721
971
|
});
|
|
@@ -8,6 +8,7 @@ type ComposeHarness = HTMLElement & {
|
|
|
8
8
|
openNew: (options?: unknown) => Promise<void>;
|
|
9
9
|
openReply: (...args: unknown[]) => Promise<void>;
|
|
10
10
|
openEdit: (id: string) => Promise<void>;
|
|
11
|
+
openDraft: (id: string) => Promise<void>;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
function dispatchShortcut(
|
|
@@ -30,6 +31,7 @@ function createComposeHarness(): ComposeHarness {
|
|
|
30
31
|
composeEl.openNew = vi.fn(async () => {});
|
|
31
32
|
composeEl.openReply = vi.fn(async () => {});
|
|
32
33
|
composeEl.openEdit = vi.fn(async () => {});
|
|
34
|
+
composeEl.openDraft = vi.fn(async () => {});
|
|
33
35
|
document.body.appendChild(composeEl);
|
|
34
36
|
return composeEl;
|
|
35
37
|
}
|
|
@@ -284,6 +286,24 @@ describe("compose shortcuts", () => {
|
|
|
284
286
|
expect(window.location.search).toBe("");
|
|
285
287
|
});
|
|
286
288
|
|
|
289
|
+
it("loads the current draft through the drafts workflow on preview pages", async () => {
|
|
290
|
+
const composeEl = createComposeHarness();
|
|
291
|
+
document.body.setAttribute("data-authenticated", "true");
|
|
292
|
+
const previewBar = document.createElement("aside");
|
|
293
|
+
previewBar.dataset.previewStatus = "";
|
|
294
|
+
document.body.appendChild(previewBar);
|
|
295
|
+
renderThreadDetailPage();
|
|
296
|
+
globalThis.history.pushState({}, "", "/preview/draft-post?edit=1");
|
|
297
|
+
|
|
298
|
+
composeShortcutsTestOnly.openEditFromQueryParam();
|
|
299
|
+
await Promise.resolve();
|
|
300
|
+
|
|
301
|
+
expect(composeEl.openDraft).toHaveBeenCalledWith("post-current");
|
|
302
|
+
expect(composeEl.openEdit).not.toHaveBeenCalled();
|
|
303
|
+
expect(window.location.pathname).toBe("/preview/draft-post");
|
|
304
|
+
expect(window.location.search).toBe("");
|
|
305
|
+
});
|
|
306
|
+
|
|
287
307
|
it("keeps using the hovered post for reply shortcuts in the timeline", () => {
|
|
288
308
|
const composeEl = createComposeHarness();
|
|
289
309
|
const article = document.createElement("article");
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
QUEUED_TOAST_STORAGE_KEY,
|
|
6
6
|
consumeQueuedToast,
|
|
7
7
|
queueToastForNextPage,
|
|
8
|
+
showToast,
|
|
8
9
|
showToastWithAction,
|
|
9
10
|
} from "../toast.js";
|
|
10
11
|
|
|
@@ -46,6 +47,69 @@ describe("toast", () => {
|
|
|
46
47
|
expect(action?.getAttribute("href")).toBe("/published-post");
|
|
47
48
|
});
|
|
48
49
|
|
|
50
|
+
it("renders error toasts without a copy button — text is selectable instead", () => {
|
|
51
|
+
showToast("Publish failed.", "error");
|
|
52
|
+
|
|
53
|
+
const toast = document.querySelector(".toast-error");
|
|
54
|
+
expect(toast).not.toBeNull();
|
|
55
|
+
expect(toast?.querySelector("button")).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("pauses auto-dismiss while hovered and resumes the remaining time", () => {
|
|
59
|
+
vi.useFakeTimers();
|
|
60
|
+
showToast("Publish failed.", "error");
|
|
61
|
+
const toast = document.querySelector<HTMLElement>(".toast");
|
|
62
|
+
if (!toast) throw new Error("toast not rendered");
|
|
63
|
+
|
|
64
|
+
vi.advanceTimersByTime(2000);
|
|
65
|
+
toast.dispatchEvent(new Event("pointerenter"));
|
|
66
|
+
vi.advanceTimersByTime(60_000);
|
|
67
|
+
expect(toast.classList.contains("toast-out")).toBe(false);
|
|
68
|
+
|
|
69
|
+
toast.dispatchEvent(new Event("pointerleave"));
|
|
70
|
+
vi.advanceTimersByTime(999);
|
|
71
|
+
expect(toast.classList.contains("toast-out")).toBe(false);
|
|
72
|
+
vi.advanceTimersByTime(1);
|
|
73
|
+
expect(toast.classList.contains("toast-out")).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("pauses auto-dismiss while focus is inside the toast", () => {
|
|
77
|
+
vi.useFakeTimers();
|
|
78
|
+
showToastWithAction("Collection created.", {
|
|
79
|
+
label: "Edit Navigation",
|
|
80
|
+
href: "/settings/navigation",
|
|
81
|
+
});
|
|
82
|
+
const toast = document.querySelector<HTMLElement>(".toast");
|
|
83
|
+
if (!toast) throw new Error("toast not rendered");
|
|
84
|
+
|
|
85
|
+
toast.dispatchEvent(new Event("focusin"));
|
|
86
|
+
vi.advanceTimersByTime(60_000);
|
|
87
|
+
expect(toast.classList.contains("toast-out")).toBe(false);
|
|
88
|
+
|
|
89
|
+
toast.dispatchEvent(new Event("focusout"));
|
|
90
|
+
vi.advanceTimersByTime(5000);
|
|
91
|
+
expect(toast.classList.contains("toast-out")).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("mounts toasts inside an open modal dialog and restores them on close", async () => {
|
|
95
|
+
const dialog = document.createElement("dialog");
|
|
96
|
+
document.body.appendChild(dialog);
|
|
97
|
+
dialog.showModal();
|
|
98
|
+
|
|
99
|
+
showToast("Publish failed.", "error");
|
|
100
|
+
|
|
101
|
+
const container = document.getElementById("toast-container");
|
|
102
|
+
expect(container?.parentElement).toBe(dialog);
|
|
103
|
+
expect(container?.querySelector(".toast span")?.textContent).toBe(
|
|
104
|
+
"Publish failed.",
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
dialog.close();
|
|
108
|
+
// the dialog close event is dispatched in a queued task
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
110
|
+
expect(container?.parentElement).toBe(document.body);
|
|
111
|
+
});
|
|
112
|
+
|
|
49
113
|
it("keeps action toasts available longer than passive notifications", () => {
|
|
50
114
|
vi.useFakeTimers();
|
|
51
115
|
showToastWithAction("Collection created.", {
|
|
@@ -57,7 +121,7 @@ describe("toast", () => {
|
|
|
57
121
|
vi.advanceTimersByTime(3000);
|
|
58
122
|
expect(toast?.classList.contains("toast-out")).toBe(false);
|
|
59
123
|
|
|
60
|
-
vi.advanceTimersByTime(
|
|
124
|
+
vi.advanceTimersByTime(2000);
|
|
61
125
|
expect(toast?.classList.contains("toast-out")).toBe(true);
|
|
62
126
|
});
|
|
63
127
|
});
|
|
@@ -55,7 +55,6 @@ describe("convertComposeFormat", () => {
|
|
|
55
55
|
"quote",
|
|
56
56
|
fields({
|
|
57
57
|
title: "My thoughts",
|
|
58
|
-
showTitle: true,
|
|
59
58
|
bodyJson: doc(paragraph("body")),
|
|
60
59
|
}),
|
|
61
60
|
);
|
|
@@ -216,7 +215,6 @@ describe("convertComposeFormat", () => {
|
|
|
216
215
|
"link",
|
|
217
216
|
fields({
|
|
218
217
|
title: "Keep me",
|
|
219
|
-
showTitle: true,
|
|
220
218
|
bodyJson: doc(paragraph("body")),
|
|
221
219
|
}),
|
|
222
220
|
);
|
|
@@ -236,7 +234,6 @@ describe("convertComposeFormat", () => {
|
|
|
236
234
|
}),
|
|
237
235
|
);
|
|
238
236
|
expect(result.title).toBe("Keep me");
|
|
239
|
-
expect(result.showTitle).toBe(true);
|
|
240
237
|
expect(result.url).toBe("");
|
|
241
238
|
expect(firstBlock(result)).toEqual({
|
|
242
239
|
type: "paragraph",
|
|
@@ -317,22 +314,6 @@ describe("convertComposeFormat", () => {
|
|
|
317
314
|
expect(result.bodyJson).toBeNull();
|
|
318
315
|
});
|
|
319
316
|
|
|
320
|
-
it("sets showTitle only for a note that ends up with a title", () => {
|
|
321
|
-
const toNote = convertComposeFormat(
|
|
322
|
-
"link",
|
|
323
|
-
"note",
|
|
324
|
-
fields({ title: "T", url: "https://example.com" }),
|
|
325
|
-
);
|
|
326
|
-
expect(toNote.showTitle).toBe(true);
|
|
327
|
-
|
|
328
|
-
const toQuote = convertComposeFormat(
|
|
329
|
-
"note",
|
|
330
|
-
"quote",
|
|
331
|
-
fields({ title: "T" }),
|
|
332
|
-
);
|
|
333
|
-
expect(toQuote.showTitle).toBe(false);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
317
|
it("treats a non-attribution last line as part of the quote text", () => {
|
|
337
318
|
const result = convertComposeFormat(
|
|
338
319
|
"note",
|
|
@@ -11,6 +11,7 @@ interface MockPaletteItem {
|
|
|
11
11
|
title: string;
|
|
12
12
|
path: string;
|
|
13
13
|
type: "post" | "collection" | "system";
|
|
14
|
+
status?: "draft" | "published";
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function mockPaletteApi(items: MockPaletteItem[] = []) {
|
|
@@ -44,6 +45,7 @@ describe("JantCommandPalette slash path mode", () => {
|
|
|
44
45
|
beforeEach(() => {
|
|
45
46
|
vi.restoreAllMocks();
|
|
46
47
|
document.body.innerHTML = "";
|
|
48
|
+
delete document.documentElement.dataset.sitePathPrefix;
|
|
47
49
|
globalThis.localStorage.clear();
|
|
48
50
|
globalThis.history.replaceState({}, "", "/");
|
|
49
51
|
});
|
|
@@ -73,6 +75,7 @@ describe("JantCommandPalette persistent search action", () => {
|
|
|
73
75
|
beforeEach(() => {
|
|
74
76
|
vi.restoreAllMocks();
|
|
75
77
|
document.body.innerHTML = "";
|
|
78
|
+
delete document.documentElement.dataset.sitePathPrefix;
|
|
76
79
|
globalThis.localStorage.clear();
|
|
77
80
|
globalThis.history.replaceState({}, "", "/");
|
|
78
81
|
});
|
|
@@ -187,4 +190,52 @@ describe("JantCommandPalette persistent search action", () => {
|
|
|
187
190
|
|
|
188
191
|
expect(globalThis.location.pathname).toBe("/match-1");
|
|
189
192
|
});
|
|
193
|
+
|
|
194
|
+
it("opens draft post matches through the authenticated preview route", async () => {
|
|
195
|
+
const el = await renderPalette("draft", [
|
|
196
|
+
{
|
|
197
|
+
title: "Draft post",
|
|
198
|
+
path: "draft-post",
|
|
199
|
+
type: "post",
|
|
200
|
+
status: "draft",
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
204
|
+
expect(dialog?.textContent).toContain("Draft · draft-post");
|
|
205
|
+
|
|
206
|
+
dialog?.dispatchEvent(
|
|
207
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
208
|
+
key: "Enter",
|
|
209
|
+
bubbles: true,
|
|
210
|
+
cancelable: true,
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(globalThis.location.pathname).toBe("/preview/draft-post");
|
|
215
|
+
expect(globalThis.location.search).toBe("?edit=1");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("keeps the site path prefix when opening a draft preview", async () => {
|
|
219
|
+
document.documentElement.dataset.sitePathPrefix = "/blog";
|
|
220
|
+
const el = await renderPalette("draft", [
|
|
221
|
+
{
|
|
222
|
+
title: "Draft post",
|
|
223
|
+
path: "draft-post",
|
|
224
|
+
type: "post",
|
|
225
|
+
status: "draft",
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
229
|
+
|
|
230
|
+
dialog?.dispatchEvent(
|
|
231
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
232
|
+
key: "Enter",
|
|
233
|
+
bubbles: true,
|
|
234
|
+
cancelable: true,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
expect(globalThis.location.pathname).toBe("/blog/preview/draft-post");
|
|
239
|
+
expect(globalThis.location.search).toBe("?edit=1");
|
|
240
|
+
});
|
|
190
241
|
});
|