@jant/core 0.2.11 → 0.2.12

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 (71) hide show
  1. package/bin/jant.js +1 -3
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/lib/image.d.ts.map +1 -1
  4. package/dist/lib/schemas.d.ts.map +1 -1
  5. package/dist/lib/sse.d.ts.map +1 -1
  6. package/dist/routes/api/upload.js +10 -2
  7. package/dist/routes/dash/collections.d.ts.map +1 -1
  8. package/dist/routes/dash/index.js +2 -1
  9. package/dist/routes/dash/pages.d.ts.map +1 -1
  10. package/dist/routes/dash/redirects.d.ts.map +1 -1
  11. package/dist/services/collection.d.ts.map +1 -1
  12. package/dist/services/post.d.ts.map +1 -1
  13. package/dist/theme/components/ActionButtons.d.ts.map +1 -1
  14. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
  15. package/dist/theme/components/EmptyState.d.ts.map +1 -1
  16. package/dist/theme/components/PageForm.d.ts.map +1 -1
  17. package/dist/theme/components/Pagination.d.ts.map +1 -1
  18. package/dist/theme/components/PostForm.d.ts.map +1 -1
  19. package/dist/theme/components/PostList.d.ts.map +1 -1
  20. package/dist/theme/components/ThreadView.d.ts.map +1 -1
  21. package/dist/theme/components/index.d.ts +1 -1
  22. package/dist/theme/components/index.d.ts.map +1 -1
  23. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  24. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  25. package/dist/types.d.ts.map +1 -1
  26. package/package.json +3 -14
  27. package/src/app.tsx +56 -12
  28. package/src/db/migrations/meta/0000_snapshot.json +16 -47
  29. package/src/db/migrations/meta/_journal.json +1 -1
  30. package/src/i18n/EXAMPLES.md +15 -13
  31. package/src/i18n/README.md +22 -18
  32. package/src/i18n/context.tsx +1 -1
  33. package/src/lib/image-processor.ts +2 -10
  34. package/src/lib/image.ts +1 -5
  35. package/src/lib/schemas.ts +6 -6
  36. package/src/lib/sse.ts +2 -8
  37. package/src/routes/api/posts.ts +4 -13
  38. package/src/routes/api/upload.ts +19 -8
  39. package/src/routes/dash/collections.tsx +102 -26
  40. package/src/routes/dash/index.tsx +5 -5
  41. package/src/routes/dash/media.tsx +51 -24
  42. package/src/routes/dash/pages.tsx +41 -21
  43. package/src/routes/dash/posts.tsx +12 -3
  44. package/src/routes/dash/redirects.tsx +53 -20
  45. package/src/routes/dash/settings.tsx +26 -6
  46. package/src/routes/pages/archive.tsx +19 -15
  47. package/src/routes/pages/collection.tsx +11 -2
  48. package/src/routes/pages/home.tsx +10 -3
  49. package/src/routes/pages/page.tsx +6 -5
  50. package/src/routes/pages/post.tsx +1 -4
  51. package/src/routes/pages/search.tsx +14 -8
  52. package/src/services/collection.ts +1 -5
  53. package/src/services/post.ts +1 -3
  54. package/src/theme/components/ActionButtons.tsx +6 -2
  55. package/src/theme/components/CrudPageHeader.tsx +4 -10
  56. package/src/theme/components/EmptyState.tsx +2 -11
  57. package/src/theme/components/PageForm.tsx +17 -9
  58. package/src/theme/components/Pagination.tsx +25 -40
  59. package/src/theme/components/PostForm.tsx +25 -8
  60. package/src/theme/components/PostList.tsx +17 -11
  61. package/src/theme/components/ThreadView.tsx +16 -19
  62. package/src/theme/components/index.ts +8 -1
  63. package/src/theme/layouts/BaseLayout.tsx +1 -3
  64. package/src/theme/layouts/DashLayout.tsx +32 -8
  65. package/src/types.ts +0 -2
  66. package/dist/plugin.d.ts +0 -3
  67. package/dist/plugin.d.ts.map +0 -1
  68. package/dist/plugin.js +0 -20
  69. package/dist/tailwind.d.ts +0 -12
  70. package/dist/tailwind.d.ts.map +0 -1
  71. package/dist/tailwind.js +0 -15
@@ -45,7 +45,10 @@ function SearchContent({
45
45
  type="search"
46
46
  name="q"
47
47
  class="input flex-1"
48
- placeholder={t({ message: "Search posts...", comment: "@context: Search input placeholder" })}
48
+ placeholder={t({
49
+ message: "Search posts...",
50
+ comment: "@context: Search input placeholder",
51
+ })}
49
52
  value={query}
50
53
  autofocus
51
54
  />
@@ -56,11 +59,7 @@ function SearchContent({
56
59
  </form>
57
60
 
58
61
  {/* Error */}
59
- {error && (
60
- <div class="p-4 rounded-lg bg-destructive/10 text-destructive mb-6">
61
- {error}
62
- </div>
63
- )}
62
+ {error && <div class="p-4 rounded-lg bg-destructive/10 text-destructive mb-6">{error}</div>}
64
63
 
65
64
  {/* Results */}
66
65
  {query && !error && (
@@ -69,8 +68,15 @@ function SearchContent({
69
68
  {results.length === 0
70
69
  ? t({ message: "No results found.", comment: "@context: Search empty results" })
71
70
  : results.length === 1
72
- ? t({ message: "Found 1 result", comment: "@context: Search results count - single" })
73
- : t({ message: "Found {count} results", comment: "@context: Search results count - multiple", values: { count: String(results.length) } })}
71
+ ? t({
72
+ message: "Found 1 result",
73
+ comment: "@context: Search results count - single",
74
+ })
75
+ : t({
76
+ message: "Found {count} results",
77
+ comment: "@context: Search results count - multiple",
78
+ values: { count: String(results.length) },
79
+ })}
74
80
  </p>
75
81
 
76
82
  {results.length > 0 && (
@@ -75,11 +75,7 @@ export function createCollectionService(db: Database): CollectionService {
75
75
  },
76
76
 
77
77
  async getByPath(path) {
78
- const result = await db
79
- .select()
80
- .from(collections)
81
- .where(eq(collections.path, path))
82
- .limit(1);
78
+ const result = await db.select().from(collections).where(eq(collections.path, path)).limit(1);
83
79
  return result[0] ? toCollection(result[0]) : null;
84
80
  },
85
81
 
@@ -239,9 +239,7 @@ export function createPostService(db: Database): PostService {
239
239
  const rows = await db
240
240
  .select()
241
241
  .from(posts)
242
- .where(
243
- and(or(eq(posts.id, rootId), eq(posts.threadId, rootId)), isNull(posts.deletedAt))
244
- )
242
+ .where(and(or(eq(posts.id, rootId), eq(posts.threadId, rootId)), isNull(posts.deletedAt)))
245
243
  .orderBy(posts.createdAt);
246
244
 
247
245
  return rows.map(toPost);
@@ -64,10 +64,14 @@ export const ActionButtons: FC<ActionButtonsProps> = ({
64
64
 
65
65
  const editClass = size === "sm" ? "btn-sm-outline" : "btn-outline";
66
66
  const viewClass = size === "sm" ? "btn-sm-ghost" : "btn-ghost";
67
- const deleteClass = size === "sm" ? "btn-sm-ghost text-destructive" : "btn-ghost text-destructive";
67
+ const deleteClass =
68
+ size === "sm" ? "btn-sm-ghost text-destructive" : "btn-ghost text-destructive";
68
69
 
69
70
  const defaultEditLabel = t({ message: "Edit", comment: "@context: Button to edit item" });
70
- const defaultViewLabel = t({ message: "View", comment: "@context: Button to view item on public site" });
71
+ const defaultViewLabel = t({
72
+ message: "View",
73
+ comment: "@context: Button to view item on public site",
74
+ });
71
75
  const defaultDeleteLabel = t({ message: "Delete", comment: "@context: Button to delete item" });
72
76
 
73
77
  return (
@@ -27,22 +27,16 @@ export interface CrudPageHeaderProps extends PropsWithChildren {
27
27
  // Optional children to render in place of default CTA button (useful for custom actions like upload buttons)
28
28
  }
29
29
 
30
- export const CrudPageHeader: FC<CrudPageHeaderProps> = ({
31
- title,
32
- ctaLabel,
33
- ctaHref,
34
- children,
35
- }) => {
30
+ export const CrudPageHeader: FC<CrudPageHeaderProps> = ({ title, ctaLabel, ctaHref, children }) => {
36
31
  return (
37
32
  <div class="flex items-center justify-between mb-6">
38
33
  <h1 class="text-2xl font-semibold">{title}</h1>
39
- {children || (
40
- ctaLabel && ctaHref && (
34
+ {children ||
35
+ (ctaLabel && ctaHref && (
41
36
  <a href={ctaHref} class="btn">
42
37
  {ctaLabel}
43
38
  </a>
44
- )
45
- )}
39
+ ))}
46
40
  </div>
47
41
  );
48
42
  };
@@ -29,18 +29,9 @@ export interface EmptyStateProps {
29
29
  centered?: boolean;
30
30
  }
31
31
 
32
- export const EmptyState: FC<EmptyStateProps> = ({
33
- message,
34
- ctaText,
35
- ctaHref,
36
- centered = true,
37
- }) => {
32
+ export const EmptyState: FC<EmptyStateProps> = ({ message, ctaText, ctaHref, centered = true }) => {
38
33
  if (!centered) {
39
- return (
40
- <p class="text-muted-foreground">
41
- {message}
42
- </p>
43
- );
34
+ return <p class="text-muted-foreground">{message}</p>;
44
35
  }
45
36
 
46
37
  return (
@@ -14,11 +14,7 @@ export interface PageFormProps {
14
14
  cancelUrl?: string;
15
15
  }
16
16
 
17
- export const PageForm: FC<PageFormProps> = ({
18
- page,
19
- action,
20
- cancelUrl = "/dash/pages",
21
- }) => {
17
+ export const PageForm: FC<PageFormProps> = ({ page, action, cancelUrl = "/dash/pages" }) => {
22
18
  const { t } = useLingui();
23
19
  const isEdit = !!page;
24
20
 
@@ -56,12 +52,18 @@ export const PageForm: FC<PageFormProps> = ({
56
52
  placeholder="about"
57
53
  value={page?.path ?? ""}
58
54
  pattern="[a-z0-9\-]+"
59
- title={t({ message: "Lowercase letters, numbers, and hyphens only", comment: "@context: Page path validation message" })}
55
+ title={t({
56
+ message: "Lowercase letters, numbers, and hyphens only",
57
+ comment: "@context: Page path validation message",
58
+ })}
60
59
  required
61
60
  />
62
61
  </div>
63
62
  <p class="text-xs text-muted-foreground mt-1">
64
- {t({ message: "The URL path for this page. Use lowercase letters, numbers, and hyphens.", comment: "@context: Page path helper text" })}
63
+ {t({
64
+ message: "The URL path for this page. Use lowercase letters, numbers, and hyphens.",
65
+ comment: "@context: Page path helper text",
66
+ })}
65
67
  </p>
66
68
  </div>
67
69
 
@@ -73,7 +75,10 @@ export const PageForm: FC<PageFormProps> = ({
73
75
  <textarea
74
76
  name="content"
75
77
  class="textarea min-h-48"
76
- placeholder={t({ message: "Page content (Markdown supported)...", comment: "@context: Page content placeholder" })}
78
+ placeholder={t({
79
+ message: "Page content (Markdown supported)...",
80
+ comment: "@context: Page content placeholder",
81
+ })}
77
82
  required
78
83
  >
79
84
  {page?.content ?? ""}
@@ -94,7 +99,10 @@ export const PageForm: FC<PageFormProps> = ({
94
99
  </option>
95
100
  </select>
96
101
  <p class="text-xs text-muted-foreground mt-1">
97
- {t({ message: "Published pages are accessible via their path. Drafts are not visible.", comment: "@context: Page status helper text" })}
102
+ {t({
103
+ message: "Published pages are accessible via their path. Drafts are not visible.",
104
+ comment: "@context: Page status helper text",
105
+ })}
98
106
  </p>
99
107
  </div>
100
108
 
@@ -42,38 +42,31 @@ export const Pagination: FC<PaginationProps> = ({
42
42
  return `${url.pathname}${url.search}`;
43
43
  };
44
44
 
45
- const prevText = t({ message: "Previous", comment: "@context: Pagination button - previous page" });
45
+ const prevText = t({
46
+ message: "Previous",
47
+ comment: "@context: Pagination button - previous page",
48
+ });
46
49
  const nextText = t({ message: "Next", comment: "@context: Pagination button - next page" });
47
50
 
48
51
  return (
49
52
  <nav class="flex items-center justify-between py-4" aria-label="Pagination">
50
53
  <div>
51
54
  {hasPrev ? (
52
- <a
53
- href={buildUrl(prevCursor)}
54
- class="btn-outline text-sm"
55
- >
55
+ <a href={buildUrl(prevCursor)} class="btn-outline text-sm">
56
56
  ← {prevText}
57
57
  </a>
58
58
  ) : (
59
- <span class="btn-outline text-sm opacity-50 cursor-not-allowed">
60
- ← {prevText}
61
- </span>
59
+ <span class="btn-outline text-sm opacity-50 cursor-not-allowed">← {prevText}</span>
62
60
  )}
63
61
  </div>
64
62
 
65
63
  <div>
66
64
  {hasNext ? (
67
- <a
68
- href={buildUrl(nextCursor)}
69
- class="btn-outline text-sm"
70
- >
65
+ <a href={buildUrl(nextCursor)} class="btn-outline text-sm">
71
66
  {nextText} →
72
67
  </a>
73
68
  ) : (
74
- <span class="btn-outline text-sm opacity-50 cursor-not-allowed">
75
- {nextText} →
76
- </span>
69
+ <span class="btn-outline text-sm opacity-50 cursor-not-allowed">{nextText} →</span>
77
70
  )}
78
71
  </div>
79
72
  </nav>
@@ -92,17 +85,14 @@ export interface LoadMoreProps {
92
85
  text?: string;
93
86
  }
94
87
 
95
- export const LoadMore: FC<LoadMoreProps> = ({
96
- href,
97
- hasMore,
98
- text,
99
- }) => {
88
+ export const LoadMore: FC<LoadMoreProps> = ({ href, hasMore, text }) => {
100
89
  const { t } = useLingui();
101
90
  if (!hasMore) {
102
91
  return null;
103
92
  }
104
93
 
105
- const buttonText = text ?? t({ message: "Load more", comment: "@context: Pagination button - load more items" });
94
+ const buttonText =
95
+ text ?? t({ message: "Load more", comment: "@context: Pagination button - load more items" });
106
96
 
107
97
  return (
108
98
  <div class="text-center py-4">
@@ -152,43 +142,38 @@ export const PagePagination: FC<PagePaginationProps> = ({
152
142
  return `${url.pathname}${url.search}`;
153
143
  };
154
144
 
155
- const prevText = t({ message: "Previous", comment: "@context: Pagination button - previous page" });
145
+ const prevText = t({
146
+ message: "Previous",
147
+ comment: "@context: Pagination button - previous page",
148
+ });
156
149
  const nextText = t({ message: "Next", comment: "@context: Pagination button - next page" });
157
- const pageText = t({ message: "Page {page}", comment: "@context: Pagination - current page indicator", values: { page: String(currentPage) } });
150
+ const pageText = t({
151
+ message: "Page {page}",
152
+ comment: "@context: Pagination - current page indicator",
153
+ values: { page: String(currentPage) },
154
+ });
158
155
 
159
156
  return (
160
157
  <nav class="flex items-center justify-between py-4" aria-label="Pagination">
161
158
  <div>
162
159
  {hasPrev ? (
163
- <a
164
- href={buildUrl(currentPage - 1)}
165
- class="btn-outline text-sm"
166
- >
160
+ <a href={buildUrl(currentPage - 1)} class="btn-outline text-sm">
167
161
  ← {prevText}
168
162
  </a>
169
163
  ) : (
170
- <span class="btn-outline text-sm opacity-50 cursor-not-allowed">
171
- ← {prevText}
172
- </span>
164
+ <span class="btn-outline text-sm opacity-50 cursor-not-allowed">← {prevText}</span>
173
165
  )}
174
166
  </div>
175
167
 
176
- <span class="text-sm text-muted-foreground">
177
- {pageText}
178
- </span>
168
+ <span class="text-sm text-muted-foreground">{pageText}</span>
179
169
 
180
170
  <div>
181
171
  {hasNext ? (
182
- <a
183
- href={buildUrl(currentPage + 1)}
184
- class="btn-outline text-sm"
185
- >
172
+ <a href={buildUrl(currentPage + 1)} class="btn-outline text-sm">
186
173
  {nextText} →
187
174
  </a>
188
175
  ) : (
189
- <span class="btn-outline text-sm opacity-50 cursor-not-allowed">
190
- {nextText} →
191
- </span>
176
+ <span class="btn-outline text-sm opacity-50 cursor-not-allowed">{nextText} →</span>
192
177
  )}
193
178
  </div>
194
179
  </nav>
@@ -20,7 +20,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
20
20
  <form method={method} action={action} class="flex flex-col gap-4">
21
21
  {/* Type selector */}
22
22
  <div class="field">
23
- <label class="label">{t({ message: "Type", comment: "@context: Post form field - post type" })}</label>
23
+ <label class="label">
24
+ {t({ message: "Type", comment: "@context: Post form field - post type" })}
25
+ </label>
24
26
  <select name="type" class="select" required>
25
27
  <option value="note" selected={post?.type === "note"}>
26
28
  {t({ message: "Note", comment: "@context: Post type option" })}
@@ -42,7 +44,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
42
44
 
43
45
  {/* Title (optional) */}
44
46
  <div class="field">
45
- <label class="label">{t({ message: "Title (optional)", comment: "@context: Post form field" })}</label>
47
+ <label class="label">
48
+ {t({ message: "Title (optional)", comment: "@context: Post form field" })}
49
+ </label>
46
50
  <input
47
51
  type="text"
48
52
  name="title"
@@ -54,11 +58,16 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
54
58
 
55
59
  {/* Content */}
56
60
  <div class="field">
57
- <label class="label">{t({ message: "Content", comment: "@context: Post form field" })}</label>
61
+ <label class="label">
62
+ {t({ message: "Content", comment: "@context: Post form field" })}
63
+ </label>
58
64
  <textarea
59
65
  name="content"
60
66
  class="textarea min-h-32"
61
- placeholder={t({ message: "What's on your mind?", comment: "@context: Post content placeholder" })}
67
+ placeholder={t({
68
+ message: "What's on your mind?",
69
+ comment: "@context: Post content placeholder",
70
+ })}
62
71
  required
63
72
  >
64
73
  {post?.content ?? ""}
@@ -67,7 +76,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
67
76
 
68
77
  {/* Source URL (for link/quote types) */}
69
78
  <div class="field">
70
- <label class="label">{t({ message: "Source URL (optional)", comment: "@context: Post form field" })}</label>
79
+ <label class="label">
80
+ {t({ message: "Source URL (optional)", comment: "@context: Post form field" })}
81
+ </label>
71
82
  <input
72
83
  type="url"
73
84
  name="sourceUrl"
@@ -79,7 +90,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
79
90
 
80
91
  {/* Visibility */}
81
92
  <div class="field">
82
- <label class="label">{t({ message: "Visibility", comment: "@context: Post form field" })}</label>
93
+ <label class="label">
94
+ {t({ message: "Visibility", comment: "@context: Post form field" })}
95
+ </label>
83
96
  <select name="visibility" class="select">
84
97
  <option value="quiet" selected={post?.visibility === "quiet" || !post}>
85
98
  {t({ message: "Quiet (normal)", comment: "@context: Post visibility option" })}
@@ -98,7 +111,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
98
111
 
99
112
  {/* Custom path (optional) */}
100
113
  <div class="field">
101
- <label class="label">{t({ message: "Custom Path (optional)", comment: "@context: Post form field" })}</label>
114
+ <label class="label">
115
+ {t({ message: "Custom Path (optional)", comment: "@context: Post form field" })}
116
+ </label>
102
117
  <input
103
118
  type="text"
104
119
  name="path"
@@ -111,7 +126,9 @@ export const PostForm: FC<PostFormProps> = ({ post, action, method = "post" }) =
111
126
  {/* Submit */}
112
127
  <div class="flex gap-2">
113
128
  <button type="submit" class="btn">
114
- {isEdit ? t({ message: "Update", comment: "@context: Button to update existing post" }) : t({ message: "Publish", comment: "@context: Button to publish new post" })}
129
+ {isEdit
130
+ ? t({ message: "Update", comment: "@context: Button to update existing post" })
131
+ : t({ message: "Publish", comment: "@context: Button to publish new post" })}
115
132
  </button>
116
133
  <a href="/dash/posts" class="btn-outline">
117
134
  {t({ message: "Cancel", comment: "@context: Button to cancel form" })}
@@ -22,8 +22,14 @@ export const PostList: FC<PostListProps> = ({ posts }) => {
22
22
  if (posts.length === 0) {
23
23
  return (
24
24
  <EmptyState
25
- message={t({ message: "No posts yet.", comment: "@context: Empty state message when no posts exist" })}
26
- ctaText={t({ message: "Create your first post", comment: "@context: Button in empty state to create first post" })}
25
+ message={t({
26
+ message: "No posts yet.",
27
+ comment: "@context: Empty state message when no posts exist",
28
+ })}
29
+ ctaText={t({
30
+ message: "Create your first post",
31
+ comment: "@context: Button in empty state to create first post",
32
+ })}
27
33
  ctaHref="/dash/posts/new"
28
34
  />
29
35
  );
@@ -39,22 +45,22 @@ export const PostList: FC<PostListProps> = ({ posts }) => {
39
45
  editHref={`/dash/posts/${sqid.encode(post.id)}/edit`}
40
46
  editLabel={t({ message: "Edit", comment: "@context: Button to edit post" })}
41
47
  viewHref={`/p/${sqid.encode(post.id)}`}
42
- viewLabel={t({ message: "View", comment: "@context: Button to view post on public site" })}
48
+ viewLabel={t({
49
+ message: "View",
50
+ comment: "@context: Button to view post on public site",
51
+ })}
43
52
  />
44
53
  }
45
54
  >
46
55
  <div class="flex items-center gap-2 mb-1">
47
56
  <TypeBadge type={post.type} />
48
57
  <VisibilityBadge visibility={post.visibility} />
49
- <span class="text-xs text-muted-foreground">
50
- {time.formatDate(post.publishedAt)}
51
- </span>
58
+ <span class="text-xs text-muted-foreground">{time.formatDate(post.publishedAt)}</span>
52
59
  </div>
53
- <a
54
- href={`/dash/posts/${sqid.encode(post.id)}`}
55
- class="font-medium hover:underline"
56
- >
57
- {post.title || post.content?.slice(0, 60) || t({ message: "Untitled", comment: "@context: Default title for untitled post" })}
60
+ <a href={`/dash/posts/${sqid.encode(post.id)}`} class="font-medium hover:underline">
61
+ {post.title ||
62
+ post.content?.slice(0, 60) ||
63
+ t({ message: "Untitled", comment: "@context: Default title for untitled post" })}
58
64
  </a>
59
65
  {post.content && !post.title && (
60
66
  <p class="text-sm text-muted-foreground mt-1 line-clamp-2">
@@ -51,14 +51,14 @@ const ThreadPost: FC<{
51
51
  </time>
52
52
  {isRoot && (
53
53
  <span class="text-xs">
54
- {t({ message: "Thread start", comment: "@context: Thread view indicator - first post in thread" })}
54
+ {t({
55
+ message: "Thread start",
56
+ comment: "@context: Thread view indicator - first post in thread",
57
+ })}
55
58
  </span>
56
59
  )}
57
60
  {!isCurrent && (
58
- <a
59
- href={`/p/${sqid.encode(post.id)}`}
60
- class="text-xs hover:underline"
61
- >
61
+ <a href={`/p/${sqid.encode(post.id)}`} class="text-xs hover:underline">
62
62
  {t({ message: "Permalink", comment: "@context: Link to individual post in thread" })}
63
63
  </a>
64
64
  )}
@@ -84,32 +84,29 @@ export const ThreadView: FC<ThreadViewProps> = ({ posts, currentPostId }) => {
84
84
  );
85
85
  }
86
86
 
87
- const threadLabel = posts.length === 1
88
- ? t({ message: "Thread with 1 post", comment: "@context: Thread view header - single post" })
89
- : t({ message: "Thread with {count} posts", comment: "@context: Thread view header - multiple posts", values: { count: String(posts.length) } });
87
+ const threadLabel =
88
+ posts.length === 1
89
+ ? t({ message: "Thread with 1 post", comment: "@context: Thread view header - single post" })
90
+ : t({
91
+ message: "Thread with {count} posts",
92
+ comment: "@context: Thread view header - multiple posts",
93
+ values: { count: String(posts.length) },
94
+ });
90
95
 
91
96
  return (
92
97
  <div class="thread-view">
93
- <div class="mb-4 text-sm text-muted-foreground">
94
- {threadLabel}
95
- </div>
98
+ <div class="mb-4 text-sm text-muted-foreground">{threadLabel}</div>
96
99
 
97
100
  <div class="flex flex-col gap-3">
98
101
  {posts.map((post, index) => (
99
102
  <div key={post.id} class="relative">
100
103
  {/* Connection line */}
101
- {index > 0 && (
102
- <div class="absolute left-6 -top-3 w-0.5 h-3 bg-border" />
103
- )}
104
+ {index > 0 && <div class="absolute left-6 -top-3 w-0.5 h-3 bg-border" />}
104
105
  {index < posts.length - 1 && (
105
106
  <div class="absolute left-6 -bottom-3 w-0.5 h-3 bg-border" />
106
107
  )}
107
108
 
108
- <ThreadPost
109
- post={post}
110
- isCurrent={post.id === currentPostId}
111
- isRoot={index === 0}
112
- />
109
+ <ThreadPost post={post} isCurrent={post.id === currentPostId} isRoot={index === 0} />
113
110
  </div>
114
111
  ))}
115
112
  </div>
@@ -4,7 +4,14 @@ export { DangerZone, type DangerZoneProps } from "./DangerZone.js";
4
4
  export { EmptyState, type EmptyStateProps } from "./EmptyState.js";
5
5
  export { ListItemRow, type ListItemRowProps } from "./ListItemRow.js";
6
6
  export { PageForm, type PageFormProps } from "./PageForm.js";
7
- export { Pagination, LoadMore, PagePagination, type PaginationProps, type LoadMoreProps, type PagePaginationProps } from "./Pagination.js";
7
+ export {
8
+ Pagination,
9
+ LoadMore,
10
+ PagePagination,
11
+ type PaginationProps,
12
+ type LoadMoreProps,
13
+ type PagePaginationProps,
14
+ } from "./Pagination.js";
8
15
  export { PostForm, type PostFormProps } from "./PostForm.js";
9
16
  export { PostList, type PostListProps } from "./PostList.js";
10
17
  export { ThreadView, type ThreadViewProps } from "./ThreadView.js";
@@ -40,9 +40,7 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
40
40
  <Link href="/src/style.css" rel="stylesheet" />
41
41
  <Script src="/src/client.ts" />
42
42
  </head>
43
- <body class="bg-background text-foreground antialiased">
44
- {content}
45
- </body>
43
+ <body class="bg-background text-foreground antialiased">{content}</body>
46
44
  </html>
47
45
  );
48
46
  };
@@ -46,7 +46,10 @@ function DashLayoutContent({
46
46
  </a>
47
47
  <nav class="flex items-center gap-4">
48
48
  <a href="/" class="text-sm text-muted-foreground hover:text-foreground">
49
- {t({ message: "View Site", comment: "@context: Dashboard header link to view the public site" })}
49
+ {t({
50
+ message: "View Site",
51
+ comment: "@context: Dashboard header link to view the public site",
52
+ })}
50
53
  </a>
51
54
  <a href="/signout" class="text-sm text-muted-foreground hover:text-foreground">
52
55
  {t({ message: "Sign Out", comment: "@context: Dashboard header link to sign out" })}
@@ -61,25 +64,46 @@ function DashLayoutContent({
61
64
  <aside class="w-48 shrink-0">
62
65
  <nav class="flex flex-col gap-1">
63
66
  <a href="/dash" class={navClass("/dash", /^\/dash$/)}>
64
- {t({ message: "Dashboard", comment: "@context: Dashboard navigation - main dashboard page" })}
67
+ {t({
68
+ message: "Dashboard",
69
+ comment: "@context: Dashboard navigation - main dashboard page",
70
+ })}
65
71
  </a>
66
72
  <a href="/dash/posts" class={navClass("/dash/posts", /^\/dash\/posts/)}>
67
- {t({ message: "Posts", comment: "@context: Dashboard navigation - posts management" })}
73
+ {t({
74
+ message: "Posts",
75
+ comment: "@context: Dashboard navigation - posts management",
76
+ })}
68
77
  </a>
69
78
  <a href="/dash/pages" class={navClass("/dash/pages", /^\/dash\/pages/)}>
70
- {t({ message: "Pages", comment: "@context: Dashboard navigation - pages management" })}
79
+ {t({
80
+ message: "Pages",
81
+ comment: "@context: Dashboard navigation - pages management",
82
+ })}
71
83
  </a>
72
84
  <a href="/dash/media" class={navClass("/dash/media", /^\/dash\/media/)}>
73
85
  {t({ message: "Media", comment: "@context: Dashboard navigation - media library" })}
74
86
  </a>
75
- <a href="/dash/collections" class={navClass("/dash/collections", /^\/dash\/collections/)}>
76
- {t({ message: "Collections", comment: "@context: Dashboard navigation - collections management" })}
87
+ <a
88
+ href="/dash/collections"
89
+ class={navClass("/dash/collections", /^\/dash\/collections/)}
90
+ >
91
+ {t({
92
+ message: "Collections",
93
+ comment: "@context: Dashboard navigation - collections management",
94
+ })}
77
95
  </a>
78
96
  <a href="/dash/redirects" class={navClass("/dash/redirects", /^\/dash\/redirects/)}>
79
- {t({ message: "Redirects", comment: "@context: Dashboard navigation - URL redirects" })}
97
+ {t({
98
+ message: "Redirects",
99
+ comment: "@context: Dashboard navigation - URL redirects",
100
+ })}
80
101
  </a>
81
102
  <a href="/dash/settings" class={navClass("/dash/settings", /^\/dash\/settings/)}>
82
- {t({ message: "Settings", comment: "@context: Dashboard navigation - site settings" })}
103
+ {t({
104
+ message: "Settings",
105
+ comment: "@context: Dashboard navigation - site settings",
106
+ })}
83
107
  </a>
84
108
  </nav>
85
109
  </aside>
package/src/types.ts CHANGED
@@ -12,7 +12,6 @@ export type PostType = (typeof POST_TYPES)[number];
12
12
  export const VISIBILITY_LEVELS = ["featured", "quiet", "unlisted", "draft"] as const;
13
13
  export type Visibility = (typeof VISIBILITY_LEVELS)[number];
14
14
 
15
-
16
15
  // =============================================================================
17
16
  // Cloudflare Bindings
18
17
  // =============================================================================
@@ -26,7 +25,6 @@ export interface Bindings {
26
25
  IMAGE_TRANSFORM_URL?: string;
27
26
  }
28
27
 
29
-
30
28
  // =============================================================================
31
29
  // Entity Types
32
30
  // =============================================================================
package/dist/plugin.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { Config } from "tailwindcss";
2
- export declare const jantPlugin: NonNullable<Config["plugins"]>[number];
3
- //# sourceMappingURL=plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiB1C,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAO7D,CAAC"}