@jant/core 0.3.23 → 0.3.24
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/dist/app.js +4 -5
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +61 -72
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/themes/{minimal → threads}/pages/ArchivePage.js +32 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/themes/{minimal → threads}/pages/HomePage.js +3 -3
- package/dist/themes/{minimal → threads}/pages/PostPage.js +12 -9
- package/dist/themes/{minimal → threads}/pages/SearchPage.js +13 -14
- package/dist/themes/{minimal → threads}/pages/SinglePage.js +4 -4
- package/dist/themes/threads/timeline/LinkCard.js +68 -0
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/themes/{minimal → threads}/timeline/ThreadPreview.js +17 -13
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/themes/{minimal → threads}/timeline/TimelineItem.js +8 -16
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +4 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +28 -12
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +199 -51
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/{minimal → threads}/index.ts +30 -13
- package/src/themes/{minimal → threads}/pages/ArchivePage.tsx +53 -53
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/themes/{minimal → threads}/pages/HomePage.tsx +3 -3
- package/src/themes/{minimal → threads}/pages/PostPage.tsx +12 -8
- package/src/themes/{minimal → threads}/pages/SearchPage.tsx +15 -13
- package/src/themes/{minimal → threads}/pages/SinglePage.tsx +4 -4
- package/src/themes/threads/style.css +336 -0
- package/src/themes/threads/timeline/LinkCard.tsx +67 -0
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/themes/{minimal → threads}/timeline/ThreadPreview.tsx +15 -13
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/themes/{minimal → threads}/timeline/TimelineItem.tsx +9 -17
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/QuoteCard.js +0 -48
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/src/routes/api/timeline.tsx +0 -159
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
package/dist/services/post.js
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Post Service
|
|
2
|
+
* Post Service (v2)
|
|
3
3
|
*
|
|
4
|
-
* CRUD operations for posts with Thread support
|
|
5
|
-
|
|
4
|
+
* CRUD operations for posts with Thread support.
|
|
5
|
+
* Posts have format (note/link/quote), status (draft/published),
|
|
6
|
+
* featured flag, and pinned flag.
|
|
7
|
+
*/ import { eq, and, isNull, desc, or, inArray, sql } from "drizzle-orm";
|
|
6
8
|
import { posts } from "../db/schema.js";
|
|
7
9
|
import { now } from "../lib/time.js";
|
|
8
|
-
import { extractDomain } from "../lib/url.js";
|
|
9
10
|
import { render as renderMarkdown } from "../lib/markdown.js";
|
|
10
11
|
export function createPostService(db) {
|
|
11
|
-
// Helper to map DB row to Post type
|
|
12
12
|
function toPost(row) {
|
|
13
13
|
return {
|
|
14
14
|
id: row.id,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
format: row.format,
|
|
16
|
+
status: row.status,
|
|
17
|
+
featured: row.featured,
|
|
18
|
+
pinned: row.pinned,
|
|
19
|
+
slug: row.slug,
|
|
17
20
|
title: row.title,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
url: row.url,
|
|
22
|
+
body: row.body,
|
|
23
|
+
bodyHtml: row.bodyHtml,
|
|
24
|
+
quoteText: row.quoteText,
|
|
25
|
+
rating: row.rating,
|
|
26
|
+
collectionId: row.collectionId,
|
|
24
27
|
replyToId: row.replyToId,
|
|
25
28
|
threadId: row.threadId,
|
|
26
29
|
deletedAt: row.deletedAt,
|
|
@@ -34,41 +37,36 @@ export function createPostService(db) {
|
|
|
34
37
|
const result = await db.select().from(posts).where(and(eq(posts.id, id), isNull(posts.deletedAt))).limit(1);
|
|
35
38
|
return result[0] ? toPost(result[0]) : null;
|
|
36
39
|
},
|
|
37
|
-
async
|
|
38
|
-
const result = await db.select().from(posts).where(and(eq(posts.
|
|
40
|
+
async getBySlug (slug) {
|
|
41
|
+
const result = await db.select().from(posts).where(and(eq(posts.slug, slug), isNull(posts.deletedAt))).limit(1);
|
|
39
42
|
return result[0] ? toPost(result[0]) : null;
|
|
40
43
|
},
|
|
41
44
|
async list (filters = {}) {
|
|
42
45
|
const conditions = [];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
conditions.push(eq(posts.visibility, filters.visibility));
|
|
49
|
-
}
|
|
46
|
+
if (filters.status) {
|
|
47
|
+
conditions.push(eq(posts.status, filters.status));
|
|
48
|
+
}
|
|
49
|
+
if (filters.featured !== undefined) {
|
|
50
|
+
conditions.push(eq(posts.featured, filters.featured ? 1 : 0));
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
conditions.push(eq(posts.type, filters.type));
|
|
52
|
+
if (filters.pinned !== undefined) {
|
|
53
|
+
conditions.push(eq(posts.pinned, filters.pinned ? 1 : 0));
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (filters.format) {
|
|
56
|
+
conditions.push(eq(posts.format, filters.format));
|
|
57
|
+
}
|
|
58
|
+
if (filters.collectionId !== undefined) {
|
|
59
|
+
conditions.push(eq(posts.collectionId, filters.collectionId));
|
|
58
60
|
}
|
|
59
|
-
// Thread filter
|
|
60
61
|
if (filters.threadId) {
|
|
61
62
|
conditions.push(eq(posts.threadId, filters.threadId));
|
|
62
63
|
}
|
|
63
|
-
// Exclude replies (posts that are part of a thread but not the root)
|
|
64
64
|
if (filters.excludeReplies) {
|
|
65
65
|
conditions.push(isNull(posts.threadId));
|
|
66
66
|
}
|
|
67
|
-
// Exclude deleted unless specified
|
|
68
67
|
if (!filters.includeDeleted) {
|
|
69
68
|
conditions.push(isNull(posts.deletedAt));
|
|
70
69
|
}
|
|
71
|
-
// Cursor pagination
|
|
72
70
|
if (filters.cursor) {
|
|
73
71
|
conditions.push(sql`${posts.id} < ${filters.cursor}`);
|
|
74
72
|
}
|
|
@@ -78,35 +76,36 @@ export function createPostService(db) {
|
|
|
78
76
|
},
|
|
79
77
|
async create (data) {
|
|
80
78
|
const timestamp = now();
|
|
81
|
-
|
|
82
|
-
const contentHtml = data.content ? renderMarkdown(data.content) : null;
|
|
83
|
-
// Extract domain from source URL
|
|
84
|
-
const sourceDomain = data.sourceUrl ? extractDomain(data.sourceUrl) : null;
|
|
79
|
+
const bodyHtml = data.body ? renderMarkdown(data.body) : null;
|
|
85
80
|
// Handle thread relationship
|
|
86
81
|
let threadId = null;
|
|
87
|
-
let
|
|
82
|
+
let status = data.status ?? "published";
|
|
83
|
+
let featured = data.featured ?? false;
|
|
88
84
|
if (data.replyToId) {
|
|
89
85
|
const parent = await this.getById(data.replyToId);
|
|
90
86
|
if (parent) {
|
|
91
|
-
// thread_id = parent's thread_id or parent's id (if parent is root)
|
|
92
87
|
threadId = parent.threadId ?? parent.id;
|
|
93
|
-
// Inherit
|
|
88
|
+
// Inherit status and featured from root
|
|
94
89
|
const root = parent.threadId ? await this.getById(parent.threadId) : parent;
|
|
95
90
|
if (root) {
|
|
96
|
-
|
|
91
|
+
status = root.status;
|
|
92
|
+
featured = root.featured === 1;
|
|
97
93
|
}
|
|
98
94
|
}
|
|
99
95
|
}
|
|
100
96
|
const result = await db.insert(posts).values({
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
format: data.format,
|
|
98
|
+
status,
|
|
99
|
+
featured: featured ? 1 : 0,
|
|
100
|
+
pinned: data.pinned ? 1 : 0,
|
|
101
|
+
slug: data.slug ?? null,
|
|
103
102
|
title: data.title ?? null,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
url: data.url ?? null,
|
|
104
|
+
body: data.body ?? null,
|
|
105
|
+
bodyHtml,
|
|
106
|
+
quoteText: data.quoteText ?? null,
|
|
107
|
+
rating: data.rating ?? null,
|
|
108
|
+
collectionId: data.collectionId ?? null,
|
|
110
109
|
replyToId: data.replyToId ?? null,
|
|
111
110
|
threadId,
|
|
112
111
|
publishedAt: data.publishedAt ?? timestamp,
|
|
@@ -123,26 +122,27 @@ export function createPostService(db) {
|
|
|
123
122
|
const updates = {
|
|
124
123
|
updatedAt: timestamp
|
|
125
124
|
};
|
|
126
|
-
if (data.
|
|
125
|
+
if (data.format !== undefined) updates.format = data.format;
|
|
126
|
+
if (data.slug !== undefined) updates.slug = data.slug;
|
|
127
127
|
if (data.title !== undefined) updates.title = data.title;
|
|
128
|
-
if (data.
|
|
128
|
+
if (data.url !== undefined) updates.url = data.url;
|
|
129
|
+
if (data.quoteText !== undefined) updates.quoteText = data.quoteText;
|
|
130
|
+
if (data.rating !== undefined) updates.rating = data.rating;
|
|
131
|
+
if (data.collectionId !== undefined) updates.collectionId = data.collectionId;
|
|
129
132
|
if (data.publishedAt !== undefined) updates.publishedAt = data.publishedAt;
|
|
130
|
-
if (data.
|
|
131
|
-
|
|
132
|
-
updates.
|
|
133
|
-
|
|
134
|
-
if (data.sourceName !== undefined) updates.sourceName = data.sourceName;
|
|
135
|
-
if (data.content !== undefined) {
|
|
136
|
-
updates.content = data.content;
|
|
137
|
-
updates.contentHtml = data.content ? renderMarkdown(data.content) : null;
|
|
133
|
+
if (data.pinned !== undefined) updates.pinned = data.pinned ? 1 : 0;
|
|
134
|
+
if (data.body !== undefined) {
|
|
135
|
+
updates.body = data.body;
|
|
136
|
+
updates.bodyHtml = data.body ? renderMarkdown(data.body) : null;
|
|
138
137
|
}
|
|
139
|
-
// Handle
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
// Handle status/featured change - cascade to thread if this is root
|
|
139
|
+
const statusChanged = data.status !== undefined && data.status !== existing.status;
|
|
140
|
+
const featuredChanged = data.featured !== undefined && (data.featured ? 1 : 0) !== existing.featured;
|
|
141
|
+
if (statusChanged) updates.status = data.status;
|
|
142
|
+
if (featuredChanged) updates.featured = data.featured ? 1 : 0;
|
|
143
|
+
// If this is a root post and status/featured changed, cascade to thread
|
|
144
|
+
if ((statusChanged || featuredChanged) && !existing.threadId) {
|
|
145
|
+
await this.updateThreadStatusAndFeatured(id, data.status ?? existing.status, data.featured !== undefined ? data.featured : existing.featured === 1);
|
|
146
146
|
}
|
|
147
147
|
const result = await db.update(posts).set(updates).where(eq(posts.id, id)).returning();
|
|
148
148
|
return result[0] ? toPost(result[0]) : null;
|
|
@@ -158,7 +158,6 @@ export function createPostService(db) {
|
|
|
158
158
|
updatedAt: timestamp
|
|
159
159
|
}).where(or(eq(posts.id, id), eq(posts.threadId, id)));
|
|
160
160
|
} else {
|
|
161
|
-
// Just delete this single post
|
|
162
161
|
await db.update(posts).set({
|
|
163
162
|
deletedAt: timestamp,
|
|
164
163
|
updatedAt: timestamp
|
|
@@ -170,10 +169,11 @@ export function createPostService(db) {
|
|
|
170
169
|
const rows = await db.select().from(posts).where(and(or(eq(posts.id, rootId), eq(posts.threadId, rootId)), isNull(posts.deletedAt))).orderBy(posts.createdAt);
|
|
171
170
|
return rows.map(toPost);
|
|
172
171
|
},
|
|
173
|
-
async
|
|
172
|
+
async updateThreadStatusAndFeatured (rootId, status, featured) {
|
|
174
173
|
const timestamp = now();
|
|
175
174
|
await db.update(posts).set({
|
|
176
|
-
|
|
175
|
+
status,
|
|
176
|
+
featured: featured ? 1 : 0,
|
|
177
177
|
updatedAt: timestamp
|
|
178
178
|
}).where(eq(posts.threadId, rootId));
|
|
179
179
|
},
|
|
@@ -194,7 +194,6 @@ export function createPostService(db) {
|
|
|
194
194
|
async getThreadPreviews (rootIds, previewCount = 3) {
|
|
195
195
|
if (rootIds.length === 0) return new Map();
|
|
196
196
|
const rows = await db.select().from(posts).where(and(inArray(posts.threadId, rootIds), isNull(posts.deletedAt))).orderBy(posts.threadId, posts.createdAt);
|
|
197
|
-
// Partition by threadId, take first previewCount per thread
|
|
198
197
|
const result = new Map();
|
|
199
198
|
for (const row of rows){
|
|
200
199
|
const post = toPost(row);
|
package/dist/services/search.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Search Service
|
|
2
|
+
* Search Service (v2)
|
|
3
3
|
*
|
|
4
4
|
* Full-text search using FTS5
|
|
5
5
|
*/ export function createSearchService(d1) {
|
|
@@ -7,19 +7,21 @@
|
|
|
7
7
|
async search (query, options = {}) {
|
|
8
8
|
const limit = options.limit ?? 20;
|
|
9
9
|
const offset = options.offset ?? 0;
|
|
10
|
-
const
|
|
11
|
-
"
|
|
12
|
-
"quiet"
|
|
10
|
+
const status = options.status ?? [
|
|
11
|
+
"published"
|
|
13
12
|
];
|
|
14
13
|
// Escape and prepare the query for FTS5
|
|
15
|
-
// FTS5 uses * for prefix matching
|
|
16
14
|
const ftsQuery = query.trim().split(/\s+/).filter((term)=>term.length > 0).map((term)=>`"${term.replace(/"/g, '""')}"*`).join(" ");
|
|
17
15
|
if (!ftsQuery) {
|
|
18
16
|
return [];
|
|
19
17
|
}
|
|
20
|
-
// Build
|
|
21
|
-
const
|
|
22
|
-
//
|
|
18
|
+
// Build status placeholders
|
|
19
|
+
const statusPlaceholders = status.map(()=>"?").join(", ");
|
|
20
|
+
// Build format filter
|
|
21
|
+
const formatFilter = options.format ? "AND posts.format = ?" : "";
|
|
22
|
+
const formatParams = options.format ? [
|
|
23
|
+
options.format
|
|
24
|
+
] : [];
|
|
23
25
|
const stmt = d1.prepare(`
|
|
24
26
|
SELECT
|
|
25
27
|
posts.*,
|
|
@@ -29,23 +31,27 @@
|
|
|
29
31
|
JOIN posts ON posts.id = posts_fts.rowid
|
|
30
32
|
WHERE posts_fts MATCH ?
|
|
31
33
|
AND posts.deleted_at IS NULL
|
|
32
|
-
AND posts.
|
|
34
|
+
AND posts.status IN (${statusPlaceholders})
|
|
35
|
+
${formatFilter}
|
|
33
36
|
ORDER BY posts_fts.rank
|
|
34
37
|
LIMIT ? OFFSET ?
|
|
35
38
|
`);
|
|
36
|
-
const { results } = await stmt.bind(ftsQuery, ...
|
|
39
|
+
const { results } = await stmt.bind(ftsQuery, ...status, ...formatParams, limit, offset).all();
|
|
37
40
|
return (results || []).map((row)=>({
|
|
38
41
|
post: {
|
|
39
42
|
id: row.id,
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
format: row.format,
|
|
44
|
+
status: row.status,
|
|
45
|
+
featured: row.featured,
|
|
46
|
+
pinned: row.pinned,
|
|
47
|
+
slug: row.slug,
|
|
42
48
|
title: row.title,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
url: row.url,
|
|
50
|
+
body: row.body,
|
|
51
|
+
bodyHtml: row.body_html,
|
|
52
|
+
quoteText: row.quote_text,
|
|
53
|
+
rating: row.rating,
|
|
54
|
+
collectionId: row.collection_id,
|
|
49
55
|
replyToId: row.reply_to_id,
|
|
50
56
|
threadId: row.thread_id,
|
|
51
57
|
deletedAt: row.deleted_at,
|
|
@@ -1,107 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Media Gallery Component
|
|
3
3
|
*
|
|
4
|
-
* Renders media attachments
|
|
5
|
-
*
|
|
6
|
-
*/ import { jsx as _jsx
|
|
4
|
+
* Renders media attachments in a horizontal scrollable row,
|
|
5
|
+
* similar to Threads.net's image carousel.
|
|
6
|
+
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
7
7
|
export const MediaGallery = ({ attachments })=>{
|
|
8
8
|
const images = attachments.filter((a)=>a.mimeType.startsWith("image/"));
|
|
9
9
|
if (images.length === 0) return null;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const single = images.length === 1;
|
|
11
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
12
|
+
class: `mt-3 flex gap-2 ${single ? "" : "overflow-x-auto scroll-smooth snap-x snap-mandatory"}`,
|
|
13
|
+
style: single ? undefined : "scrollbar-width: none; -ms-overflow-style: none;",
|
|
14
|
+
children: images.map((img)=>{
|
|
15
|
+
const aspectRatio = img.width && img.height ? img.width / img.height : 4 / 3;
|
|
16
|
+
const itemWidth = single ? undefined : `${Math.round(320 * Math.min(Math.max(aspectRatio, 0.6), 1.6))}px`;
|
|
17
|
+
return /*#__PURE__*/ _jsx("a", {
|
|
16
18
|
href: img.url,
|
|
17
19
|
target: "_blank",
|
|
18
20
|
rel: "noopener noreferrer",
|
|
21
|
+
class: `${single ? "" : "shrink-0 snap-start"} block rounded-lg overflow-hidden`,
|
|
22
|
+
style: single ? undefined : {
|
|
23
|
+
width: itemWidth,
|
|
24
|
+
maxWidth: "85%"
|
|
25
|
+
},
|
|
19
26
|
children: /*#__PURE__*/ _jsx("img", {
|
|
20
27
|
src: img.thumbnailUrl,
|
|
21
28
|
alt: img.altText || "",
|
|
22
|
-
|
|
23
|
-
height: img.height ?? undefined,
|
|
24
|
-
class: "rounded-lg max-w-full h-auto",
|
|
29
|
+
class: single ? "rounded-lg max-w-full max-h-96 h-auto object-contain" : "h-80 w-full object-cover",
|
|
25
30
|
loading: "lazy"
|
|
26
31
|
})
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
if (images.length === 2) {
|
|
31
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
32
|
-
class: "mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden",
|
|
33
|
-
children: images.map((img)=>/*#__PURE__*/ _jsx("a", {
|
|
34
|
-
href: img.url,
|
|
35
|
-
target: "_blank",
|
|
36
|
-
rel: "noopener noreferrer",
|
|
37
|
-
class: "aspect-square",
|
|
38
|
-
children: /*#__PURE__*/ _jsx("img", {
|
|
39
|
-
src: img.thumbnailUrl,
|
|
40
|
-
alt: img.altText || "",
|
|
41
|
-
class: "w-full h-full object-cover",
|
|
42
|
-
loading: "lazy"
|
|
43
|
-
})
|
|
44
|
-
}, img.id))
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
if (images.length === 3) {
|
|
48
|
-
const [first, ...rest] = images;
|
|
49
|
-
if (!first) return null;
|
|
50
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
51
|
-
class: "mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden",
|
|
52
|
-
children: [
|
|
53
|
-
/*#__PURE__*/ _jsx("a", {
|
|
54
|
-
href: first.url,
|
|
55
|
-
target: "_blank",
|
|
56
|
-
rel: "noopener noreferrer",
|
|
57
|
-
class: "row-span-2",
|
|
58
|
-
children: /*#__PURE__*/ _jsx("img", {
|
|
59
|
-
src: first.thumbnailUrl,
|
|
60
|
-
alt: first.altText || "",
|
|
61
|
-
class: "w-full h-full object-cover",
|
|
62
|
-
loading: "lazy"
|
|
63
|
-
})
|
|
64
|
-
}),
|
|
65
|
-
rest.map((img)=>/*#__PURE__*/ _jsx("a", {
|
|
66
|
-
href: img.url,
|
|
67
|
-
target: "_blank",
|
|
68
|
-
rel: "noopener noreferrer",
|
|
69
|
-
class: "aspect-square",
|
|
70
|
-
children: /*#__PURE__*/ _jsx("img", {
|
|
71
|
-
src: img.thumbnailUrl,
|
|
72
|
-
alt: img.altText || "",
|
|
73
|
-
class: "w-full h-full object-cover",
|
|
74
|
-
loading: "lazy"
|
|
75
|
-
})
|
|
76
|
-
}, img.id))
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
// 4+ images: 2-column grid, show first 4 with remaining count
|
|
81
|
-
const shown = images.slice(0, 4);
|
|
82
|
-
const remaining = images.length - 4;
|
|
83
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
84
|
-
class: "mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden",
|
|
85
|
-
children: shown.map((img, i)=>/*#__PURE__*/ _jsxs("a", {
|
|
86
|
-
href: img.url,
|
|
87
|
-
target: "_blank",
|
|
88
|
-
rel: "noopener noreferrer",
|
|
89
|
-
class: "relative aspect-square",
|
|
90
|
-
children: [
|
|
91
|
-
/*#__PURE__*/ _jsx("img", {
|
|
92
|
-
src: img.thumbnailUrl,
|
|
93
|
-
alt: img.altText || "",
|
|
94
|
-
class: "w-full h-full object-cover",
|
|
95
|
-
loading: "lazy"
|
|
96
|
-
}),
|
|
97
|
-
i === 3 && remaining > 0 && /*#__PURE__*/ _jsxs("div", {
|
|
98
|
-
class: "absolute inset-0 bg-black/50 flex items-center justify-center text-white text-xl font-semibold",
|
|
99
|
-
children: [
|
|
100
|
-
"+",
|
|
101
|
-
remaining
|
|
102
|
-
]
|
|
103
|
-
})
|
|
104
|
-
]
|
|
105
|
-
}, img.id))
|
|
32
|
+
}, img.id);
|
|
33
|
+
})
|
|
106
34
|
});
|
|
107
35
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page Creation/Edit Form
|
|
3
3
|
*
|
|
4
|
-
* For managing
|
|
4
|
+
* For managing standalone pages (about, now, etc.)
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
7
|
export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
@@ -9,9 +9,9 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
9
9
|
const isEdit = !!page;
|
|
10
10
|
const signals = JSON.stringify({
|
|
11
11
|
title: page?.title ?? "",
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
slug: page?.slug ?? "",
|
|
13
|
+
body: page?.body ?? "",
|
|
14
|
+
status: page?.status ?? "published"
|
|
15
15
|
}).replace(/</g, "\\u003c");
|
|
16
16
|
return /*#__PURE__*/ _jsxs("form", {
|
|
17
17
|
"data-signals": signals,
|
|
@@ -50,8 +50,8 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
50
50
|
/*#__PURE__*/ _jsx("label", {
|
|
51
51
|
class: "label",
|
|
52
52
|
children: $__i18n._({
|
|
53
|
-
id: "
|
|
54
|
-
message: "
|
|
53
|
+
id: "L85WcV",
|
|
54
|
+
message: "Slug"
|
|
55
55
|
})
|
|
56
56
|
}),
|
|
57
57
|
/*#__PURE__*/ _jsxs("div", {
|
|
@@ -63,7 +63,7 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
63
63
|
}),
|
|
64
64
|
/*#__PURE__*/ _jsx("input", {
|
|
65
65
|
type: "text",
|
|
66
|
-
"data-bind": "
|
|
66
|
+
"data-bind": "slug",
|
|
67
67
|
class: "input flex-1",
|
|
68
68
|
placeholder: "about",
|
|
69
69
|
pattern: "[a-z0-9\\-]+",
|
|
@@ -95,14 +95,14 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
95
95
|
})
|
|
96
96
|
}),
|
|
97
97
|
/*#__PURE__*/ _jsx("textarea", {
|
|
98
|
-
"data-bind": "
|
|
98
|
+
"data-bind": "body",
|
|
99
99
|
class: "textarea min-h-48",
|
|
100
100
|
placeholder: $__i18n._({
|
|
101
101
|
id: "7G4SBz",
|
|
102
102
|
message: "Page content (Markdown supported)..."
|
|
103
103
|
}),
|
|
104
104
|
required: true,
|
|
105
|
-
children: page?.
|
|
105
|
+
children: page?.body ?? ""
|
|
106
106
|
})
|
|
107
107
|
]
|
|
108
108
|
}),
|
|
@@ -117,12 +117,12 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
117
117
|
})
|
|
118
118
|
}),
|
|
119
119
|
/*#__PURE__*/ _jsxs("select", {
|
|
120
|
-
"data-bind": "
|
|
120
|
+
"data-bind": "status",
|
|
121
121
|
class: "select",
|
|
122
122
|
children: [
|
|
123
123
|
/*#__PURE__*/ _jsx("option", {
|
|
124
|
-
value: "
|
|
125
|
-
selected: page?.
|
|
124
|
+
value: "published",
|
|
125
|
+
selected: page?.status === "published" || !page,
|
|
126
126
|
children: $__i18n._({
|
|
127
127
|
id: "u3wRF+",
|
|
128
128
|
message: "Published"
|
|
@@ -130,7 +130,7 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
130
130
|
}),
|
|
131
131
|
/*#__PURE__*/ _jsx("option", {
|
|
132
132
|
value: "draft",
|
|
133
|
-
selected: page?.
|
|
133
|
+
selected: page?.status === "draft",
|
|
134
134
|
children: $__i18n._({
|
|
135
135
|
id: "eneWvv",
|
|
136
136
|
message: "Draft"
|
|
@@ -141,8 +141,8 @@ export const PageForm = ({ page, action, cancelUrl = "/dash/pages" })=>{
|
|
|
141
141
|
/*#__PURE__*/ _jsx("p", {
|
|
142
142
|
class: "text-xs text-muted-foreground mt-1",
|
|
143
143
|
children: $__i18n._({
|
|
144
|
-
id: "
|
|
145
|
-
message: "Published pages are accessible via their
|
|
144
|
+
id: "jSRrXo",
|
|
145
|
+
message: "Published pages are accessible via their slug. Drafts are not visible."
|
|
146
146
|
})
|
|
147
147
|
})
|
|
148
148
|
]
|