@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
|
@@ -87,7 +87,7 @@ export class JantSettingsAvatar extends LitElement {
|
|
|
87
87
|
new CustomEvent("jant:settings-save", {
|
|
88
88
|
bubbles: true,
|
|
89
89
|
detail: {
|
|
90
|
-
endpoint: "/
|
|
90
|
+
endpoint: "/settings/avatar/display",
|
|
91
91
|
data: { showHeaderAvatar: this._showInHeader ? "true" : "" },
|
|
92
92
|
section: "avatar-display",
|
|
93
93
|
},
|
|
@@ -101,7 +101,7 @@ export class JantSettingsAvatar extends LitElement {
|
|
|
101
101
|
this.dispatchEvent(
|
|
102
102
|
new CustomEvent("jant:avatar-remove", {
|
|
103
103
|
bubbles: true,
|
|
104
|
-
detail: { endpoint: "/
|
|
104
|
+
detail: { endpoint: "/settings/avatar/remove" },
|
|
105
105
|
}),
|
|
106
106
|
);
|
|
107
107
|
}
|
|
@@ -146,7 +146,7 @@ export class JantSettingsAvatar extends LitElement {
|
|
|
146
146
|
${this._renderPreview()}
|
|
147
147
|
<div class="flex flex-col gap-2">
|
|
148
148
|
<form
|
|
149
|
-
action="/
|
|
149
|
+
action="/settings/avatar"
|
|
150
150
|
method="post"
|
|
151
151
|
enctype="multipart/form-data"
|
|
152
152
|
class="inline"
|
|
@@ -170,7 +170,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
170
170
|
new CustomEvent("jant:settings-save", {
|
|
171
171
|
bubbles: true,
|
|
172
172
|
detail: {
|
|
173
|
-
endpoint: "/
|
|
173
|
+
endpoint: "/settings/general",
|
|
174
174
|
data: {
|
|
175
175
|
siteName: this._siteName,
|
|
176
176
|
siteDescription: this._siteDescription,
|
|
@@ -203,7 +203,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
203
203
|
new CustomEvent("jant:settings-save", {
|
|
204
204
|
bubbles: true,
|
|
205
205
|
detail: {
|
|
206
|
-
endpoint: "/
|
|
206
|
+
endpoint: "/settings/general/seo",
|
|
207
207
|
data: { noindex: this._noindex ? "" : "true" },
|
|
208
208
|
section: "seo",
|
|
209
209
|
},
|
|
@@ -211,6 +211,24 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
211
211
|
);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** Submit on Enter from non-textarea fields */
|
|
215
|
+
private _onKeydown(
|
|
216
|
+
e: globalThis.KeyboardEvent,
|
|
217
|
+
save: () => void,
|
|
218
|
+
dirty: boolean,
|
|
219
|
+
loading: boolean,
|
|
220
|
+
) {
|
|
221
|
+
if (
|
|
222
|
+
e.key === "Enter" &&
|
|
223
|
+
!loading &&
|
|
224
|
+
dirty &&
|
|
225
|
+
!(e.target instanceof HTMLTextAreaElement)
|
|
226
|
+
) {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
save();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
// ── Render helpers ────────────────────────────────────────────────
|
|
215
233
|
|
|
216
234
|
private _renderActions(
|
|
@@ -258,7 +276,15 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
258
276
|
|
|
259
277
|
private _renderGeneralForm() {
|
|
260
278
|
return html`
|
|
261
|
-
<div
|
|
279
|
+
<div
|
|
280
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
281
|
+
this._onKeydown(
|
|
282
|
+
e,
|
|
283
|
+
() => this._saveGeneral(),
|
|
284
|
+
this._generalDirty,
|
|
285
|
+
this._generalLoading,
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
262
288
|
<h2 class="text-lg font-semibold mb-4">${this.labels.general}</h2>
|
|
263
289
|
<div class="flex flex-col gap-4">
|
|
264
290
|
<div class="field">
|
|
@@ -366,7 +392,15 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
366
392
|
|
|
367
393
|
private _renderSeoForm() {
|
|
368
394
|
return html`
|
|
369
|
-
<div
|
|
395
|
+
<div
|
|
396
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
397
|
+
this._onKeydown(
|
|
398
|
+
e,
|
|
399
|
+
() => this._saveSeo(),
|
|
400
|
+
this._seoDirty,
|
|
401
|
+
this._seoLoading,
|
|
402
|
+
)}
|
|
403
|
+
>
|
|
370
404
|
<h2 class="text-lg font-semibold mb-4">${this.labels.seo}</h2>
|
|
371
405
|
<div>
|
|
372
406
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Preview Dialog
|
|
3
|
+
*
|
|
4
|
+
* Displays attached text content (TipTap-authored) in a modal dialog.
|
|
5
|
+
* Intercepts clicks on [data-text-preview-url] buttons, fetches the
|
|
6
|
+
* stored { json, html } envelope from the URL, and renders the HTML
|
|
7
|
+
* in a native <dialog>.
|
|
8
|
+
*
|
|
9
|
+
* Light DOM only — BaseCoat and Tailwind classes apply directly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { LitElement, html, nothing } from "lit";
|
|
13
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
14
|
+
import { showToast } from "../toast.js";
|
|
15
|
+
import { jsonToMarkdown } from "../tiptap/create-editor.js";
|
|
16
|
+
|
|
17
|
+
export class JantTextPreview extends LitElement {
|
|
18
|
+
static properties = {
|
|
19
|
+
_open: { state: true },
|
|
20
|
+
_html: { state: true },
|
|
21
|
+
_loading: { state: true },
|
|
22
|
+
_copied: { state: true },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
declare _open: boolean;
|
|
26
|
+
declare _html: string;
|
|
27
|
+
declare _loading: boolean;
|
|
28
|
+
declare _copied: boolean;
|
|
29
|
+
/** Raw text for the copy button (markdown / plain text source) */
|
|
30
|
+
#rawText = "";
|
|
31
|
+
|
|
32
|
+
createRenderRoot() {
|
|
33
|
+
this.innerHTML = "";
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
this._open = false;
|
|
40
|
+
this._html = "";
|
|
41
|
+
this._loading = false;
|
|
42
|
+
this._copied = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
connectedCallback() {
|
|
46
|
+
super.connectedCallback();
|
|
47
|
+
document.addEventListener("click", this.#handleDocumentClick);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
disconnectedCallback() {
|
|
51
|
+
super.disconnectedCallback();
|
|
52
|
+
document.removeEventListener("click", this.#handleDocumentClick);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#handleDocumentClick = (e: Event) => {
|
|
56
|
+
const target = e.target as HTMLElement;
|
|
57
|
+
const btn = target.closest<HTMLButtonElement>("[data-text-preview-id]");
|
|
58
|
+
if (!btn) return;
|
|
59
|
+
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
const mediaId = btn.dataset.textPreviewId;
|
|
62
|
+
if (mediaId) this.#openPreview(mediaId);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
async #openPreview(mediaId: string) {
|
|
66
|
+
this._loading = true;
|
|
67
|
+
this._open = true;
|
|
68
|
+
|
|
69
|
+
document.body.style.overflow = "hidden";
|
|
70
|
+
|
|
71
|
+
await this.updateComplete;
|
|
72
|
+
this.querySelector<HTMLDialogElement>(".text-preview-dialog")?.showModal();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`/api/media/${mediaId}/content`);
|
|
76
|
+
if (!res.ok) throw new Error("Fetch failed");
|
|
77
|
+
|
|
78
|
+
const raw = await res.text();
|
|
79
|
+
|
|
80
|
+
// Try parsing as { json, html } envelope (TipTap rich text)
|
|
81
|
+
try {
|
|
82
|
+
const envelope = JSON.parse(raw) as {
|
|
83
|
+
json?: import("@tiptap/core").JSONContent;
|
|
84
|
+
html?: string;
|
|
85
|
+
};
|
|
86
|
+
this._html = envelope.html || "";
|
|
87
|
+
// Serialize JSON → markdown via headless TipTap editor
|
|
88
|
+
this.#rawText = envelope.json ? jsonToMarkdown(envelope.json) : "";
|
|
89
|
+
} catch {
|
|
90
|
+
// Not JSON — raw markdown / plain text, copy as-is
|
|
91
|
+
this.#rawText = raw;
|
|
92
|
+
this._html = `<pre>${raw.replace(/</g, "<")}</pre>`;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
this._html = "<p>Failed to load content.</p>";
|
|
96
|
+
this.#rawText = "";
|
|
97
|
+
} finally {
|
|
98
|
+
this._loading = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#close() {
|
|
103
|
+
this.querySelector<HTMLDialogElement>(".text-preview-dialog")?.close();
|
|
104
|
+
document.body.style.overflow = "";
|
|
105
|
+
this._open = false;
|
|
106
|
+
this._html = "";
|
|
107
|
+
this.#rawText = "";
|
|
108
|
+
this._copied = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async #copy() {
|
|
112
|
+
if (!this.#rawText) return;
|
|
113
|
+
try {
|
|
114
|
+
await globalThis.navigator.clipboard.writeText(this.#rawText);
|
|
115
|
+
this._copied = true;
|
|
116
|
+
showToast("Copied.");
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
this._copied = false;
|
|
119
|
+
}, 2000);
|
|
120
|
+
} catch {
|
|
121
|
+
showToast("Could not copy.", "error");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#handleKeydown = (e: globalThis.KeyboardEvent) => {
|
|
126
|
+
if (e.key === "Escape") {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
this.#close();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
render() {
|
|
134
|
+
if (!this._open) return nothing;
|
|
135
|
+
|
|
136
|
+
return html`
|
|
137
|
+
<dialog
|
|
138
|
+
class="text-preview-dialog"
|
|
139
|
+
@cancel=${(e: Event) => {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
this.#close();
|
|
142
|
+
}}
|
|
143
|
+
@keydown=${this.#handleKeydown}
|
|
144
|
+
@click=${(e: Event) => {
|
|
145
|
+
// Close on backdrop click
|
|
146
|
+
if ((e.target as HTMLElement).tagName === "DIALOG") {
|
|
147
|
+
this.#close();
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<div class="text-preview-content">
|
|
152
|
+
<div class="text-preview-toolbar">
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
class="text-preview-btn"
|
|
156
|
+
@click=${() => this.#copy()}
|
|
157
|
+
?disabled=${this._loading || !this.#rawText}
|
|
158
|
+
title="Copy"
|
|
159
|
+
>
|
|
160
|
+
${this._copied
|
|
161
|
+
? html`<svg
|
|
162
|
+
width="16"
|
|
163
|
+
height="16"
|
|
164
|
+
viewBox="0 0 24 24"
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="currentColor"
|
|
167
|
+
stroke-width="2"
|
|
168
|
+
stroke-linecap="round"
|
|
169
|
+
stroke-linejoin="round"
|
|
170
|
+
>
|
|
171
|
+
<path d="M20 6 9 17l-5-5" />
|
|
172
|
+
</svg>`
|
|
173
|
+
: html`<svg
|
|
174
|
+
width="16"
|
|
175
|
+
height="16"
|
|
176
|
+
viewBox="0 0 24 24"
|
|
177
|
+
fill="none"
|
|
178
|
+
stroke="currentColor"
|
|
179
|
+
stroke-width="2"
|
|
180
|
+
stroke-linecap="round"
|
|
181
|
+
stroke-linejoin="round"
|
|
182
|
+
>
|
|
183
|
+
<rect width="14" height="14" x="8" y="8" rx="2" />
|
|
184
|
+
<path
|
|
185
|
+
d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
|
|
186
|
+
/>
|
|
187
|
+
</svg>`}
|
|
188
|
+
</button>
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
class="text-preview-btn"
|
|
192
|
+
@click=${() => this.#close()}
|
|
193
|
+
title="Close"
|
|
194
|
+
>
|
|
195
|
+
<svg
|
|
196
|
+
width="16"
|
|
197
|
+
height="16"
|
|
198
|
+
viewBox="0 0 24 24"
|
|
199
|
+
fill="none"
|
|
200
|
+
stroke="currentColor"
|
|
201
|
+
stroke-width="2"
|
|
202
|
+
stroke-linecap="round"
|
|
203
|
+
stroke-linejoin="round"
|
|
204
|
+
>
|
|
205
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
206
|
+
</svg>
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
${this._loading
|
|
210
|
+
? html`<div class="text-preview-loading">
|
|
211
|
+
<svg
|
|
212
|
+
class="animate-spin size-5"
|
|
213
|
+
viewBox="0 0 24 24"
|
|
214
|
+
fill="none"
|
|
215
|
+
stroke="currentColor"
|
|
216
|
+
stroke-width="2"
|
|
217
|
+
stroke-linecap="round"
|
|
218
|
+
stroke-linejoin="round"
|
|
219
|
+
>
|
|
220
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
221
|
+
</svg>
|
|
222
|
+
</div>`
|
|
223
|
+
: html`<div class="text-preview-body prose">
|
|
224
|
+
${unsafeHTML(this._html)}
|
|
225
|
+
</div>`}
|
|
226
|
+
</div>
|
|
227
|
+
</dialog>
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
customElements.define("jant-text-preview", JantTextPreview);
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export interface NavManagerItem {
|
|
6
|
-
id:
|
|
7
|
-
type: "
|
|
6
|
+
id: string;
|
|
7
|
+
type: "link" | "system";
|
|
8
8
|
label: string;
|
|
9
9
|
url: string;
|
|
10
|
-
pageId: number | null;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export interface SystemNavConfig {
|
|
@@ -17,17 +16,10 @@ export interface SystemNavConfig {
|
|
|
17
16
|
description: string;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export interface AvailablePage {
|
|
21
|
-
id: number;
|
|
22
|
-
title: string;
|
|
23
|
-
slug: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
export interface NavManagerLabels {
|
|
27
20
|
preview: string;
|
|
28
21
|
navigationItems: string;
|
|
29
22
|
emptyState: string;
|
|
30
|
-
page: string;
|
|
31
23
|
link: string;
|
|
32
24
|
system: string;
|
|
33
25
|
toggleEdit: string;
|
|
@@ -35,7 +27,6 @@ export interface NavManagerLabels {
|
|
|
35
27
|
url: string;
|
|
36
28
|
save: string;
|
|
37
29
|
delete: string;
|
|
38
|
-
editPage: string;
|
|
39
30
|
remove: string;
|
|
40
31
|
orderSaved: string;
|
|
41
32
|
labelRequired: string;
|
|
@@ -43,15 +34,9 @@ export interface NavManagerLabels {
|
|
|
43
34
|
deleteFailed: string;
|
|
44
35
|
systemLinks: string;
|
|
45
36
|
systemLinksDescription: string;
|
|
46
|
-
addPageToNavigation: string;
|
|
47
37
|
addCustomLinkToNavigation: string;
|
|
48
|
-
choosePage: string;
|
|
49
|
-
searchPages: string;
|
|
50
|
-
noPagesFound: string;
|
|
51
38
|
addLink: string;
|
|
52
39
|
addLinkDescription: string;
|
|
53
|
-
allPagesInNav: string;
|
|
54
|
-
createPage: string;
|
|
55
40
|
urlPlaceholder: string;
|
|
56
41
|
labelAndUrlRequired: string;
|
|
57
42
|
maxVisibleLinks: string;
|
|
@@ -65,11 +50,11 @@ export interface NavManagerLabels {
|
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
export interface NavManagerUpdateDetail {
|
|
68
|
-
id:
|
|
53
|
+
id: string;
|
|
69
54
|
label: string;
|
|
70
55
|
url?: string;
|
|
71
56
|
}
|
|
72
57
|
|
|
73
58
|
export interface NavManagerDeleteDetail {
|
|
74
|
-
id:
|
|
59
|
+
id: string;
|
|
75
60
|
}
|
|
@@ -1,6 +1,91 @@
|
|
|
1
1
|
import { html, nothing } from "lit";
|
|
2
|
+
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
|
|
2
3
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
3
4
|
import type { JantPostForm } from "./jant-post-form.js";
|
|
5
|
+
import type { PostMediaItem } from "./post-form-types.js";
|
|
6
|
+
import { getMediaCategory } from "../../lib/upload.js";
|
|
7
|
+
|
|
8
|
+
function renderFileIcon(mimeType: string) {
|
|
9
|
+
const doc = `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`;
|
|
10
|
+
|
|
11
|
+
let inner: string;
|
|
12
|
+
if (mimeType === "application/pdf") {
|
|
13
|
+
inner = `<text x="12" y="16.5" text-anchor="middle" fill="currentColor" stroke="none" font-size="6" font-weight="700" font-family="system-ui, sans-serif">PDF</text>`;
|
|
14
|
+
} else if (mimeType === "text/markdown") {
|
|
15
|
+
inner = `<text x="12" y="16.5" text-anchor="middle" fill="currentColor" stroke="none" font-size="10" font-weight="700" font-family="system-ui, sans-serif">#</text>`;
|
|
16
|
+
} else if (mimeType === "text/csv") {
|
|
17
|
+
inner = `<line x1="8" y1="12" x2="16" y2="12"/><line x1="8" y1="15" x2="16" y2="15"/><line x1="8" y1="18" x2="16" y2="18"/><line x1="10.7" y1="12" x2="10.7" y2="18"/><line x1="13.3" y1="12" x2="13.3" y2="18"/>`;
|
|
18
|
+
} else if (getMediaCategory(mimeType) === "archive") {
|
|
19
|
+
inner = `<line x1="12" y1="10" x2="12" y2="11.5"/><line x1="12" y1="13" x2="12" y2="14.5"/><line x1="12" y1="16" x2="12" y2="17.5"/>`;
|
|
20
|
+
} else if (mimeType.startsWith("audio/")) {
|
|
21
|
+
return html`<svg
|
|
22
|
+
width="24"
|
|
23
|
+
height="24"
|
|
24
|
+
viewBox="0 0 24 24"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="currentColor"
|
|
27
|
+
stroke-width="1.5"
|
|
28
|
+
stroke-linecap="round"
|
|
29
|
+
stroke-linejoin="round"
|
|
30
|
+
>
|
|
31
|
+
${unsafeSVG(
|
|
32
|
+
`<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>`,
|
|
33
|
+
)}
|
|
34
|
+
</svg>`;
|
|
35
|
+
} else if (mimeType.startsWith("video/")) {
|
|
36
|
+
return html`<svg
|
|
37
|
+
width="24"
|
|
38
|
+
height="24"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
stroke-width="1.5"
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
45
|
+
>
|
|
46
|
+
${unsafeSVG(
|
|
47
|
+
`<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>`,
|
|
48
|
+
)}
|
|
49
|
+
</svg>`;
|
|
50
|
+
} else {
|
|
51
|
+
inner = `<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/>`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return html`<svg
|
|
55
|
+
width="24"
|
|
56
|
+
height="24"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
stroke-width="1.5"
|
|
61
|
+
stroke-linecap="round"
|
|
62
|
+
stroke-linejoin="round"
|
|
63
|
+
>
|
|
64
|
+
${unsafeSVG(doc + inner)}
|
|
65
|
+
</svg>`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderMediaThumb(item: PostMediaItem) {
|
|
69
|
+
const category = getMediaCategory(item.mimeType);
|
|
70
|
+
|
|
71
|
+
if (category === "image") {
|
|
72
|
+
return html`<img
|
|
73
|
+
src=${item.thumbUrl}
|
|
74
|
+
alt=${item.alt}
|
|
75
|
+
class="w-full h-full object-cover rounded-lg border"
|
|
76
|
+
loading="lazy"
|
|
77
|
+
/>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return html`<div
|
|
81
|
+
class="w-full h-full rounded-lg border bg-muted flex flex-col items-center justify-center gap-1 p-1 text-muted-foreground"
|
|
82
|
+
>
|
|
83
|
+
${renderFileIcon(item.mimeType)}
|
|
84
|
+
<span class="text-[10px] leading-tight text-center truncate w-full px-1"
|
|
85
|
+
>${item.originalName}</span
|
|
86
|
+
>
|
|
87
|
+
</div>`;
|
|
88
|
+
}
|
|
4
89
|
|
|
5
90
|
function renderMediaList(component: JantPostForm) {
|
|
6
91
|
const { media, labels, _mediaIds } = component;
|
|
@@ -32,12 +117,7 @@ function renderMediaList(component: JantPostForm) {
|
|
|
32
117
|
}
|
|
33
118
|
|
|
34
119
|
return html`<div class="relative group aspect-square" data-media-id=${id}>
|
|
35
|
-
|
|
36
|
-
src=${item.thumbUrl}
|
|
37
|
-
alt=${item.alt}
|
|
38
|
-
class="w-full h-full object-cover rounded-lg border"
|
|
39
|
-
loading="lazy"
|
|
40
|
-
/>
|
|
120
|
+
${renderMediaThumb(item)}
|
|
41
121
|
<button
|
|
42
122
|
type="button"
|
|
43
123
|
class="absolute top-1 right-1 w-5 h-5 flex items-center justify-center bg-black/60 text-white rounded-full text-xs opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
|
@@ -107,10 +187,28 @@ export function renderPostForm(component: JantPostForm) {
|
|
|
107
187
|
class="input"
|
|
108
188
|
placeholder=${component.labels.titlePlaceholder}
|
|
109
189
|
.value=${component._title}
|
|
110
|
-
@input=${(e: Event) => component.
|
|
190
|
+
@input=${(e: Event) => component.handleTitleInput(e)}
|
|
111
191
|
/>
|
|
112
192
|
</div>
|
|
113
193
|
|
|
194
|
+
<div class="field">
|
|
195
|
+
<label class="label">${component.labels.slugLabel}</label>
|
|
196
|
+
<input
|
|
197
|
+
type="text"
|
|
198
|
+
class="input"
|
|
199
|
+
placeholder=${component.labels.slugPlaceholder}
|
|
200
|
+
.value=${component._slug}
|
|
201
|
+
@input=${(e: Event) => component.handleSlugInput(e)}
|
|
202
|
+
/>
|
|
203
|
+
${component._slug
|
|
204
|
+
? html`<p class="text-xs text-muted-foreground mt-1">
|
|
205
|
+
${component.siteUrl}/${component._slug}
|
|
206
|
+
</p>`
|
|
207
|
+
: html`<p class="text-xs text-muted-foreground mt-1">
|
|
208
|
+
${component.labels.slugHelp}
|
|
209
|
+
</p>`}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
114
212
|
<div class="field">
|
|
115
213
|
<label class="label">${component.labels.bodyLabel}</label>
|
|
116
214
|
<div
|
|
@@ -178,13 +276,10 @@ export function renderPostForm(component: JantPostForm) {
|
|
|
178
276
|
@change=${(e: Event) => {
|
|
179
277
|
const target = e.target as HTMLSelectElement;
|
|
180
278
|
component._visibility =
|
|
181
|
-
(target.value as typeof component._visibility) ?? "
|
|
279
|
+
(target.value as typeof component._visibility) ?? "public";
|
|
182
280
|
}}
|
|
183
281
|
>
|
|
184
|
-
<option value="
|
|
185
|
-
<option value="featured">
|
|
186
|
-
${component.labels.visibilityFeatured}
|
|
187
|
-
</option>
|
|
282
|
+
<option value="public">${component.labels.visibilityPublic}</option>
|
|
188
283
|
<option value="unlisted">
|
|
189
284
|
${component.labels.visibilityUnlisted}
|
|
190
285
|
</option>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared type definitions for the
|
|
2
|
+
* Shared type definitions for the post form Lit component.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export type PostFormat = "note" | "link" | "quote";
|
|
6
6
|
export type PostStatus = "published" | "draft";
|
|
7
|
-
export type PostVisibility = "
|
|
7
|
+
export type PostVisibility = "public" | "unlisted";
|
|
8
8
|
|
|
9
9
|
export interface PostFormLabels {
|
|
10
10
|
formatLabel: string;
|
|
@@ -13,6 +13,9 @@ export interface PostFormLabels {
|
|
|
13
13
|
quoteOption: string;
|
|
14
14
|
titleLabel: string;
|
|
15
15
|
titlePlaceholder: string;
|
|
16
|
+
slugLabel: string;
|
|
17
|
+
slugPlaceholder: string;
|
|
18
|
+
slugHelp: string;
|
|
16
19
|
bodyLabel: string;
|
|
17
20
|
bodyPlaceholder: string;
|
|
18
21
|
urlLabel: string;
|
|
@@ -27,8 +30,7 @@ export interface PostFormLabels {
|
|
|
27
30
|
statusPublished: string;
|
|
28
31
|
statusDraft: string;
|
|
29
32
|
visibilityLabel: string;
|
|
30
|
-
|
|
31
|
-
visibilityFeatured: string;
|
|
33
|
+
visibilityPublic: string;
|
|
32
34
|
visibilityUnlisted: string;
|
|
33
35
|
pinnedLabel: string;
|
|
34
36
|
collectionsLabel: string;
|
|
@@ -39,11 +41,13 @@ export interface PostFormLabels {
|
|
|
39
41
|
mediaDialogLoading: string;
|
|
40
42
|
submitSuccessMessage: string;
|
|
41
43
|
submitErrorMessage: string;
|
|
44
|
+
draftFallbackMessage: string;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export interface PostFormInitial {
|
|
45
48
|
format: PostFormat;
|
|
46
49
|
title: string;
|
|
50
|
+
slug: string;
|
|
47
51
|
body: string;
|
|
48
52
|
url: string;
|
|
49
53
|
quoteText: string;
|
|
@@ -66,6 +70,8 @@ export interface PostMediaItem {
|
|
|
66
70
|
id: string;
|
|
67
71
|
thumbUrl: string;
|
|
68
72
|
alt: string;
|
|
73
|
+
mimeType: string;
|
|
74
|
+
originalName: string;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export interface PostSubmitDetail {
|
|
@@ -74,6 +80,7 @@ export interface PostSubmitDetail {
|
|
|
74
80
|
data: {
|
|
75
81
|
format: PostFormat;
|
|
76
82
|
title?: string;
|
|
83
|
+
slug?: string;
|
|
77
84
|
body?: string;
|
|
78
85
|
status: PostStatus;
|
|
79
86
|
visibility: PostVisibility;
|