@openrails/contentkit-ui 0.97.1 → 0.98.1

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 (41) hide show
  1. package/README.md +57 -4
  2. package/dist/client/beacons.d.ts +17 -7
  3. package/dist/client/beacons.js +205 -41
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/browser-reader-preferences.d.ts +15 -2
  6. package/dist/client/browser-reader-preferences.js +67 -30
  7. package/dist/client/browser-reader-preferences.js.map +1 -1
  8. package/dist/client/discovery.d.ts +13 -3
  9. package/dist/client/discovery.js +31 -16
  10. package/dist/client/discovery.js.map +1 -1
  11. package/dist/client/generated/routes.d.ts +14 -1
  12. package/dist/client/generated/routes.js +18 -0
  13. package/dist/client/generated/routes.js.map +1 -1
  14. package/dist/client/generated/wire.d.ts +47 -4
  15. package/dist/client/generated/wire.js.map +1 -1
  16. package/dist/client/http.d.ts +7 -3
  17. package/dist/client/http.js +26 -7
  18. package/dist/client/http.js.map +1 -1
  19. package/dist/client/index.d.ts +2 -2
  20. package/dist/client/reader-preferences.d.ts +7 -0
  21. package/dist/client/reader-preferences.js +16 -4
  22. package/dist/client/reader-preferences.js.map +1 -1
  23. package/dist/client.js +17 -17
  24. package/dist/components/content/card.js +50 -40
  25. package/dist/components/content/card.js.map +1 -1
  26. package/dist/components/content/filters.d.ts +5 -0
  27. package/dist/components/content/filters.js +61 -52
  28. package/dist/components/content/filters.js.map +1 -1
  29. package/dist/components/content/popular.d.ts +1 -1
  30. package/dist/components/content/popular.js.map +1 -1
  31. package/dist/components/content/reader-settings.d.ts +4 -0
  32. package/dist/components/content/reader-settings.js +3 -1
  33. package/dist/components/content/reader-settings.js.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/react/discovery.d.ts +7 -2
  36. package/dist/react/discovery.js +208 -87
  37. package/dist/react/discovery.js.map +1 -1
  38. package/dist/react/provider.js +4 -1
  39. package/dist/react/provider.js.map +1 -1
  40. package/dist/styles.css +1 -1
  41. package/package.json +1 -1
@@ -7,59 +7,68 @@ import { Skeleton as a } from "../../ui/skeleton.js";
7
7
  import { ErrorLine as o, contentError as s, useAction as c } from "../social/parts.js";
8
8
  import { Switch as l } from "../../ui/switch.js";
9
9
  import { useContentFilters as u, useReleaseDates as d } from "../../react/discovery.js";
10
- import { useContext as f, useId as p } from "react";
11
- import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
12
- import { cn as _ } from "cn";
10
+ import { labelOf as f } from "../groups/parts.js";
11
+ import { useContext as p, useId as m } from "react";
12
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
13
+ import { cn as v } from "cn";
13
14
  //#region src/components/content/filters.tsx
14
- function v(r) {
15
- let d = e(), { t: v } = d, y = p(), b = f(n), x = u({
15
+ function y(r) {
16
+ let d = e(), { t: y } = d, b = m(), x = p(n), S = u({
16
17
  language: r.language,
17
18
  client: r.client
18
- }), S = c("filters.save", r.onError), C = (e) => /* @__PURE__ */ h(t, {
19
+ }), C = c("filters.save", r.onError), w = (e) => /* @__PURE__ */ g(t, {
19
20
  appearance: r.appearance,
20
- className: _("grid gap-3 text-sm", r.className),
21
+ className: v("grid gap-3 text-sm", r.className),
21
22
  "data-ckui": "content-filters",
22
23
  children: e
23
24
  });
24
- if (!x.filters) return x.error ? C(/* @__PURE__ */ h(o, { children: s(d, x.error) })) : C(/* @__PURE__ */ h(a, { className: "h-24 w-full" }));
25
- let w = x.filters;
26
- return !w.filters.length && r.includeDeleted === !1 ? null : C(/* @__PURE__ */ g(m, { children: [
27
- w.filters.length ? /* @__PURE__ */ h("ul", {
28
- className: "grid divide-y divide-border rounded-lg border border-border",
29
- children: w.filters.map((e) => /* @__PURE__ */ g("li", {
30
- className: "flex flex-wrap items-center gap-3 p-3",
31
- "data-ckui": "content-filter",
32
- "data-slug": e.entity.slug,
33
- "data-visible": e.visible,
34
- children: [/* @__PURE__ */ h(l, {
35
- id: `${y}-${e.entity.id}`,
36
- checked: e.visible,
37
- disabled: S.pending,
38
- onCheckedChange: (t) => void S.run(() => x.setVisible(e.entity.id, t))
39
- }), /* @__PURE__ */ h(i, {
40
- htmlFor: `${y}-${e.entity.id}`,
41
- className: "min-w-0 flex-1",
42
- children: e.entity.name || e.entity.slug
43
- })]
44
- }, e.entity.id))
45
- }) : null,
46
- r.includeDeleted !== !1 && b?.viewer !== null && /* @__PURE__ */ g("div", {
25
+ if (!S.filters) return S.error ? w(/* @__PURE__ */ g(o, { children: s(d, S.error) })) : w(/* @__PURE__ */ g(a, { className: "h-24 w-full" }));
26
+ let T = S.filters, E = Object.entries(T.kinds).filter(([e, t]) => t.length && (!r.kinds || r.kinds.includes(e)));
27
+ return !E.length && r.includeDeleted === !1 ? null : w(/* @__PURE__ */ _(h, { children: [
28
+ E.map(([e, t]) => /* @__PURE__ */ _("section", {
29
+ className: "grid gap-2",
30
+ "data-content-kind": e,
31
+ children: [E.length > 1 && /* @__PURE__ */ g("h4", {
32
+ className: "font-medium",
33
+ children: f(r.labels, e)
34
+ }), /* @__PURE__ */ g("ul", {
35
+ className: "grid divide-y divide-border rounded-lg border border-border",
36
+ children: t.map((t) => /* @__PURE__ */ _("li", {
37
+ className: "flex flex-wrap items-center gap-3 p-3",
38
+ "data-ckui": "content-filter",
39
+ "data-kind": e,
40
+ "data-slug": t.entity.slug,
41
+ "data-visible": t.visible,
42
+ children: [/* @__PURE__ */ g(l, {
43
+ id: `${b}-${e}-${t.entity.id}`,
44
+ checked: t.visible,
45
+ disabled: C.pending,
46
+ onCheckedChange: (n) => void C.run(() => S.setVisible(e, t.entity.id, n))
47
+ }), /* @__PURE__ */ g(i, {
48
+ htmlFor: `${b}-${e}-${t.entity.id}`,
49
+ className: "min-w-0 flex-1",
50
+ children: t.entity.name || t.entity.slug
51
+ })]
52
+ }, t.entity.id))
53
+ })]
54
+ }, e)),
55
+ r.includeDeleted !== !1 && x?.viewer !== null && /* @__PURE__ */ _("div", {
47
56
  className: "flex items-center gap-3",
48
- children: [/* @__PURE__ */ h(l, {
49
- id: `${y}-deleted`,
50
- checked: w.include_deleted,
51
- disabled: S.pending,
52
- onCheckedChange: (e) => void S.run(() => x.setIncludeDeleted(e))
53
- }), /* @__PURE__ */ h(i, {
54
- htmlFor: `${y}-deleted`,
55
- children: v("contents.includeDeleted")
57
+ children: [/* @__PURE__ */ g(l, {
58
+ id: `${b}-deleted`,
59
+ checked: T.include_deleted,
60
+ disabled: C.pending,
61
+ onCheckedChange: (e) => void C.run(() => S.setIncludeDeleted(e))
62
+ }), /* @__PURE__ */ g(i, {
63
+ htmlFor: `${b}-deleted`,
64
+ children: y("contents.includeDeleted")
56
65
  })]
57
66
  }),
58
- /* @__PURE__ */ h(o, { error: S.error })
67
+ /* @__PURE__ */ g(o, { error: C.error })
59
68
  ] }));
60
69
  }
61
- function y({ query: n = {}, selected: i, onSelect: c, client: l, className: u, appearance: f }) {
62
- let p = e(), { t: m } = p, v = d(n, { client: l }), y = (e) => {
70
+ function b({ query: n = {}, selected: i, onSelect: c, client: l, className: u, appearance: f }) {
71
+ let p = e(), { t: m } = p, h = d(n, { client: l }), y = (e) => {
63
72
  let [t, n, r] = e.split("-").map(Number), i = new Date(Date.UTC(t, (n ?? 1) - 1, r ?? 1));
64
73
  return new Intl.DateTimeFormat(p.language, r ? {
65
74
  dateStyle: "medium",
@@ -70,38 +79,38 @@ function y({ query: n = {}, selected: i, onSelect: c, client: l, className: u, a
70
79
  timeZone: "UTC"
71
80
  }).format(i);
72
81
  };
73
- return /* @__PURE__ */ g(t, {
82
+ return /* @__PURE__ */ _(t, {
74
83
  appearance: f,
75
- className: _("grid gap-2 text-sm", u),
84
+ className: v("grid gap-2 text-sm", u),
76
85
  "data-ckui": "release-dates",
77
- children: [v.loading && !v.items.length ? /* @__PURE__ */ h(a, { className: "h-24 w-full" }) : v.error && !v.items.length ? /* @__PURE__ */ h(o, { children: s(p, v.error) }) : v.items.length ? /* @__PURE__ */ h("ul", {
86
+ children: [h.loading && !h.items.length ? /* @__PURE__ */ g(a, { className: "h-24 w-full" }) : h.error && !h.items.length ? /* @__PURE__ */ g(o, { children: s(p, h.error) }) : h.items.length ? /* @__PURE__ */ g("ul", {
78
87
  className: "grid gap-1",
79
- children: v.items.map((e) => /* @__PURE__ */ h("li", { children: /* @__PURE__ */ g("button", {
88
+ children: h.items.map((e) => /* @__PURE__ */ g("li", { children: /* @__PURE__ */ _("button", {
80
89
  type: "button",
81
90
  "aria-pressed": i === e.date,
82
- className: _("flex w-full items-center justify-between gap-3 rounded-md px-2 py-1.5 text-start hover:bg-muted", i === e.date && "bg-muted font-medium"),
91
+ className: v("flex w-full items-center justify-between gap-3 rounded-md px-2 py-1.5 text-start hover:bg-muted", i === e.date && "bg-muted font-medium"),
83
92
  onClick: () => c?.(e.date),
84
93
  "data-ckui": "release-date",
85
94
  "data-date": e.date,
86
- children: [/* @__PURE__ */ h("span", { children: y(e.date) }), /* @__PURE__ */ g("span", {
95
+ children: [/* @__PURE__ */ g("span", { children: y(e.date) }), /* @__PURE__ */ _("span", {
87
96
  className: "text-xs text-muted-foreground tabular-nums",
88
97
  children: [p.plural("contents.items", e.count), e.free < e.count && ` · ${p.plural("contents.free", e.free)}`]
89
98
  })]
90
99
  }) }, e.date))
91
- }) : /* @__PURE__ */ h("p", {
100
+ }) : /* @__PURE__ */ g("p", {
92
101
  className: "text-muted-foreground",
93
102
  children: m("contents.noDates")
94
- }), v.hasMore && /* @__PURE__ */ h(r, {
103
+ }), h.hasMore && /* @__PURE__ */ g(r, {
95
104
  variant: "outline",
96
105
  size: "sm",
97
106
  className: "justify-self-center",
98
- disabled: v.loadingMore,
99
- onClick: () => void v.loadMore(),
107
+ disabled: h.loadingMore,
108
+ onClick: () => void h.loadMore(),
100
109
  children: m("contents.loadMore")
101
110
  })]
102
111
  });
103
112
  }
104
113
  //#endregion
105
- export { v as ContentFilterSettings, y as ReleaseDateIndex };
114
+ export { y as ContentFilterSettings, b as ReleaseDateIndex };
106
115
 
107
116
  //# sourceMappingURL=filters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.js","names":[],"sources":["../../../src/components/content/filters.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useId } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ReleaseDatesQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters, useReleaseDates } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Switch } from \"#ckui/ui/switch\";\nimport { contentError, ErrorLine, useAction } from \"../social/parts.js\";\n\nexport interface ContentFilterSettingsProps {\n /** Entity names in this language; default the provider's. */\n language?: string;\n /** Offer listing deleted content the reader holds. Default true. */\n includeDeleted?: boolean;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Show or hide configured content for this browser or signed-in account. */\nexport function ContentFilterSettings(p: ContentFilterSettingsProps) {\n const m = useMessages();\n const { t } = m;\n const id = useId();\n const ctx = useContext(ContentKitContext);\n const f = useContentFilters({ language: p.language, client: p.client });\n const save = useAction(\"filters.save\", p.onError);\n const root = (children: React.ReactNode) => (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-filters\">\n {children}\n </ContentKitUiRoot>\n );\n if (!f.filters) {\n if (f.error) return root(<ErrorLine>{contentError(m, f.error)}</ErrorLine>);\n return root(<Skeleton className=\"h-24 w-full\" />);\n }\n const filters = f.filters;\n if (!filters.filters.length && p.includeDeleted === false) return null;\n return root(\n <>\n {filters.filters.length ? (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {filters.filters.map((x) => (\n <li key={x.entity.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"content-filter\" data-slug={x.entity.slug} data-visible={x.visible}>\n <Switch id={`${id}-${x.entity.id}`} checked={x.visible} disabled={save.pending} onCheckedChange={(v) => void save.run(() => f.setVisible(x.entity.id, v))} />\n <Label htmlFor={`${id}-${x.entity.id}`} className=\"min-w-0 flex-1\">\n {x.entity.name || x.entity.slug}\n </Label>\n </li>\n ))}\n </ul>\n ) : null}\n {p.includeDeleted !== false && ctx?.viewer !== null && (\n <div className=\"flex items-center gap-3\">\n <Switch id={`${id}-deleted`} checked={filters.include_deleted} disabled={save.pending} onCheckedChange={(v) => void save.run(() => f.setIncludeDeleted(v))} />\n <Label htmlFor={`${id}-deleted`}>{t(\"contents.includeDeleted\")}</Label>\n </div>\n )}\n <ErrorLine error={save.error} />\n </>,\n );\n}\n\nexport interface ReleaseDateIndexProps {\n /** Which contents count: the listing's filters. */\n query?: ReleaseDatesQuery;\n /** The bucket chosen (a day YYYY-MM-DD or month YYYY-MM): list with released_on set to it. */\n selected?: string;\n onSelect?: (date: string) => void;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** The release date index: per month or day, newest first, how many contents and how many free. */\nexport function ReleaseDateIndex({ query = {}, selected, onSelect, client, className, appearance }: ReleaseDateIndexProps) {\n const m = useMessages();\n const { t } = m;\n const dates = useReleaseDates(query, { client });\n const label = (date: string) => {\n const [y, mo, d] = date.split(\"-\").map(Number);\n const at = new Date(Date.UTC(y!, (mo ?? 1) - 1, d ?? 1));\n return new Intl.DateTimeFormat(m.language, d ? { dateStyle: \"medium\", timeZone: \"UTC\" } : { year: \"numeric\", month: \"long\", timeZone: \"UTC\" }).format(at);\n };\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-2 text-sm\", className)} data-ckui=\"release-dates\">\n {dates.loading && !dates.items.length ? (\n <Skeleton className=\"h-24 w-full\" />\n ) : dates.error && !dates.items.length ? (\n <ErrorLine>{contentError(m, dates.error)}</ErrorLine>\n ) : !dates.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"contents.noDates\")}</p>\n ) : (\n <ul className=\"grid gap-1\">\n {dates.items.map((d) => (\n <li key={d.date}>\n <button\n type=\"button\"\n aria-pressed={selected === d.date}\n className={cn(\"flex w-full items-center justify-between gap-3 rounded-md px-2 py-1.5 text-start hover:bg-muted\", selected === d.date && \"bg-muted font-medium\")}\n onClick={() => onSelect?.(d.date)}\n data-ckui=\"release-date\"\n data-date={d.date}\n >\n <span>{label(d.date)}</span>\n <span className=\"text-xs text-muted-foreground tabular-nums\">\n {m.plural(\"contents.items\", d.count)}\n {d.free < d.count && ` · ${m.plural(\"contents.free\", d.free)}`}\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n {dates.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={dates.loadingMore} onClick={() => void dates.loadMore()}>\n {t(\"contents.loadMore\")}\n </Button>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,SAAgB,EAAsB,GAA+B;CACnE,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAK,EAAM,GACX,IAAM,EAAW,CAAiB,GAClC,IAAI,EAAkB;EAAE,UAAU,EAAE;EAAU,QAAQ,EAAE;CAAO,CAAC,GAChE,IAAO,EAAU,gBAAgB,EAAE,OAAO,GAC1C,KAAQ,MACZ,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EACrG;CACe,CAAA;CAEpB,IAAI,CAAC,EAAE,SAEL,OADI,EAAE,QAAc,EAAK,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAE,KAAK,EAAa,CAAA,CAAC,IACnE,EAAK,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,CAAC;CAElD,IAAM,IAAU,EAAE;CAElB,OADI,CAAC,EAAQ,QAAQ,UAAU,EAAE,mBAAmB,KAAc,OAC3D,EACL,kBAAA,GAAA,EAAA,UAAA;EACG,EAAQ,QAAQ,SACf,kBAAC,MAAD;GAAI,WAAU;GACX,UAAA,EAAQ,QAAQ,KAAK,MACpB,kBAAC,MAAD;IAAsB,WAAU;IAAwC,aAAU;IAAiB,aAAW,EAAE,OAAO;IAAM,gBAAc,EAAE;IAA7I,UAAA,CACE,kBAAC,GAAD;KAAQ,IAAI,GAAG,EAAG,GAAG,EAAE,OAAO;KAAM,SAAS,EAAE;KAAS,UAAU,EAAK;KAAS,kBAAkB,MAAM,KAAK,EAAK,UAAU,EAAE,WAAW,EAAE,OAAO,IAAI,CAAC,CAAC;IAAI,CAAA,GAC5J,kBAAC,GAAD;KAAO,SAAS,GAAG,EAAG,GAAG,EAAE,OAAO;KAAM,WAAU;KAC/C,UAAA,EAAE,OAAO,QAAQ,EAAE,OAAO;IACtB,CAAA,CACL;GALK,GAAA,EAAE,OAAO,EAKd,CACL;EACC,CAAA,IACF;EACH,EAAE,mBAAmB,MAAS,GAAK,WAAW,QAC7C,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,GAAD;IAAQ,IAAI,GAAG,EAAG;IAAW,SAAS,EAAQ;IAAiB,UAAU,EAAK;IAAS,kBAAkB,MAAM,KAAK,EAAK,UAAU,EAAE,kBAAkB,CAAC,CAAC;GAAI,CAAA,GAC7J,kBAAC,GAAD;IAAO,SAAS,GAAG,EAAG;IAAY,UAAA,EAAE,yBAAyB;GAAS,CAAA,CACnE;;EAEP,kBAAC,GAAD,EAAW,OAAO,EAAK,MAAQ,CAAA;CAC/B,EAAA,CAAA,CACJ;AACF;AAcA,SAAgB,EAAiB,EAAE,WAAQ,CAAC,GAAG,aAAU,aAAU,WAAQ,cAAW,iBAAqC;CACzH,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAQ,EAAgB,GAAO,EAAE,UAAO,CAAC,GACzC,KAAS,MAAiB;EAC9B,IAAM,CAAC,GAAG,GAAI,KAAK,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,GACvC,IAAK,IAAI,KAAK,KAAK,IAAI,IAAK,KAAM,KAAK,GAAG,KAAK,CAAC,CAAC;EACvD,OAAO,IAAI,KAAK,eAAe,EAAE,UAAU,IAAI;GAAE,WAAW;GAAU,UAAU;EAAM,IAAI;GAAE,MAAM;GAAW,OAAO;GAAQ,UAAU;EAAM,CAAC,CAAC,CAAC,OAAO,CAAE;CAC1J;CACA,OACE,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,sBAAsB,CAAS;EAAG,aAAU;EAApG,UAAA,CACG,EAAM,WAAW,CAAC,EAAM,MAAM,SAC7B,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,IACjC,EAAM,SAAS,CAAC,EAAM,MAAM,SAC9B,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAM,KAAK,EAAa,CAAA,IACjD,EAAM,MAAM,SAGf,kBAAC,MAAD;GAAI,WAAU;GACX,UAAA,EAAM,MAAM,KAAK,MAChB,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;IACE,MAAK;IACL,gBAAc,MAAa,EAAE;IAC7B,WAAW,EAAG,mGAAmG,MAAa,EAAE,QAAQ,sBAAsB;IAC9J,eAAe,IAAW,EAAE,IAAI;IAChC,aAAU;IACV,aAAW,EAAE;IANf,UAAA,CAQE,kBAAC,QAAD,EAAA,UAAO,EAAM,EAAE,IAAI,EAAQ,CAAA,GAC3B,kBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CACG,EAAE,OAAO,kBAAkB,EAAE,KAAK,GAClC,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,OAAO,iBAAiB,EAAE,IAAI,GACvD;IACA,CAAA,CAAA;GACN,CAAA,EAAA,GAfK,EAAE,IAeP,CACL;EACC,CAAA,IArBJ,kBAAC,KAAD;GAAG,WAAU;GAAyB,UAAA,EAAE,kBAAkB;EAAK,CAAA,GAuBhE,EAAM,WACL,kBAAC,GAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,WAAU;GAAsB,UAAU,EAAM;GAAa,eAAe,KAAK,EAAM,SAAS;GACjI,UAAA,EAAE,mBAAmB;EAChB,CAAA,CAEM;;AAEtB"}
1
+ {"version":3,"file":"filters.js","names":[],"sources":["../../../src/components/content/filters.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useId } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ReleaseDatesQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters, useReleaseDates } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Switch } from \"#ckui/ui/switch\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { contentError, ErrorLine, useAction } from \"../social/parts.js\";\n\nexport interface ContentFilterSettingsProps {\n /** Content kinds to show. Omitted displays every configured kind. */\n kinds?: readonly string[];\n /** Display labels for content-kind sections, when more than one is shown. */\n labels?: Labels;\n /** Entity names in this language; default the provider's. */\n language?: string;\n /** Offer listing deleted content the reader holds. Default true. */\n includeDeleted?: boolean;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Show or hide configured content for this browser or signed-in account. */\nexport function ContentFilterSettings(p: ContentFilterSettingsProps) {\n const m = useMessages();\n const { t } = m;\n const id = useId();\n const ctx = useContext(ContentKitContext);\n const f = useContentFilters({ language: p.language, client: p.client });\n const save = useAction(\"filters.save\", p.onError);\n const root = (children: React.ReactNode) => (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-filters\">\n {children}\n </ContentKitUiRoot>\n );\n if (!f.filters) {\n if (f.error) return root(<ErrorLine>{contentError(m, f.error)}</ErrorLine>);\n return root(<Skeleton className=\"h-24 w-full\" />);\n }\n const filters = f.filters;\n const groups = Object.entries(filters.kinds).filter(([kind, rows]) => rows.length && (!p.kinds || p.kinds.includes(kind)));\n if (!groups.length && p.includeDeleted === false) return null;\n return root(\n <>\n {groups.map(([kind, rows]) => (\n <section key={kind} className=\"grid gap-2\" data-content-kind={kind}>\n {groups.length > 1 && <h4 className=\"font-medium\">{labelOf(p.labels, kind)}</h4>}\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {rows.map((x) => (\n <li key={x.entity.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"content-filter\" data-kind={kind} data-slug={x.entity.slug} data-visible={x.visible}>\n <Switch id={`${id}-${kind}-${x.entity.id}`} checked={x.visible} disabled={save.pending} onCheckedChange={(v) => void save.run(() => f.setVisible(kind, x.entity.id, v))} />\n <Label htmlFor={`${id}-${kind}-${x.entity.id}`} className=\"min-w-0 flex-1\">\n {x.entity.name || x.entity.slug}\n </Label>\n </li>\n ))}\n </ul>\n </section>\n ))}\n {p.includeDeleted !== false && ctx?.viewer !== null && (\n <div className=\"flex items-center gap-3\">\n <Switch id={`${id}-deleted`} checked={filters.include_deleted} disabled={save.pending} onCheckedChange={(v) => void save.run(() => f.setIncludeDeleted(v))} />\n <Label htmlFor={`${id}-deleted`}>{t(\"contents.includeDeleted\")}</Label>\n </div>\n )}\n <ErrorLine error={save.error} />\n </>,\n );\n}\n\nexport interface ReleaseDateIndexProps {\n /** Which contents count: the listing's filters. */\n query?: ReleaseDatesQuery;\n /** The bucket chosen (a day YYYY-MM-DD or month YYYY-MM): list with released_on set to it. */\n selected?: string;\n onSelect?: (date: string) => void;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** The release date index: per month or day, newest first, how many contents and how many free. */\nexport function ReleaseDateIndex({ query = {}, selected, onSelect, client, className, appearance }: ReleaseDateIndexProps) {\n const m = useMessages();\n const { t } = m;\n const dates = useReleaseDates(query, { client });\n const label = (date: string) => {\n const [y, mo, d] = date.split(\"-\").map(Number);\n const at = new Date(Date.UTC(y!, (mo ?? 1) - 1, d ?? 1));\n return new Intl.DateTimeFormat(m.language, d ? { dateStyle: \"medium\", timeZone: \"UTC\" } : { year: \"numeric\", month: \"long\", timeZone: \"UTC\" }).format(at);\n };\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-2 text-sm\", className)} data-ckui=\"release-dates\">\n {dates.loading && !dates.items.length ? (\n <Skeleton className=\"h-24 w-full\" />\n ) : dates.error && !dates.items.length ? (\n <ErrorLine>{contentError(m, dates.error)}</ErrorLine>\n ) : !dates.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"contents.noDates\")}</p>\n ) : (\n <ul className=\"grid gap-1\">\n {dates.items.map((d) => (\n <li key={d.date}>\n <button\n type=\"button\"\n aria-pressed={selected === d.date}\n className={cn(\"flex w-full items-center justify-between gap-3 rounded-md px-2 py-1.5 text-start hover:bg-muted\", selected === d.date && \"bg-muted font-medium\")}\n onClick={() => onSelect?.(d.date)}\n data-ckui=\"release-date\"\n data-date={d.date}\n >\n <span>{label(d.date)}</span>\n <span className=\"text-xs text-muted-foreground tabular-nums\">\n {m.plural(\"contents.items\", d.count)}\n {d.free < d.count && ` · ${m.plural(\"contents.free\", d.free)}`}\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n {dates.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={dates.loadingMore} onClick={() => void dates.loadMore()}>\n {t(\"contents.loadMore\")}\n </Button>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
@@ -7,5 +7,5 @@ export interface PopularContentGridProps extends Omit<CardListProps, "infinite">
7
7
  onPeriodChange?: (period: PopularPeriod) => void;
8
8
  locked?: LockedCards;
9
9
  }
10
- /** A popularity listing with matched score/age windows and infinite paging. */
10
+ /** Rank activity in the selected window, across publication ages, with infinite paging. */
11
11
  export declare function PopularContentGrid({ query, period, onPeriodChange, locked, ...p }: PopularContentGridProps): import('react').JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"popular.js","names":[],"sources":["../../../src/components/content/popular.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useMemo, useState } from \"react\";\nimport { popularQuery, type PopularPeriod, type PopularQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { ContentGrid, type CardListProps, type LockedCards } from \"./grid.js\";\n\nexport interface PopularContentGridProps extends Omit<CardListProps, \"infinite\"> {\n query?: PopularQuery;\n /** Controlled period; otherwise the selector starts at 7d. */\n period?: PopularPeriod;\n onPeriodChange?: (period: PopularPeriod) => void;\n locked?: LockedCards;\n}\n\nconst PERIODS: readonly PopularPeriod[] = [\"7d\", \"90d\", \"365d\", \"all\"];\n\n/** A popularity listing with matched score/age windows and infinite paging. */\nexport function PopularContentGrid({ query, period, onPeriodChange, locked, ...p }: PopularContentGridProps) {\n const m = useMessages();\n const [ownPeriod, setOwnPeriod] = useState<PopularPeriod>(\"7d\");\n const current = period ?? ownPeriod;\n const list = useMemo(() => popularQuery(current, query), [current, query]);\n const label = (value: PopularPeriod) => value === \"all\" ? m.t(\"listing.period_all\") : m.plural(\"listing.pastDays\", Number(value.slice(0, -1)));\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"popular-grid\">\n <Tabs value={current} onValueChange={(value) => {\n if (!value || !PERIODS.includes(value as PopularPeriod)) return;\n const next = value as PopularPeriod;\n if (period === undefined) setOwnPeriod(next);\n onPeriodChange?.(next);\n }} className=\"gap-4\">\n <TabsList aria-label={m.t(\"listing.popularPeriod\")} className=\"grid w-full grid-cols-4 group-data-horizontal/tabs:h-auto sm:w-fit\">\n {PERIODS.map((value) => (\n <TabsTrigger key={value} value={value} className=\"min-h-10 whitespace-normal px-3 py-2 text-center\">\n {label(value)}\n </TabsTrigger>\n ))}\n </TabsList>\n <TabsContent value={current}>\n <ContentGrid {...p} surface={p.surface ?? \"popular\"} query={list} locked={locked} infinite />\n </TabsContent>\n </Tabs>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAM,IAAoC;CAAC;CAAM;CAAO;CAAQ;AAAK;AAGrE,SAAgB,EAAmB,EAAE,UAAO,WAAQ,mBAAgB,WAAQ,GAAG,KAA8B;CAC3G,IAAM,IAAI,EAAY,GAChB,CAAC,GAAW,KAAgB,EAAwB,IAAI,GACxD,IAAU,KAAU,GACpB,IAAO,QAAc,EAAa,GAAS,CAAK,GAAG,CAAC,GAAS,CAAK,CAAC,GACnE,KAAS,MAAyB,MAAU,QAAQ,EAAE,EAAE,oBAAoB,IAAI,EAAE,OAAO,oBAAoB,OAAO,EAAM,MAAM,GAAG,EAAE,CAAC,CAAC;CAC7I,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EACtG,UAAA,kBAAC,GAAD;GAAM,OAAO;GAAS,gBAAgB,MAAU;IAC9C,IAAI,CAAC,KAAS,CAAC,EAAQ,SAAS,CAAsB,GAAG;IACzD,IAAM,IAAO;IAEb,AADI,MAAW,KAAA,KAAW,EAAa,CAAI,GAC3C,IAAiB,CAAI;GACvB;GAAG,WAAU;GALb,UAAA,CAME,kBAAC,GAAD;IAAU,cAAY,EAAE,EAAE,uBAAuB;IAAG,WAAU;IAC3D,UAAA,EAAQ,KAAK,MACZ,kBAAC,GAAD;KAAgC;KAAO,WAAU;KAC9C,UAAA,EAAM,CAAK;IACD,GAFK,CAEL,CACd;GACO,CAAA,GACV,kBAAC,GAAD;IAAa,OAAO;IAClB,UAAA,kBAAC,GAAD;KAAa,GAAI;KAAG,SAAS,EAAE,WAAW;KAAW,OAAO;KAAc;KAAQ,UAAA;IAAU,CAAA;GACjF,CAAA,CACT;;CACU,CAAA;AAEtB"}
1
+ {"version":3,"file":"popular.js","names":[],"sources":["../../../src/components/content/popular.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useMemo, useState } from \"react\";\nimport { popularQuery, type PopularPeriod, type PopularQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { ContentGrid, type CardListProps, type LockedCards } from \"./grid.js\";\n\nexport interface PopularContentGridProps extends Omit<CardListProps, \"infinite\"> {\n query?: PopularQuery;\n /** Controlled period; otherwise the selector starts at 7d. */\n period?: PopularPeriod;\n onPeriodChange?: (period: PopularPeriod) => void;\n locked?: LockedCards;\n}\n\nconst PERIODS: readonly PopularPeriod[] = [\"7d\", \"90d\", \"365d\", \"all\"];\n\n/** Rank activity in the selected window, across publication ages, with infinite paging. */\nexport function PopularContentGrid({ query, period, onPeriodChange, locked, ...p }: PopularContentGridProps) {\n const m = useMessages();\n const [ownPeriod, setOwnPeriod] = useState<PopularPeriod>(\"7d\");\n const current = period ?? ownPeriod;\n const list = useMemo(() => popularQuery(current, query), [current, query]);\n const label = (value: PopularPeriod) => value === \"all\" ? m.t(\"listing.period_all\") : m.plural(\"listing.pastDays\", Number(value.slice(0, -1)));\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"popular-grid\">\n <Tabs value={current} onValueChange={(value) => {\n if (!value || !PERIODS.includes(value as PopularPeriod)) return;\n const next = value as PopularPeriod;\n if (period === undefined) setOwnPeriod(next);\n onPeriodChange?.(next);\n }} className=\"gap-4\">\n <TabsList aria-label={m.t(\"listing.popularPeriod\")} className=\"grid w-full grid-cols-4 group-data-horizontal/tabs:h-auto sm:w-fit\">\n {PERIODS.map((value) => (\n <TabsTrigger key={value} value={value} className=\"min-h-10 whitespace-normal px-3 py-2 text-center\">\n {label(value)}\n </TabsTrigger>\n ))}\n </TabsList>\n <TabsContent value={current}>\n <ContentGrid {...p} surface={p.surface ?? \"popular\"} query={list} locked={locked} infinite />\n </TabsContent>\n </Tabs>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAM,IAAoC;CAAC;CAAM;CAAO;CAAQ;AAAK;AAGrE,SAAgB,EAAmB,EAAE,UAAO,WAAQ,mBAAgB,WAAQ,GAAG,KAA8B;CAC3G,IAAM,IAAI,EAAY,GAChB,CAAC,GAAW,KAAgB,EAAwB,IAAI,GACxD,IAAU,KAAU,GACpB,IAAO,QAAc,EAAa,GAAS,CAAK,GAAG,CAAC,GAAS,CAAK,CAAC,GACnE,KAAS,MAAyB,MAAU,QAAQ,EAAE,EAAE,oBAAoB,IAAI,EAAE,OAAO,oBAAoB,OAAO,EAAM,MAAM,GAAG,EAAE,CAAC,CAAC;CAC7I,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EACtG,UAAA,kBAAC,GAAD;GAAM,OAAO;GAAS,gBAAgB,MAAU;IAC9C,IAAI,CAAC,KAAS,CAAC,EAAQ,SAAS,CAAsB,GAAG;IACzD,IAAM,IAAO;IAEb,AADI,MAAW,KAAA,KAAW,EAAa,CAAI,GAC3C,IAAiB,CAAI;GACvB;GAAG,WAAU;GALb,UAAA,CAME,kBAAC,GAAD;IAAU,cAAY,EAAE,EAAE,uBAAuB;IAAG,WAAU;IAC3D,UAAA,EAAQ,KAAK,MACZ,kBAAC,GAAD;KAAgC;KAAO,WAAU;KAC9C,UAAA,EAAM,CAAK;IACD,GAFK,CAEL,CACd;GACO,CAAA,GACV,kBAAC,GAAD;IAAa,OAAO;IAClB,UAAA,kBAAC,GAAD;KAAa,GAAI;KAAG,SAAS,EAAE,WAAW;KAAW,OAAO;KAAc;KAAQ,UAAA;IAAU,CAAA;GACjF,CAAA,CACT;;CACU,CAAA;AAEtB"}
@@ -1,7 +1,11 @@
1
1
  import { ContentKitUiAppearance } from '../../appearance.js';
2
2
  import { ContentKitClient } from '../../client/client.js';
3
3
  import { ContentKitErrorHandler } from '../../react/context.js';
4
+ import { ContentFilterSettingsProps } from './filters.js';
4
5
  export interface ReaderSettingsProps {
6
+ /** Content kinds offered in this dialog; omitted shows all configured kinds. */
7
+ kinds?: ContentFilterSettingsProps["kinds"];
8
+ labels?: ContentFilterSettingsProps["labels"];
5
9
  languages: readonly {
6
10
  value: string;
7
11
  label: string;
@@ -72,6 +72,8 @@ function j(y) {
72
72
  }),
73
73
  j && /* @__PURE__ */ w(M, {
74
74
  language: y.language,
75
+ kinds: y.kinds,
76
+ labels: y.labels,
75
77
  client: j,
76
78
  onError: y.onError
77
79
  }),
@@ -99,7 +101,7 @@ function M(t) {
99
101
  language: t.language,
100
102
  client: t.client
101
103
  });
102
- return i?.filters.length === 0 ? null : /* @__PURE__ */ T("section", {
104
+ return i && !Object.entries(i.kinds).some(([e, n]) => n.length && (!t.kinds || t.kinds.includes(e))) ? null : /* @__PURE__ */ T("section", {
103
105
  "aria-labelledby": r,
104
106
  className: "grid gap-3",
105
107
  children: [/* @__PURE__ */ w("h3", {
@@ -1 +1 @@
1
- {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && <ReaderFilters language={p.language} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters?.filters.length === 0) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings, type ContentFilterSettingsProps } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n /** Content kinds offered in this dialog; omitted shows all configured kinds. */\n kinds?: ContentFilterSettingsProps[\"kinds\"];\n labels?: ContentFilterSettingsProps[\"labels\"];\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && <ReaderFilters language={p.language} kinds={p.kinds} labels={p.labels} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\" | \"kinds\" | \"labels\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters && !Object.entries(filters.kinds).some(([kind, rows]) => rows.length && (!p.kinds || p.kinds.includes(kind)))) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}