@jant/core 0.6.2 → 0.6.4

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 (34) hide show
  1. package/dist/{app-Ct9c4zYF.js → app-B-wKZB8f.js} +267 -205
  2. package/dist/app-qwMcaTML.js +6 -0
  3. package/dist/client/.vite/manifest.json +3 -3
  4. package/dist/client/_assets/{client-Bp2IPjDe.js → client-B1XjvRqE.js} +1 -1
  5. package/dist/client/_assets/client-BMPMuwvV.css +2 -0
  6. package/dist/client/_assets/{client-auth-C4hQWqH1.js → client-auth-B9T2QFl2.js} +21 -21
  7. package/dist/{export-O2w3AsZX.js → export-CzuQyg5h.js} +28 -15
  8. package/dist/{github-sync-BUzIYouS.js → github-sync-CerNYCAn.js} +2 -2
  9. package/dist/{github-sync-D49RADci.js → github-sync-Dbrb1DS5.js} +5 -2
  10. package/dist/index.js +3 -3
  11. package/dist/node.js +4 -4
  12. package/package.json +1 -1
  13. package/src/__tests__/export-service.test.ts +127 -0
  14. package/src/app.tsx +7 -0
  15. package/src/client/components/__tests__/jant-collection-directory.test.ts +0 -42
  16. package/src/client/components/collection-manager-types.ts +0 -2
  17. package/src/client/components/jant-collection-directory.ts +0 -23
  18. package/src/i18n/locales/public/en.po +0 -12
  19. package/src/i18n/locales/public/zh-Hans.po +0 -12
  20. package/src/i18n/locales/public/zh-Hant.po +0 -12
  21. package/src/lib/github-sync-site-config.ts +4 -2
  22. package/src/middleware/__tests__/cache-control.test.ts +50 -0
  23. package/src/middleware/cache-control.ts +60 -0
  24. package/src/services/export-theme/styles/main.css +4 -3
  25. package/src/services/export.ts +47 -17
  26. package/src/services/github-sync.ts +8 -2
  27. package/src/styles/ui.css +23 -46
  28. package/src/ui/feed/ThreadPreview.tsx +17 -9
  29. package/src/ui/feed/__tests__/thread-preview.test.ts +131 -6
  30. package/src/ui/feed/thread-preview-state.ts +43 -0
  31. package/src/ui/pages/CollectionsPage.tsx +0 -22
  32. package/src/ui/shared/CollectionsManager.tsx +6 -41
  33. package/dist/app-CUZaVgsC.js +0 -6
  34. package/dist/client/_assets/client-YVrRjAid.css +0 -2
package/src/styles/ui.css CHANGED
@@ -611,12 +611,12 @@
611
611
  }
612
612
 
613
613
  .site-logo-avatar {
614
- width: calc(var(--avatar-size) + 2px);
615
- height: calc(var(--avatar-size) + 2px);
614
+ box-sizing: border-box;
615
+ width: calc(var(--avatar-size) + 4px);
616
+ height: calc(var(--avatar-size) + 4px);
616
617
  border-radius: var(--avatar-radius);
617
618
  object-fit: cover;
618
- box-shadow: 0 0 0 1px
619
- color-mix(in srgb, var(--site-divider) 82%, transparent);
619
+ border: 1px solid color-mix(in srgb, var(--site-divider) 82%, transparent);
620
620
  }
621
621
 
622
622
  .jant-brand-mark {
@@ -1115,6 +1115,10 @@
1115
1115
  flex: 1 1 18rem;
1116
1116
  }
1117
1117
 
1118
+ .collections-page-heading .page-intro-title-row {
1119
+ align-items: center;
1120
+ }
1121
+
1118
1122
  .collections-page-actions {
1119
1123
  display: flex;
1120
1124
  align-items: center;
@@ -1129,7 +1133,7 @@
1129
1133
  }
1130
1134
 
1131
1135
  .collections-page-action-group[data-collections-toolbar] {
1132
- gap: 0.2rem;
1136
+ gap: 0.25rem;
1133
1137
  }
1134
1138
 
1135
1139
  .collections-page-toolbar-button {
@@ -1137,13 +1141,13 @@
1137
1141
  display: inline-flex;
1138
1142
  align-items: center;
1139
1143
  justify-content: center;
1140
- width: 2rem;
1141
- height: 2rem;
1144
+ width: 2.25rem;
1145
+ height: 2.25rem;
1142
1146
  padding: 0;
1143
1147
  border: none;
1144
1148
  border-radius: 999px;
1145
1149
  background: transparent;
1146
- color: var(--foreground);
1150
+ color: var(--site-text-secondary);
1147
1151
  cursor: pointer;
1148
1152
  transition:
1149
1153
  background-color 0.16s ease,
@@ -1152,41 +1156,15 @@
1152
1156
  border-color 0.16s ease;
1153
1157
  }
1154
1158
 
1155
- .collections-page-toolbar-button:hover {
1156
- background: color-mix(in srgb, var(--accent) 42%, transparent);
1157
- }
1158
-
1159
- .collections-page-toolbar-button:focus-visible {
1160
- outline: none;
1161
- background: color-mix(in srgb, var(--accent) 46%, transparent);
1162
- box-shadow: 0 0 0 3px
1163
- color-mix(in srgb, var(--site-accent) 12%, transparent);
1164
- }
1165
-
1166
- .collections-page-more-btn {
1167
- min-width: 2.1rem;
1168
- width: 2.1rem;
1169
- height: 2.1rem;
1170
- border: 1px solid color-mix(in srgb, var(--border) 76%, transparent);
1171
- background: color-mix(
1172
- in srgb,
1173
- var(--background) 90%,
1174
- var(--site-page-bg) 10%
1175
- );
1176
- color: var(--site-text-secondary);
1177
- box-shadow: 0 12px 24px -24px rgba(15, 23, 42, 0.26);
1178
- }
1179
-
1180
- .collections-page-more-btn:hover,
1181
- .collections-page-more-btn[aria-expanded="true"] {
1182
- border-color: color-mix(in srgb, var(--site-accent) 18%, var(--border));
1183
- background: color-mix(in srgb, var(--accent) 34%, var(--background));
1159
+ .collections-page-toolbar-button:hover,
1160
+ .collections-page-toolbar-button[aria-expanded="true"] {
1161
+ background: var(--accent);
1184
1162
  color: var(--foreground);
1185
1163
  }
1186
1164
 
1187
- .collections-page-more-btn:focus-visible {
1165
+ .collections-page-toolbar-button:focus-visible {
1188
1166
  outline: none;
1189
- border-color: color-mix(in srgb, var(--site-accent) 26%, var(--border));
1167
+ background: var(--accent);
1190
1168
  box-shadow: 0 0 0 3px
1191
1169
  color-mix(in srgb, var(--site-accent) 12%, transparent);
1192
1170
  }
@@ -1377,7 +1355,6 @@
1377
1355
  line-height: var(--collection-directory-title-line-height);
1378
1356
  letter-spacing: -0.02em;
1379
1357
  text-wrap: pretty;
1380
- user-select: all;
1381
1358
  }
1382
1359
 
1383
1360
  .collection-directory-title-marker {
@@ -2210,7 +2187,6 @@
2210
2187
 
2211
2188
  .feed-link-title-link {
2212
2189
  text-decoration: none;
2213
- user-select: all;
2214
2190
  }
2215
2191
 
2216
2192
  .feed-link-title-link:hover {
@@ -2228,10 +2204,6 @@
2228
2204
  text-wrap: pretty;
2229
2205
  }
2230
2206
 
2231
- .feed-note-title a {
2232
- user-select: all;
2233
- }
2234
-
2235
2207
  .feed-continue-link {
2236
2208
  display: inline-block;
2237
2209
  margin-top: 0.5rem;
@@ -7002,10 +6974,15 @@
7002
6974
  opacity: 0.82;
7003
6975
  }
7004
6976
 
7005
- /* Single image: constrain to container width */
6977
+ /* Single image: constrain to container width. min-width keeps the preview
6978
+ wide enough for the remove button; a very long image overflows it and is
6979
+ cropped from the top (object-fit: cover) instead of shrinking to a sliver. */
7006
6980
  .compose-attachment:only-child .compose-attachment-img {
7007
6981
  max-width: 100%;
7008
6982
  max-height: min(200px, 22dvh);
6983
+ min-width: 48px;
6984
+ object-fit: cover;
6985
+ object-position: center top;
7009
6986
  }
7010
6987
 
7011
6988
  .compose-attachment:only-child .compose-attachment-preview-fallback {
@@ -11,7 +11,10 @@ import { useLingui } from "../../i18n/context.js";
11
11
  import type { ThreadPreviewProps } from "../../types.js";
12
12
  import { TimelineItem } from "./TimelineItem.js";
13
13
  import { TimelineItemFromPost } from "./TimelineItem.js";
14
- import { getThreadPreviewState } from "./thread-preview-state.js";
14
+ import {
15
+ getThreadPreviewState,
16
+ threadContextAssumesOverflow,
17
+ } from "./thread-preview-state.js";
15
18
 
16
19
  const ROOT_CONTEXT_DISPLAY = {
17
20
  footer: {
@@ -42,6 +45,10 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
42
45
  latestReply,
43
46
  totalReplyCount,
44
47
  });
48
+ const assumeOverflow = threadContextAssumesOverflow({
49
+ rootPost,
50
+ totalReplyCount,
51
+ });
45
52
  const hiddenPostsLabel = i18n._(
46
53
  msg({
47
54
  message: "{count, plural, one {# more post} other {# more posts}}",
@@ -75,14 +82,14 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
75
82
  : undefined;
76
83
  const gapHref = renderedSecondReply?.permalink ?? latestReply.permalink;
77
84
 
78
- // Always render the collapsible shell for thread previews. Structural
79
- // signals (how many ancestor posts exist) aren't a reliable proxy for
80
- // visual height — a single long root article will push the hero far
81
- // off-screen just as much as several short replies would. The server
82
- // assumes overflow (the common case) and renders the cap + fade + toggle
83
- // immediately; client-side measurement removes the affordance when the
84
- // content actually fits inside the cap, so users never see a no-op
85
- // "Show more" button.
85
+ // Always render the collapsible shell + toggle: the cap and fade are a
86
+ // constant "this is context" affordance. The toggle's *initial* visibility
87
+ // is a server-side guess (threadContextAssumesOverflow) the real rendered
88
+ // height is unknown until the client measures. Guessing matters because a
89
+ // 2-post thread's shell holds only the root, and a short root genuinely
90
+ // fits the cap; rendering the toggle visible anyway makes it flash in then
91
+ // out on load. Client-side measurement (thread-context.ts) re-measures and
92
+ // corrects, so the guess only ever affects that first paint.
86
93
 
87
94
  const rootItem = (
88
95
  <div class="thread-item thread-item-context">
@@ -138,6 +145,7 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
138
145
  data-label-more={showMoreLabel}
139
146
  data-label-less={showLessLabel}
140
147
  aria-expanded="false"
148
+ hidden={!assumeOverflow}
141
149
  >
142
150
  <span class="thread-context-toggle-label">{showMoreLabel}</span>
143
151
  <svg
@@ -7,7 +7,10 @@ import { createI18n } from "../../../i18n/i18n.js";
7
7
  import type { PostView, TimelineItemView } from "../../../types.js";
8
8
  import { CuratedThreadPreview } from "../CuratedThreadPreview.js";
9
9
  import { ThreadPreview } from "../ThreadPreview.js";
10
- import { getThreadPreviewState } from "../thread-preview-state.js";
10
+ import {
11
+ getThreadPreviewState,
12
+ threadContextAssumesOverflow,
13
+ } from "../thread-preview-state.js";
11
14
 
12
15
  function createPostView(overrides: Partial<PostView> = {}): PostView {
13
16
  return {
@@ -48,6 +51,13 @@ function renderWithI18n(
48
51
  return renderToString(render());
49
52
  }
50
53
 
54
+ /** The opening tag of the show-more toggle button, for attribute assertions. */
55
+ function toggleTag(html: string): string {
56
+ return (
57
+ html.match(/<button[^>]*\bdata-thread-context-toggle\b[^>]*>/)?.[0] ?? ""
58
+ );
59
+ }
60
+
51
61
  describe("getThreadPreviewState", () => {
52
62
  it("has no hidden posts for a 2-post thread", () => {
53
63
  const latestReply = createPostView({
@@ -236,13 +246,17 @@ describe("getThreadPreviewState", () => {
236
246
  expect(html).toMatch(/data-label-less="[^"]+"/);
237
247
  });
238
248
 
239
- it("still wraps a root-only preview in the shell so the cap applies to long single posts", () => {
240
- // root + hero no second/penultimate/gap. The server can't know the
241
- // root's rendered height, so it always renders the shell + toggle and
242
- // lets client-side measurement strip them when the root actually fits.
249
+ it("hides the show-more toggle on first paint for a short lone root that fits the cap", () => {
250
+ // 2-post thread: the shell holds only the root. A short root genuinely
251
+ // fits the height cap, so the toggle is rendered hidden to avoid flashing
252
+ // it in then out. Client-side measurement (thread-context.ts) re-reveals
253
+ // it if the rendered height actually overflows.
243
254
  const html = renderWithI18n(() =>
244
255
  ThreadPreview({
245
- rootPost: createPostView({ bodyHtml: "<p>Root</p>" }),
256
+ rootPost: createPostView({
257
+ bodyHtml: "<p>Root</p>",
258
+ summary: "Root",
259
+ }),
246
260
  latestReply: createPostView({
247
261
  id: "post-2",
248
262
  permalink: "/post-2",
@@ -257,10 +271,35 @@ describe("getThreadPreviewState", () => {
257
271
  expect(html).toContain("thread-context-shell");
258
272
  expect(html).toContain("data-collapsed");
259
273
  expect(html).toContain("data-thread-context-toggle");
274
+ expect(toggleTag(html)).toContain("hidden");
260
275
  expect(html).toContain("<p>Root</p>");
261
276
  expect(html).toContain("<p>Latest</p>");
262
277
  });
263
278
 
279
+ it("shows the show-more toggle on first paint for a long lone root", () => {
280
+ // 2-post thread with a long root — the shell almost certainly overflows
281
+ // the cap, so the toggle is rendered visible immediately (no flash).
282
+ const html = renderWithI18n(() =>
283
+ ThreadPreview({
284
+ rootPost: createPostView({
285
+ bodyHtml: "<p>Root</p>",
286
+ summary: "word ".repeat(60),
287
+ }),
288
+ latestReply: createPostView({
289
+ id: "post-2",
290
+ permalink: "/post-2",
291
+ slug: "post-2",
292
+ bodyHtml: "<p>Latest</p>",
293
+ isLastInThread: true,
294
+ }),
295
+ totalReplyCount: 1,
296
+ }),
297
+ );
298
+
299
+ expect(html).toContain("data-thread-context-toggle");
300
+ expect(toggleTag(html)).not.toContain("hidden");
301
+ });
302
+
264
303
  it("renders the collapsible shell even when just one extra item precedes the latest reply", () => {
265
304
  // root + secondReply + hero — one extra context item. The server can't
266
305
  // measure actual height, so it renders the shell assuming overflow (the
@@ -292,6 +331,9 @@ describe("getThreadPreviewState", () => {
292
331
  expect(html).toContain("<p>Root</p>");
293
332
  expect(html).toContain("<p>Second</p>");
294
333
  expect(html).toContain("<p>Latest</p>");
334
+ // 3-post thread (totalReplyCount 2): the shell stacks 2 cards, so the
335
+ // toggle is rendered visible on first paint.
336
+ expect(toggleTag(html)).not.toContain("hidden");
295
337
  });
296
338
 
297
339
  it("points the hidden-posts gap link to the second reply so the detail page opens just above the hidden range", () => {
@@ -352,6 +394,22 @@ describe("getThreadPreviewState", () => {
352
394
  );
353
395
  });
354
396
 
397
+ it("renders curated thread previews without a collapsible context shell", () => {
398
+ // Curated previews render each segment in flow — no shell, no toggle —
399
+ // so the overflow heuristic never applies to them.
400
+ const post = createPostView({ summary: "Curated note" });
401
+ const html = renderWithI18n(() =>
402
+ CuratedThreadPreview({
403
+ curatedThread: {
404
+ rootPost: post,
405
+ segments: [{ post, hiddenBeforeCount: 0, highlighted: true }],
406
+ },
407
+ }),
408
+ );
409
+
410
+ expect(html).not.toContain("data-thread-context-toggle");
411
+ });
412
+
355
413
  it("renders article summaries in curated thread previews", () => {
356
414
  const articlePost = createPostView({
357
415
  title: "Curated article",
@@ -381,3 +439,70 @@ describe("getThreadPreviewState", () => {
381
439
  expect(html).not.toContain('id="continue"');
382
440
  });
383
441
  });
442
+
443
+ describe("threadContextAssumesOverflow", () => {
444
+ it("assumes overflow for 3+ post threads (the shell stacks 2+ cards)", () => {
445
+ expect(
446
+ threadContextAssumesOverflow({
447
+ rootPost: createPostView({ summary: "short" }),
448
+ totalReplyCount: 2,
449
+ }),
450
+ ).toBe(true);
451
+ });
452
+
453
+ it("assumes a short lone root fits the cap (2-post thread)", () => {
454
+ expect(
455
+ threadContextAssumesOverflow({
456
+ rootPost: createPostView({
457
+ summary: "Took the long way home because the light was good.",
458
+ }),
459
+ totalReplyCount: 1,
460
+ }),
461
+ ).toBe(false);
462
+ });
463
+
464
+ it("assumes overflow for a long lone root", () => {
465
+ expect(
466
+ threadContextAssumesOverflow({
467
+ rootPost: createPostView({ summary: "x".repeat(200) }),
468
+ totalReplyCount: 1,
469
+ }),
470
+ ).toBe(true);
471
+ });
472
+
473
+ it("assumes overflow for a short lone root that carries media", () => {
474
+ expect(
475
+ threadContextAssumesOverflow({
476
+ rootPost: createPostView({
477
+ summary: "short",
478
+ media: [{} as PostView["media"][number]],
479
+ }),
480
+ totalReplyCount: 1,
481
+ }),
482
+ ).toBe(true);
483
+ });
484
+
485
+ it("assumes overflow for a short lone root with a link preview image", () => {
486
+ expect(
487
+ threadContextAssumesOverflow({
488
+ rootPost: createPostView({
489
+ format: "link",
490
+ summary: "short",
491
+ previewImageUrl: "https://example.com/cover.jpg",
492
+ }),
493
+ totalReplyCount: 1,
494
+ }),
495
+ ).toBe(true);
496
+ });
497
+
498
+ it("counts plain-text code points, not UTF-16 units, against the limit", () => {
499
+ // 130 CJK code points — comfortably over the 120 limit even though each
500
+ // is a single BMP character. Confirms the threshold reads real length.
501
+ expect(
502
+ threadContextAssumesOverflow({
503
+ rootPost: createPostView({ summary: "字".repeat(130) }),
504
+ totalReplyCount: 1,
505
+ }),
506
+ ).toBe(true);
507
+ });
508
+ });
@@ -22,3 +22,46 @@ export function getThreadPreviewState({
22
22
  hiddenCount,
23
23
  };
24
24
  }
25
+
26
+ /**
27
+ * A lone root post shorter than this (plain-text code points) comfortably
28
+ * fits the thread-context height cap. Pairs with the
29
+ * `--site-thread-context-max-height` token (160px) in tokens.css — revisit
30
+ * both together if that cap changes.
31
+ */
32
+ const SHORT_ROOT_CHAR_LIMIT = 120;
33
+
34
+ /**
35
+ * First-paint guess for whether the thread-context shell will overflow its
36
+ * height cap, used to pick the initial "Show more" toggle visibility.
37
+ *
38
+ * The client (thread-context.ts) always re-measures and corrects this, so a
39
+ * wrong guess costs at most a one-time flash on load — never a wrong final
40
+ * state. Kept deliberately coarse:
41
+ *
42
+ * - 3+ post threads stack 2+ cards in the shell — effectively always overflow.
43
+ * - A lone root carrying media is tall regardless of its text length.
44
+ * - Otherwise fall back to a plain-text length threshold. Only clearly short
45
+ * roots flip to "fits"; anything at or above the limit keeps the historical
46
+ * "assume overflow" default, so this can only remove flashes, never add them.
47
+ *
48
+ * @param rootPost - the thread's root post (the only post in a 2-post thread's
49
+ * shell)
50
+ * @param totalReplyCount - replies in the thread; `>= 2` means 3+ posts total
51
+ * @returns whether to render the toggle visible on first paint
52
+ */
53
+ export function threadContextAssumesOverflow({
54
+ rootPost,
55
+ totalReplyCount,
56
+ }: {
57
+ rootPost: PostView;
58
+ totalReplyCount: number;
59
+ }): boolean {
60
+ if (totalReplyCount >= 2) return true;
61
+ if (rootPost.media.length > 0 || Boolean(rootPost.previewImageUrl)) {
62
+ return true;
63
+ }
64
+ // `summary` is format-normalized at the viewmodel layer: quote text for
65
+ // quotes, body text for notes, the URL for bare links.
66
+ return [...(rootPost.summary ?? "")].length >= SHORT_ROOT_CHAR_LIMIT;
67
+ }
@@ -11,37 +11,18 @@ import type { CollectionsPageProps } from "../../types.js";
11
11
  import { CollectionDirectory } from "../shared/CollectionDirectory.js";
12
12
  import { CollectionsManager } from "../shared/CollectionsManager.js";
13
13
 
14
- const countCollections = (items: CollectionsPageProps["items"]) =>
15
- items.filter((item) => item.type === "collection" && item.collection).length;
16
-
17
14
  export const CollectionsPage: FC<CollectionsPageProps> = ({
18
15
  items,
19
16
  isAuthenticated,
20
17
  sitePathPrefix = "",
21
18
  }) => {
22
19
  const { i18n } = useLingui();
23
- const collectionCount = countCollections(items);
24
20
  const emptyMessage = i18n._(
25
21
  msg({
26
22
  message: "No collections yet. Start one to organize posts by topic.",
27
23
  comment: "@context: Empty state message on collections page",
28
24
  }),
29
25
  );
30
- const collectionCountLabel = `${collectionCount} ${
31
- collectionCount === 1
32
- ? i18n._(
33
- msg({
34
- message: "collection",
35
- comment: "@context: Singular collection count label",
36
- }),
37
- )
38
- : i18n._(
39
- msg({
40
- message: "collections",
41
- comment: "@context: Plural collection count label",
42
- }),
43
- )
44
- }`;
45
26
 
46
27
  if (isAuthenticated) {
47
28
  return (
@@ -66,9 +47,6 @@ export const CollectionsPage: FC<CollectionsPageProps> = ({
66
47
  )}
67
48
  </h1>
68
49
  </div>
69
- <div class="page-intro-meta-row">
70
- <p class="page-intro-meta">{collectionCountLabel}</p>
71
- </div>
72
50
  </div>
73
51
  </header>
74
52
 
@@ -13,9 +13,6 @@ import { getCollectionMutationLabels } from "./collection-management-labels.js";
13
13
  const escapeJson = (data: unknown) =>
14
14
  JSON.stringify(data).replace(/</g, "\\u003c");
15
15
 
16
- const countCollections = (items: CollectionDirectoryItem[]) =>
17
- items.filter((item) => item.type === "collection" && item.collection).length;
18
-
19
16
  export interface CollectionsManagerProps {
20
17
  items: CollectionDirectoryItem[];
21
18
  sitePathPrefix?: string;
@@ -34,22 +31,6 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
34
31
  `${getNewCollectionPath()}?returnTo=${encodeURIComponent(collectionsHref)}`,
35
32
  sitePathPrefix,
36
33
  );
37
- const collectionCount = countCollections(items);
38
- const collectionCountLabel = `${collectionCount} ${
39
- collectionCount === 1
40
- ? i18n._(
41
- msg({
42
- message: "collection",
43
- comment: "@context: Singular collection count label",
44
- }),
45
- )
46
- : i18n._(
47
- msg({
48
- message: "collections",
49
- comment: "@context: Plural collection count label",
50
- }),
51
- )
52
- }`;
53
34
  const mutationLabels = getCollectionMutationLabels(i18n);
54
35
 
55
36
  const labels = {
@@ -59,18 +40,6 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
59
40
  comment: "@context: Collections page heading",
60
41
  }),
61
42
  ),
62
- collectionSingular: i18n._(
63
- msg({
64
- message: "collection",
65
- comment: "@context: Singular collection count label",
66
- }),
67
- ),
68
- collectionPlural: i18n._(
69
- msg({
70
- message: "collections",
71
- comment: "@context: Plural collection count label",
72
- }),
73
- ),
74
43
  organize: i18n._(
75
44
  msg({
76
45
  message: "Organize",
@@ -164,11 +133,6 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
164
133
  <div class="collections-page-heading page-intro">
165
134
  <div class="page-intro-title-row">
166
135
  <h1 class="page-intro-title">{labels.collectionsTitle}</h1>
167
- </div>
168
- <div class="page-intro-meta-row">
169
- <p class="page-intro-meta" data-collections-count>
170
- {collectionCountLabel}
171
- </p>
172
136
  <div class="collections-page-actions">
173
137
  <div
174
138
  class="collections-page-action-group"
@@ -202,14 +166,15 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
202
166
  >
203
167
  <svg
204
168
  xmlns="http://www.w3.org/2000/svg"
205
- width="16"
206
- height="16"
169
+ width="18"
170
+ height="18"
207
171
  viewBox="0 0 24 24"
208
172
  fill="none"
209
173
  stroke="currentColor"
210
174
  stroke-width="2"
211
175
  stroke-linecap="round"
212
176
  stroke-linejoin="round"
177
+ aria-hidden="true"
213
178
  >
214
179
  <path d="M12 5v14" />
215
180
  <path d="M5 12h14" />
@@ -218,7 +183,7 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
218
183
  <div class="relative">
219
184
  <button
220
185
  type="button"
221
- class="collections-page-toolbar-button collections-page-more-btn"
186
+ class="collections-page-toolbar-button"
222
187
  aria-label={labels.moreActions}
223
188
  aria-expanded="false"
224
189
  aria-haspopup="menu"
@@ -227,8 +192,8 @@ export const CollectionsManager: FC<CollectionsManagerProps> = ({
227
192
  >
228
193
  <svg
229
194
  xmlns="http://www.w3.org/2000/svg"
230
- width="16"
231
- height="16"
195
+ width="18"
196
+ height="18"
232
197
  viewBox="0 0 24 24"
233
198
  fill="currentColor"
234
199
  >
@@ -1,6 +0,0 @@
1
- import "./url-XF0GbKGO.js";
2
- import { t as createApp } from "./app-Ct9c4zYF.js";
3
- import "./export-O2w3AsZX.js";
4
- import "./env-CoSe-1y4.js";
5
- import "./github-sync-D49RADci.js";
6
- export { createApp };