@jant/core 0.3.39 → 0.3.40

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 (39) hide show
  1. package/README.md +1 -0
  2. package/dist/{app-BfoG98VD.js → app-CAtsuLLh.js} +306 -72
  3. package/dist/client/_assets/client-auth.js +77 -68
  4. package/dist/client/_assets/client.css +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/node.js +2 -2
  7. package/package.json +1 -1
  8. package/src/app.tsx +5 -0
  9. package/src/client/components/__tests__/jant-collection-sidebar.test.ts +56 -0
  10. package/src/client/components/jant-collection-form.ts +2 -0
  11. package/src/client/components/jant-collection-sidebar.ts +19 -6
  12. package/src/i18n/locales/en.po +14 -0
  13. package/src/i18n/locales/en.ts +1 -1
  14. package/src/i18n/locales/zh-Hans.po +14 -0
  15. package/src/i18n/locales/zh-Hans.ts +1 -1
  16. package/src/i18n/locales/zh-Hant.po +14 -0
  17. package/src/i18n/locales/zh-Hant.ts +1 -1
  18. package/src/lib/__tests__/collection-groups.test.ts +63 -0
  19. package/src/lib/__tests__/constants.test.ts +23 -1
  20. package/src/lib/__tests__/schemas.test.ts +18 -0
  21. package/src/lib/__tests__/slug-format.test.ts +8 -0
  22. package/src/lib/__tests__/timeline.test.ts +84 -2
  23. package/src/lib/collection-groups.ts +69 -0
  24. package/src/lib/constants.ts +20 -0
  25. package/src/lib/schemas.ts +8 -1
  26. package/src/lib/slug-format.ts +8 -0
  27. package/src/lib/timeline.ts +30 -23
  28. package/src/routes/pages/collection.tsx +89 -37
  29. package/src/runtime/__tests__/readiness.test.ts +89 -0
  30. package/src/runtime/readiness.ts +129 -0
  31. package/src/services/__tests__/collection.test.ts +45 -0
  32. package/src/services/__tests__/post-timeline.test.ts +58 -0
  33. package/src/services/__tests__/post.test.ts +35 -1
  34. package/src/services/collection.ts +55 -0
  35. package/src/services/post.ts +121 -12
  36. package/src/styles/ui.css +17 -0
  37. package/src/types/props.ts +2 -1
  38. package/src/ui/pages/CollectionPage.tsx +84 -17
  39. package/src/ui/shared/CollectionDirectory.tsx +18 -4
@@ -16,11 +16,12 @@ const escapeJson = (data: unknown) =>
16
16
  JSON.stringify(data).replace(/</g, "\\u003c");
17
17
 
18
18
  export const CollectionPage: FC<CollectionPageProps> = ({
19
- collection,
19
+ collections,
20
20
  items,
21
21
  totalThreadCount,
22
22
  currentPage,
23
23
  totalPages,
24
+ pagePath,
24
25
  baseUrl,
25
26
  currentSort,
26
27
  defaultSort,
@@ -28,14 +29,24 @@ export const CollectionPage: FC<CollectionPageProps> = ({
28
29
  isAuthenticated,
29
30
  sitePathPrefix = "",
30
31
  }) => {
32
+ const primaryCollection = collections[0];
33
+ if (!primaryCollection) return null;
34
+
31
35
  const { t } = useLingui();
32
- const collectionUrl = toPublicPath(`/c/${collection.slug}`, sitePathPrefix);
36
+ const isAggregate = collections.length > 1;
37
+ const selectionTitle = collections
38
+ .map((collection) => collection.title)
39
+ .join(" + ");
40
+ const collectionUrl = toPublicPath(pagePath, sitePathPrefix);
33
41
  const editCollectionUrl = toPublicPath(
34
- `/c/${collection.slug}/edit?returnTo=${encodeURIComponent(collectionUrl)}`,
42
+ `/c/${primaryCollection.slug}/edit?returnTo=${encodeURIComponent(collectionUrl)}`,
35
43
  sitePathPrefix,
36
44
  );
37
- const sortTriggerId = `collection-sort-trigger-${collection.id}`;
38
- const sortPopoverId = `collection-sort-popover-${collection.id}`;
45
+ const sortUiId = isAggregate
46
+ ? collections.map((collection) => collection.slug).join("-")
47
+ : primaryCollection.id;
48
+ const sortTriggerId = `collection-sort-trigger-${sortUiId}`;
49
+ const sortPopoverId = `collection-sort-popover-${sortUiId}`;
39
50
  const pageLabel =
40
51
  currentPage > 1 ? formatPageLabel(currentPage, totalPages) : null;
41
52
  const mutationLabels = getCollectionMutationLabels(t);
@@ -125,7 +136,15 @@ export const CollectionPage: FC<CollectionPageProps> = ({
125
136
  sortOptions[0].label;
126
137
 
127
138
  return (
128
- <div class="py-6" data-page="collection" data-collection-id={collection.id}>
139
+ <div
140
+ class="py-6"
141
+ data-page="collection"
142
+ data-collection-mode={isAggregate ? "aggregate" : "single"}
143
+ data-collection-id={isAggregate ? undefined : primaryCollection.id}
144
+ data-collection-slugs={collections
145
+ .map((collection) => collection.slug)
146
+ .join(",")}
147
+ >
129
148
  <header class="collection-page-header">
130
149
  <div class="collection-page-topbar">
131
150
  <nav
@@ -160,7 +179,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
160
179
  </svg>
161
180
  </li>
162
181
  <li>
163
- <span>{collection.title}</span>
182
+ <span>{selectionTitle}</span>
164
183
  </li>
165
184
  </ol>
166
185
  </nav>
@@ -168,16 +187,56 @@ export const CollectionPage: FC<CollectionPageProps> = ({
168
187
 
169
188
  <div class="collection-page-title-block">
170
189
  <h1 class="collection-page-title">
171
- <span>{collection.title}</span>
190
+ <span>{selectionTitle}</span>
172
191
  </h1>
173
- {collection.description ? (
174
- <p class="collection-page-description">{collection.description}</p>
192
+ {!isAggregate && primaryCollection.description ? (
193
+ <p class="collection-page-description">
194
+ {primaryCollection.description}
195
+ </p>
196
+ ) : null}
197
+ {isAggregate ? (
198
+ <div class="collection-page-meta">
199
+ <span>
200
+ {t({
201
+ message: "Includes",
202
+ comment:
203
+ "@context: Label above the included collections list on an aggregate collection page",
204
+ })}
205
+ </span>{" "}
206
+ {collections.map((collection, index) => (
207
+ <span key={collection.id}>
208
+ {index > 0 ? <span>, </span> : null}
209
+ <a
210
+ href={toPublicPath(`/c/${collection.slug}`, sitePathPrefix)}
211
+ >
212
+ {collection.title}
213
+ </a>
214
+ </span>
215
+ ))}
216
+ </div>
175
217
  ) : null}
176
218
  </div>
177
219
 
178
220
  <div class="collection-page-subhead">
179
221
  <div class="collection-page-meta-row">
180
222
  <p class="collection-page-meta">
223
+ {isAggregate ? (
224
+ <>
225
+ {collections.length}{" "}
226
+ {collections.length === 1
227
+ ? t({
228
+ message: "collection",
229
+ comment:
230
+ "@context: Singular collection count label on an aggregate collection page",
231
+ })
232
+ : t({
233
+ message: "collections",
234
+ comment:
235
+ "@context: Plural collection count label on an aggregate collection page",
236
+ })}
237
+ <span> / </span>
238
+ </>
239
+ ) : null}
181
240
  {totalThreadCount}{" "}
182
241
  {totalThreadCount === 1
183
242
  ? t({
@@ -291,14 +350,14 @@ export const CollectionPage: FC<CollectionPageProps> = ({
291
350
  </div>
292
351
  </div>
293
352
 
294
- {isAuthenticated ? (
353
+ {isAuthenticated && !isAggregate ? (
295
354
  <div class="collection-page-owner-tools">
296
355
  <button
297
356
  type="button"
298
357
  class="collection-page-compose-trigger"
299
358
  aria-label={newPostLabel}
300
359
  title={newPostLabel}
301
- data-on:click={`document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew(${escapeJson({ collectionId: collection.id })})`}
360
+ data-on:click={`document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew(${escapeJson({ collectionId: primaryCollection.id })})`}
302
361
  >
303
362
  <svg
304
363
  xmlns="http://www.w3.org/2000/svg"
@@ -320,7 +379,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
320
379
  <div
321
380
  class="collection-page-manage"
322
381
  data-collection-page-actions
323
- data-collection-id={collection.id}
382
+ data-collection-id={primaryCollection.id}
324
383
  data-collection-page-labels={escapeJson(mutationLabels)}
325
384
  data-collection-page-redirect-url={toPublicPath(
326
385
  "/c",
@@ -424,10 +483,18 @@ export const CollectionPage: FC<CollectionPageProps> = ({
424
483
  <main>
425
484
  {items.length === 0 ? (
426
485
  <p class="text-muted-foreground">
427
- {t({
428
- message: "This collection is empty. Add posts from the editor.",
429
- comment: "@context: Empty state message",
430
- })}
486
+ {isAggregate
487
+ ? t({
488
+ message:
489
+ "Nothing here yet. Add posts to one of these collections to fill this view.",
490
+ comment:
491
+ "@context: Empty state message on an aggregate collection page",
492
+ })
493
+ : t({
494
+ message:
495
+ "This collection is empty. Add posts from the editor.",
496
+ comment: "@context: Empty state message",
497
+ })}
431
498
  </p>
432
499
  ) : (
433
500
  <TimelineFeed
@@ -1,6 +1,7 @@
1
1
  import type { FC } from "hono/jsx";
2
2
  import { useLingui } from "@lingui/react/macro";
3
3
  import type { CollectionDirectoryItem } from "../../types.js";
4
+ import { getDividerCollectionGroup } from "../../lib/collection-groups.js";
4
5
  import { formatRelativeAge, toISOString } from "../../lib/time.js";
5
6
  import { toPublicPath } from "../../lib/url.js";
6
7
 
@@ -39,9 +40,10 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
39
40
 
40
41
  return (
41
42
  <div class="collection-directory">
42
- {items.map((item) => {
43
+ {items.map((item, index) => {
43
44
  if (item.type === "divider" || !item.collection) {
44
45
  const hasLabel = !!item.label;
46
+ const group = getDividerCollectionGroup(items, index);
45
47
  return (
46
48
  <div key={item.id} class="collection-directory-divider">
47
49
  <div
@@ -50,9 +52,21 @@ export const CollectionDirectory: FC<CollectionDirectoryProps> = ({
50
52
  >
51
53
  {hasLabel ? (
52
54
  <>
53
- <span class="collection-directory-divider-text">
54
- {item.label}
55
- </span>
55
+ {group ? (
56
+ <a
57
+ href={toPublicPath(
58
+ `/c/${group.slugExpression}`,
59
+ sitePathPrefix,
60
+ )}
61
+ class="collection-directory-divider-link collection-directory-divider-text"
62
+ >
63
+ {item.label}
64
+ </a>
65
+ ) : (
66
+ <span class="collection-directory-divider-text">
67
+ {item.label}
68
+ </span>
69
+ )}
56
70
  <hr class="collection-directory-divider-line" />
57
71
  </>
58
72
  ) : (