@jant/core 0.3.35 → 0.3.36

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.
Files changed (156) hide show
  1. package/dist/client/assets/module-RjUF93sV.js +716 -0
  2. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  3. package/dist/client/assets/url-8Dj-5CLW.js +1 -0
  4. package/dist/client/client.css +1 -1
  5. package/dist/client/client.js +3109 -2294
  6. package/dist/index.js +3026 -2778
  7. package/package.json +13 -4
  8. package/src/__tests__/helpers/app.ts +1 -1
  9. package/src/__tests__/helpers/db.ts +6 -0
  10. package/src/app.tsx +1 -5
  11. package/src/{lib → client}/avatar-upload.ts +1 -1
  12. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  13. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  14. package/src/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
  15. package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
  16. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
  17. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  18. package/src/client/components/collection-sidebar-types.ts +45 -0
  19. package/src/{ui → client}/components/collection-types.ts +3 -4
  20. package/src/{ui → client}/components/compose-types.ts +3 -1
  21. package/src/{ui → client}/components/jant-collection-form.ts +301 -182
  22. package/src/client/components/jant-collection-sidebar.ts +801 -0
  23. package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
  24. package/src/client/components/jant-compose-editor.ts +1249 -0
  25. package/src/client/components/jant-compose-fullscreen.ts +338 -0
  26. package/src/client/components/jant-media-lightbox.ts +257 -0
  27. package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
  28. package/src/{ui → client}/components/jant-post-form.ts +57 -8
  29. package/src/{ui → client}/components/jant-settings-general.ts +2 -2
  30. package/src/{ui → client}/components/nav-manager-types.ts +3 -0
  31. package/src/{ui → client}/components/post-form-template.ts +35 -31
  32. package/src/{ui → client}/components/post-form-types.ts +7 -3
  33. package/src/{lib → client}/compose-bridge.ts +9 -7
  34. package/src/client/lazy-slugify.ts +51 -0
  35. package/src/{lib → client}/media-upload.ts +16 -3
  36. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  37. package/src/client/page-slug-bridge.ts +42 -0
  38. package/src/{lib → client}/post-form-bridge.ts +2 -2
  39. package/src/{lib → client}/settings-bridge.ts +3 -3
  40. package/src/client/tiptap/bubble-menu.ts +205 -0
  41. package/src/client/tiptap/create-editor.ts +40 -0
  42. package/src/client/tiptap/exitable-marks.ts +73 -0
  43. package/src/client/tiptap/extensions.ts +60 -0
  44. package/src/client/tiptap/image-node.ts +488 -0
  45. package/src/client/tiptap/link-toolbar.ts +371 -0
  46. package/src/client/tiptap/more-break.ts +50 -0
  47. package/src/client/tiptap/paste-image.ts +140 -0
  48. package/src/client/tiptap/slash-commands.ts +328 -0
  49. package/src/{types → client/types}/sortablejs.d.ts +1 -1
  50. package/src/client.ts +24 -17
  51. package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
  52. package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
  53. package/src/db/schema.ts +6 -1
  54. package/src/i18n/locales/en.po +641 -215
  55. package/src/i18n/locales/en.ts +1 -1
  56. package/src/i18n/locales/zh-Hans.po +642 -204
  57. package/src/i18n/locales/zh-Hans.ts +1 -1
  58. package/src/i18n/locales/zh-Hant.po +642 -204
  59. package/src/i18n/locales/zh-Hant.ts +1 -1
  60. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  61. package/src/lib/__tests__/schemas.test.ts +9 -6
  62. package/src/lib/__tests__/url.test.ts +2 -2
  63. package/src/lib/__tests__/view.test.ts +9 -9
  64. package/src/lib/emoji-catalog.ts +146 -0
  65. package/src/lib/feed.ts +1 -1
  66. package/src/lib/media-helpers.ts +10 -9
  67. package/src/lib/render.tsx +4 -3
  68. package/src/lib/resolve-config.ts +8 -1
  69. package/src/lib/schemas.ts +2 -3
  70. package/src/lib/summary.ts +92 -0
  71. package/src/lib/timeline.ts +2 -0
  72. package/src/lib/tiptap-render.ts +196 -0
  73. package/src/lib/upload.ts +97 -9
  74. package/src/lib/url.ts +7 -23
  75. package/src/lib/view.ts +33 -19
  76. package/src/middleware/error-handler.ts +3 -3
  77. package/src/preset.css +38 -0
  78. package/src/routes/api/collections.ts +20 -3
  79. package/src/routes/api/posts.ts +48 -33
  80. package/src/routes/api/upload.ts +7 -5
  81. package/src/routes/auth/reset.tsx +5 -4
  82. package/src/routes/auth/setup.tsx +26 -11
  83. package/src/routes/auth/signin.tsx +10 -7
  84. package/src/routes/compose.tsx +20 -11
  85. package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
  86. package/src/routes/dash/index.tsx +7 -1
  87. package/src/routes/dash/media.tsx +3 -0
  88. package/src/routes/dash/pages.tsx +8 -2
  89. package/src/routes/dash/posts.tsx +6 -2
  90. package/src/routes/dash/redirects.tsx +15 -9
  91. package/src/routes/dash/settings.tsx +336 -32
  92. package/src/routes/feed/__tests__/rss.test.ts +7 -7
  93. package/src/routes/feed/rss.ts +8 -6
  94. package/src/routes/pages/__tests__/featured.test.ts +6 -7
  95. package/src/routes/pages/archive.tsx +11 -7
  96. package/src/routes/pages/collection.tsx +32 -15
  97. package/src/routes/pages/collections.tsx +11 -2
  98. package/src/routes/pages/featured.tsx +1 -1
  99. package/src/routes/pages/home.tsx +1 -1
  100. package/src/services/__tests__/post.test.ts +124 -33
  101. package/src/services/__tests__/settings.test.ts +3 -3
  102. package/src/services/page.ts +16 -3
  103. package/src/services/post.ts +96 -37
  104. package/src/services/search.ts +4 -2
  105. package/src/services/settings.ts +6 -2
  106. package/src/styles/components.css +240 -60
  107. package/src/styles/tokens.css +10 -0
  108. package/src/styles/ui.css +1157 -81
  109. package/src/types/bindings.ts +5 -0
  110. package/src/types/config.ts +23 -1
  111. package/src/types/constants.ts +3 -0
  112. package/src/types/entities.ts +9 -2
  113. package/src/types/operations.ts +9 -3
  114. package/src/types/props.ts +3 -3
  115. package/src/types/views.ts +3 -2
  116. package/src/ui/compose/ComposeDialog.tsx +24 -7
  117. package/src/ui/dash/PageForm.tsx +2 -0
  118. package/src/ui/dash/PostList.tsx +5 -5
  119. package/src/ui/dash/StatusBadge.tsx +13 -5
  120. package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
  121. package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
  122. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  123. package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
  124. package/src/ui/dash/media/MediaListContent.tsx +9 -4
  125. package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
  126. package/src/ui/dash/pages/PagesContent.tsx +2 -1
  127. package/src/ui/dash/posts/PostForm.tsx +19 -7
  128. package/src/ui/dash/settings/AccountContent.tsx +133 -138
  129. package/src/ui/dash/settings/AvatarContent.tsx +70 -0
  130. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  131. package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
  132. package/src/ui/layouts/DashLayout.tsx +157 -75
  133. package/src/ui/layouts/SiteLayout.tsx +13 -13
  134. package/src/ui/pages/ArchivePage.tsx +10 -7
  135. package/src/ui/pages/CollectionPage.tsx +6 -35
  136. package/src/ui/pages/CollectionsPage.tsx +2 -1
  137. package/src/ui/pages/FeaturedPage.tsx +2 -1
  138. package/src/ui/pages/HomePage.tsx +1 -1
  139. package/src/ui/pages/SearchPage.tsx +1 -1
  140. package/src/ui/shared/CollectionsSidebar.tsx +228 -3
  141. package/src/ui/shared/MediaGallery.tsx +179 -41
  142. package/src/lib/collections-reorder.ts +0 -28
  143. package/src/routes/dash/appearance.tsx +0 -240
  144. package/src/routes/dash/collections.tsx +0 -211
  145. package/src/ui/components/jant-compose-editor.ts +0 -814
  146. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  147. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  148. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  149. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  150. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  151. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  152. /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
  153. /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
  154. /package/src/{ui → client}/components/settings-types.ts +0 -0
  155. /package/src/{lib → client}/image-processor.ts +0 -0
  156. /package/src/{lib → client}/toast.ts +0 -0
@@ -15,13 +15,16 @@ import type {
15
15
  ComposeLabels,
16
16
  ComposeCollection,
17
17
  ComposeSubmitDetail,
18
+ ComposeAttachment,
18
19
  } from "./compose-types.js";
19
20
  import type { JantComposeEditor } from "./jant-compose-editor.js";
21
+ import { getMediaCategory } from "../../lib/upload.js";
20
22
 
21
23
  export class JantComposeDialog extends LitElement {
22
24
  static properties = {
23
25
  collections: { type: Array },
24
26
  labels: { type: Object },
27
+ uploadMaxFileSize: { type: Number, attribute: "upload-max-file-size" },
25
28
  _format: { state: true },
26
29
  _status: { state: true },
27
30
  _loading: { state: true },
@@ -29,10 +32,14 @@ export class JantComposeDialog extends LitElement {
29
32
  _showCollection: { state: true },
30
33
  _showMoreMenu: { state: true },
31
34
  _collectionSearch: { state: true },
35
+ _altPanelOpen: { state: true },
36
+ _altPanelIndex: { state: true },
37
+ _attachedPanelOpen: { state: true },
32
38
  };
33
39
 
34
40
  declare collections: ComposeCollection[];
35
41
  declare labels: ComposeLabels;
42
+ declare uploadMaxFileSize: number;
36
43
  declare _format: ComposeFormat;
37
44
  declare _status: "published" | "draft";
38
45
  declare _loading: boolean;
@@ -40,6 +47,9 @@ export class JantComposeDialog extends LitElement {
40
47
  declare _showCollection: boolean;
41
48
  declare _showMoreMenu: boolean;
42
49
  declare _collectionSearch: string;
50
+ declare _altPanelOpen: boolean;
51
+ declare _altPanelIndex: number;
52
+ declare _attachedPanelOpen: boolean;
43
53
 
44
54
  createRenderRoot() {
45
55
  this.innerHTML = "";
@@ -50,6 +60,7 @@ export class JantComposeDialog extends LitElement {
50
60
  super();
51
61
  this.collections = [];
52
62
  this.labels = {} as ComposeLabels;
63
+ this.uploadMaxFileSize = 500;
53
64
  this._format = "note";
54
65
  this._status = "published";
55
66
  this._loading = false;
@@ -57,6 +68,9 @@ export class JantComposeDialog extends LitElement {
57
68
  this._showCollection = false;
58
69
  this._showMoreMenu = false;
59
70
  this._collectionSearch = "";
71
+ this._altPanelOpen = false;
72
+ this._altPanelIndex = 0;
73
+ this._attachedPanelOpen = false;
60
74
  }
61
75
 
62
76
  private get _editor(): JantComposeEditor | null {
@@ -71,6 +85,9 @@ export class JantComposeDialog extends LitElement {
71
85
  this._showCollection = false;
72
86
  this._showMoreMenu = false;
73
87
  this._collectionSearch = "";
88
+ this._altPanelOpen = false;
89
+ this._altPanelIndex = 0;
90
+ this._attachedPanelOpen = false;
74
91
  this._editor?.reset();
75
92
  }
76
93
 
@@ -171,11 +188,32 @@ export class JantComposeDialog extends LitElement {
171
188
  connectedCallback() {
172
189
  super.connectedCallback();
173
190
  this.addEventListener("keydown", this._handleKeydown);
191
+ this.addEventListener("jant:alt-panel-open", this._handleAltPanelOpen);
192
+ this.addEventListener("jant:alt-panel-close", this._handleAltPanelClose);
193
+ this.addEventListener(
194
+ "jant:attached-panel-open",
195
+ this._handleAttachedPanelOpen,
196
+ );
197
+ // Listen on document — fullscreen element lives on document.body, outside the dialog
198
+ document.addEventListener(
199
+ "jant:fullscreen-close",
200
+ this._handleFullscreenClose as EventListener,
201
+ );
174
202
  }
175
203
 
176
204
  disconnectedCallback() {
177
205
  super.disconnectedCallback();
178
206
  this.removeEventListener("keydown", this._handleKeydown);
207
+ this.removeEventListener("jant:alt-panel-open", this._handleAltPanelOpen);
208
+ this.removeEventListener("jant:alt-panel-close", this._handleAltPanelClose);
209
+ this.removeEventListener(
210
+ "jant:attached-panel-open",
211
+ this._handleAttachedPanelOpen,
212
+ );
213
+ document.removeEventListener(
214
+ "jant:fullscreen-close",
215
+ this._handleFullscreenClose as EventListener,
216
+ );
179
217
  }
180
218
 
181
219
  private _handleKeydown = (e: Event) => {
@@ -186,6 +224,63 @@ export class JantComposeDialog extends LitElement {
186
224
  }
187
225
  };
188
226
 
227
+ private _handleAltPanelOpen = (e: Event) => {
228
+ const detail = (e as CustomEvent<{ index: number }>).detail;
229
+ this._altPanelIndex = detail.index;
230
+ this._altPanelOpen = true;
231
+ this.updateComplete.then(() => {
232
+ this.querySelector<HTMLInputElement>(".compose-alt-input")?.focus();
233
+ });
234
+ };
235
+
236
+ private _handleAltPanelClose = () => {
237
+ this._altPanelOpen = false;
238
+ };
239
+
240
+ private _getAltAttachment(): ComposeAttachment | null {
241
+ return this._editor?._attachments[this._altPanelIndex] ?? null;
242
+ }
243
+
244
+ private _onAltInput(e: Event) {
245
+ const value = (e.target as HTMLInputElement).value;
246
+ this._editor?.updateAlt(this._altPanelIndex, value);
247
+ }
248
+
249
+ private _closeAltPanel() {
250
+ this._altPanelOpen = false;
251
+ }
252
+
253
+ private _handleFullscreenClose = (
254
+ e: CustomEvent<{ json: unknown; title: string }>,
255
+ ) => {
256
+ const editor = this._editor;
257
+ if (editor) {
258
+ editor.setEditorState(
259
+ e.detail.json as import("@tiptap/core").JSONContent,
260
+ e.detail.title,
261
+ );
262
+ }
263
+ };
264
+
265
+ private _handleAttachedPanelOpen = () => {
266
+ this._attachedPanelOpen = true;
267
+ this.updateComplete.then(() => {
268
+ this.querySelector<HTMLTextAreaElement>(
269
+ ".compose-attached-textarea",
270
+ )?.focus();
271
+ });
272
+ };
273
+
274
+ private _onAttachedTextInput(e: Event) {
275
+ const value = (e.target as HTMLTextAreaElement).value;
276
+ this._editor?.updateAttachedText(value);
277
+ }
278
+
279
+ private _closeAttachedPanel() {
280
+ this._attachedPanelOpen = false;
281
+ this._editor?.closeAttachedPanel();
282
+ }
283
+
189
284
  // ── Render helpers ────────────────────────────────────────────────
190
285
 
191
286
  private _renderHeader() {
@@ -344,7 +439,7 @@ export class JantComposeDialog extends LitElement {
344
439
  }}
345
440
  ></div>`
346
441
  : nothing}
347
- <div class="select compose-collection-select">
442
+ <div class="select compose-collection-select" data-select-initialized>
348
443
  <button
349
444
  type="button"
350
445
  class="compose-collection-trigger"
@@ -452,6 +547,139 @@ export class JantComposeDialog extends LitElement {
452
547
  `;
453
548
  }
454
549
 
550
+ private _renderAttachedPanel() {
551
+ if (!this._attachedPanelOpen) return nothing;
552
+ const editor = this._editor;
553
+ const attachedText = editor?._attachedText ?? "";
554
+
555
+ return html`
556
+ <div class="compose-attached-panel">
557
+ <div class="compose-alt-header">
558
+ <button
559
+ type="button"
560
+ class="compose-attached-panel-back"
561
+ @click=${() => this._closeAttachedPanel()}
562
+ >
563
+ <svg
564
+ class="icon-fine"
565
+ width="16"
566
+ height="16"
567
+ viewBox="0 0 16 16"
568
+ fill="none"
569
+ stroke="currentColor"
570
+ stroke-width="1.5"
571
+ stroke-linecap="round"
572
+ stroke-linejoin="round"
573
+ >
574
+ <path d="M11 3L6 8l5 5" />
575
+ </svg>
576
+ </button>
577
+ <span class="compose-alt-title">${this.labels.attachedText}</span>
578
+ ${attachedText.length > 0
579
+ ? html`<span
580
+ class="compose-attached-charcount text-xs text-muted-foreground tracking-wide"
581
+ >${attachedText.length.toLocaleString()} chars</span
582
+ >`
583
+ : nothing}
584
+ </div>
585
+ <div class="flex-1 p-4 overflow-hidden flex flex-col">
586
+ <textarea
587
+ .value=${attachedText}
588
+ @input=${(e: Event) => this._onAttachedTextInput(e)}
589
+ class="compose-input compose-attached-textarea"
590
+ placeholder=${this.labels.attachedTextPlaceholder}
591
+ ></textarea>
592
+ </div>
593
+ <div class="compose-alt-footer">
594
+ <span class="text-xs text-muted-foreground"
595
+ >${this.labels.attachedTextHint}</span
596
+ >
597
+ <button
598
+ type="button"
599
+ class="compose-post-btn"
600
+ @click=${() => this._closeAttachedPanel()}
601
+ >
602
+ ${this.labels.done}
603
+ </button>
604
+ </div>
605
+ </div>
606
+ `;
607
+ }
608
+
609
+ private _renderAltPanel() {
610
+ if (!this._altPanelOpen) return nothing;
611
+ const attachment = this._getAltAttachment();
612
+ if (!attachment) return nothing;
613
+
614
+ const category = getMediaCategory(attachment.file.type);
615
+
616
+ return html`
617
+ <div class="compose-alt-panel">
618
+ <div class="compose-alt-header">
619
+ <button
620
+ type="button"
621
+ class="compose-attached-panel-back"
622
+ @click=${() => this._closeAltPanel()}
623
+ >
624
+ <svg
625
+ class="icon-fine"
626
+ width="16"
627
+ height="16"
628
+ viewBox="0 0 16 16"
629
+ fill="none"
630
+ stroke="currentColor"
631
+ stroke-width="1.5"
632
+ stroke-linecap="round"
633
+ stroke-linejoin="round"
634
+ >
635
+ <path d="M11 3L6 8l5 5" />
636
+ </svg>
637
+ </button>
638
+ <span class="compose-alt-title">${this.labels.addAltTitle}</span>
639
+ </div>
640
+ <div class="compose-alt-preview">
641
+ ${category === "image"
642
+ ? html`<img
643
+ src=${attachment.previewUrl}
644
+ alt=""
645
+ class="compose-alt-preview-img"
646
+ />`
647
+ : category === "video"
648
+ ? html`<video
649
+ src=${attachment.previewUrl}
650
+ class="compose-alt-preview-img"
651
+ preload="metadata"
652
+ muted
653
+ ></video>`
654
+ : html`<span class="text-sm text-muted-foreground"
655
+ >${attachment.file.name}</span
656
+ >`}
657
+ </div>
658
+ <div class="compose-alt-input-row">
659
+ <input
660
+ type="text"
661
+ .value=${attachment.alt}
662
+ @input=${(e: Event) => this._onAltInput(e)}
663
+ class="compose-input compose-alt-input"
664
+ placeholder=${this.labels.altPlaceholder}
665
+ />
666
+ </div>
667
+ <div class="compose-alt-footer">
668
+ <span class="text-xs text-muted-foreground"
669
+ >${this.labels.altHint}</span
670
+ >
671
+ <button
672
+ type="button"
673
+ class="compose-post-btn"
674
+ @click=${() => this._closeAltPanel()}
675
+ >
676
+ ${this.labels.done}
677
+ </button>
678
+ </div>
679
+ </div>
680
+ `;
681
+ }
682
+
455
683
  render() {
456
684
  return html`
457
685
  <div class="compose-dialog-inner">
@@ -459,6 +687,7 @@ export class JantComposeDialog extends LitElement {
459
687
  <jant-compose-editor
460
688
  .format=${this._format}
461
689
  .labels=${this.labels}
690
+ .uploadMaxFileSize=${this.uploadMaxFileSize}
462
691
  ></jant-compose-editor>
463
692
 
464
693
  <div class="compose-action-row">
@@ -487,6 +716,7 @@ export class JantComposeDialog extends LitElement {
487
716
  ${this.labels.post}
488
717
  </button>
489
718
  </div>
719
+ ${this._renderAttachedPanel()} ${this._renderAltPanel()}
490
720
  </div>
491
721
  `;
492
722
  }