@openrails/contentkit-ui 0.86.0 → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -14
- package/dist/client/beacons.js +5 -1
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/content/types.d.ts +4 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +8 -6
- package/dist/client/contents.js +10 -7
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.js +3 -0
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/errors.d.ts +21 -3
- package/dist/client/errors.js +44 -14
- package/dist/client/errors.js.map +1 -1
- package/dist/client/gallery.d.ts +3 -5
- package/dist/client/gallery.js +16 -20
- package/dist/client/gallery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +8 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +16 -3
- package/dist/client/generated/routes.js +28 -3
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +43 -14
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/http.js +27 -27
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/media/client.d.ts +10 -4
- package/dist/client/media/client.js +33 -7
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +1 -1
- package/dist/client/media/rules.js +6 -6
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/media/transport.d.ts +1 -1
- package/dist/client/media/transport.js +16 -16
- package/dist/client/media/transport.js.map +1 -1
- package/dist/client/media/windows.js +1 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/public.d.ts +2 -2
- package/dist/client/public.js +2 -2
- package/dist/client/public.js.map +1 -1
- package/dist/client/versions.d.ts +6 -4
- package/dist/client/versions.js +24 -26
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +25 -25
- package/dist/components/admin/content-manager.js +233 -232
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/content/card.d.ts +5 -3
- package/dist/components/content/card.js +131 -109
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/download-button.d.ts +26 -0
- package/dist/components/content/download-button.js +51 -0
- package/dist/components/content/download-button.js.map +1 -0
- package/dist/components/content/editors.d.ts +8 -0
- package/dist/components/content/editors.js +299 -229
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/grid.d.ts +8 -1
- package/dist/components/content/grid.js +125 -76
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/index.d.ts +3 -2
- package/dist/components/content/listing.d.ts +3 -1
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/paywall.js +61 -61
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/media-gallery.d.ts +1 -4
- package/dist/components/media-gallery.js +19 -34
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/social/comments.js +1 -1
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +2 -1
- package/dist/components/social/engagement.js +5 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/i18n/messages.js +49 -46
- package/dist/i18n/messages.js.map +1 -1
- package/dist/index.js +12 -11
- package/dist/locales/de.js +15 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +3 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +3 -0
- package/dist/locales/discovery/en.js +3 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +3 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +3 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +3 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +3 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +17 -2
- package/dist/locales/en.js +15 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +15 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +15 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +15 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +15 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/comments.d.ts +1 -1
- package/dist/react/comments.js +3 -2
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +13 -5
- package/dist/react/contents.js +163 -149
- package/dist/react/contents.js.map +1 -1
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/detail.d.ts +7 -1
- package/dist/react/detail.js +9 -6
- package/dist/react/detail.js.map +1 -1
- package/dist/react/discovery.d.ts +11 -0
- package/dist/react/discovery.js +49 -30
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/engagement.d.ts +6 -2
- package/dist/react/engagement.js +2 -6
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/public.js +17 -16
- package/dist/react/public.js.map +1 -1
- package/dist/react/versions.d.ts +25 -5
- package/dist/react/versions.js +58 -37
- package/dist/react/versions.js.map +1 -1
- package/dist/react.js +9 -9
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/react/detail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detail.js","names":[],"sources":["../../src/react/detail.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Content, Decision, Version } from \"../client/generated/wire.js\";\nimport { parseVersionCode } from \"../urls/index.js\";\nimport { useVersionParam } from \"./location.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** A content's live versions, first first. */\nexport function liveVersions(c: Content | null | undefined): Version[] {\n return (c?.versions ?? []).filter((v) => v.live).sort((a, b) => a.position - b.position);\n}\n\n
|
|
1
|
+
{"version":3,"file":"detail.js","names":[],"sources":["../../src/react/detail.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Content, Decision, Version } from \"../client/generated/wire.js\";\nimport { parseVersionCode } from \"../urls/index.js\";\nimport { useVersionParam } from \"./location.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** A content's live versions, first first. */\nexport function liveVersions(c: Content | null | undefined): Version[] {\n return (c?.versions ?? []).filter((v) => v.live).sort((a, b) => a.position - b.position);\n}\n\n/**\n * The version a reader of language opens by default: the first live one by\n * position serving it, else the first live one. The server picks cards'\n * versions, covers and meta by the same rule.\n */\nexport function defaultVersion(c: Content | null | undefined, language?: string): Version | null {\n return editionOf(liveVersions(c), language);\n}\n\n/** defaultVersion among versions: the first by position serving language, else the first. */\nexport function editionOf(versions: readonly Version[], language?: string): Version | null {\n const sorted = [...versions].sort((a, b) => a.position - b.position);\n return (language ? sorted.find((v) => v.languages.includes(language)) : undefined) ?? sorted[0] ?? null;\n}\n\n/** The version selected names: its code (any spelling, the page's ?v=) or its id; undefined when none. */\nexport function findVersion(versions: readonly Version[], selected: string | null | undefined): Version | undefined {\n if (!selected) return undefined;\n const code = parseVersionCode(selected);\n return versions.find((v) => v.id === selected) ?? (code ? versions.find((v) => v.code === code) : undefined);\n}\n\nexport interface UseContentDetail {\n content: Content | null;\n /** Live versions, first first. */\n versions: Version[];\n /** The version shown: the one asked for (?v=) when live, else the language's default. */\n version: Version | null;\n /** The shown version's access decision: allowed, what is sold, previews. */\n decision: Decision | null;\n /** Every language a live version serves, in version order. */\n languages: string[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\nexport interface ContentDetailOptions {\n language?: string;\n /** The version to show, by code or id; default the page's ?v=. Unknown or not live: the language's default. */\n version?: string | null;\n /** The page's location for its ?v=; default window.location. */\n location?: string;\n client?: ContentKitClient;\n}\n\n/**\n * A content page's data: text in the reader's language, live versions with\n * their segments and access decisions, groups, attributes and the version to show.\n */\nexport function useContentDetail(id: string | null | undefined, o: ContentDetailOptions = {}): UseContentDetail {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const [param] = useVersionParam({ location: o.location });\n const selected = o.version === undefined ? param : o.version;\n const key = id ? keyOf(\"content-detail\", scope, id, language) : null;\n const r = useResource<Content>(store, key, { type: \"content-detail\", id: id ?? undefined }, (s) => client.contents.get(id!, language, s));\n const content = r.data ?? null;\n return useMemo(() => {\n const versions = liveVersions(content);\n const version = findVersion(versions, selected) ?? defaultVersion(content, language);\n return {\n content,\n versions,\n version,\n decision: version?.decision ?? content?.decision ?? null,\n languages: [...new Set(versions.flatMap((v) => v.languages))],\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n reload: r.reload,\n };\n }, [content, selected, language, key, r.loading, r.loaded, r.error, r.reload]);\n}\n"],"mappings":";;;;;AASA,SAAgB,EAAa,GAA0C;CACrE,QAAQ,GAAG,YAAY,CAAC,EAAA,CAAG,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACzF;AAOA,SAAgB,EAAe,GAA+B,GAAmC;CAC/F,OAAO,EAAU,EAAa,CAAC,GAAG,CAAQ;AAC5C;AAGA,SAAgB,EAAU,GAA8B,GAAmC;CACzF,IAAM,IAAS,CAAC,GAAG,CAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CACnE,QAAQ,IAAW,EAAO,MAAM,MAAM,EAAE,UAAU,SAAS,CAAQ,CAAC,IAAI,KAAA,MAAc,EAAO,MAAM;AACrG;AAGA,SAAgB,EAAY,GAA8B,GAA0D;CAClH,IAAI,CAAC,GAAU;CACf,IAAM,IAAO,EAAiB,CAAQ;CACtC,OAAO,EAAS,MAAM,MAAM,EAAE,OAAO,CAAQ,MAAM,IAAO,EAAS,MAAM,MAAM,EAAE,SAAS,CAAI,IAAI,KAAA;AACpG;AA8BA,SAAgB,EAAiB,GAA+B,IAA0B,CAAC,GAAqB;CAC9G,IAAM,EAAE,WAAQ,UAAO,UAAO,UAAU,MAAa,EAAgB,EAAE,MAAM,GACvE,IAAW,EAAE,YAAY,GACzB,CAAC,KAAS,EAAgB,EAAE,UAAU,EAAE,SAAS,CAAC,GAClD,IAAW,EAAE,YAAY,KAAA,IAAY,IAAQ,EAAE,SAC/C,IAAM,IAAK,EAAM,kBAAkB,GAAO,GAAI,CAAQ,IAAI,MAC1D,IAAI,EAAqB,GAAO,GAAK;EAAE,MAAM;EAAkB,IAAI,KAAM,KAAA;CAAU,IAAI,MAAM,EAAO,SAAS,IAAI,GAAK,GAAU,CAAC,CAAC,GAClI,IAAU,EAAE,QAAQ;CAC1B,OAAO,QAAc;EACnB,IAAM,IAAW,EAAa,CAAO,GAC/B,IAAU,EAAY,GAAU,CAAQ,KAAK,EAAe,GAAS,CAAQ;EACnF,OAAO;GACL;GACA;GACA;GACA,UAAU,GAAS,YAAY,GAAS,YAAY;GACpD,WAAW,CAAC,GAAG,IAAI,IAAI,EAAS,SAAS,MAAM,EAAE,SAAS,CAAC,CAAC;GAC5D,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAW,CAAC,EAAE;GACnC,OAAO,EAAE;GACT,QAAQ,EAAE;EACZ;CACF,GAAG;EAAC;EAAS;EAAU;EAAU;EAAK,EAAE;EAAS,EAAE;EAAQ,EAAE;EAAO,EAAE;CAAM,CAAC;AAC/E"}
|
|
@@ -9,6 +9,17 @@ import { UseList } from './use-resource.js';
|
|
|
9
9
|
export declare function useContents(q: ContentQuery | null, o?: {
|
|
10
10
|
client?: ContentKitClient;
|
|
11
11
|
}): UseList<Card>;
|
|
12
|
+
/**
|
|
13
|
+
* How many contents of a list the reader doesn't open: its filters' count
|
|
14
|
+
* with locked cards less without them (sort and paging aside); null reads
|
|
15
|
+
* nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useLockedCount(q: ContentQuery | null, o?: {
|
|
18
|
+
client?: ContentKitClient;
|
|
19
|
+
}): {
|
|
20
|
+
count?: number;
|
|
21
|
+
loading: boolean;
|
|
22
|
+
};
|
|
12
23
|
/** Keyword search results as cards; nothing while q.q is empty. */
|
|
13
24
|
export declare function useSearch(q: SearchQuery | null, o?: {
|
|
14
25
|
client?: ContentKitClient;
|
package/dist/react/discovery.js
CHANGED
|
@@ -23,13 +23,32 @@ function _(e, t = {}) {
|
|
|
23
23
|
}, t)));
|
|
24
24
|
}
|
|
25
25
|
function v(e, t = {}) {
|
|
26
|
+
let { client: n, store: a, scope: s } = i(t.client), c = g(f(() => {
|
|
27
|
+
if (!e) return null;
|
|
28
|
+
let { sort: t, cursor: n, limit: r, card_groups: i, mode: a, ...o } = e;
|
|
29
|
+
return o;
|
|
30
|
+
}, [e])), l = o(a, c ? r("release-dates", s, "locked", c) : null, { type: "release-dates" }, async (e) => {
|
|
31
|
+
let t = async (t) => (await n.discovery.releaseDates({
|
|
32
|
+
...c,
|
|
33
|
+
mode: t,
|
|
34
|
+
by: "month",
|
|
35
|
+
limit: 1e3
|
|
36
|
+
}, e)).dates.reduce((e, t) => e + t.count, 0), [r, i] = await Promise.all([t("lock"), t("hide")]);
|
|
37
|
+
return Math.max(0, r - i);
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
count: l.data,
|
|
41
|
+
loading: l.loading
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function y(e, t = {}) {
|
|
26
45
|
let { client: n, store: o, scope: s } = i(t.client), c = g(e?.q.trim() ? e : null);
|
|
27
46
|
return a(o, c ? r("cards", s, "search", c) : null, { type: "cards" }, async (e, t) => h(await n.discovery.search({
|
|
28
47
|
...c,
|
|
29
48
|
cursor: e
|
|
30
49
|
}, t)));
|
|
31
50
|
}
|
|
32
|
-
function
|
|
51
|
+
function b(e, t, n) {
|
|
33
52
|
let { store: r } = i(n.client), a = o(r, e, { type: "cards" }, async (e) => (await t(e)).contents), s = a.error?.code === "not_configured";
|
|
34
53
|
return {
|
|
35
54
|
items: a.data ?? [],
|
|
@@ -39,15 +58,15 @@ function y(e, t, n) {
|
|
|
39
58
|
reload: a.reload
|
|
40
59
|
};
|
|
41
60
|
}
|
|
42
|
-
function
|
|
61
|
+
function x(e, t = {}, n = {}) {
|
|
43
62
|
let { client: a, scope: o } = i(n.client), s = g(t);
|
|
44
|
-
return
|
|
63
|
+
return b(e ? r("cards", o, "similar", e, s) : null, (t) => a.discovery.similar(e, s, t), n);
|
|
45
64
|
}
|
|
46
|
-
function
|
|
65
|
+
function S(e = {}, t = {}) {
|
|
47
66
|
let { client: n, scope: a, viewer: o } = i(t.client), s = g(e);
|
|
48
|
-
return
|
|
67
|
+
return b(o === null || !s ? null : r("cards", a, "recommended", s), (e) => n.discovery.recommendations(s, e), t);
|
|
49
68
|
}
|
|
50
|
-
function
|
|
69
|
+
function C(e = {}, t = {}) {
|
|
51
70
|
let { client: n, store: o, scope: s } = i(t.client), c = g(e);
|
|
52
71
|
return a(o, r("release-dates", s, c), { type: "release-dates" }, async (e, t) => {
|
|
53
72
|
let r = await n.discovery.releaseDates({
|
|
@@ -63,7 +82,7 @@ function S(e = {}, t = {}) {
|
|
|
63
82
|
};
|
|
64
83
|
});
|
|
65
84
|
}
|
|
66
|
-
function
|
|
85
|
+
function w(e = {}, t = {}) {
|
|
67
86
|
let { client: o, store: s, scope: c, viewer: u } = i(t.client), d = g(e), f = n(), p = a(s, u === null || !d ? null : r("history", c, d), { type: "history" }, async (e, t) => h(await o.discovery.history({
|
|
68
87
|
...d,
|
|
69
88
|
cursor: e
|
|
@@ -73,7 +92,7 @@ function C(e = {}, t = {}) {
|
|
|
73
92
|
forget: m
|
|
74
93
|
};
|
|
75
94
|
}
|
|
76
|
-
function
|
|
95
|
+
function T(e = {}) {
|
|
77
96
|
let { client: t, store: a, scope: s } = i(e.client), c = n(), u = e.enabled === !1 ? null : r("searches", s, e.limit ?? 10), d = o(a, u, { type: "searches" }, async (n) => (await t.discovery.recentSearches({ limit: e.limit }, n)).searches), f = l((e) => c(() => t.discovery.clearSearches(e ? { query: e } : {})), [t, c]);
|
|
78
97
|
return {
|
|
79
98
|
searches: d.data ?? [],
|
|
@@ -82,28 +101,28 @@ function w(e = {}) {
|
|
|
82
101
|
clear: f
|
|
83
102
|
};
|
|
84
103
|
}
|
|
85
|
-
var
|
|
104
|
+
var E = (e) => ({
|
|
86
105
|
items: e.items.map((e) => ({
|
|
87
106
|
...e,
|
|
88
107
|
id: e.content?.id ?? e.group?.id ?? ""
|
|
89
108
|
})),
|
|
90
109
|
next: e.next_cursor ?? null
|
|
91
110
|
});
|
|
92
|
-
function
|
|
111
|
+
function D(e = {}, t = {}) {
|
|
93
112
|
let { client: n, store: o, scope: s, viewer: c } = i(t.client), l = g(e);
|
|
94
|
-
return a(o, c === null ? null : r("favorites", s, l), { type: "favorites" }, async (e, t) =>
|
|
113
|
+
return a(o, c === null ? null : r("favorites", s, l), { type: "favorites" }, async (e, t) => E(await n.favorites.mine({
|
|
95
114
|
...l,
|
|
96
115
|
cursor: e
|
|
97
116
|
}, t)));
|
|
98
117
|
}
|
|
99
|
-
function
|
|
118
|
+
function O(e = {}, t = {}) {
|
|
100
119
|
let { client: n, store: o, scope: s, viewer: c } = i(t.client), l = g(e);
|
|
101
|
-
return a(o, c === null ? null : r("reactions", s, l), { type: "reactions" }, async (e, t) =>
|
|
120
|
+
return a(o, c === null ? null : r("reactions", s, l), { type: "reactions" }, async (e, t) => E(await n.reactions.mine({
|
|
102
121
|
...l,
|
|
103
122
|
cursor: e
|
|
104
123
|
}, t)));
|
|
105
124
|
}
|
|
106
|
-
function
|
|
125
|
+
function k(e = {}) {
|
|
107
126
|
let { client: t, store: a, scope: s, viewer: c, language: u } = i(e.client), d = e.language ?? u, f = c === null ? null : r("content-filters", s, d), h = o(a, f, { type: "content-filters" }, (e) => t.discovery.contentFilters(d, e)), g = n(), [_, v] = m(0), y = p(h.data);
|
|
108
127
|
y.current = h.data;
|
|
109
128
|
let b = l(async (e) => {
|
|
@@ -149,11 +168,11 @@ function O(e = {}) {
|
|
|
149
168
|
setIncludeDeleted: C
|
|
150
169
|
};
|
|
151
170
|
}
|
|
152
|
-
var
|
|
171
|
+
var A = {
|
|
153
172
|
contents: [],
|
|
154
173
|
groups: []
|
|
155
174
|
};
|
|
156
|
-
function
|
|
175
|
+
function j(t, n = {}) {
|
|
157
176
|
let { client: r, language: a } = i(n.client), { debounce: o = 150, minLength: s = 1, client: c, ...l } = n, u = t.trim(), p = f(() => ({
|
|
158
177
|
display_language: a,
|
|
159
178
|
...l,
|
|
@@ -163,14 +182,14 @@ function A(t, n = {}) {
|
|
|
163
182
|
u,
|
|
164
183
|
JSON.stringify(l)
|
|
165
184
|
]), [h, g] = m({
|
|
166
|
-
...
|
|
185
|
+
...A,
|
|
167
186
|
query: "",
|
|
168
187
|
loading: !1
|
|
169
188
|
});
|
|
170
189
|
return d(() => {
|
|
171
190
|
if (u.length < s) {
|
|
172
191
|
g({
|
|
173
|
-
...
|
|
192
|
+
...A,
|
|
174
193
|
query: "",
|
|
175
194
|
loading: !1
|
|
176
195
|
});
|
|
@@ -189,7 +208,7 @@ function A(t, n = {}) {
|
|
|
189
208
|
}), (t) => {
|
|
190
209
|
let n = e(t);
|
|
191
210
|
n.code !== "aborted" && g({
|
|
192
|
-
...
|
|
211
|
+
...A,
|
|
193
212
|
query: u,
|
|
194
213
|
loading: !1,
|
|
195
214
|
error: n
|
|
@@ -207,25 +226,25 @@ function A(t, n = {}) {
|
|
|
207
226
|
s
|
|
208
227
|
]), h;
|
|
209
228
|
}
|
|
210
|
-
var
|
|
211
|
-
function
|
|
212
|
-
let n =
|
|
213
|
-
return n ||
|
|
229
|
+
var M = /* @__PURE__ */ new WeakMap();
|
|
230
|
+
function N(e, t) {
|
|
231
|
+
let n = M.get(e);
|
|
232
|
+
return n || M.set(e, n = new s(e, t)), n;
|
|
214
233
|
}
|
|
215
|
-
function
|
|
234
|
+
function P(e = {}) {
|
|
216
235
|
let { client: t } = i(e.client);
|
|
217
|
-
return
|
|
236
|
+
return N(t);
|
|
218
237
|
}
|
|
219
|
-
function
|
|
220
|
-
let r =
|
|
238
|
+
function F(e, t, n = {}) {
|
|
239
|
+
let r = P(n), { client: i, ...a } = n, o = f(() => e ? r.view(e, t, a) : null, [
|
|
221
240
|
r,
|
|
222
241
|
e,
|
|
223
242
|
t
|
|
224
243
|
]);
|
|
225
244
|
return l((e) => o?.update(e), [o]);
|
|
226
245
|
}
|
|
227
|
-
function
|
|
228
|
-
let n =
|
|
246
|
+
function I(e, t) {
|
|
247
|
+
let n = P(t), r = f(() => c(), [t.render]), i = f(() => ({
|
|
229
248
|
render_id: r,
|
|
230
249
|
surface: t.surface,
|
|
231
250
|
ranker: t.ranker,
|
|
@@ -294,6 +313,6 @@ function F(e, t) {
|
|
|
294
313
|
};
|
|
295
314
|
}
|
|
296
315
|
//#endregion
|
|
297
|
-
export {
|
|
316
|
+
export { N as beaconsFor, P as useBeacons, k as useContentFilters, _ as useContents, I as useExposure, w as useHistory, v as useLockedCount, D as useMyFavorites, O as useMyReactions, T as useRecentSearches, S as useRecommendations, C as useReleaseDates, y as useSearch, x as useSimilar, j as useTypeahead, F as useViewSignal };
|
|
298
317
|
|
|
299
318
|
//# sourceMappingURL=discovery.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** The reader's recent searches, newest first (signed out, of this tab's session); enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope } = useContentScope(o.client);\n const guarded = useGuarded();\n const key = o.enabled === false ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n const clear = useCallback((query?: string) => guarded(() => client.discovery.clearSearches(query ? { query } : {})), [client, guarded]);\n return { searches: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.group?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until read (and signed out). */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces every setting; the lists showing re-read. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** The signed-in reader's content filters: each configured group, its default and their setting. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = viewer === null ? null : keyOf(\"content-filters\", scope, language);\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const current = useRef(r.data);\n current.current = r.data;\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.discovery.setContentFilters(update, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, guarded, language, key, store],\n );\n const settings = (f: ContentFilters) => f.filters.filter((x) => x.overridden).map((x) => ({ group_id: x.group.id, visible: x.visible }));\n const setVisible = useCallback(\n (groupId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: [...settings(f).filter((x) => x.group_id !== groupId), { group_id: groupId, visible }], include_deleted: f.include_deleted });\n },\n [save],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: settings(f), include_deleted: include });\n },\n [save],\n );\n return { filters: r.data ?? null, loading: !!key && (r.loading || !r.loaded), error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n groups: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"groups\"> = { contents: [], groups: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient } = {},\n): UseTypeahead {\n const { client, language } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, ...rest } = o;\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead>({ ...NONE, query: \"\", loading: false });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false });\n return;\n }\n setState((s) => ({ ...s, loading: true }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n client.discovery.typeahead(query, ctl.signal).then(\n (r) => setState({ contents: r.contents, groups: r.groups, query: q, loading: false }),\n (e) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength]);\n return state;\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const b = useBeacons(o);\n const { client: _c, ...attribution } = o;\n const session = useMemo(() => (content ? b.view(content, version, attribution) : null), [b, content, version]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => session?.update(u), [session]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string }[], o: ExposureOptions): UseExposure {\n const b = useBeacons(o);\n const renderId = useMemo(() => eventId(), [o.render]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ render_id: renderId, surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language }), [renderId, o.surface, o.ranker, o.query_id, o.language]);\n const rendered = useRef(new Set<string>());\n const visible = useRef(new Set<string>());\n useEffect(() => {\n rendered.current = new Set();\n visible.current = new Set();\n }, [renderId]);\n useEffect(() => {\n const shown = items.flatMap((it, i) => (rendered.current.has(it.id) ? [] : [{ content_id: it.id, position: i + 1 }]));\n for (const s of shown) rendered.current.add(s.content_id);\n if (shown.length) b.exposure({ ...base, stage: \"rendered\", occurred_at: new Date().toISOString(), shown });\n }, [b, base, items]);\n const pending = useRef<{ content_id: string; position: number }[]>([]);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver(\n (entries) => {\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement;\n const content = el.dataset.exposureContent!;\n if (visible.current.has(content)) continue;\n visible.current.add(content);\n pending.current.push({ content_id: content, position: Number(el.dataset.exposurePosition) });\n }\n timer.current ??= setTimeout(() => {\n timer.current = undefined;\n const shown = pending.current.splice(0);\n if (shown.length) b.exposure({ ...base, stage: \"visible\", occurred_at: new Date().toISOString(), shown });\n }, 300);\n },\n { threshold: 0.5 },\n );\n }, [b, base]);\n useEffect(() => () => observer?.disconnect(), [observer]);\n const track = useCallback(\n (content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n },\n [observer],\n );\n const click = useCallback((content: string, position: number) => b.click(content, { render_id: renderId, surface: o.surface, position }), [b, renderId, o.surface]);\n return { render_id: renderId, track, click };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { AccessMode, ContentQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/**\n * How many contents of a list the reader doesn't open: its filters' count\n * with locked cards less without them (sort and paging aside); null reads\n * nothing.\n */\nexport function useLockedCount(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): { count?: number; loading: boolean } {\n const { client, store, scope } = useContentScope(o.client);\n const filter = useMemo(() => {\n if (!q) return null;\n const { sort: _s, cursor: _c, limit: _l, card_groups: _g, mode: _m, ...f } = q;\n return f;\n }, [q]);\n const query = useDisplay(filter);\n const r = useResource<number>(store, query ? keyOf(\"release-dates\", scope, \"locked\", query) : null, { type: \"release-dates\" }, async (signal) => {\n const count = async (mode: AccessMode) =>\n (await client.discovery.releaseDates({ ...query, mode, by: \"month\", limit: 1000 }, signal)).dates.reduce((n, d) => n + d.count, 0);\n const [all, open] = await Promise.all([count(\"lock\"), count(\"hide\")]);\n return Math.max(0, all - open);\n });\n return { count: r.data, loading: r.loading };\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** The reader's recent searches, newest first (signed out, of this tab's session); enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope } = useContentScope(o.client);\n const guarded = useGuarded();\n const key = o.enabled === false ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n const clear = useCallback((query?: string) => guarded(() => client.discovery.clearSearches(query ? { query } : {})), [client, guarded]);\n return { searches: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.group?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until read (and signed out). */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces every setting; the lists showing re-read. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** The signed-in reader's content filters: each configured group, its default and their setting. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = viewer === null ? null : keyOf(\"content-filters\", scope, language);\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const current = useRef(r.data);\n current.current = r.data;\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.discovery.setContentFilters(update, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, guarded, language, key, store],\n );\n const settings = (f: ContentFilters) => f.filters.filter((x) => x.overridden).map((x) => ({ group_id: x.group.id, visible: x.visible }));\n const setVisible = useCallback(\n (groupId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: [...settings(f).filter((x) => x.group_id !== groupId), { group_id: groupId, visible }], include_deleted: f.include_deleted });\n },\n [save],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: settings(f), include_deleted: include });\n },\n [save],\n );\n return { filters: r.data ?? null, loading: !!key && (r.loading || !r.loaded), error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n groups: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"groups\"> = { contents: [], groups: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient } = {},\n): UseTypeahead {\n const { client, language } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, ...rest } = o;\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead>({ ...NONE, query: \"\", loading: false });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false });\n return;\n }\n setState((s) => ({ ...s, loading: true }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n client.discovery.typeahead(query, ctl.signal).then(\n (r) => setState({ contents: r.contents, groups: r.groups, query: q, loading: false }),\n (e) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength]);\n return state;\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const b = useBeacons(o);\n const { client: _c, ...attribution } = o;\n const session = useMemo(() => (content ? b.view(content, version, attribution) : null), [b, content, version]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => session?.update(u), [session]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string }[], o: ExposureOptions): UseExposure {\n const b = useBeacons(o);\n const renderId = useMemo(() => eventId(), [o.render]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ render_id: renderId, surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language }), [renderId, o.surface, o.ranker, o.query_id, o.language]);\n const rendered = useRef(new Set<string>());\n const visible = useRef(new Set<string>());\n useEffect(() => {\n rendered.current = new Set();\n visible.current = new Set();\n }, [renderId]);\n useEffect(() => {\n const shown = items.flatMap((it, i) => (rendered.current.has(it.id) ? [] : [{ content_id: it.id, position: i + 1 }]));\n for (const s of shown) rendered.current.add(s.content_id);\n if (shown.length) b.exposure({ ...base, stage: \"rendered\", occurred_at: new Date().toISOString(), shown });\n }, [b, base, items]);\n const pending = useRef<{ content_id: string; position: number }[]>([]);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver(\n (entries) => {\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement;\n const content = el.dataset.exposureContent!;\n if (visible.current.has(content)) continue;\n visible.current.add(content);\n pending.current.push({ content_id: content, position: Number(el.dataset.exposurePosition) });\n }\n timer.current ??= setTimeout(() => {\n timer.current = undefined;\n const shown = pending.current.splice(0);\n if (shown.length) b.exposure({ ...base, stage: \"visible\", occurred_at: new Date().toISOString(), shown });\n }, 300);\n },\n { threshold: 0.5 },\n );\n }, [b, base]);\n useEffect(() => () => observer?.disconnect(), [observer]);\n const track = useCallback(\n (content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n },\n [observer],\n );\n const click = useCallback((content: string, position: number) => b.click(content, { render_id: renderId, surface: o.surface, position }), [b, renderId, o.surface]);\n return { render_id: renderId, track, click };\n}\n"],"mappings":"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"}
|
|
@@ -11,14 +11,18 @@ export interface UseReaction {
|
|
|
11
11
|
/** Sets value, or clears it when it is the current one. */
|
|
12
12
|
toggle: (value: 1 | -1) => Promise<void>;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* A target's likes and dislikes and the caller's own reaction (initial:
|
|
16
|
+
* counts the host already has). The counts are absent where the target's
|
|
17
|
+
* kind's locked view hides them.
|
|
18
|
+
*/
|
|
15
19
|
export declare function useReaction(target: Target, o?: {
|
|
16
20
|
initial?: ReactionCounts;
|
|
17
21
|
client?: ContentKitClient;
|
|
18
22
|
}): UseReaction;
|
|
19
23
|
export interface UseFavorite {
|
|
20
24
|
favorited: boolean;
|
|
21
|
-
/** How many favorited the item; undefined until read. */
|
|
25
|
+
/** How many favorited the item; undefined until read, or where its kind's locked view hides it. */
|
|
22
26
|
count?: number;
|
|
23
27
|
loading: boolean;
|
|
24
28
|
/** A write is in flight. */
|
package/dist/react/engagement.js
CHANGED
|
@@ -3,11 +3,7 @@ import { keyOf as t, useContentScope as n, useResource as r } from "./use-resour
|
|
|
3
3
|
import { withReaction as i } from "./comments.js";
|
|
4
4
|
import { useCallback as a, useState as o } from "react";
|
|
5
5
|
//#region src/react/engagement.ts
|
|
6
|
-
var s = {
|
|
7
|
-
likes: 0,
|
|
8
|
-
dislikes: 0,
|
|
9
|
-
mine: 0
|
|
10
|
-
};
|
|
6
|
+
var s = { mine: 0 };
|
|
11
7
|
function c(o, c = {}) {
|
|
12
8
|
let { client: l, store: u, scope: d } = n(c.client), { type: f, id: p, version: m } = o, h = t("reaction", d, f, p, m), g = r(u, h, {
|
|
13
9
|
type: "reaction",
|
|
@@ -65,7 +61,7 @@ function l(i, s = {}) {
|
|
|
65
61
|
let n = l.snapshot(m).data, r = l.mark(m);
|
|
66
62
|
n && n.favorited !== t && l.set(m, {
|
|
67
63
|
favorited: t,
|
|
68
|
-
count: Math.max(0, n.count + (t ? 1 : -1))
|
|
64
|
+
count: n.count === void 0 ? void 0 : Math.max(0, n.count + (t ? 1 : -1))
|
|
69
65
|
}), v(!0);
|
|
70
66
|
try {
|
|
71
67
|
await c.favorites.set({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engagement.js","names":[],"sources":["../../src/react/engagement.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { toContentKitError } from \"../client/errors.js\";\nimport type { Reaction, Target } from \"../client/content/types.js\";\nimport type { FavoriteState, ReactionCounts } from \"../client/generated/wire.js\";\nimport { withReaction } from \"./comments.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaction {\n counts: ReactionCounts;\n loading: boolean;\n error?: ContentKitError;\n /** Sets the caller's reaction at once; rolled back if the server refuses it. */\n set: (value: Reaction) => Promise<void>;\n /** Sets value, or clears it when it is the current one. */\n toggle: (value: 1 | -1) => Promise<void>;\n}\n\nconst ZERO: ReactionCounts = {
|
|
1
|
+
{"version":3,"file":"engagement.js","names":[],"sources":["../../src/react/engagement.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { toContentKitError } from \"../client/errors.js\";\nimport type { Reaction, Target } from \"../client/content/types.js\";\nimport type { FavoriteState, ReactionCounts } from \"../client/generated/wire.js\";\nimport { withReaction } from \"./comments.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaction {\n counts: ReactionCounts;\n loading: boolean;\n error?: ContentKitError;\n /** Sets the caller's reaction at once; rolled back if the server refuses it. */\n set: (value: Reaction) => Promise<void>;\n /** Sets value, or clears it when it is the current one. */\n toggle: (value: 1 | -1) => Promise<void>;\n}\n\nconst ZERO: ReactionCounts = { mine: 0 };\n\n/**\n * A target's likes and dislikes and the caller's own reaction (initial:\n * counts the host already has). The counts are absent where the target's\n * kind's locked view hides them.\n */\nexport function useReaction(target: Target, o: { initial?: ReactionCounts; client?: ContentKitClient } = {}): UseReaction {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const key = keyOf(\"reaction\", scope, type, id, version);\n const r = useResource<ReactionCounts>(store, key, { type: \"reaction\", target: { type, id, version } }, (s) => client.reactions.get({ type, id, version }, s), {\n initial: o.initial,\n });\n const counts = r.data ?? ZERO;\n const set = useCallback(\n async (value: Reaction) => {\n const prev = store.snapshot<ReactionCounts>(key).data ?? counts;\n const token = store.mark(key);\n store.set(key, withReaction(prev, value));\n try {\n await client.reactions.set({ type, id, version }, value);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n }\n },\n [client, store, key, type, id, version, counts],\n );\n const toggle = useCallback((value: 1 | -1) => set(counts.mine === value ? 0 : value), [set, counts.mine]);\n return { counts, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, set, toggle };\n}\n\nexport interface UseFavorite {\n favorited: boolean;\n /** How many favorited the item; undefined until read, or where its kind's locked view hides it. */\n count?: number;\n loading: boolean;\n /** A write is in flight. */\n pending: boolean;\n error?: ContentKitError;\n /** Sets it at once (count moved by one); rolled back if the server refuses it. */\n set: (favorited: boolean) => Promise<void>;\n toggle: () => Promise<void>;\n}\n\n/**\n * An item's favorite count and whether the signed-in caller favorited it\n * (initial: what the host already has). Signed out, favorited stays false.\n */\nexport function useFavorite(target: Target, o: { initial?: FavoriteState; client?: ContentKitClient } = {}): UseFavorite {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const key = keyOf(\"favorite\", scope, type, id, version);\n const r = useResource<FavoriteState>(store, key, { type: \"favorite\", target: { type, id, version } }, (s) => client.favorites.get({ type, id, version }, s), {\n initial: o.initial,\n });\n const favorited = !!r.data?.favorited;\n const [pending, setPending] = useState(false);\n const set = useCallback(\n async (next: boolean) => {\n const prev = store.snapshot<FavoriteState>(key).data;\n const token = store.mark(key);\n if (prev && prev.favorited !== next)\n store.set<FavoriteState>(key, { favorited: next, count: prev.count === undefined ? undefined : Math.max(0, prev.count + (next ? 1 : -1)) });\n setPending(true);\n try {\n await client.favorites.set({ type, id, version }, next);\n } catch (e) {\n if (prev) store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, type, id, version],\n );\n const toggle = useCallback(() => set(!favorited), [set, favorited]);\n return { favorited, count: r.data?.count, loading: r.loading || (!r.loaded && o.initial === undefined), pending, error: r.error, set, toggle };\n}\n"],"mappings":";;;;;AAmBA,IAAM,IAAuB,EAAE,MAAM,EAAE;AAOvC,SAAgB,EAAY,GAAgB,IAA6D,CAAC,GAAgB;CACxH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,EAAE,SAAM,OAAI,eAAY,GACxB,IAAM,EAAM,YAAY,GAAO,GAAM,GAAI,CAAO,GAChD,IAAI,EAA4B,GAAO,GAAK;EAAE,MAAM;EAAY,QAAQ;GAAE;GAAM;GAAI;EAAQ;CAAE,IAAI,MAAM,EAAO,UAAU,IAAI;EAAE;EAAM;EAAI;CAAQ,GAAG,CAAC,GAAG,EAC5J,SAAS,EAAE,QACb,CAAC,GACK,IAAS,EAAE,QAAQ,GACnB,IAAM,EACV,OAAO,MAAoB;EACzB,IAAM,IAAO,EAAM,SAAyB,CAAG,CAAC,CAAC,QAAQ,GACnD,IAAQ,EAAM,KAAK,CAAG;EAC5B,EAAM,IAAI,GAAK,EAAa,GAAM,CAAK,CAAC;EACxC,IAAI;GACF,MAAM,EAAO,UAAU,IAAI;IAAE;IAAM;IAAI;GAAQ,GAAG,CAAK;EACzD,SAAS,GAAG;GAEV,MADA,EAAM,SAAS,GAAK,GAAO,CAAI,GACzB,EAAkB,CAAC;EAC3B;CACF,GACA;EAAC;EAAQ;EAAO;EAAK;EAAM;EAAI;EAAS;CAAM,CAChD,GACM,IAAS,GAAa,MAAkB,EAAI,EAAO,SAAS,IAAQ,IAAI,CAAK,GAAG,CAAC,GAAK,EAAO,IAAI,CAAC;CACxG,OAAO;EAAE;EAAQ,SAAS,EAAE,WAAY,CAAC,EAAE,UAAU,EAAE,YAAY,KAAA;EAAY,OAAO,EAAE;EAAO;EAAK;CAAO;AAC7G;AAmBA,SAAgB,EAAY,GAAgB,IAA4D,CAAC,GAAgB;CACvH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,EAAE,SAAM,OAAI,eAAY,GACxB,IAAM,EAAM,YAAY,GAAO,GAAM,GAAI,CAAO,GAChD,IAAI,EAA2B,GAAO,GAAK;EAAE,MAAM;EAAY,QAAQ;GAAE;GAAM;GAAI;EAAQ;CAAE,IAAI,MAAM,EAAO,UAAU,IAAI;EAAE;EAAM;EAAI;CAAQ,GAAG,CAAC,GAAG,EAC3J,SAAS,EAAE,QACb,CAAC,GACK,IAAY,CAAC,CAAC,EAAE,MAAM,WACtB,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,IAAM,EACV,OAAO,MAAkB;EACvB,IAAM,IAAO,EAAM,SAAwB,CAAG,CAAC,CAAC,MAC1C,IAAQ,EAAM,KAAK,CAAG;EAG5B,AAFI,KAAQ,EAAK,cAAc,KAC7B,EAAM,IAAmB,GAAK;GAAE,WAAW;GAAM,OAAO,EAAK,UAAU,KAAA,IAAY,KAAA,IAAY,KAAK,IAAI,GAAG,EAAK,SAAS,IAAO,IAAI,GAAG;EAAE,CAAC,GAC5I,EAAW,EAAI;EACf,IAAI;GACF,MAAM,EAAO,UAAU,IAAI;IAAE;IAAM;IAAI;GAAQ,GAAG,CAAI;EACxD,SAAS,GAAG;GAEV,MADI,KAAM,EAAM,SAAS,GAAK,GAAO,CAAI,GACnC,EAAkB,CAAC;EAC3B,UAAU;GACR,EAAW,EAAK;EAClB;CACF,GACA;EAAC;EAAQ;EAAO;EAAK;EAAM;EAAI;CAAO,CACxC,GACM,IAAS,QAAkB,EAAI,CAAC,CAAS,GAAG,CAAC,GAAK,CAAS,CAAC;CAClE,OAAO;EAAE;EAAW,OAAO,EAAE,MAAM;EAAO,SAAS,EAAE,WAAY,CAAC,EAAE,UAAU,EAAE,YAAY,KAAA;EAAY;EAAS,OAAO,EAAE;EAAO;EAAK;CAAO;AAC/I"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -13,11 +13,11 @@ export { useFavorite, useReaction, type UseFavorite, type UseReaction } from './
|
|
|
13
13
|
export { usePoll, usePollEditor, usePolls, withVote, type PollFilter, type PollImages, type UsePoll, type UsePollEditor } from './polls.js';
|
|
14
14
|
export { useContent, useManagedContents, type ManageFilter, type UseContent } from './contents.js';
|
|
15
15
|
export { useActivity, useMyStats, useTopContents, type UseStats } from './stats.js';
|
|
16
|
-
export { defaultVersion, findVersion, liveVersions, useContentDetail, type ContentDetailOptions, type UseContentDetail } from './detail.js';
|
|
16
|
+
export { defaultVersion, editionOf, findVersion, liveVersions, useContentDetail, type ContentDetailOptions, type UseContentDetail } from './detail.js';
|
|
17
17
|
export { useVersionParam } from './location.js';
|
|
18
18
|
export { memberId, useContentGroups, useGroup, useGroupMembers, useGroups, useKinds, useMyGroups, type GroupFilter, type GroupSort, type Member, type UseContentGroups, type UseGroup, type UseGroupMembers, type UseKinds, } from './groups.js';
|
|
19
|
-
export { beaconsFor, useBeacons, useContentFilters, useContents, useExposure, useHistory, useMyFavorites, useMyReactions, useRecentSearches, useRecommendations, useReleaseDates, useSearch, useSimilar, useTypeahead, useViewSignal, type ExposureOptions, type MyInteraction, type ReleaseDate, type UseContentFilters, type UseExposure, type UseHistory, type UseRail, type UseRecentSearches, type UseTypeahead, } from './discovery.js';
|
|
20
|
-
export { nextRelease, useReaderPlan, useSegmentPages, useVersionDownload, type ReaderPage, type UseReaderPlan, type UseSegmentPages, type UseVersionDownload, } from './versions.js';
|
|
19
|
+
export { beaconsFor, useBeacons, useContentFilters, useContents, useExposure, useHistory, useLockedCount, useMyFavorites, useMyReactions, useRecentSearches, useRecommendations, useReleaseDates, useSearch, useSimilar, useTypeahead, useViewSignal, type ExposureOptions, type MyInteraction, type ReleaseDate, type UseContentFilters, type UseExposure, type UseHistory, type UseRail, type UseRecentSearches, type UseTypeahead, } from './discovery.js';
|
|
20
|
+
export { downloadMessage, nextRelease, startDownload, useReaderPlan, useSegmentPages, useVersionDownload, type ReaderPage, type UseReaderPlan, type UseSegmentPages, type UseVersionDownload, } from './versions.js';
|
|
21
21
|
export { useContentScope, usePaged, type UseList, type UsePaged } from './use-resource.js';
|
|
22
22
|
export { ResourceStore, resourcesFor, type Page, type Resource } from './resources.js';
|
|
23
23
|
export { useCrop, useEncodeProgress, useUpload, useUploadQueue, type UploadQueueOptions, type UploadStatus, type UseCrop, type UseCropOptions, type UseEncodeProgress, type UseUpload, type UseUploadQueue, } from './upload.js';
|
package/dist/react/public.js
CHANGED
|
@@ -23,27 +23,27 @@ function f(e = {}) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function p(r, a, s = {}) {
|
|
26
|
-
let { kind: l, id: u } = r,
|
|
27
|
-
prefix:
|
|
28
|
-
read:
|
|
29
|
-
client:
|
|
26
|
+
let { kind: l, id: u, version: d } = r, p = i(s.client), { presets: m, loaded: h } = f({ client: p }), g = m.find((e) => e.kind === l && e.name === a), _ = s.image !== void 0, v = g ? t(g.from) ? n(g.from) : g.from : void 0, y = o(r, {
|
|
27
|
+
prefix: v,
|
|
28
|
+
read: _ || !h ? null : s.read,
|
|
29
|
+
client: p
|
|
30
30
|
});
|
|
31
31
|
return {
|
|
32
32
|
...c(() => {
|
|
33
|
-
let t =
|
|
33
|
+
let t = _ ? s.image : y.read?.public?.find((e) => e.preset === a);
|
|
34
34
|
if (t && t.renditions.length) return {
|
|
35
35
|
image: {
|
|
36
36
|
preset: a,
|
|
37
37
|
renditions: t.renditions,
|
|
38
|
-
aspect: "aspect" in t && t.aspect ||
|
|
38
|
+
aspect: "aspect" in t && t.aspect || g?.aspect
|
|
39
39
|
},
|
|
40
40
|
isDefault: !1
|
|
41
41
|
};
|
|
42
|
-
if (!
|
|
42
|
+
if (!_ && !y.read) return {
|
|
43
43
|
image: null,
|
|
44
44
|
isDefault: !1
|
|
45
45
|
};
|
|
46
|
-
let n =
|
|
46
|
+
let n = g && e(g, u, d);
|
|
47
47
|
return n ? {
|
|
48
48
|
image: n,
|
|
49
49
|
isDefault: !0
|
|
@@ -52,17 +52,18 @@ function p(r, a, s = {}) {
|
|
|
52
52
|
isDefault: !1
|
|
53
53
|
};
|
|
54
54
|
}, [
|
|
55
|
-
|
|
55
|
+
_,
|
|
56
56
|
s.image,
|
|
57
|
-
|
|
57
|
+
y.read,
|
|
58
58
|
a,
|
|
59
|
-
|
|
60
|
-
u
|
|
59
|
+
g,
|
|
60
|
+
u,
|
|
61
|
+
d
|
|
61
62
|
]),
|
|
62
|
-
rule:
|
|
63
|
-
loading: !
|
|
64
|
-
error:
|
|
65
|
-
reload:
|
|
63
|
+
rule: g,
|
|
64
|
+
loading: !h || y.loading,
|
|
65
|
+
error: y.error,
|
|
66
|
+
reload: y.reload
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
function m(e, t = {}) {
|
package/dist/react/public.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.js","names":[],"sources":["../../src/react/public.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir, stableImage } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.js\";\nimport { useKinds } from \"./groups.js\";\nimport { useMediaRead } from \"./read.js\";\n\nexport interface UsePresets {\n /** Every kind's public presets; empty until they load (or when the server has none). */\n presets: PresetRule[];\n loaded: boolean;\n error?: ContentKitError;\n}\n\nconst settled = new WeakMap<ContentKitClient, { presets: PresetRule[]; error?: ContentKitError }>();\nconst inflight = new WeakMap<ContentKitClient, Promise<void>>();\n\n/** The public presets (`GET /media/presets`), fetched once per client. */\nexport function usePresets(o: { client?: ContentKitClient | null } = {}): UsePresets {\n const client = useOptionalContentKitClient(o.client);\n const [, rerender] = useState(0);\n const done = client ? settled.get(client) : undefined;\n useEffect(() => {\n if (!client || settled.has(client)) return;\n let live = true;\n let load = inflight.get(client);\n if (!load) {\n load = client.media.presets().then(\n (presets) => void settled.set(client, { presets }),\n (error: ContentKitError) => void settled.set(client, { presets: [], error }),\n ).finally(() => inflight.delete(client));\n inflight.set(client, load);\n }\n void load.then(() => live && rerender((n) => n + 1));\n return () => {\n live = false;\n };\n }, [client]);\n return { presets: done?.presets ?? [], loaded: !!done || !client, error: done?.error };\n}\n\nexport interface PublicImageOptions {\n /** The host's listing of the item's image (its API's): shown without a read. */\n image?: PublicPreset | null;\n /** A read of the item the host already has. */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UsePublicImage {\n /** The item's current renditions; else the kind's default image (when the preset has one); null while unknown or when there is neither. */\n image: PublicPreset | null;\n /** The preset's rules (aspect, widths, min_width) once the presets load. */\n rule?: PresetRule;\n /** image is the kind's default, not the item's own. */\n isDefault: boolean;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/**\n * An item's public image for a preset (\"avatar\", \"cover\", a version's\n * \"poster\"): the exact renditions from a read's `public` (or the host's\n * listing), else the kind's default image, else none. Reads refresh after\n * commits through the same client.\n */\nexport function usePublicImage(item: RefBody, preset: string, o: PublicImageOptions = {}): UsePublicImage {\n const { kind, id } = item;\n const client = useOptionalContentKitClient(o.client);\n const { presets, loaded } = usePresets({ client });\n const rule = presets.find((p) => p.kind === kind && p.name === preset);\n const listed = o.image !== undefined;\n // The read waits for the presets, so it lists only the preset's upload.\n const prefix = rule ? (isPattern(rule.from) ? ruleDir(rule.from) : rule.from) : undefined;\n const r = useMediaRead(item, { prefix, read: listed || !loaded ? null : o.read, client });\n const image = useMemo((): { image: PublicPreset | null; isDefault: boolean } => {\n const own = listed ? o.image : r.read?.public?.find((p) => p.preset === preset);\n if (own && own.renditions.length) return { image: { preset, renditions: own.renditions, aspect: (\"aspect\" in own && own.aspect) || rule?.aspect }, isDefault: false };\n if (!listed && !r.read) return { image: null, isDefault: false };\n const def = rule && defaultImage(rule, id);\n return def ? { image: def, isDefault: true } : { image: null, isDefault: false };\n }, [listed, o.image, r.read, preset, rule, id]);\n return { ...image, rule, loading: !loaded || r.loading, error: r.error, reload: r.reload };\n}\n\n/**\n * A person's avatar (the content model's `avatars`, a Stable preset): URLs\n * built from their id on this site's media host, showing their image or\n * the default. null while the model loads or when the site shows none.\n */\nexport function useAvatar(userId: string | null | undefined, o: { client?: ContentKitClient } = {}): PublicPreset | null {\n const { kinds } = useKinds(o);\n const rule = kinds?.avatars;\n return useMemo(() => (rule && userId ? stableImage(rule, userId) : null), [rule, userId]);\n}\n"],"mappings":";;;;;;AAkBA,IAAM,oBAAU,IAAI,QAA8E,GAC5F,oBAAW,IAAI,QAAyC;AAG9D,SAAgB,EAAW,IAA0C,CAAC,GAAe;CACnF,IAAM,IAAS,EAA4B,EAAE,MAAM,GAC7C,GAAG,KAAY,EAAS,CAAC,GACzB,IAAO,IAAS,EAAQ,IAAI,CAAM,IAAI,KAAA;CAiB5C,OAhBA,QAAgB;EACd,IAAI,CAAC,KAAU,EAAQ,IAAI,CAAM,GAAG;EACpC,IAAI,IAAO,IACP,IAAO,EAAS,IAAI,CAAM;EAS9B,OARK,MACH,IAAO,EAAO,MAAM,QAAQ,CAAC,CAAC,MAC3B,MAAY,KAAK,EAAQ,IAAI,GAAQ,EAAE,WAAQ,CAAC,IAChD,MAA2B,KAAK,EAAQ,IAAI,GAAQ;GAAE,SAAS,CAAC;GAAG;EAAM,CAAC,CAC7E,CAAC,CAAC,cAAc,EAAS,OAAO,CAAM,CAAC,GACvC,EAAS,IAAI,GAAQ,CAAI,IAE3B,EAAU,WAAW,KAAQ,GAAU,MAAM,IAAI,CAAC,CAAC,SACtC;GACX,IAAO;EACT;CACF,GAAG,CAAC,CAAM,CAAC,GACJ;EAAE,SAAS,GAAM,WAAW,CAAC;EAAG,QAAQ,CAAC,CAAC,KAAQ,CAAC;EAAQ,OAAO,GAAM;CAAM;AACvF;AA6BA,SAAgB,EAAe,GAAe,GAAgB,IAAwB,CAAC,GAAmB;CACxG,IAAM,EAAE,SAAM,
|
|
1
|
+
{"version":3,"file":"public.js","names":[],"sources":["../../src/react/public.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir, stableImage } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.js\";\nimport { useKinds } from \"./groups.js\";\nimport { useMediaRead } from \"./read.js\";\n\nexport interface UsePresets {\n /** Every kind's public presets; empty until they load (or when the server has none). */\n presets: PresetRule[];\n loaded: boolean;\n error?: ContentKitError;\n}\n\nconst settled = new WeakMap<ContentKitClient, { presets: PresetRule[]; error?: ContentKitError }>();\nconst inflight = new WeakMap<ContentKitClient, Promise<void>>();\n\n/** The public presets (`GET /media/presets`), fetched once per client. */\nexport function usePresets(o: { client?: ContentKitClient | null } = {}): UsePresets {\n const client = useOptionalContentKitClient(o.client);\n const [, rerender] = useState(0);\n const done = client ? settled.get(client) : undefined;\n useEffect(() => {\n if (!client || settled.has(client)) return;\n let live = true;\n let load = inflight.get(client);\n if (!load) {\n load = client.media.presets().then(\n (presets) => void settled.set(client, { presets }),\n (error: ContentKitError) => void settled.set(client, { presets: [], error }),\n ).finally(() => inflight.delete(client));\n inflight.set(client, load);\n }\n void load.then(() => live && rerender((n) => n + 1));\n return () => {\n live = false;\n };\n }, [client]);\n return { presets: done?.presets ?? [], loaded: !!done || !client, error: done?.error };\n}\n\nexport interface PublicImageOptions {\n /** The host's listing of the item's image (its API's): shown without a read. */\n image?: PublicPreset | null;\n /** A read of the item the host already has. */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UsePublicImage {\n /** The item's current renditions; else the kind's default image (when the preset has one); null while unknown or when there is neither. */\n image: PublicPreset | null;\n /** The preset's rules (aspect, widths, min_width) once the presets load. */\n rule?: PresetRule;\n /** image is the kind's default, not the item's own. */\n isDefault: boolean;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/**\n * An item's public image for a preset (\"avatar\", \"cover\", a version's\n * \"poster\"): the exact renditions from a read's `public` (or the host's\n * listing), else the kind's default image, else none. Reads refresh after\n * commits through the same client.\n */\nexport function usePublicImage(item: RefBody, preset: string, o: PublicImageOptions = {}): UsePublicImage {\n const { kind, id, version } = item;\n const client = useOptionalContentKitClient(o.client);\n const { presets, loaded } = usePresets({ client });\n const rule = presets.find((p) => p.kind === kind && p.name === preset);\n const listed = o.image !== undefined;\n // The read waits for the presets, so it lists only the preset's upload.\n const prefix = rule ? (isPattern(rule.from) ? ruleDir(rule.from) : rule.from) : undefined;\n const r = useMediaRead(item, { prefix, read: listed || !loaded ? null : o.read, client });\n const image = useMemo((): { image: PublicPreset | null; isDefault: boolean } => {\n const own = listed ? o.image : r.read?.public?.find((p) => p.preset === preset);\n if (own && own.renditions.length) return { image: { preset, renditions: own.renditions, aspect: (\"aspect\" in own && own.aspect) || rule?.aspect }, isDefault: false };\n if (!listed && !r.read) return { image: null, isDefault: false };\n const def = rule && defaultImage(rule, id, version);\n return def ? { image: def, isDefault: true } : { image: null, isDefault: false };\n }, [listed, o.image, r.read, preset, rule, id, version]);\n return { ...image, rule, loading: !loaded || r.loading, error: r.error, reload: r.reload };\n}\n\n/**\n * A person's avatar (the content model's `avatars`, a Stable preset): URLs\n * built from their id on this site's media host, showing their image or\n * the default. null while the model loads or when the site shows none.\n */\nexport function useAvatar(userId: string | null | undefined, o: { client?: ContentKitClient } = {}): PublicPreset | null {\n const { kinds } = useKinds(o);\n const rule = kinds?.avatars;\n return useMemo(() => (rule && userId ? stableImage(rule, userId) : null), [rule, userId]);\n}\n"],"mappings":";;;;;;AAkBA,IAAM,oBAAU,IAAI,QAA8E,GAC5F,oBAAW,IAAI,QAAyC;AAG9D,SAAgB,EAAW,IAA0C,CAAC,GAAe;CACnF,IAAM,IAAS,EAA4B,EAAE,MAAM,GAC7C,GAAG,KAAY,EAAS,CAAC,GACzB,IAAO,IAAS,EAAQ,IAAI,CAAM,IAAI,KAAA;CAiB5C,OAhBA,QAAgB;EACd,IAAI,CAAC,KAAU,EAAQ,IAAI,CAAM,GAAG;EACpC,IAAI,IAAO,IACP,IAAO,EAAS,IAAI,CAAM;EAS9B,OARK,MACH,IAAO,EAAO,MAAM,QAAQ,CAAC,CAAC,MAC3B,MAAY,KAAK,EAAQ,IAAI,GAAQ,EAAE,WAAQ,CAAC,IAChD,MAA2B,KAAK,EAAQ,IAAI,GAAQ;GAAE,SAAS,CAAC;GAAG;EAAM,CAAC,CAC7E,CAAC,CAAC,cAAc,EAAS,OAAO,CAAM,CAAC,GACvC,EAAS,IAAI,GAAQ,CAAI,IAE3B,EAAU,WAAW,KAAQ,GAAU,MAAM,IAAI,CAAC,CAAC,SACtC;GACX,IAAO;EACT;CACF,GAAG,CAAC,CAAM,CAAC,GACJ;EAAE,SAAS,GAAM,WAAW,CAAC;EAAG,QAAQ,CAAC,CAAC,KAAQ,CAAC;EAAQ,OAAO,GAAM;CAAM;AACvF;AA6BA,SAAgB,EAAe,GAAe,GAAgB,IAAwB,CAAC,GAAmB;CACxG,IAAM,EAAE,SAAM,OAAI,eAAY,GACxB,IAAS,EAA4B,EAAE,MAAM,GAC7C,EAAE,YAAS,cAAW,EAAW,EAAE,UAAO,CAAC,GAC3C,IAAO,EAAQ,MAAM,MAAM,EAAE,SAAS,KAAQ,EAAE,SAAS,CAAM,GAC/D,IAAS,EAAE,UAAU,KAAA,GAErB,IAAS,IAAQ,EAAU,EAAK,IAAI,IAAI,EAAQ,EAAK,IAAI,IAAI,EAAK,OAAQ,KAAA,GAC1E,IAAI,EAAa,GAAM;EAAE;EAAQ,MAAM,KAAU,CAAC,IAAS,OAAO,EAAE;EAAM;CAAO,CAAC;CAQxF,OAAO;EAAE,GAPK,QAAkE;GAC9E,IAAM,IAAM,IAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,MAAM,MAAM,EAAE,WAAW,CAAM;GAC9E,IAAI,KAAO,EAAI,WAAW,QAAQ,OAAO;IAAE,OAAO;KAAE;KAAQ,YAAY,EAAI;KAAY,QAAS,YAAY,KAAO,EAAI,UAAW,GAAM;IAAO;IAAG,WAAW;GAAM;GACpK,IAAI,CAAC,KAAU,CAAC,EAAE,MAAM,OAAO;IAAE,OAAO;IAAM,WAAW;GAAM;GAC/D,IAAM,IAAM,KAAQ,EAAa,GAAM,GAAI,CAAO;GAClD,OAAO,IAAM;IAAE,OAAO;IAAK,WAAW;GAAK,IAAI;IAAE,OAAO;IAAM,WAAW;GAAM;EACjF,GAAG;GAAC;GAAQ,EAAE;GAAO,EAAE;GAAM;GAAQ;GAAM;GAAI;EAAO,CAC1C;EAAO;EAAM,SAAS,CAAC,KAAU,EAAE;EAAS,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;AAC3F;AAOA,SAAgB,EAAU,GAAmC,IAAmC,CAAC,GAAwB;CACvH,IAAM,EAAE,aAAU,EAAS,CAAC,GACtB,IAAO,GAAO;CACpB,OAAO,QAAe,KAAQ,IAAS,EAAY,GAAM,CAAM,IAAI,MAAO,CAAC,GAAM,CAAM,CAAC;AAC1F"}
|
package/dist/react/versions.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { ContentKitError } from '../client/errors.js';
|
|
3
|
-
import { PlanSegment, ReaderPlan } from '../client/generated/wire.js';
|
|
3
|
+
import { FileDownload, PlanSegment, ReaderPlan } from '../client/generated/wire.js';
|
|
4
|
+
import { Translator } from '../i18n/messages.js';
|
|
4
5
|
import { ReadWindow } from './read.js';
|
|
5
6
|
export interface UseReaderPlan {
|
|
6
7
|
/** null while loading, pending or failed. */
|
|
@@ -44,14 +45,33 @@ export declare function useSegmentPages(segment: PlanSegment | null | undefined,
|
|
|
44
45
|
client?: ContentKitClient;
|
|
45
46
|
}): UseSegmentPages;
|
|
46
47
|
export interface UseVersionDownload {
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Grants the download (asking again while it is prepared), starts it in the
|
|
50
|
+
* browser and resolves with the grant.
|
|
51
|
+
*/
|
|
52
|
+
download: () => Promise<FileDownload>;
|
|
53
|
+
/** Being prepared by the server, or granted and starting. */
|
|
50
54
|
preparing: boolean;
|
|
51
55
|
error?: ContentKitError;
|
|
56
|
+
/** While a quota refused: when the viewer may download again (ms). */
|
|
57
|
+
retryAt?: number;
|
|
58
|
+
/**
|
|
59
|
+
* What to tell the viewer about the last refusal: when a quota frees, when
|
|
60
|
+
* a download the site holds back opens ("available to download in N
|
|
61
|
+
* days"), a locked version, nothing to download, or a failure.
|
|
62
|
+
*/
|
|
63
|
+
message?: string;
|
|
52
64
|
}
|
|
53
|
-
/**
|
|
65
|
+
/** Starts a granted download: the browser fetches its URL, an attachment. */
|
|
66
|
+
export declare function startDownload(d: FileDownload): void;
|
|
67
|
+
/** The viewer's line for a download refusal at now. */
|
|
68
|
+
export declare function downloadMessage(m: Translator, err: ContentKitError | undefined, now?: number): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* A version's download, charged to the viewer's download quota: the
|
|
71
|
+
* original or rendition its kind serves, or its pages as a zip.
|
|
72
|
+
*/
|
|
54
73
|
export declare function useVersionDownload(content: string, version: string, o?: {
|
|
55
74
|
poll?: number;
|
|
56
75
|
client?: ContentKitClient;
|
|
76
|
+
start?: (d: FileDownload) => void;
|
|
57
77
|
}): UseVersionDownload;
|