@jant/core 0.3.36 → 0.3.38
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/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4012 -3276
- package/dist/index.js +10285 -5809
- package/package.json +11 -3
- package/src/__tests__/helpers/app.ts +9 -9
- package/src/__tests__/helpers/db.ts +91 -93
- package/src/app.tsx +157 -27
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/client/avatar-upload.ts +3 -2
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
- package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
- package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
- 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-sidebar-types.ts +7 -9
- package/src/client/components/compose-types.ts +101 -4
- package/src/client/components/jant-collection-form.ts +43 -7
- package/src/client/components/jant-collection-sidebar.ts +88 -84
- package/src/client/components/jant-compose-dialog.ts +1655 -219
- package/src/client/components/jant-compose-editor.ts +732 -168
- package/src/client/components/jant-compose-fullscreen.ts +23 -78
- package/src/client/components/jant-media-lightbox.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +24 -284
- package/src/client/components/jant-post-form.ts +89 -9
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/client/components/jant-settings-avatar.ts +3 -3
- package/src/client/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/client/components/nav-manager-types.ts +4 -19
- package/src/client/components/post-form-template.ts +107 -12
- package/src/client/components/post-form-types.ts +11 -4
- package/src/client/compose-bridge.ts +410 -109
- package/src/client/image-processor.ts +26 -8
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/client/post-form-bridge.ts +52 -1
- package/src/client/settings-bridge.ts +0 -12
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/create-editor.ts +46 -0
- package/src/client/tiptap/extensions.ts +5 -0
- package/src/client/tiptap/image-node.ts +2 -8
- package/src/client/tiptap/paste-image.ts +2 -13
- package/src/client/tiptap/slash-commands.ts +173 -63
- package/src/client/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +15 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +5 -2
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -145
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +487 -1217
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +613 -996
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +624 -1007
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/schemas.test.ts +181 -63
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/view.test.ts +141 -66
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +885 -68
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +78 -32
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +12 -3
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +20 -2
- package/src/lib/resolve-config.ts +6 -2
- package/src/lib/schemas.ts +224 -55
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +66 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +74 -34
- package/src/lib/tiptap-render.ts +5 -10
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +190 -29
- package/src/lib/url.ts +31 -0
- package/src/lib/view.ts +204 -37
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +45 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +51 -42
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +43 -39
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +85 -19
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/setup.tsx +26 -33
- package/src/routes/auth/signin.tsx +3 -7
- package/src/routes/compose.tsx +10 -55
- package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +304 -232
- package/src/routes/feed/__tests__/rss.test.ts +27 -28
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +41 -22
- package/src/routes/pages/archive.tsx +175 -39
- package/src/routes/pages/collection.tsx +22 -10
- package/src/routes/pages/collections.tsx +3 -3
- package/src/routes/pages/featured.tsx +28 -4
- package/src/routes/pages/home.tsx +16 -15
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +713 -234
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +687 -154
- package/src/services/search.ts +160 -75
- package/src/styles/components.css +58 -7
- package/src/styles/tokens.css +84 -6
- package/src/styles/ui.css +2964 -457
- package/src/types/bindings.ts +4 -1
- package/src/types/config.ts +12 -4
- package/src/types/constants.ts +15 -3
- package/src/types/entities.ts +74 -35
- package/src/types/operations.ts +11 -24
- package/src/types/props.ts +51 -16
- package/src/types/views.ts +45 -22
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +239 -17
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +3 -1
- package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
- package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
- package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
- package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +3 -57
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +8 -0
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +105 -92
- package/src/ui/pages/ArchivePage.tsx +923 -98
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +181 -37
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +47 -37
- package/src/ui/shared/MediaGallery.tsx +445 -149
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/dist/client/assets/url-8Dj-5CLW.js +0 -1
- package/src/client/media-upload.ts +0 -161
- package/src/client/page-slug-bridge.ts +0 -42
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/index.tsx +0 -109
- package/src/routes/dash/media.tsx +0 -135
- package/src/routes/dash/pages.tsx +0 -245
- package/src/routes/dash/posts.tsx +0 -338
- package/src/routes/dash/redirects.tsx +0 -263
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -216
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/ui/dash/PageForm.tsx +0 -187
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/media/MediaListContent.tsx +0 -206
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -75
- package/src/ui/dash/posts/PostForm.tsx +0 -260
- package/src/ui/layouts/DashLayout.tsx +0 -247
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
|
@@ -54,6 +54,7 @@ const labels: ComposeLabels = {
|
|
|
54
54
|
collection: "Collection",
|
|
55
55
|
searchCollections: "Search...",
|
|
56
56
|
noCollections: "No collections found.",
|
|
57
|
+
emptyCollections: "Create a collection to get started.",
|
|
57
58
|
post: "Post",
|
|
58
59
|
addAlt: "+ ALT",
|
|
59
60
|
addAltTitle: "Add alt text",
|
|
@@ -62,7 +63,54 @@ const labels: ComposeLabels = {
|
|
|
62
63
|
addMore: "Add",
|
|
63
64
|
uploading: "Uploading...",
|
|
64
65
|
published: "Published!",
|
|
65
|
-
|
|
66
|
+
view: "View",
|
|
67
|
+
retryAll: "Tap to retry",
|
|
68
|
+
editPost: "Edit post",
|
|
69
|
+
update: "Done",
|
|
70
|
+
confirmCloseTitle: "Save to drafts?",
|
|
71
|
+
confirmCloseSubtitle: "Save to drafts to edit and post at a later time.",
|
|
72
|
+
confirmCloseSave: "Save",
|
|
73
|
+
confirmCloseCancel: "Cancel",
|
|
74
|
+
confirmCloseDiscard: "Don't save",
|
|
75
|
+
confirmEditTitle: "You have unsaved changes",
|
|
76
|
+
confirmEditSubtitle: "Do you want to publish your changes or discard them?",
|
|
77
|
+
confirmEditPublish: "Publish",
|
|
78
|
+
confirmEditDiscard: "Discard",
|
|
79
|
+
drafts: "Drafts",
|
|
80
|
+
draftsEmpty: "No drafts yet. Save a draft to find it here.",
|
|
81
|
+
deleteDraft: "Delete Draft",
|
|
82
|
+
draftDeleted: "Draft deleted.",
|
|
83
|
+
publishFailedDraft: "Couldn't publish. Saved as draft.",
|
|
84
|
+
uploadFailedDraft: "Some uploads failed. Saved as draft.",
|
|
85
|
+
addCollection: "Add Collection",
|
|
86
|
+
collectionCountLabel: "%name% + %count% more",
|
|
87
|
+
draftRestored: "Draft restored.",
|
|
88
|
+
reply: "Reply",
|
|
89
|
+
publishFeatured: "Post as Featured",
|
|
90
|
+
publishUnlisted: "Post Unlisted",
|
|
91
|
+
publishPrivate: "Post as Private",
|
|
92
|
+
showMore: "Show more",
|
|
93
|
+
showLess: "Show less",
|
|
94
|
+
collectionFormLabels: {
|
|
95
|
+
titleLabel: "Title",
|
|
96
|
+
titlePlaceholder: "My Collection",
|
|
97
|
+
slugLabel: "Slug",
|
|
98
|
+
slugHelp: "URL-safe identifier",
|
|
99
|
+
descriptionLabel: "Description (optional)",
|
|
100
|
+
descriptionPlaceholder: "What's this collection about?",
|
|
101
|
+
removeIcon: "Remove",
|
|
102
|
+
iconsTab: "Icons",
|
|
103
|
+
emojisTab: "Emojis",
|
|
104
|
+
searchIconsPlaceholder: "Search icons...",
|
|
105
|
+
searchEmojisPlaceholder: "Search emojis...",
|
|
106
|
+
sortOrderLabel: "Sort Order",
|
|
107
|
+
sortNewest: "Newest first",
|
|
108
|
+
sortOldest: "Oldest first",
|
|
109
|
+
sortRatingDesc: "Highest rated",
|
|
110
|
+
sortRatingAsc: "Lowest rated",
|
|
111
|
+
submitLabel: "Save",
|
|
112
|
+
cancelLabel: "Cancel",
|
|
113
|
+
},
|
|
66
114
|
};
|
|
67
115
|
|
|
68
116
|
async function createElement(
|
|
@@ -160,11 +208,13 @@ describe("JantComposeEditor", () => {
|
|
|
160
208
|
expect(el._rating).toBe(0);
|
|
161
209
|
});
|
|
162
210
|
|
|
163
|
-
it("dispatches attached panel open event", async () => {
|
|
211
|
+
it("dispatches attached panel open event and creates new item", async () => {
|
|
164
212
|
const el = await createElement("note");
|
|
165
213
|
|
|
166
|
-
const events:
|
|
167
|
-
el.addEventListener("jant:attached-panel-open", (e) =>
|
|
214
|
+
const events: CustomEvent[] = [];
|
|
215
|
+
el.addEventListener("jant:attached-panel-open", (e) =>
|
|
216
|
+
events.push(e as CustomEvent),
|
|
217
|
+
);
|
|
168
218
|
|
|
169
219
|
// Click attached text tool button
|
|
170
220
|
const toolBtns =
|
|
@@ -178,7 +228,9 @@ describe("JantComposeEditor", () => {
|
|
|
178
228
|
await el.updateComplete;
|
|
179
229
|
|
|
180
230
|
expect(events).toHaveLength(1);
|
|
181
|
-
expect(
|
|
231
|
+
expect(events[0].detail.index).toBe(0);
|
|
232
|
+
expect(el._attachedTexts).toHaveLength(1);
|
|
233
|
+
expect(el._attachedTexts[0].bodyJson).toBeNull();
|
|
182
234
|
});
|
|
183
235
|
|
|
184
236
|
it("shows title toggle only in note mode", async () => {
|
|
@@ -202,13 +254,29 @@ describe("JantComposeEditor", () => {
|
|
|
202
254
|
],
|
|
203
255
|
};
|
|
204
256
|
el._rating = 4;
|
|
205
|
-
el.
|
|
257
|
+
el._attachedTexts = [
|
|
258
|
+
{
|
|
259
|
+
clientId: "t1",
|
|
260
|
+
bodyJson: {
|
|
261
|
+
type: "doc",
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "paragraph",
|
|
265
|
+
content: [{ type: "text", text: "Some attached text" }],
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
summary: "Some attached text",
|
|
270
|
+
bodyHtml: "<p>Some attached text</p>",
|
|
271
|
+
},
|
|
272
|
+
];
|
|
206
273
|
|
|
207
274
|
const data = el.getData();
|
|
208
275
|
expect(data.title).toBe("Test Title");
|
|
209
276
|
expect(data.body).toContain("Test Body");
|
|
210
277
|
expect(data.rating).toBe(4);
|
|
211
|
-
expect(data.
|
|
278
|
+
expect(data.attachedTexts).toHaveLength(1);
|
|
279
|
+
expect(data.attachedTexts[0].bodyJson).not.toBeNull();
|
|
212
280
|
expect(data.url).toBe("");
|
|
213
281
|
expect(data.quoteText).toBe("");
|
|
214
282
|
expect(data.quoteAuthor).toBe("");
|
|
@@ -252,8 +320,22 @@ describe("JantComposeEditor", () => {
|
|
|
252
320
|
};
|
|
253
321
|
el._rating = 3;
|
|
254
322
|
el._showRating = true;
|
|
255
|
-
el.
|
|
256
|
-
|
|
323
|
+
el._attachedTexts = [
|
|
324
|
+
{
|
|
325
|
+
clientId: "t1",
|
|
326
|
+
bodyJson: {
|
|
327
|
+
type: "doc",
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: "paragraph",
|
|
331
|
+
content: [{ type: "text", text: "text" }],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
summary: "text",
|
|
336
|
+
bodyHtml: "<p>text</p>",
|
|
337
|
+
},
|
|
338
|
+
];
|
|
257
339
|
|
|
258
340
|
el.reset();
|
|
259
341
|
|
|
@@ -261,18 +343,33 @@ describe("JantComposeEditor", () => {
|
|
|
261
343
|
expect(el._bodyJson).toBeNull();
|
|
262
344
|
expect(el._rating).toBe(0);
|
|
263
345
|
expect(el._showRating).toBe(false);
|
|
264
|
-
expect(el.
|
|
265
|
-
expect(el._showAttachedText).toBe(false);
|
|
346
|
+
expect(el._attachedTexts).toEqual([]);
|
|
266
347
|
});
|
|
267
348
|
|
|
268
|
-
it("shows attached text
|
|
349
|
+
it("shows attached text card in attachment strip", async () => {
|
|
269
350
|
const el = await createElement("note");
|
|
270
|
-
el.
|
|
351
|
+
el._attachedTexts = [
|
|
352
|
+
{
|
|
353
|
+
clientId: "t1",
|
|
354
|
+
bodyJson: {
|
|
355
|
+
type: "doc",
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "paragraph",
|
|
359
|
+
content: [{ type: "text", text: "Some content here" }],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
summary: "Some content here",
|
|
364
|
+
bodyHtml: "<p>Some content here</p>",
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
el._attachmentOrder = ["t1"];
|
|
271
368
|
await el.updateComplete;
|
|
272
369
|
|
|
273
|
-
const
|
|
274
|
-
expect(
|
|
275
|
-
expect(
|
|
370
|
+
const card = el.querySelector(".compose-attachment-text-card");
|
|
371
|
+
expect(card).not.toBeNull();
|
|
372
|
+
expect(card?.textContent).toContain("Some content here");
|
|
276
373
|
});
|
|
277
374
|
|
|
278
375
|
it("media button shows inline add label when attachments are present", async () => {
|
|
@@ -291,9 +388,12 @@ describe("JantComposeEditor", () => {
|
|
|
291
388
|
file,
|
|
292
389
|
previewUrl: URL.createObjectURL(blob),
|
|
293
390
|
status: "done",
|
|
391
|
+
progress: null,
|
|
294
392
|
mediaId: "m1",
|
|
295
393
|
alt: "",
|
|
296
394
|
error: null,
|
|
395
|
+
summary: null,
|
|
396
|
+
chars: null,
|
|
297
397
|
},
|
|
298
398
|
];
|
|
299
399
|
await el.updateComplete;
|
|
@@ -309,4 +409,96 @@ describe("JantComposeEditor", () => {
|
|
|
309
409
|
expect(label).not.toBeNull();
|
|
310
410
|
expect(label?.textContent).toBe("Add");
|
|
311
411
|
});
|
|
412
|
+
|
|
413
|
+
it("moves attachments later with keyboard controls", async () => {
|
|
414
|
+
const el = await createElement("note");
|
|
415
|
+
const blob = new Blob(["fake"], { type: "image/png" });
|
|
416
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
417
|
+
el._attachments = [
|
|
418
|
+
{
|
|
419
|
+
clientId: "a1",
|
|
420
|
+
file,
|
|
421
|
+
previewUrl: URL.createObjectURL(blob),
|
|
422
|
+
status: "done",
|
|
423
|
+
progress: null,
|
|
424
|
+
mediaId: "m1",
|
|
425
|
+
alt: "",
|
|
426
|
+
error: null,
|
|
427
|
+
summary: null,
|
|
428
|
+
chars: null,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
clientId: "a2",
|
|
432
|
+
file,
|
|
433
|
+
previewUrl: URL.createObjectURL(blob),
|
|
434
|
+
status: "done",
|
|
435
|
+
progress: null,
|
|
436
|
+
mediaId: "m2",
|
|
437
|
+
alt: "",
|
|
438
|
+
error: null,
|
|
439
|
+
summary: null,
|
|
440
|
+
chars: null,
|
|
441
|
+
},
|
|
442
|
+
];
|
|
443
|
+
el._attachmentOrder = ["a1", "a2"];
|
|
444
|
+
await el.updateComplete;
|
|
445
|
+
|
|
446
|
+
const attachment = requireElement(
|
|
447
|
+
el.querySelector<HTMLElement>(
|
|
448
|
+
'[data-attachment-id="a1"] [data-attachment-sortable]',
|
|
449
|
+
),
|
|
450
|
+
"expected attachment card",
|
|
451
|
+
);
|
|
452
|
+
attachment.dispatchEvent(
|
|
453
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
454
|
+
key: "ArrowRight",
|
|
455
|
+
bubbles: true,
|
|
456
|
+
}),
|
|
457
|
+
);
|
|
458
|
+
await el.updateComplete;
|
|
459
|
+
|
|
460
|
+
expect(el._attachmentOrder).toEqual(["a2", "a1"]);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("preserves mixed attachment order when populate provides one", async () => {
|
|
464
|
+
const el = await createElement("note");
|
|
465
|
+
|
|
466
|
+
el.populate({
|
|
467
|
+
format: "note",
|
|
468
|
+
media: [
|
|
469
|
+
{
|
|
470
|
+
id: "m1",
|
|
471
|
+
previewUrl: "/a.png",
|
|
472
|
+
mimeType: "image/png",
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
textAttachments: [
|
|
476
|
+
{
|
|
477
|
+
clientId: "t1",
|
|
478
|
+
bodyJson: JSON.stringify({
|
|
479
|
+
type: "doc",
|
|
480
|
+
content: [
|
|
481
|
+
{
|
|
482
|
+
type: "paragraph",
|
|
483
|
+
content: [{ type: "text", text: "Text attachment" }],
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
}),
|
|
487
|
+
bodyHtml: "<p>Text attachment</p>",
|
|
488
|
+
summary: "Text attachment",
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
attachmentOrder: ["t1", "m1"],
|
|
492
|
+
});
|
|
493
|
+
await el.updateComplete;
|
|
494
|
+
|
|
495
|
+
const items = [
|
|
496
|
+
...el.querySelectorAll<HTMLElement>("[data-attachment-id]"),
|
|
497
|
+
].map((item) => item.dataset.attachmentId);
|
|
498
|
+
|
|
499
|
+
expect(items).toHaveLength(2);
|
|
500
|
+
expect(items[0]).toBe(el._attachmentOrder[0]);
|
|
501
|
+
expect(items[1]).toBe(el._attachmentOrder[1]);
|
|
502
|
+
expect(el._attachmentOrder[0]).toBe("t1");
|
|
503
|
+
});
|
|
312
504
|
});
|
|
@@ -18,6 +18,9 @@ const labels: PostFormLabels = {
|
|
|
18
18
|
quoteOption: "Quote",
|
|
19
19
|
titleLabel: "Title",
|
|
20
20
|
titlePlaceholder: "Title...",
|
|
21
|
+
slugLabel: "Slug",
|
|
22
|
+
slugPlaceholder: "auto-generated",
|
|
23
|
+
slugHelp: "Auto-generated from title",
|
|
21
24
|
bodyLabel: "Body",
|
|
22
25
|
bodyPlaceholder: "Body...",
|
|
23
26
|
urlLabel: "URL",
|
|
@@ -32,8 +35,7 @@ const labels: PostFormLabels = {
|
|
|
32
35
|
statusPublished: "Published",
|
|
33
36
|
statusDraft: "Draft",
|
|
34
37
|
visibilityLabel: "Visibility",
|
|
35
|
-
|
|
36
|
-
visibilityFeatured: "Featured",
|
|
38
|
+
visibilityPublic: "Public",
|
|
37
39
|
visibilityUnlisted: "Unlisted",
|
|
38
40
|
pinnedLabel: "Pinned",
|
|
39
41
|
collectionsLabel: "Collections",
|
|
@@ -44,16 +46,18 @@ const labels: PostFormLabels = {
|
|
|
44
46
|
mediaDialogLoading: "Loading...",
|
|
45
47
|
submitSuccessMessage: "Saved!",
|
|
46
48
|
submitErrorMessage: "Failed.",
|
|
49
|
+
draftFallbackMessage: "Couldn't publish. Saved as draft.",
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
const initial: PostFormInitial = {
|
|
50
53
|
format: "note",
|
|
51
54
|
title: "",
|
|
55
|
+
slug: "",
|
|
52
56
|
body: "",
|
|
53
57
|
url: "",
|
|
54
58
|
quoteText: "",
|
|
55
59
|
status: "published",
|
|
56
|
-
visibility: "
|
|
60
|
+
visibility: "public",
|
|
57
61
|
pinned: false,
|
|
58
62
|
rating: 0,
|
|
59
63
|
collectionIds: [],
|
|
@@ -66,7 +70,13 @@ const collections: PostCollectionOption[] = [
|
|
|
66
70
|
];
|
|
67
71
|
|
|
68
72
|
const media: PostMediaItem[] = [
|
|
69
|
-
{
|
|
73
|
+
{
|
|
74
|
+
id: "m1",
|
|
75
|
+
thumbUrl: "https://cdn.example.com/m1.jpg",
|
|
76
|
+
alt: "Media 1",
|
|
77
|
+
mimeType: "image/jpeg",
|
|
78
|
+
originalName: "photo.jpg",
|
|
79
|
+
},
|
|
70
80
|
];
|
|
71
81
|
|
|
72
82
|
async function createElement(
|
|
@@ -77,7 +87,7 @@ async function createElement(
|
|
|
77
87
|
el.initial = { ...initial };
|
|
78
88
|
el.collections = [...collections];
|
|
79
89
|
el.media = [...media];
|
|
80
|
-
el.action = "/
|
|
90
|
+
el.action = "/compose";
|
|
81
91
|
Object.assign(el, overrides);
|
|
82
92
|
document.body.appendChild(el);
|
|
83
93
|
await el.updateComplete;
|
|
@@ -131,12 +141,12 @@ describe("JantPostForm", () => {
|
|
|
131
141
|
};
|
|
132
142
|
el._body = JSON.stringify(el._bodyJson);
|
|
133
143
|
|
|
134
|
-
// Set visibility to "
|
|
144
|
+
// Set visibility to "unlisted" via the select dropdown
|
|
135
145
|
const visibilitySelect =
|
|
136
146
|
el.querySelectorAll<HTMLSelectElement>("select.select")[2]; // [0]=format, [1]=status, [2]=visibility
|
|
137
147
|
expect(visibilitySelect).not.toBeNull();
|
|
138
148
|
if (!visibilitySelect) throw new Error("Visibility select not found");
|
|
139
|
-
visibilitySelect.value = "
|
|
149
|
+
visibilitySelect.value = "unlisted";
|
|
140
150
|
visibilitySelect.dispatchEvent(new Event("change", { bubbles: true }));
|
|
141
151
|
|
|
142
152
|
const checkboxList =
|
|
@@ -162,10 +172,10 @@ describe("JantPostForm", () => {
|
|
|
162
172
|
|
|
163
173
|
expect(detail).not.toBeNull();
|
|
164
174
|
const d = detail as unknown as PostSubmitDetail;
|
|
165
|
-
expect(d.endpoint).toBe("/
|
|
175
|
+
expect(d.endpoint).toBe("/compose");
|
|
166
176
|
expect(d.data.title).toBe("Sample Post");
|
|
167
177
|
expect(d.data.body).toContain("Hello world");
|
|
168
|
-
expect(d.data.visibility).toBe("
|
|
178
|
+
expect(d.data.visibility).toBe("unlisted");
|
|
169
179
|
expect(d.data.collectionIds).toEqual([collections[0].id]);
|
|
170
180
|
expect(d.data.mediaIds).toEqual(["m1"]);
|
|
171
181
|
});
|
|
@@ -179,7 +179,7 @@ describe("JantSettingsAvatar", () => {
|
|
|
179
179
|
|
|
180
180
|
expect(detail).not.toBeNull();
|
|
181
181
|
const d = detail as unknown as SettingsSaveDetail;
|
|
182
|
-
expect(d.endpoint).toBe("/
|
|
182
|
+
expect(d.endpoint).toBe("/settings/avatar/display");
|
|
183
183
|
expect(d.section).toBe("avatar-display");
|
|
184
184
|
expect(d.data.showHeaderAvatar).toBe("true");
|
|
185
185
|
});
|
|
@@ -201,7 +201,7 @@ describe("JantSettingsAvatar", () => {
|
|
|
201
201
|
|
|
202
202
|
expect(detail).not.toBeNull();
|
|
203
203
|
const d = detail as unknown as AvatarRemoveDetail;
|
|
204
|
-
expect(d.endpoint).toBe("/
|
|
204
|
+
expect(d.endpoint).toBe("/settings/avatar/remove");
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
it("saved() resets dirty state", async () => {
|
|
@@ -188,7 +188,7 @@ describe("JantSettingsGeneral", () => {
|
|
|
188
188
|
|
|
189
189
|
expect(detail).not.toBeNull();
|
|
190
190
|
const d = detail as unknown as SettingsSaveDetail;
|
|
191
|
-
expect(d.endpoint).toBe("/
|
|
191
|
+
expect(d.endpoint).toBe("/settings/general");
|
|
192
192
|
expect(d.section).toBe("general");
|
|
193
193
|
expect(d.data.siteName).toBe("New Name");
|
|
194
194
|
});
|
|
@@ -255,7 +255,7 @@ describe("JantSettingsGeneral", () => {
|
|
|
255
255
|
|
|
256
256
|
expect(detail).not.toBeNull();
|
|
257
257
|
const d = detail as unknown as SettingsSaveDetail;
|
|
258
|
-
expect(d.endpoint).toBe("/
|
|
258
|
+
expect(d.endpoint).toBe("/settings/general");
|
|
259
259
|
expect(d.section).toBe("general");
|
|
260
260
|
expect(d.data.siteFooter).toBe("New footer");
|
|
261
261
|
});
|
|
@@ -286,7 +286,7 @@ describe("JantSettingsGeneral", () => {
|
|
|
286
286
|
|
|
287
287
|
expect(detail).not.toBeNull();
|
|
288
288
|
const d = detail as unknown as SettingsSaveDetail;
|
|
289
|
-
expect(d.endpoint).toBe("/
|
|
289
|
+
expect(d.endpoint).toBe("/settings/general/seo");
|
|
290
290
|
expect(d.section).toBe("seo");
|
|
291
291
|
});
|
|
292
292
|
|
|
@@ -25,21 +25,19 @@ export interface CollectionSidebarLabels {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface SidebarCollection {
|
|
28
|
-
id:
|
|
28
|
+
id: string;
|
|
29
29
|
slug: string;
|
|
30
30
|
title: string;
|
|
31
31
|
description: string | null;
|
|
32
32
|
icon: string | null;
|
|
33
33
|
sortOrder: string;
|
|
34
|
-
position: number;
|
|
35
34
|
postCount: number;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
export interface
|
|
39
|
-
id:
|
|
40
|
-
|
|
37
|
+
export interface ClientSidebarItem {
|
|
38
|
+
id: string;
|
|
39
|
+
type: "collection" | "divider";
|
|
40
|
+
collectionId: string | null;
|
|
41
|
+
position: string;
|
|
42
|
+
collection?: SidebarCollection;
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
export type SidebarItem =
|
|
44
|
-
| { kind: "collection"; data: SidebarCollection }
|
|
45
|
-
| { kind: "divider"; data: SidebarDivider };
|
|
@@ -5,16 +5,74 @@
|
|
|
5
5
|
* Lit Web Components, and the compose bridge script.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { JSONContent } from "@tiptap/core";
|
|
9
|
+
import type { CollectionFormLabels } from "./collection-types.js";
|
|
10
|
+
|
|
8
11
|
export type ComposeFormat = "note" | "link" | "quote";
|
|
9
12
|
|
|
10
13
|
export interface ComposeAttachment {
|
|
11
14
|
clientId: string;
|
|
12
15
|
file: File;
|
|
13
16
|
previewUrl: string;
|
|
14
|
-
status: "pending" | "uploading" | "done" | "error";
|
|
17
|
+
status: "pending" | "processing" | "uploading" | "done" | "error";
|
|
18
|
+
progress: number | null;
|
|
15
19
|
mediaId: string | null;
|
|
16
20
|
alt: string;
|
|
17
21
|
error: string | null;
|
|
22
|
+
/** Text content preview for text files (first ~100 chars) */
|
|
23
|
+
summary: string | null;
|
|
24
|
+
/** Character count of text content */
|
|
25
|
+
chars: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AttachedTextItem {
|
|
29
|
+
clientId: string;
|
|
30
|
+
bodyJson: JSONContent | null;
|
|
31
|
+
/** Pre-rendered HTML from TipTap, used for preview on the public page */
|
|
32
|
+
bodyHtml: string;
|
|
33
|
+
summary: string;
|
|
34
|
+
/** Set for already-persisted text media items (edit mode) */
|
|
35
|
+
mediaId?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DraftItem {
|
|
39
|
+
id: string;
|
|
40
|
+
format: ComposeFormat;
|
|
41
|
+
title: string | null;
|
|
42
|
+
bodyText: string | null;
|
|
43
|
+
bodyHtml: string | null;
|
|
44
|
+
url: string | null;
|
|
45
|
+
quoteText: string | null;
|
|
46
|
+
replyToId: string | null;
|
|
47
|
+
updatedAt: number;
|
|
48
|
+
mediaAttachments: {
|
|
49
|
+
id: string;
|
|
50
|
+
previewUrl: string;
|
|
51
|
+
alt: string | null;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
}[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface LocalDraft {
|
|
57
|
+
format: ComposeFormat;
|
|
58
|
+
title: string;
|
|
59
|
+
bodyJson: JSONContent | null;
|
|
60
|
+
url: string;
|
|
61
|
+
quoteText: string;
|
|
62
|
+
quoteAuthor: string;
|
|
63
|
+
rating: number;
|
|
64
|
+
showTitle: boolean;
|
|
65
|
+
showRating: boolean;
|
|
66
|
+
collectionIds: string[];
|
|
67
|
+
replyToId: string | null;
|
|
68
|
+
attachedTexts: Array<{
|
|
69
|
+
clientId: string;
|
|
70
|
+
bodyJson: JSONContent | null;
|
|
71
|
+
bodyHtml: string;
|
|
72
|
+
summary: string;
|
|
73
|
+
}>;
|
|
74
|
+
attachmentOrder?: string[];
|
|
75
|
+
savedAt: number;
|
|
18
76
|
}
|
|
19
77
|
|
|
20
78
|
export interface ComposeLabels {
|
|
@@ -44,6 +102,7 @@ export interface ComposeLabels {
|
|
|
44
102
|
collection: string;
|
|
45
103
|
searchCollections: string;
|
|
46
104
|
noCollections: string;
|
|
105
|
+
emptyCollections: string;
|
|
47
106
|
post: string;
|
|
48
107
|
addAlt: string;
|
|
49
108
|
addAltTitle: string;
|
|
@@ -52,9 +111,39 @@ export interface ComposeLabels {
|
|
|
52
111
|
addMore: string;
|
|
53
112
|
uploading: string;
|
|
54
113
|
published: string;
|
|
114
|
+
view: string;
|
|
55
115
|
retryAll: string;
|
|
116
|
+
editPost: string;
|
|
117
|
+
update: string;
|
|
118
|
+
confirmCloseTitle: string;
|
|
119
|
+
confirmCloseSubtitle: string;
|
|
120
|
+
confirmCloseSave: string;
|
|
121
|
+
confirmCloseCancel: string;
|
|
122
|
+
confirmCloseDiscard: string;
|
|
123
|
+
confirmEditTitle: string;
|
|
124
|
+
confirmEditSubtitle: string;
|
|
125
|
+
confirmEditPublish: string;
|
|
126
|
+
confirmEditDiscard: string;
|
|
127
|
+
drafts: string;
|
|
128
|
+
draftsEmpty: string;
|
|
129
|
+
deleteDraft: string;
|
|
130
|
+
draftDeleted: string;
|
|
131
|
+
publishFailedDraft: string;
|
|
132
|
+
uploadFailedDraft: string;
|
|
133
|
+
addCollection: string;
|
|
134
|
+
collectionCountLabel: string;
|
|
135
|
+
draftRestored: string;
|
|
136
|
+
reply: string;
|
|
137
|
+
publishFeatured: string;
|
|
138
|
+
publishUnlisted: string;
|
|
139
|
+
publishPrivate: string;
|
|
140
|
+
showMore: string;
|
|
141
|
+
showLess: string;
|
|
142
|
+
collectionFormLabels: CollectionFormLabels;
|
|
56
143
|
}
|
|
57
144
|
|
|
145
|
+
export type ComposeVisibility = "public" | "unlisted" | "private";
|
|
146
|
+
|
|
58
147
|
export interface ComposeSubmitDetail {
|
|
59
148
|
format: ComposeFormat;
|
|
60
149
|
title: string;
|
|
@@ -63,15 +152,23 @@ export interface ComposeSubmitDetail {
|
|
|
63
152
|
quoteText: string;
|
|
64
153
|
quoteAuthor: string;
|
|
65
154
|
status: "published" | "draft";
|
|
155
|
+
visibility: ComposeVisibility;
|
|
66
156
|
rating: number;
|
|
67
|
-
collectionIds:
|
|
157
|
+
collectionIds: string[];
|
|
68
158
|
mediaIds: string[];
|
|
69
159
|
mediaAlts: Record<string, string>;
|
|
70
|
-
|
|
160
|
+
attachedTexts: AttachedTextItem[];
|
|
161
|
+
/** Interleaved order of media clientIds + text clientIds */
|
|
162
|
+
attachmentOrder: string[];
|
|
163
|
+
/** clientId → mediaId for already-uploaded file attachments (captured at submit time) */
|
|
164
|
+
mediaClientMap: Record<string, string>;
|
|
165
|
+
featured?: boolean;
|
|
166
|
+
editPostId?: string;
|
|
167
|
+
replyToId?: string;
|
|
71
168
|
}
|
|
72
169
|
|
|
73
170
|
export interface ComposeCollection {
|
|
74
|
-
id:
|
|
171
|
+
id: string;
|
|
75
172
|
title: string;
|
|
76
173
|
iconHtml: string;
|
|
77
174
|
}
|