@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
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Slash Commands Extension
3
+ *
4
+ * Provides a "/" command menu for block formatting.
5
+ * Built on @tiptap/suggestion for cursor tracking and filtering.
6
+ */
7
+
8
+ import { Extension } from "@tiptap/core";
9
+ import Suggestion, {
10
+ type SuggestionOptions,
11
+ type SuggestionProps,
12
+ type SuggestionKeyDownProps,
13
+ } from "@tiptap/suggestion";
14
+ import type { Editor, Range } from "@tiptap/core";
15
+
16
+ interface SlashCommandItem {
17
+ label: string;
18
+ icon: string;
19
+ command: (editor: Editor, range: Range) => void;
20
+ }
21
+
22
+ const SLASH_COMMANDS: SlashCommandItem[] = [
23
+ {
24
+ label: "Heading 1",
25
+ icon: "H1",
26
+ command: (editor, range) => {
27
+ editor
28
+ .chain()
29
+ .focus()
30
+ .deleteRange(range)
31
+ .toggleHeading({ level: 1 })
32
+ .run();
33
+ },
34
+ },
35
+ {
36
+ label: "Heading 2",
37
+ icon: "H2",
38
+ command: (editor, range) => {
39
+ editor
40
+ .chain()
41
+ .focus()
42
+ .deleteRange(range)
43
+ .toggleHeading({ level: 2 })
44
+ .run();
45
+ },
46
+ },
47
+ {
48
+ label: "Heading 3",
49
+ icon: "H3",
50
+ command: (editor, range) => {
51
+ editor
52
+ .chain()
53
+ .focus()
54
+ .deleteRange(range)
55
+ .toggleHeading({ level: 3 })
56
+ .run();
57
+ },
58
+ },
59
+ {
60
+ label: "Bullet List",
61
+ icon: "•",
62
+ command: (editor, range) => {
63
+ editor.chain().focus().deleteRange(range).toggleBulletList().run();
64
+ },
65
+ },
66
+ {
67
+ label: "Ordered List",
68
+ icon: "1.",
69
+ command: (editor, range) => {
70
+ editor.chain().focus().deleteRange(range).toggleOrderedList().run();
71
+ },
72
+ },
73
+ {
74
+ label: "Blockquote",
75
+ icon: '"',
76
+ command: (editor, range) => {
77
+ editor.chain().focus().deleteRange(range).toggleBlockquote().run();
78
+ },
79
+ },
80
+ {
81
+ label: "Code Block",
82
+ icon: "</>",
83
+ command: (editor, range) => {
84
+ editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
85
+ },
86
+ },
87
+ {
88
+ label: "Horizontal Rule",
89
+ icon: "—",
90
+ command: (editor, range) => {
91
+ editor.chain().focus().deleteRange(range).setHorizontalRule().run();
92
+ },
93
+ },
94
+ {
95
+ label: "Image",
96
+ icon: "🖼",
97
+ command: (editor, range) => {
98
+ editor.chain().focus().deleteRange(range).run();
99
+ document.dispatchEvent(
100
+ new CustomEvent("jant:slash-image", { bubbles: true }),
101
+ );
102
+ },
103
+ },
104
+ {
105
+ label: "Table",
106
+ icon: "▦",
107
+ command: (editor, range) => {
108
+ editor
109
+ .chain()
110
+ .focus()
111
+ .deleteRange(range)
112
+ .insertTable({ rows: 3, cols: 3, withHeaderRow: true })
113
+ .run();
114
+ },
115
+ },
116
+ {
117
+ label: "Read More",
118
+ icon: "↓",
119
+ command: (editor, range) => {
120
+ editor
121
+ .chain()
122
+ .focus()
123
+ .deleteRange(range)
124
+ .insertContent({ type: "moreBreak" })
125
+ .run();
126
+ },
127
+ },
128
+ ];
129
+
130
+ /**
131
+ * Returns the slash commands list, used by both the extension and the + menu.
132
+ */
133
+ export function getSlashCommands(): SlashCommandItem[] {
134
+ return SLASH_COMMANDS;
135
+ }
136
+
137
+ // Popup element management
138
+ let popupEl: HTMLElement | null = null;
139
+ let selectedIndex = 0;
140
+ let filteredItems: SlashCommandItem[] = [];
141
+ let commandFn: ((item: { index: number }) => void) | null = null;
142
+
143
+ function createPopup(): HTMLElement {
144
+ const el = document.createElement("div");
145
+ el.className = "tiptap-slash-menu";
146
+ el.style.position = "fixed";
147
+ return el;
148
+ }
149
+
150
+ function renderPopup(
151
+ items: SlashCommandItem[],
152
+ onSelect: (index: number) => void,
153
+ ) {
154
+ if (!popupEl) return;
155
+
156
+ filteredItems = items;
157
+ if (selectedIndex >= items.length) selectedIndex = 0;
158
+
159
+ popupEl.innerHTML = items
160
+ .map(
161
+ (item, i) =>
162
+ `<div class="tiptap-slash-item${i === selectedIndex ? " is-selected" : ""}" data-index="${i}">
163
+ <span class="tiptap-slash-item-icon">${item.icon}</span>
164
+ <span class="tiptap-slash-item-label">${item.label}</span>
165
+ </div>`,
166
+ )
167
+ .join("");
168
+
169
+ // Click handlers
170
+ popupEl.querySelectorAll<HTMLElement>(".tiptap-slash-item").forEach((el) => {
171
+ el.addEventListener("mousedown", (e) => {
172
+ e.preventDefault();
173
+ const idx = parseInt(el.dataset.index ?? "0", 10);
174
+ onSelect(idx);
175
+ });
176
+ el.addEventListener("mouseenter", () => {
177
+ selectedIndex = parseInt(el.dataset.index ?? "0", 10);
178
+ popupEl
179
+ ?.querySelectorAll(".tiptap-slash-item")
180
+ .forEach((item, i) =>
181
+ item.classList.toggle("is-selected", i === selectedIndex),
182
+ );
183
+ });
184
+ });
185
+ }
186
+
187
+ function destroyPopup() {
188
+ popupEl?.remove();
189
+ popupEl = null;
190
+ selectedIndex = 0;
191
+ filteredItems = [];
192
+ commandFn = null;
193
+ }
194
+
195
+ /**
196
+ * Position the popup relative to the cursor, accounting for dialog containing block.
197
+ * When a `<dialog>` has CSS animation, it creates a containing block that makes
198
+ * `position: fixed` relative to the dialog instead of the viewport.
199
+ */
200
+ function positionPopup(
201
+ rect: globalThis.DOMRect,
202
+ container: HTMLElement | null,
203
+ ) {
204
+ if (!popupEl) return;
205
+ const offsetX = container?.getBoundingClientRect().left ?? 0;
206
+ const offsetY = container?.getBoundingClientRect().top ?? 0;
207
+ popupEl.style.left = `${rect.left - offsetX}px`;
208
+ popupEl.style.top = `${rect.bottom + 4 - offsetY}px`;
209
+ }
210
+
211
+ /**
212
+ * Slash commands Tiptap extension.
213
+ */
214
+ export const SlashCommands = Extension.create({
215
+ name: "slashCommands",
216
+
217
+ addOptions() {
218
+ return {
219
+ suggestion: {
220
+ char: "/",
221
+ startOfLine: false,
222
+ items: ({ query }: { query: string }) => {
223
+ const q = query.toLowerCase();
224
+ return SLASH_COMMANDS.filter((item) =>
225
+ item.label.toLowerCase().includes(q),
226
+ );
227
+ },
228
+ render: () => {
229
+ function getEditorElement(editor: Editor): globalThis.Element | null {
230
+ const el = editor.options.element;
231
+ return el instanceof globalThis.Element ? el : null;
232
+ }
233
+
234
+ return {
235
+ onStart: (
236
+ props: SuggestionProps<SlashCommandItem, { index: number }>,
237
+ ) => {
238
+ popupEl = createPopup();
239
+ selectedIndex = 0;
240
+ commandFn = props.command;
241
+ renderPopup(props.items, (index) => props.command({ index }));
242
+
243
+ // Append inside the closest dialog (top-layer) or body
244
+ const editorEl = getEditorElement(props.editor);
245
+ const dialog = editorEl?.closest("dialog") ?? null;
246
+ (dialog ?? document.body).appendChild(popupEl);
247
+
248
+ const rect = props.clientRect?.();
249
+ if (rect) {
250
+ positionPopup(rect, dialog);
251
+ }
252
+ },
253
+ onUpdate: (
254
+ props: SuggestionProps<SlashCommandItem, { index: number }>,
255
+ ) => {
256
+ commandFn = props.command;
257
+ renderPopup(props.items, (index) => props.command({ index }));
258
+ const rect = props.clientRect?.();
259
+ if (rect) {
260
+ const editorEl = getEditorElement(props.editor);
261
+ const dialog = editorEl?.closest("dialog") ?? null;
262
+ positionPopup(rect, dialog);
263
+ }
264
+ },
265
+ onKeyDown: (props: SuggestionKeyDownProps) => {
266
+ const { event } = props;
267
+ if (event.key === "ArrowDown") {
268
+ selectedIndex = (selectedIndex + 1) % filteredItems.length;
269
+ popupEl
270
+ ?.querySelectorAll(".tiptap-slash-item")
271
+ .forEach((item, i) =>
272
+ item.classList.toggle("is-selected", i === selectedIndex),
273
+ );
274
+ return true;
275
+ }
276
+ if (event.key === "ArrowUp") {
277
+ selectedIndex =
278
+ (selectedIndex - 1 + filteredItems.length) %
279
+ filteredItems.length;
280
+ popupEl
281
+ ?.querySelectorAll(".tiptap-slash-item")
282
+ .forEach((item, i) =>
283
+ item.classList.toggle("is-selected", i === selectedIndex),
284
+ );
285
+ return true;
286
+ }
287
+ if (event.key === "Enter") {
288
+ commandFn?.({ index: selectedIndex });
289
+ return true;
290
+ }
291
+ if (event.key === "Escape") {
292
+ destroyPopup();
293
+ return true;
294
+ }
295
+ return false;
296
+ },
297
+ onExit: () => {
298
+ destroyPopup();
299
+ },
300
+ };
301
+ },
302
+ command: ({
303
+ editor,
304
+ range,
305
+ props,
306
+ }: {
307
+ editor: Editor;
308
+ range: Range;
309
+ props: { index: number };
310
+ }) => {
311
+ const item = filteredItems[props.index];
312
+ if (item) {
313
+ item.command(editor, range);
314
+ }
315
+ },
316
+ } satisfies Partial<SuggestionOptions>,
317
+ };
318
+ },
319
+
320
+ addProseMirrorPlugins() {
321
+ return [
322
+ Suggestion({
323
+ editor: this.editor,
324
+ ...this.options.suggestion,
325
+ }),
326
+ ];
327
+ },
328
+ });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Minimal type declarations for sortablejs
3
3
  *
4
- * Only covers the API surface used by jant-nav-manager and collections-reorder.
4
+ * Only covers the API surface used by jant-nav-manager and jant-collection-sidebar.
5
5
  */
6
6
 
7
7
  declare module "sortablejs" {
package/src/client.ts CHANGED
@@ -9,21 +9,28 @@
9
9
 
10
10
  import "./vendor/datastar.js";
11
11
  import "basecoat-css/all";
12
- import "./lib/image-processor.js";
13
- import "./lib/media-upload.js";
14
- import "./lib/avatar-upload.js";
15
- import "./lib/collections-reorder.js";
12
+ import "./client/image-processor.js";
13
+ import "./client/media-upload.js";
14
+ import "./client/avatar-upload.js";
16
15
 
17
- // Lit Web Components
18
- import "./ui/components/jant-compose-dialog.js";
19
- import "./ui/components/jant-compose-editor.js";
20
- import "./lib/compose-bridge.js";
21
- import "./ui/components/jant-settings-general.js";
22
- import "./ui/components/jant-settings-avatar.js";
23
- import "./lib/settings-bridge.js";
24
- import "./ui/components/jant-collection-form.js";
25
- import "./lib/collection-form-bridge.js";
26
- import "./ui/components/jant-post-form.js";
27
- import "./lib/post-form-bridge.js";
28
- import "./ui/components/jant-nav-manager.js";
29
- import "./lib/nav-manager-bridge.js";
16
+ // Lit Web Components (and their bridge modules)
17
+ import "./client/components/jant-compose-dialog.js";
18
+ import "./client/components/jant-compose-editor.js";
19
+ import "./client/components/jant-compose-fullscreen.js";
20
+
21
+ // Mount fullscreen overlay at body level to escape the dialog's containing block
22
+ // (dialog animation creates a containing block that traps position:fixed descendants)
23
+ document.body.appendChild(document.createElement("jant-compose-fullscreen"));
24
+ import "./client/compose-bridge.js";
25
+ import "./client/components/jant-settings-general.js";
26
+ import "./client/components/jant-settings-avatar.js";
27
+ import "./client/settings-bridge.js";
28
+ import "./client/components/jant-collection-form.js";
29
+ import "./client/components/jant-collection-sidebar.js";
30
+ import "./client/collection-form-bridge.js";
31
+ import "./client/components/jant-post-form.js";
32
+ import "./client/post-form-bridge.js";
33
+ import "./client/page-slug-bridge.js";
34
+ import "./client/components/jant-nav-manager.js";
35
+ import "./client/nav-manager-bridge.js";
36
+ import "./client/components/jant-media-lightbox.js";
@@ -0,0 +1,2 @@
1
+ -- Add summary column for Tiptap-based excerpt storage
2
+ ALTER TABLE posts ADD COLUMN summary TEXT;
@@ -0,0 +1,8 @@
1
+ -- Replace binary `featured` flag with three-value `visibility` text column.
2
+ -- Values: 'listed' (default), 'featured', 'unlisted'.
3
+
4
+ ALTER TABLE posts ADD COLUMN visibility TEXT NOT NULL DEFAULT 'listed';
5
+ --> statement-breakpoint
6
+ UPDATE posts SET visibility = 'featured' WHERE featured = 1;
7
+ --> statement-breakpoint
8
+ ALTER TABLE posts DROP COLUMN featured;
package/src/db/schema.ts CHANGED
@@ -20,7 +20,11 @@ export const posts = sqliteTable("posts", {
20
20
  })
21
21
  .notNull()
22
22
  .default("published"),
23
- featured: integer("featured").notNull().default(0),
23
+ visibility: text("visibility", {
24
+ enum: ["listed", "featured", "unlisted"],
25
+ })
26
+ .notNull()
27
+ .default("listed"),
24
28
  pinned: integer("pinned").notNull().default(0),
25
29
  path: text("path").unique(),
26
30
  title: text("title"),
@@ -28,6 +32,7 @@ export const posts = sqliteTable("posts", {
28
32
  body: text("body"),
29
33
  bodyHtml: text("body_html"),
30
34
  quoteText: text("quote_text"),
35
+ summary: text("summary"),
31
36
  rating: integer("rating"),
32
37
  replyToId: integer("reply_to_id"),
33
38
  threadId: integer("thread_id"),