@openrails/contentkit-ui 0.79.0 → 0.81.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 +82 -54
- package/dist/client/beacons.d.ts +53 -0
- package/dist/client/beacons.js +72 -0
- package/dist/client/beacons.js.map +1 -0
- package/dist/client/catalog.d.ts +2 -2
- package/dist/client/catalog.js +1 -1
- package/dist/client/catalog.js.map +1 -1
- package/dist/client/client.d.ts +24 -11
- package/dist/client/client.js +37 -29
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/comments.d.ts +12 -8
- package/dist/client/content/comments.js +33 -20
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.d.ts +24 -9
- package/dist/client/content/favorites.js +21 -22
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.d.ts +1 -1
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/reactions.d.ts +11 -6
- package/dist/client/content/reactions.js +23 -18
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/content/types.d.ts +40 -21
- package/dist/client/content/types.js +5 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +55 -0
- package/dist/client/contents.js +144 -0
- package/dist/client/contents.js.map +1 -0
- package/dist/client/discovery.d.ts +126 -0
- package/dist/client/discovery.js +125 -0
- package/dist/client/discovery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +12 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +1244 -370
- package/dist/client/generated/routes.js +1870 -554
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +960 -134
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/group-fields.d.ts +25 -0
- package/dist/client/group-fields.js +63 -0
- package/dist/client/group-fields.js.map +1 -0
- package/dist/client/groups.d.ts +58 -0
- package/dist/client/groups.js +119 -0
- package/dist/client/groups.js.map +1 -0
- package/dist/client/http.d.ts +24 -12
- package/dist/client/http.js +20 -24
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +11 -6
- package/dist/client/media/api.d.ts +1 -1
- package/dist/client/media/api.js +4 -2
- package/dist/client/media/api.js.map +1 -1
- package/dist/client/media/client.d.ts +8 -9
- package/dist/client/media/client.js +58 -71
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/ref.d.ts +30 -0
- package/dist/client/media/ref.js +10 -3
- package/dist/client/media/ref.js.map +1 -1
- package/dist/client/offers.d.ts +41 -0
- package/dist/client/offers.js +66 -0
- package/dist/client/offers.js.map +1 -0
- package/dist/client/route.d.ts +2 -0
- package/dist/client/route.js +2 -1
- package/dist/client/route.js.map +1 -1
- package/dist/client/versions.d.ts +20 -0
- package/dist/client/versions.js +53 -0
- package/dist/client/versions.js.map +1 -0
- package/dist/client.js +35 -30
- package/dist/components/admin/content-manager.d.ts +80 -0
- package/dist/components/admin/content-manager.js +591 -0
- package/dist/components/admin/content-manager.js.map +1 -0
- package/dist/components/admin/group-manager.d.ts +24 -0
- package/dist/components/admin/group-manager.js +244 -0
- package/dist/components/admin/group-manager.js.map +1 -0
- package/dist/components/admin/media-item-panel.js +1 -1
- package/dist/components/admin/media-item-panel.js.map +1 -1
- package/dist/components/content/card.d.ts +44 -0
- package/dist/components/content/card.js +285 -0
- package/dist/components/content/card.js.map +1 -0
- package/dist/components/content/editors.d.ts +40 -0
- package/dist/components/content/editors.js +764 -0
- package/dist/components/content/editors.js.map +1 -0
- package/dist/components/content/filters.d.ts +28 -0
- package/dist/components/content/filters.js +134 -0
- package/dist/components/content/filters.js.map +1 -0
- package/dist/components/content/grid.d.ts +62 -0
- package/dist/components/content/grid.js +233 -0
- package/dist/components/content/grid.js.map +1 -0
- package/dist/components/content/index.d.ts +8 -0
- package/dist/components/content/listing.d.ts +20 -0
- package/dist/components/content/listing.js +150 -0
- package/dist/components/content/listing.js.map +1 -0
- package/dist/components/content/offer-fields.d.ts +8 -0
- package/dist/components/content/offer-fields.js +127 -0
- package/dist/components/content/offer-fields.js.map +1 -0
- package/dist/components/content/paywall.d.ts +53 -0
- package/dist/components/content/paywall.js +221 -0
- package/dist/components/content/paywall.js.map +1 -0
- package/dist/components/content/search.d.ts +27 -0
- package/dist/components/content/search.js +164 -0
- package/dist/components/content/search.js.map +1 -0
- package/dist/components/groups/banner.d.ts +51 -0
- package/dist/components/groups/banner.js +194 -0
- package/dist/components/groups/banner.js.map +1 -0
- package/dist/components/groups/directory.d.ts +37 -0
- package/dist/components/groups/directory.js +117 -0
- package/dist/components/groups/directory.js.map +1 -0
- package/dist/components/groups/editor.d.ts +44 -0
- package/dist/components/groups/editor.js +860 -0
- package/dist/components/groups/editor.js.map +1 -0
- package/dist/components/groups/fields.d.ts +13 -0
- package/dist/components/groups/fields.js +68 -0
- package/dist/components/groups/fields.js.map +1 -0
- package/dist/components/groups/header.d.ts +25 -0
- package/dist/components/groups/header.js +131 -0
- package/dist/components/groups/header.js.map +1 -0
- package/dist/components/groups/index.d.ts +6 -0
- package/dist/components/groups/memberships.d.ts +55 -0
- package/dist/components/groups/memberships.js +527 -0
- package/dist/components/groups/memberships.js.map +1 -0
- package/dist/components/groups/parts.d.ts +49 -0
- package/dist/components/groups/parts.js +172 -0
- package/dist/components/groups/parts.js.map +1 -0
- package/dist/components/media-folder-editor.js +153 -152
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/comments.d.ts +7 -5
- package/dist/components/social/comments.js +6 -2
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +4 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/components/social/moderation.d.ts +1 -1
- package/dist/components/social/moderation.js +3 -3
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/editor/rich-text-editor.d.ts +4 -4
- package/dist/editor/rich-text-editor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +23 -9
- package/dist/locales/admin/de.js +5 -66
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +5 -66
- package/dist/locales/admin/en.js +19 -80
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +5 -66
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +5 -66
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +5 -66
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +5 -66
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/content/de.d.ts +2 -0
- package/dist/locales/content/de.js +98 -0
- package/dist/locales/content/de.js.map +1 -0
- package/dist/locales/content/en.d.ts +93 -0
- package/dist/locales/content/en.js +98 -0
- package/dist/locales/content/en.js.map +1 -0
- package/dist/locales/content/es.d.ts +2 -0
- package/dist/locales/content/es.js +98 -0
- package/dist/locales/content/es.js.map +1 -0
- package/dist/locales/content/ja.d.ts +2 -0
- package/dist/locales/content/ja.js +98 -0
- package/dist/locales/content/ja.js.map +1 -0
- package/dist/locales/content/ko.d.ts +2 -0
- package/dist/locales/content/ko.js +98 -0
- package/dist/locales/content/ko.js.map +1 -0
- package/dist/locales/content/zh.d.ts +2 -0
- package/dist/locales/content/zh.js +98 -0
- package/dist/locales/content/zh.js.map +1 -0
- package/dist/locales/de.js +11 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.d.ts +2 -0
- package/dist/locales/discovery/de.js +68 -0
- package/dist/locales/discovery/de.js.map +1 -0
- package/dist/locales/discovery/en.d.ts +63 -0
- package/dist/locales/discovery/en.js +68 -0
- package/dist/locales/discovery/en.js.map +1 -0
- package/dist/locales/discovery/es.d.ts +2 -0
- package/dist/locales/discovery/es.js +68 -0
- package/dist/locales/discovery/es.js.map +1 -0
- package/dist/locales/discovery/ja.d.ts +2 -0
- package/dist/locales/discovery/ja.js +68 -0
- package/dist/locales/discovery/ja.js.map +1 -0
- package/dist/locales/discovery/ko.d.ts +2 -0
- package/dist/locales/discovery/ko.js +68 -0
- package/dist/locales/discovery/ko.js.map +1 -0
- package/dist/locales/discovery/zh.d.ts +2 -0
- package/dist/locales/discovery/zh.js +68 -0
- package/dist/locales/discovery/zh.js.map +1 -0
- package/dist/locales/en.d.ts +288 -67
- package/dist/locales/en.js +11 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +11 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.d.ts +2 -0
- package/dist/locales/groups/de.js +133 -0
- package/dist/locales/groups/de.js.map +1 -0
- package/dist/locales/groups/en.d.ts +128 -0
- package/dist/locales/groups/en.js +133 -0
- package/dist/locales/groups/en.js.map +1 -0
- package/dist/locales/groups/es.d.ts +2 -0
- package/dist/locales/groups/es.js +133 -0
- package/dist/locales/groups/es.js.map +1 -0
- package/dist/locales/groups/ja.d.ts +2 -0
- package/dist/locales/groups/ja.js +133 -0
- package/dist/locales/groups/ja.js.map +1 -0
- package/dist/locales/groups/ko.d.ts +2 -0
- package/dist/locales/groups/ko.js +133 -0
- package/dist/locales/groups/ko.js.map +1 -0
- package/dist/locales/groups/zh.d.ts +2 -0
- package/dist/locales/groups/zh.js +133 -0
- package/dist/locales/groups/zh.js.map +1 -0
- package/dist/locales/ja.js +11 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +11 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +2 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +2 -1
- package/dist/locales/social/en.js +2 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +2 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +2 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +2 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +2 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +11 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.d.ts +1 -1
- package/dist/react/admin.js +8 -6
- package/dist/react/admin.js.map +1 -1
- package/dist/react/comments.d.ts +9 -7
- package/dist/react/comments.js +42 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +57 -0
- package/dist/react/contents.js +214 -0
- package/dist/react/contents.js.map +1 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/crop.js +45 -44
- package/dist/react/crop.js.map +1 -1
- package/dist/react/detail.d.ts +30 -0
- package/dist/react/detail.js +42 -0
- package/dist/react/detail.js.map +1 -0
- package/dist/react/discovery.d.ts +138 -0
- package/dist/react/discovery.js +299 -0
- package/dist/react/discovery.js.map +1 -0
- package/dist/react/engagement.d.ts +5 -5
- package/dist/react/engagement.js +55 -41
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.js +54 -53
- package/dist/react/folder.js.map +1 -1
- package/dist/react/groups.d.ts +115 -0
- package/dist/react/groups.js +229 -0
- package/dist/react/groups.js.map +1 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/public.d.ts +6 -5
- package/dist/react/public.js +19 -22
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +105 -106
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +2 -2
- package/dist/react/resources.js +69 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/store.js +41 -39
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.js +54 -53
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -1
- package/dist/react/use-resource.js +1 -0
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.d.ts +57 -0
- package/dist/react/versions.js +94 -0
- package/dist/react/versions.js.map +1 -0
- package/dist/react/video.js +87 -86
- package/dist/react/video.js.map +1 -1
- package/dist/react.js +9 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/client/content/posts.d.ts +0 -52
- package/dist/client/content/posts.js +0 -84
- package/dist/client/content/posts.js.map +0 -1
- package/dist/client/taxonomy.d.ts +0 -34
- package/dist/client/taxonomy.js +0 -84
- package/dist/client/taxonomy.js.map +0 -1
- package/dist/components/admin/post-manager.d.ts +0 -45
- package/dist/components/admin/post-manager.js +0 -602
- package/dist/components/admin/post-manager.js.map +0 -1
- package/dist/components/admin/taxonomy-manager.d.ts +0 -54
- package/dist/components/admin/taxonomy-manager.js +0 -976
- package/dist/components/admin/taxonomy-manager.js.map +0 -1
- package/dist/react/posts.d.ts +0 -61
- package/dist/react/posts.js +0 -183
- package/dist/react/posts.js.map +0 -1
package/dist/react/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","names":[],"sources":["../../src/react/read.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } 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 { ReadOptions } from \"../client/media/api.js\";\nimport { chunkOffsets, mergeReads, processing as isProcessing, READ_CHUNK } from \"../client/media/windows.js\";\nimport { useOptionalContentKitClient, useReadScope } from \"./context.js\";\nimport { readKey, storeFor, type ReadEntry } from \"./store.js\";\n\n/** Files [start, end) get URLs. */\nexport interface ReadWindow {\n start: number;\n end: number;\n /** Files per read. Default 50. */\n chunk?: number;\n}\n\nexport interface MediaReadOptions extends Omit<ReadOptions, \"offset\" | \"limit\"> {\n /**\n * Read in windows of `chunk` files and merge them: the files in\n * [start, end) get URLs, and windows once read stay loaded (and refreshed)\n * until the item or options change. Without it, one read of the server's\n * default range (or offset and limit).\n */\n window?: ReadWindow;\n offset?: number;\n limit?: number;\n /** Poll at this interval (ms) while uploads process. Default 2500 for editor reads, else off. */\n poll?: number | false;\n /** Read again shortly before the URLs expire. Default true. */\n refresh?: boolean;\n /** A read the host already has: shown until reload() or set(). */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UseMediaRead {\n /** The read (windows merged); null until it loads or when it failed. */\n read: ReadResult | null;\n loading: boolean;\n error?: ContentKitError;\n /** Uploads are still processing (editor reads). */\n processing: boolean;\n /** Reads again now, restarting a read in flight. */\n reload: () => void;\n /** Reads again unless a read is in flight: a player's grant refresh. */\n refresh: () => void;\n /** Replaces the read without refetching, for every hook showing it. */\n set: (r: ReadResult) => void;\n}\n\nconst POLL = 2500;\nconst NONE: readonly ReadEntry[] = [];\n\n/**\n * A read of the item, shared with every hook reading the same item and\n * options: refreshed before its URLs expire, retried after a read rate limit,\n * polled while uploads process, reloaded after a commit through the client.\n * A window splits it into merged reads. Without a client (none passed, no\n * provider) it only shows a supplied read.\n */\nexport function useMediaRead(ref: RefBody, o: MediaReadOptions = {}): UseMediaRead {\n const { read: given, client: own, prefix, offset, limit, download, editor, window, refresh: refreshing = true } = o;\n const poll = o.poll ?? (editor ? POLL : false);\n const client = useOptionalContentKitClient(own);\n const scope = useReadScope();\n const store = client ? storeFor(client) : null;\n const chunk = window?.chunk ?? READ_CHUNK;\n const base = scope + readKey(ref, { prefix, download, editor }) + (window ? `#${chunk}` : `#${offset ?? 0}:${limit ?? 0}`);\n\n // Windows once wanted stay loaded until the item or options change.\n const [total, setTotal] = useState<{ base: string; n?: number }>({ base });\n const known = total.base === base ? total.n : undefined;\n // Until the first read states the total, only the window's first chunk.\n const wanted = window ? chunkOffsets(window.start, window.end, known ?? window.start + 1, chunk) : [];\n const [kept, setKept] = useState<{ base: string; offsets: number[] }>({ base, offsets: [] });\n const prior = kept.base === base ? kept.offsets : [];\n if (window && (kept.base !== base || wanted.some((w) => !prior.includes(w)))) {\n setKept({ base, offsets: [...new Set([...prior, ...wanted])].sort((a, b) => a - b) });\n }\n const offsets = window ? (prior.length ? prior : wanted.length ? wanted : [0]) : [offset ?? 0];\n const windows = useMemo(\n () => offsets.map((off) => ({ offset: off, limit: window ? chunk : limit })),\n // offsets is derived from the stable kept list\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [offsets.join(\",\"), window ? chunk : limit],\n );\n const keys = useMemo(() => windows.map((w) => scope + readKey(ref, { prefix, download, editor, offset: w.offset, limit: w.limit })), [scope, windows, ref.kind, ref.id, prefix, download, editor]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // A supplied read is shown until reload() asks the server or set() replaces it.\n const [requested, setRequested] = useState<{ base: string; given?: ReadResult | null } | null>(null);\n const [local, setLocal] = useState<{ base: string; given?: ReadResult | null; read: ReadResult } | null>(null);\n const asked = requested?.base === base && requested.given === given ? requested : null;\n const fetching = !!store && (given === undefined || !!asked);\n\n const subscribe = useCallback(\n (l: () => void) => {\n if (!store || !fetching) return () => {};\n const offs = keys.map((key, i) => store.subscribe(key, { kind: ref.kind, id: ref.id }, { prefix, download, editor, ...windows[i] }, l, { poll, refresh: refreshing }, scope));\n return () => offs.forEach((off) => off());\n },\n // ref and options are in keys.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, fetching, keys, poll, refreshing],\n );\n const last = useRef<readonly ReadEntry[]>(NONE);\n const snapshot = useCallback(() => {\n if (!store || !fetching) return NONE;\n const next = keys.map((k) => store.snapshot(k));\n const prev = last.current;\n if (prev.length === next.length && prev.every((e, i) => e === next[i])) return prev;\n return (last.current = next);\n }, [store, fetching, keys]);\n const entries = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (store && fetching) for (const k of keys) store.load(k, asked ? \"reload\" : \"initial\");\n }, [store, fetching, keys, asked]);\n\n const merged = useMemo(() => mergeReads(entries.map((e) => e.read)), [entries]);\n useEffect(() => {\n if (window && merged && merged.total !== known) setTotal({ base, n: merged.total });\n }, [window, merged, known, base]);\n\n const reload = useCallback(() => {\n if (given !== undefined) setRequested({ base, given });\n else if (store) for (const k of keys) store.load(k, \"reload\");\n setLocal(null);\n }, [store, keys, base, given]);\n const refresh = useCallback(() => {\n if (given !== undefined && !asked) setRequested({ base, given });\n else if (store) for (const k of keys) store.load(k, \"refresh\");\n }, [store, keys, base, given, asked]);\n const set = useCallback(\n (read: ReadResult) => {\n if (store && fetching) for (const k of keys) store.set(k, read);\n else setLocal({ base, given, read });\n },\n [store, fetching, keys, base, given],\n );\n\n let read: ReadResult | null;\n let loading = false;\n let error: ContentKitError | undefined;\n if (local && local.base === base && local.given === given) read = local.read;\n else if (!entries.length) read = given ?? null;\n else {\n const loaded = entries.every((e) => e.loaded);\n read = merged ?? (loaded ? null : (given ?? null));\n loading = entries.some((e) => e.loading || !e.loaded);\n error = entries.find((e) => e.error)?.error;\n }\n return { read, loading, error, processing: isProcessing(read), reload, refresh, set };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"read.js","names":[],"sources":["../../src/react/read.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } 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 { ReadOptions } from \"../client/media/api.js\";\nimport { chunkOffsets, mergeReads, processing as isProcessing, READ_CHUNK } from \"../client/media/windows.js\";\nimport { itemRef } from \"../client/media/ref.js\";\nimport { useOptionalContentKitClient, useReadScope } from \"./context.js\";\nimport { readKey, storeFor, type ReadEntry } from \"./store.js\";\n\n/** Files [start, end) get URLs. */\nexport interface ReadWindow {\n start: number;\n end: number;\n /** Files per read. Default 50. */\n chunk?: number;\n}\n\nexport interface MediaReadOptions extends Omit<ReadOptions, \"offset\" | \"limit\"> {\n /**\n * Read in windows of `chunk` files and merge them: the files in\n * [start, end) get URLs, and windows once read stay loaded (and refreshed)\n * until the item or options change. Without it, one read of the server's\n * default range (or offset and limit).\n */\n window?: ReadWindow;\n offset?: number;\n limit?: number;\n /** Poll at this interval (ms) while uploads process. Default 2500 for editor reads, else off. */\n poll?: number | false;\n /** Read again shortly before the URLs expire. Default true. */\n refresh?: boolean;\n /** A read the host already has: shown until reload() or set(). */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UseMediaRead {\n /** The read (windows merged); null until it loads or when it failed. */\n read: ReadResult | null;\n loading: boolean;\n error?: ContentKitError;\n /** Uploads are still processing (editor reads). */\n processing: boolean;\n /** Reads again now, restarting a read in flight. */\n reload: () => void;\n /** Reads again unless a read is in flight: a player's grant refresh. */\n refresh: () => void;\n /** Replaces the read without refetching, for every hook showing it. */\n set: (r: ReadResult) => void;\n}\n\nconst POLL = 2500;\nconst NONE: readonly ReadEntry[] = [];\n\n/**\n * A read of the item, shared with every hook reading the same item and\n * options: refreshed before its URLs expire, retried after a read rate limit,\n * polled while uploads process, reloaded after a commit through the client.\n * A window splits it into merged reads. Without a client (none passed, no\n * provider) it only shows a supplied read.\n */\nexport function useMediaRead(ref: RefBody, o: MediaReadOptions = {}): UseMediaRead {\n const { read: given, client: own, prefix, offset, limit, download, editor, window, refresh: refreshing = true } = o;\n const poll = o.poll ?? (editor ? POLL : false);\n const client = useOptionalContentKitClient(own);\n const scope = useReadScope();\n const store = client ? storeFor(client) : null;\n const chunk = window?.chunk ?? READ_CHUNK;\n const base = scope + readKey(ref, { prefix, download, editor }) + (window ? `#${chunk}` : `#${offset ?? 0}:${limit ?? 0}`);\n\n // Windows once wanted stay loaded until the item or options change.\n const [total, setTotal] = useState<{ base: string; n?: number }>({ base });\n const known = total.base === base ? total.n : undefined;\n // Until the first read states the total, only the window's first chunk.\n const wanted = window ? chunkOffsets(window.start, window.end, known ?? window.start + 1, chunk) : [];\n const [kept, setKept] = useState<{ base: string; offsets: number[] }>({ base, offsets: [] });\n const prior = kept.base === base ? kept.offsets : [];\n if (window && (kept.base !== base || wanted.some((w) => !prior.includes(w)))) {\n setKept({ base, offsets: [...new Set([...prior, ...wanted])].sort((a, b) => a - b) });\n }\n const offsets = window ? (prior.length ? prior : wanted.length ? wanted : [0]) : [offset ?? 0];\n const windows = useMemo(\n () => offsets.map((off) => ({ offset: off, limit: window ? chunk : limit })),\n // offsets is derived from the stable kept list\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [offsets.join(\",\"), window ? chunk : limit],\n );\n const keys = useMemo(() => windows.map((w) => scope + readKey(ref, { prefix, download, editor, offset: w.offset, limit: w.limit })), [scope, windows, ref.kind, ref.id, ref.version, prefix, download, editor]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // A supplied read is shown until reload() asks the server or set() replaces it.\n const [requested, setRequested] = useState<{ base: string; given?: ReadResult | null } | null>(null);\n const [local, setLocal] = useState<{ base: string; given?: ReadResult | null; read: ReadResult } | null>(null);\n const asked = requested?.base === base && requested.given === given ? requested : null;\n const fetching = !!store && (given === undefined || !!asked);\n\n const subscribe = useCallback(\n (l: () => void) => {\n if (!store || !fetching) return () => {};\n const offs = keys.map((key, i) => store.subscribe(key, itemRef(ref), { prefix, download, editor, ...windows[i] }, l, { poll, refresh: refreshing }, scope));\n return () => offs.forEach((off) => off());\n },\n // ref and options are in keys.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, fetching, keys, poll, refreshing],\n );\n const last = useRef<readonly ReadEntry[]>(NONE);\n const snapshot = useCallback(() => {\n if (!store || !fetching) return NONE;\n const next = keys.map((k) => store.snapshot(k));\n const prev = last.current;\n if (prev.length === next.length && prev.every((e, i) => e === next[i])) return prev;\n return (last.current = next);\n }, [store, fetching, keys]);\n const entries = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (store && fetching) for (const k of keys) store.load(k, asked ? \"reload\" : \"initial\");\n }, [store, fetching, keys, asked]);\n\n const merged = useMemo(() => mergeReads(entries.map((e) => e.read)), [entries]);\n useEffect(() => {\n if (window && merged && merged.total !== known) setTotal({ base, n: merged.total });\n }, [window, merged, known, base]);\n\n const reload = useCallback(() => {\n if (given !== undefined) setRequested({ base, given });\n else if (store) for (const k of keys) store.load(k, \"reload\");\n setLocal(null);\n }, [store, keys, base, given]);\n const refresh = useCallback(() => {\n if (given !== undefined && !asked) setRequested({ base, given });\n else if (store) for (const k of keys) store.load(k, \"refresh\");\n }, [store, keys, base, given, asked]);\n const set = useCallback(\n (read: ReadResult) => {\n if (store && fetching) for (const k of keys) store.set(k, read);\n else setLocal({ base, given, read });\n },\n [store, fetching, keys, base, given],\n );\n\n let read: ReadResult | null;\n let loading = false;\n let error: ContentKitError | undefined;\n if (local && local.base === base && local.given === given) read = local.read;\n else if (!entries.length) read = given ?? null;\n else {\n const loaded = entries.every((e) => e.loaded);\n read = merged ?? (loaded ? null : (given ?? null));\n loading = entries.some((e) => e.loading || !e.loaded);\n error = entries.find((e) => e.error)?.error;\n }\n return { read, loading, error, processing: isProcessing(read), reload, refresh, set };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { ContentKitError } from '../client/errors.js';
|
|
3
|
-
import {
|
|
3
|
+
import { Target } from '../client/content/types.js';
|
|
4
4
|
/** One shared read: its data, whether a fetch is in flight, and its last failure. */
|
|
5
5
|
export interface Resource<T> {
|
|
6
6
|
data: T | undefined;
|
|
@@ -21,7 +21,7 @@ export type Fetcher<T> = (signal: AbortSignal) => Promise<T>;
|
|
|
21
21
|
export interface Tag {
|
|
22
22
|
type: string;
|
|
23
23
|
readScope?: string;
|
|
24
|
-
|
|
24
|
+
target?: Target;
|
|
25
25
|
id?: string;
|
|
26
26
|
scope?: string;
|
|
27
27
|
}
|
package/dist/react/resources.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { toContentKitError as e } from "../client/errors.js";
|
|
2
|
+
import { sameTarget as t } from "../client/content/types.js";
|
|
2
3
|
//#region src/react/resources.ts
|
|
3
|
-
var
|
|
4
|
+
var n = {
|
|
4
5
|
data: void 0,
|
|
5
6
|
loading: !1,
|
|
6
7
|
loaded: !1
|
|
7
|
-
},
|
|
8
|
+
}, r = class {
|
|
8
9
|
slots = /* @__PURE__ */ new Map();
|
|
9
10
|
version = 0;
|
|
10
11
|
constructor(e) {
|
|
11
12
|
e.subscribe((e, t) => this.apply(e, t));
|
|
12
13
|
}
|
|
13
|
-
subscribe(e,
|
|
14
|
+
subscribe(e, t, r) {
|
|
14
15
|
let i = this.slots.get(e);
|
|
15
16
|
i || this.slots.set(e, i = {
|
|
16
|
-
tag:
|
|
17
|
+
tag: t,
|
|
17
18
|
listeners: /* @__PURE__ */ new Set(),
|
|
18
|
-
entry:
|
|
19
|
+
entry: n,
|
|
19
20
|
version: 0,
|
|
20
21
|
readVersion: ++this.version
|
|
21
22
|
});
|
|
@@ -27,7 +28,7 @@ var t = {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
snapshot(e) {
|
|
30
|
-
return this.slots.get(e)?.entry ??
|
|
31
|
+
return this.slots.get(e)?.entry ?? n;
|
|
31
32
|
}
|
|
32
33
|
bind(e, t, n) {
|
|
33
34
|
let r = this.slots.get(e);
|
|
@@ -113,9 +114,9 @@ var t = {
|
|
|
113
114
|
invalidate(e) {
|
|
114
115
|
for (let [t, n] of this.slots) e(n.tag) && n.listeners.size && this.load(t, !0);
|
|
115
116
|
}
|
|
116
|
-
apply(e,
|
|
117
|
-
let r = (e,
|
|
118
|
-
this.patch((
|
|
117
|
+
apply(e, n) {
|
|
118
|
+
let r = (e, t) => {
|
|
119
|
+
this.patch((t) => e(t) && (t.readScope ?? "{}") === n, t), this.invalidate((t) => e(t) && (t.readScope ?? "{}") !== n);
|
|
119
120
|
}, i = (e, t) => r((t) => e.includes(t.type), (e) => {
|
|
120
121
|
let n = !1, r = [];
|
|
121
122
|
for (let i of e.items) {
|
|
@@ -131,20 +132,28 @@ var t = {
|
|
|
131
132
|
"replies",
|
|
132
133
|
"latest",
|
|
133
134
|
"admin-comments"
|
|
134
|
-
]
|
|
135
|
+
], o = (e, t) => r((e) => e.type === "cards" || e.type === "history", (n) => {
|
|
136
|
+
let r = (n) => n.some((t) => t.id === e) ? n.map((n) => n.id === e ? t(n) : n) : n;
|
|
137
|
+
if (Array.isArray(n)) return r(n);
|
|
138
|
+
let i = r(n.items);
|
|
139
|
+
return i === n.items ? n : {
|
|
140
|
+
...n,
|
|
141
|
+
items: i
|
|
142
|
+
};
|
|
143
|
+
});
|
|
135
144
|
switch (e.type) {
|
|
136
145
|
case "notifications.updated":
|
|
137
|
-
this.updateNotification(e.item,
|
|
146
|
+
this.updateNotification(e.item, n);
|
|
138
147
|
break;
|
|
139
148
|
case "notifications.changed":
|
|
140
149
|
this.invalidate((e) => e.type === "notifications");
|
|
141
150
|
break;
|
|
142
151
|
case "comment.created": {
|
|
143
|
-
let
|
|
144
|
-
r((r) =>
|
|
145
|
-
...
|
|
146
|
-
items:
|
|
147
|
-
}),
|
|
152
|
+
let n = e.comment.reply_to_id, a = !e.comment.moderation;
|
|
153
|
+
r((r) => n ? r.type === "replies" && r.id === n : r.type === "comments" && t(r.target, e.target), (t) => t.items.some((t) => t.id === e.comment.id) ? t : {
|
|
154
|
+
...t,
|
|
155
|
+
items: n ? [...t.items, e.comment] : [e.comment, ...t.items]
|
|
156
|
+
}), n && a && i(["comments"], (e) => e.id === n ? {
|
|
148
157
|
...e,
|
|
149
158
|
reply_count: e.reply_count + 1
|
|
150
159
|
} : e), this.invalidate((e) => e.type === "latest" || e.type === "admin-comments");
|
|
@@ -186,35 +195,43 @@ var t = {
|
|
|
186
195
|
this.invalidate((e) => a.includes(e.type));
|
|
187
196
|
break;
|
|
188
197
|
case "reaction.changed":
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
198
|
+
r((n) => n.type === "reaction" && t(n.target, e.target), () => e.counts), e.target.type === "contents" && o(e.target.id, (t) => ({
|
|
199
|
+
...t,
|
|
200
|
+
counts: {
|
|
201
|
+
...t.counts,
|
|
202
|
+
likes: e.counts.likes,
|
|
203
|
+
dislikes: e.counts.dislikes
|
|
204
|
+
}
|
|
205
|
+
})), this.invalidate((e) => e.type === "reactions");
|
|
197
206
|
break;
|
|
198
207
|
case "favorite.changed":
|
|
199
|
-
r((
|
|
208
|
+
r((n) => n.type === "favorite" && t(n.target, e.target), () => ({
|
|
200
209
|
favorited: e.favorited,
|
|
201
210
|
count: e.count
|
|
211
|
+
})), e.target.type === "contents" && o(e.target.id, (t) => ({
|
|
212
|
+
...t,
|
|
213
|
+
counts: {
|
|
214
|
+
...t.counts,
|
|
215
|
+
favorites: e.count
|
|
216
|
+
},
|
|
217
|
+
viewer: t.viewer && {
|
|
218
|
+
...t.viewer,
|
|
219
|
+
favorited: e.favorited
|
|
220
|
+
}
|
|
202
221
|
})), this.invalidate((e) => e.type === "favorites");
|
|
203
222
|
break;
|
|
204
|
-
case "
|
|
205
|
-
this.invalidate((
|
|
223
|
+
case "content.saved":
|
|
224
|
+
r((t) => t.type === "content" && t.id === e.content.id, () => e.content), this.invalidate((t) => t.type === "contents" || t.type === "cards" || t.type === "content-detail" && t.id === e.content.id);
|
|
206
225
|
break;
|
|
207
|
-
case "
|
|
208
|
-
|
|
226
|
+
case "content.changed":
|
|
227
|
+
this.invalidate((t) => (t.type === "content" || t.type === "content-detail") && t.id === e.id || t.type === "contents" || t.type === "cards");
|
|
209
228
|
break;
|
|
210
|
-
case "
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
case "post.restored":
|
|
217
|
-
this.invalidate((t) => t.type === "post" && t.id === e.post.id || t.type === "posts");
|
|
229
|
+
case "content.deleted":
|
|
230
|
+
i([
|
|
231
|
+
"contents",
|
|
232
|
+
"cards",
|
|
233
|
+
"history"
|
|
234
|
+
], (t) => t.id === e.id ? null : t), this.invalidate((t) => (t.type === "content" || t.type === "content-detail") && t.id === e.id || t.type === "contents" && t.scope === "deleted");
|
|
218
235
|
break;
|
|
219
236
|
case "poll.created":
|
|
220
237
|
this.invalidate((e) => e.type === "polls" || e.type === "poll" && !e.id);
|
|
@@ -232,10 +249,22 @@ var t = {
|
|
|
232
249
|
case "ban.lifted":
|
|
233
250
|
this.invalidate((t) => t.type === "bans" && t.scope === e.scope || t.type === "standing");
|
|
234
251
|
break;
|
|
235
|
-
case "
|
|
236
|
-
this.invalidate((e) => e.type.startsWith("
|
|
252
|
+
case "groups.changed":
|
|
253
|
+
this.invalidate((e) => e.type.startsWith("group"));
|
|
254
|
+
break;
|
|
255
|
+
case "contentFilters.changed":
|
|
256
|
+
this.invalidate((e) => e.type === "cards" || e.type === "history" || e.type === "release-dates");
|
|
257
|
+
break;
|
|
258
|
+
case "access.changed":
|
|
259
|
+
this.invalidate((e) => e.type === "cards" || e.type === "history" || e.type === "content-detail" || e.type === "reader-plan" || e.type.startsWith("group"));
|
|
260
|
+
break;
|
|
261
|
+
case "history.changed":
|
|
262
|
+
this.invalidate((e) => e.type === "history" || e.type === "cards");
|
|
263
|
+
break;
|
|
264
|
+
case "searches.changed":
|
|
265
|
+
this.invalidate((e) => e.type === "searches");
|
|
237
266
|
break;
|
|
238
|
-
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "
|
|
267
|
+
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "content" && t.id === e.id || t.type === "contents");
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
updateNotification(e, t) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Item, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"notifications.updated\":\n this.updateNotification(c.item, scope);\n break;\n case \"notifications.changed\":\n this.invalidate((t) => t.type === \"notifications\");\n break;\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") {\n const totals = (p: Post): Post => (p.id === c.ref.id ? { ...p, total_likes: c.counts.likes, total_dislikes: c.counts.dislikes } : p);\n patch<Post>((t) => t.type === \"post\" && t.id === c.ref.id, totals);\n items<Post>([\"posts\"], totals);\n }\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"taxonomy.changed\":\n this.invalidate((t) => t.type.startsWith(\"taxonomy\"));\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n // Preserve loaded pages and canonical receipt revisions. Reads that started\n // before a successful mutation cannot overwrite it when they finish later.\n private updateNotification(item: Item, scope: string | null): void {\n for (const [key, slot] of this.slots) {\n if (slot.tag.type !== \"notifications\" || !slot.listeners.size) continue;\n const page = slot.entry.data as Page<Item> | undefined;\n const cached = page?.items.find((n) => n.id === item.id);\n // Mutations from separate mounted views can complete out of order.\n // In particular, an old active receipt must not evict a newer archive.\n if ((slot.tag.readScope ?? \"{}\") === scope && cached && cached.revision > item.revision) continue;\n const archived = slot.tag.scope?.startsWith(\"archived:\") ?? false;\n const matches = archived === !!item.dismissed_at;\n if ((slot.tag.readScope ?? \"{}\") !== scope || !page || slot.tag.scope?.endsWith(\":unread\") || (matches && !page.items.some((n) => n.id === item.id))) {\n this.load(key, true);\n continue;\n }\n if (!matches && !page.items.some((n) => n.id === item.id)) continue;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n slot.ctl = slot.moreCtl = undefined;\n this.update(slot, { loading: false, loadingMore: false, data: { ...page, items: page.items.flatMap((n) => n.id === item.id ? matches ? [item] : [] : [n]) } });\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport { sameTarget, type Target } from \"../client/content/types.js\";\nimport type { Card, Comment, Content, Item, Poll } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n target?: Target;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n // Cards in lists (pages) and rails (arrays) show a content's counts and the viewer's state.\n const cards = (id: string, fn: (card: Card) => Card) =>\n patch<Page<Card> | Card[]>(\n (t) => t.type === \"cards\" || t.type === \"history\",\n (data) => {\n const each = (list: Card[]) => (list.some((x) => x.id === id) ? list.map((x) => (x.id === id ? fn(x) : x)) : list);\n if (Array.isArray(data)) return each(data);\n const items = each(data.items);\n return items === data.items ? data : { ...data, items };\n },\n );\n switch (c.type) {\n case \"notifications.updated\":\n this.updateNotification(c.item, scope);\n break;\n case \"notifications.changed\":\n this.invalidate((t) => t.type === \"notifications\");\n break;\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameTarget(t.target, c.target));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameTarget(t.target, c.target), () => c.counts);\n if (c.target.type === \"contents\") cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, likes: c.counts.likes, dislikes: c.counts.dislikes } }));\n this.invalidate((t) => t.type === \"reactions\");\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameTarget(t.target, c.target), () => ({ favorited: c.favorited, count: c.count }));\n if (c.target.type === \"contents\")\n cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, favorites: c.count }, viewer: x.viewer && { ...x.viewer, favorited: c.favorited } }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"content.saved\":\n patch<Content | null>((t) => t.type === \"content\" && t.id === c.content.id, () => c.content);\n // A state, kind or text change can move it between lists: refetch them.\n this.invalidate((t) => t.type === \"contents\" || t.type === \"cards\" || (t.type === \"content-detail\" && t.id === c.content.id));\n break;\n case \"content.changed\":\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || t.type === \"contents\" || t.type === \"cards\");\n break;\n case \"content.deleted\":\n items<{ id: string }>([\"contents\", \"cards\", \"history\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || (t.type === \"contents\" && t.scope === \"deleted\"));\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"groups.changed\":\n this.invalidate((t) => t.type.startsWith(\"group\"));\n break;\n case \"contentFilters.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"release-dates\");\n break;\n case \"access.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"content-detail\" || t.type === \"reader-plan\" || t.type.startsWith(\"group\"));\n break;\n case \"history.changed\":\n this.invalidate((t) => t.type === \"history\" || t.type === \"cards\");\n break;\n case \"searches.changed\":\n this.invalidate((t) => t.type === \"searches\");\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"content\" && t.id === c.id) || t.type === \"contents\");\n break;\n }\n }\n\n // Preserve loaded pages and canonical receipt revisions. Reads that started\n // before a successful mutation cannot overwrite it when they finish later.\n private updateNotification(item: Item, scope: string | null): void {\n for (const [key, slot] of this.slots) {\n if (slot.tag.type !== \"notifications\" || !slot.listeners.size) continue;\n const page = slot.entry.data as Page<Item> | undefined;\n const cached = page?.items.find((n) => n.id === item.id);\n // Mutations from separate mounted views can complete out of order.\n // In particular, an old active receipt must not evict a newer archive.\n if ((slot.tag.readScope ?? \"{}\") === scope && cached && cached.revision > item.revision) continue;\n const archived = slot.tag.scope?.startsWith(\"archived:\") ?? false;\n const matches = archived === !!item.dismissed_at;\n if ((slot.tag.readScope ?? \"{}\") !== scope || !page || slot.tag.scope?.endsWith(\":unread\") || (matches && !page.items.some((n) => n.id === item.id))) {\n this.load(key, true);\n continue;\n }\n if (!matches && !page.items.some((n) => n.id === item.id)) continue;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n slot.ctl = slot.moreCtl = undefined;\n this.update(slot, { loading: false, loadingMore: false, data: { ...page, items: page.items.flatMap((n) => n.id === item.id ? matches ? [item] : [] : [n]) } });\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
package/dist/react/store.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { toContentKitError as e } from "../client/errors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { sameItem as t } from "../client/media/ref.js";
|
|
3
|
+
import { samePath as n, sleep as r } from "../client/media/client.js";
|
|
4
|
+
import { isProcessing as i, processing as a } from "../client/media/windows.js";
|
|
5
|
+
import { expiryDelay as o } from "../client/playback.js";
|
|
5
6
|
//#region src/react/store.ts
|
|
6
|
-
var
|
|
7
|
+
var s = {
|
|
7
8
|
read: null,
|
|
8
9
|
loading: !1,
|
|
9
10
|
loaded: !1
|
|
10
|
-
},
|
|
11
|
-
function
|
|
11
|
+
}, c = 3;
|
|
12
|
+
function l(e, t = {}) {
|
|
12
13
|
return JSON.stringify([
|
|
13
14
|
e.kind,
|
|
14
15
|
e.id,
|
|
16
|
+
e.version ?? "",
|
|
15
17
|
t.prefix ?? "",
|
|
16
18
|
t.offset ?? 0,
|
|
17
19
|
t.limit ?? 0,
|
|
@@ -19,8 +21,8 @@ function c(e, t = {}) {
|
|
|
19
21
|
!!t.editor
|
|
20
22
|
]);
|
|
21
23
|
}
|
|
22
|
-
function
|
|
23
|
-
let i = e?.files ?? [], a = i.findIndex((e) => e.upload &&
|
|
24
|
+
function u(e, t, r) {
|
|
25
|
+
let i = e?.files ?? [], a = i.findIndex((e) => e.upload && n(e.path, t)), o = i.filter((e, t) => t !== a);
|
|
24
26
|
return r && o.splice(a < 0 ? o.length : a, 0, r), {
|
|
25
27
|
access: "full",
|
|
26
28
|
expires: 0,
|
|
@@ -31,23 +33,23 @@ function l(e, n, r) {
|
|
|
31
33
|
files: o
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
|
-
var
|
|
36
|
+
var d = class {
|
|
35
37
|
client;
|
|
36
38
|
slots = /* @__PURE__ */ new Map();
|
|
37
39
|
constructor(e) {
|
|
38
40
|
this.client = e, e.subscribe((e, t) => this.apply(e, t));
|
|
39
41
|
}
|
|
40
42
|
subscribe(e, t, n, r, i = {}, a = "{}") {
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
+
let o = this.slots.get(e);
|
|
44
|
+
o || this.slots.set(e, o = {
|
|
43
45
|
key: e,
|
|
44
46
|
scope: a,
|
|
45
47
|
ref: t,
|
|
46
48
|
options: n,
|
|
47
49
|
listeners: /* @__PURE__ */ new Map(),
|
|
48
|
-
entry:
|
|
50
|
+
entry: s
|
|
49
51
|
});
|
|
50
|
-
let c =
|
|
52
|
+
let c = o;
|
|
51
53
|
return c.listeners.set(r, i), this.schedule(c), () => {
|
|
52
54
|
c.listeners.delete(r), queueMicrotask(() => {
|
|
53
55
|
if (this.slots.get(e) === c) {
|
|
@@ -58,7 +60,7 @@ var u = class {
|
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
snapshot(e) {
|
|
61
|
-
return this.slots.get(e)?.entry ??
|
|
63
|
+
return this.slots.get(e)?.entry ?? s;
|
|
62
64
|
}
|
|
63
65
|
load(t, n = "initial") {
|
|
64
66
|
let r = this.slots.get(t);
|
|
@@ -77,7 +79,7 @@ var u = class {
|
|
|
77
79
|
loading: !1,
|
|
78
80
|
loaded: !0,
|
|
79
81
|
error: void 0
|
|
80
|
-
}), this.schedule(r), r.options.editor &&
|
|
82
|
+
}), this.schedule(r), r.options.editor && f(t, e) && this.reloadViewers(r.ref);
|
|
81
83
|
}, (t) => {
|
|
82
84
|
r.ctl === i && (r.ctl = void 0, this.update(r, {
|
|
83
85
|
read: null,
|
|
@@ -96,16 +98,16 @@ var u = class {
|
|
|
96
98
|
error: void 0
|
|
97
99
|
}), this.schedule(n));
|
|
98
100
|
}
|
|
99
|
-
async fetch(t,
|
|
101
|
+
async fetch(t, n) {
|
|
100
102
|
for (let i = 0;; i++) try {
|
|
101
103
|
return await this.client.media.read(t.ref, {
|
|
102
104
|
...t.options,
|
|
103
|
-
signal:
|
|
105
|
+
signal: n
|
|
104
106
|
});
|
|
105
107
|
} catch (t) {
|
|
106
108
|
let a = e(t);
|
|
107
|
-
if (a.code !== "rate_limited" || i >=
|
|
108
|
-
await
|
|
109
|
+
if (a.code !== "rate_limited" || i >= c || n.aborted) throw a;
|
|
110
|
+
await r(Math.min(30, a.retryAfter ?? 1) * 1e3, n);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
schedule(e) {
|
|
@@ -113,31 +115,31 @@ var u = class {
|
|
|
113
115
|
let t = e.entry.read;
|
|
114
116
|
if (!t || e.ctl || !e.listeners.size) return;
|
|
115
117
|
let n = null, r = [...e.listeners.values()].map((e) => e.poll).filter((e) => typeof e == "number" && e > 0);
|
|
116
|
-
if (r.length &&
|
|
117
|
-
let e =
|
|
118
|
+
if (r.length && a(t) && (n = Math.min(...r)), [...e.listeners.values()].some((e) => e.refresh)) {
|
|
119
|
+
let e = o(t.expires);
|
|
118
120
|
e !== null && (n = n === null ? e : Math.min(n, e));
|
|
119
121
|
}
|
|
120
122
|
n !== null && (e.timer = setTimeout(() => this.load(e.key, "refresh"), n));
|
|
121
123
|
}
|
|
122
124
|
reloadViewers(e) {
|
|
123
|
-
for (let [
|
|
125
|
+
for (let [n, r] of this.slots) !r.options.editor && t(r.ref, e) && (r.entry.loaded || r.ctl) && this.load(n, "reload");
|
|
124
126
|
}
|
|
125
|
-
apply(e,
|
|
127
|
+
apply(e, n) {
|
|
126
128
|
if (e.type !== "media.committed" && e.type !== "media.processed") return;
|
|
127
|
-
let { ref:
|
|
128
|
-
for (let [
|
|
129
|
-
if (
|
|
129
|
+
let { ref: r } = e;
|
|
130
|
+
for (let [i, a] of this.slots) {
|
|
131
|
+
if (!t(a.ref, r)) continue;
|
|
130
132
|
if (e.type === "media.committed") {
|
|
131
|
-
(
|
|
133
|
+
(a.entry.loaded || a.ctl) && this.load(i, "reload");
|
|
132
134
|
continue;
|
|
133
135
|
}
|
|
134
|
-
let
|
|
135
|
-
if (
|
|
136
|
-
if (!
|
|
137
|
-
this.load(
|
|
136
|
+
let o = a.entry.read, { file: s } = e;
|
|
137
|
+
if (o) {
|
|
138
|
+
if (!a.options.editor || a.scope !== n) {
|
|
139
|
+
this.load(i, "reload");
|
|
138
140
|
continue;
|
|
139
141
|
}
|
|
140
|
-
(!
|
|
142
|
+
(!a.options.prefix || s.path.startsWith(a.options.prefix)) && (this.update(a, { read: u(o, s.path, s) }), a.ctl && this.load(i, "reload"));
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
}
|
|
@@ -149,17 +151,17 @@ var u = class {
|
|
|
149
151
|
for (let t of e.listeners.keys()) t();
|
|
150
152
|
}
|
|
151
153
|
};
|
|
152
|
-
function
|
|
154
|
+
function f(e, t) {
|
|
153
155
|
if (!e) return !1;
|
|
154
156
|
let n = new Map(t.files.filter((e) => e.upload).map((e) => [e.path, e]));
|
|
155
|
-
return e.files.some((e) =>
|
|
157
|
+
return e.files.some((e) => i(e) && n.has(e.path) && !i(n.get(e.path)));
|
|
156
158
|
}
|
|
157
|
-
var
|
|
158
|
-
function
|
|
159
|
-
let t =
|
|
160
|
-
return t ||
|
|
159
|
+
var p = /* @__PURE__ */ new WeakMap();
|
|
160
|
+
function m(e) {
|
|
161
|
+
let t = p.get(e);
|
|
162
|
+
return t || p.set(e, t = new d(e)), t;
|
|
161
163
|
}
|
|
162
164
|
//#endregion
|
|
163
|
-
export {
|
|
165
|
+
export { d as ReadStore, l as readKey, m as storeFor, u as withUpload };
|
|
164
166
|
|
|
165
167
|
//# sourceMappingURL=store.js.map
|
package/dist/react/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../../src/react/store.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ReadOptions } from \"../client/media/api.js\";\nimport { samePath, sleep } from \"../client/media/client.js\";\nimport { isProcessing, processing } from \"../client/media/windows.js\";\nimport { expiryDelay } from \"../client/playback.js\";\n\nexport interface ReadEntry {\n read: ReadResult | null;\n loading: boolean;\n /** A read or set() has completed. */\n loaded: boolean;\n error?: ContentKitError;\n}\n\n/** What one reader asks of a shared read beyond the read itself. */\nexport interface ReadPolicy {\n /** Poll at this interval (ms) while uploads process (editor reads). */\n poll?: number | false;\n /** Read again shortly before the URLs expire. */\n refresh?: boolean;\n}\n\n/**\n * How load() treats a read in flight: \"initial\" reads only when nothing is\n * loaded or loading; \"refresh\" joins one in flight (timers, a player's grant\n * refresh); \"reload\" restarts it (after a change the in-flight read predates).\n */\nexport type LoadMode = \"initial\" | \"refresh\" | \"reload\";\n\ninterface Slot {\n key: string;\n scope: string;\n ref: RefBody;\n options: ReadOptions;\n listeners: Map<() => void, ReadPolicy>;\n entry: ReadEntry;\n ctl?: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\nconst IDLE: ReadEntry = { read: null, loading: false, loaded: false };\n\n/** rate_limited reads are retried this often before the error shows. */\nconst RATE_LIMIT_RETRIES = 3;\n\nexport function readKey(ref: RefBody, o: ReadOptions = {}): string {\n return JSON.stringify([ref.kind, ref.id, o.prefix ?? \"\", o.offset ?? 0, o.limit ?? 0, !!o.download, !!o.editor]);\n}\n\n/** The read with the upload at path replaced by file in place (appended when new, removed when null). */\nexport function withUpload(read: ReadResult | null, path: string, file: FileInfo | null): ReadResult {\n const all = read?.files ?? [];\n const at = all.findIndex((x) => x.upload && samePath(x.path, path));\n const files = all.filter((_, i) => i !== at);\n if (file) files.splice(at < 0 ? files.length : at, 0, file);\n return { access: \"full\", expires: 0, total: 0, offset: 0, limit: 0, ...read, files };\n}\n\n/**\n * The reads hooks share, per client: one request per item and options while\n * any hook shows it. set() updates every reader in place; a commit reloads\n * the item's reads and a processed upload is patched into its editor reads.\n * Readers may ask for polling while uploads process and a read before the\n * URLs expire; the slot runs one timer for all of them.\n */\nexport class ReadStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(private readonly client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy: ReadPolicy = {}, scope = \"{}\"): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { key, scope, ref, options, listeners: new Map(), entry: IDLE }));\n const slot = s;\n slot.listeners.set(listener, policy);\n this.schedule(slot);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (this.slots.get(key) !== slot) return;\n if (slot.listeners.size) return this.schedule(slot);\n slot.ctl?.abort();\n clearTimeout(slot.timer);\n this.slots.delete(key);\n });\n };\n }\n\n snapshot(key: string): ReadEntry {\n return this.slots.get(key)?.entry ?? IDLE;\n }\n\n load(key: string, mode: LoadMode = \"initial\"): void {\n const s = this.slots.get(key);\n if (!s) return;\n if (mode === \"initial\" && (s.entry.loaded || s.ctl)) return;\n if (mode === \"refresh\" && s.ctl) return;\n s.ctl?.abort();\n clearTimeout(s.timer);\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, error: undefined });\n this.fetch(s, ctl.signal).then(\n (read) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const before = s.entry.read;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n if (s.options.editor && finished(before, read)) this.reloadViewers(s.ref);\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { read: null, loading: false, loaded: true, error: toContentKitError(e) });\n },\n );\n }\n\n /** Replaces the read for every hook showing it, without refetching. */\n set(key: string, read: ReadResult): void {\n const s = this.slots.get(key);\n if (!s) return;\n s.ctl?.abort();\n s.ctl = undefined;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n }\n\n // The client retries transport faults; a read rate limit waits it out here.\n private async fetch(s: Slot, signal: AbortSignal): Promise<ReadResult> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.client.media.read(s.ref, { ...s.options, signal });\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"rate_limited\" || attempt >= RATE_LIMIT_RETRIES || signal.aborted) throw err;\n await sleep(Math.min(30, err.retryAfter ?? 1) * 1000, signal);\n }\n }\n }\n\n // One timer per slot: the soonest of a poll (while uploads process) and the refresh before expiry.\n private schedule(s: Slot): void {\n clearTimeout(s.timer);\n s.timer = undefined;\n const read = s.entry.read;\n if (!read || s.ctl || !s.listeners.size) return;\n let delay: number | null = null;\n const polls = [...s.listeners.values()].map((p) => p.poll).filter((p): p is number => typeof p === \"number\" && p > 0);\n if (polls.length && processing(read)) delay = Math.min(...polls);\n if ([...s.listeners.values()].some((p) => p.refresh)) {\n const d = expiryDelay(read.expires);\n if (d !== null) delay = delay === null ? d : Math.min(delay, d);\n }\n if (delay !== null) s.timer = setTimeout(() => this.load(s.key, \"refresh\"), delay);\n }\n\n // Uploads finished processing: the item's viewer reads list new files and URLs.\n private reloadViewers(ref: RefBody): void {\n for (const [key, s] of this.slots) {\n if (!s.options.editor && s.ref.kind === ref.kind && s.ref.id === ref.id && (s.entry.loaded || s.ctl)) this.load(key, \"reload\");\n }\n }\n\n private apply(change: ContentKitChange, scope: string | null): void {\n if (change.type !== \"media.committed\" && change.type !== \"media.processed\") return;\n const { ref } = change;\n for (const [key, s] of this.slots) {\n if (s.ref.kind !== ref.kind || s.ref.id !== ref.id) continue;\n if (change.type === \"media.committed\") {\n if (s.entry.loaded || s.ctl) this.load(key, \"reload\");\n continue;\n }\n const read = s.entry.read;\n const { file } = change;\n if (!read) continue;\n // Viewer URLs and responses from an earlier scope require a fresh read.\n if (!s.options.editor || s.scope !== scope) {\n this.load(key, \"reload\");\n continue;\n }\n if (s.options.prefix && !file.path.startsWith(s.options.prefix)) continue;\n this.update(s, { read: withUpload(read, file.path, file) });\n // A read in flight may predate the processing.\n if (s.ctl) this.load(key, \"reload\");\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<ReadEntry>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners.keys()) l();\n }\n}\n\n/** Some upload processing in before is done (or failed) in after. */\nfunction finished(before: ReadResult | null, after: ReadResult): boolean {\n if (!before) return false;\n const now = new Map(after.files.filter((f) => f.upload).map((f) => [f.path, f]));\n return before.files.some((f) => isProcessing(f) && now.has(f.path) && !isProcessing(now.get(f.path)!));\n}\n\nconst stores = new WeakMap<ContentKitClient, ReadStore>();\n\n/** The client's shared read store. */\nexport function storeFor(client: ContentKitClient): ReadStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ReadStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../src/react/store.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { sameItem } from \"../client/media/ref.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ReadOptions } from \"../client/media/api.js\";\nimport { samePath, sleep } from \"../client/media/client.js\";\nimport { isProcessing, processing } from \"../client/media/windows.js\";\nimport { expiryDelay } from \"../client/playback.js\";\n\nexport interface ReadEntry {\n read: ReadResult | null;\n loading: boolean;\n /** A read or set() has completed. */\n loaded: boolean;\n error?: ContentKitError;\n}\n\n/** What one reader asks of a shared read beyond the read itself. */\nexport interface ReadPolicy {\n /** Poll at this interval (ms) while uploads process (editor reads). */\n poll?: number | false;\n /** Read again shortly before the URLs expire. */\n refresh?: boolean;\n}\n\n/**\n * How load() treats a read in flight: \"initial\" reads only when nothing is\n * loaded or loading; \"refresh\" joins one in flight (timers, a player's grant\n * refresh); \"reload\" restarts it (after a change the in-flight read predates).\n */\nexport type LoadMode = \"initial\" | \"refresh\" | \"reload\";\n\ninterface Slot {\n key: string;\n scope: string;\n ref: RefBody;\n options: ReadOptions;\n listeners: Map<() => void, ReadPolicy>;\n entry: ReadEntry;\n ctl?: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\nconst IDLE: ReadEntry = { read: null, loading: false, loaded: false };\n\n/** rate_limited reads are retried this often before the error shows. */\nconst RATE_LIMIT_RETRIES = 3;\n\nexport function readKey(ref: RefBody, o: ReadOptions = {}): string {\n return JSON.stringify([ref.kind, ref.id, ref.version ?? \"\", o.prefix ?? \"\", o.offset ?? 0, o.limit ?? 0, !!o.download, !!o.editor]);\n}\n\n/** The read with the upload at path replaced by file in place (appended when new, removed when null). */\nexport function withUpload(read: ReadResult | null, path: string, file: FileInfo | null): ReadResult {\n const all = read?.files ?? [];\n const at = all.findIndex((x) => x.upload && samePath(x.path, path));\n const files = all.filter((_, i) => i !== at);\n if (file) files.splice(at < 0 ? files.length : at, 0, file);\n return { access: \"full\", expires: 0, total: 0, offset: 0, limit: 0, ...read, files };\n}\n\n/**\n * The reads hooks share, per client: one request per item and options while\n * any hook shows it. set() updates every reader in place; a commit reloads\n * the item's reads and a processed upload is patched into its editor reads.\n * Readers may ask for polling while uploads process and a read before the\n * URLs expire; the slot runs one timer for all of them.\n */\nexport class ReadStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(private readonly client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy: ReadPolicy = {}, scope = \"{}\"): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { key, scope, ref, options, listeners: new Map(), entry: IDLE }));\n const slot = s;\n slot.listeners.set(listener, policy);\n this.schedule(slot);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (this.slots.get(key) !== slot) return;\n if (slot.listeners.size) return this.schedule(slot);\n slot.ctl?.abort();\n clearTimeout(slot.timer);\n this.slots.delete(key);\n });\n };\n }\n\n snapshot(key: string): ReadEntry {\n return this.slots.get(key)?.entry ?? IDLE;\n }\n\n load(key: string, mode: LoadMode = \"initial\"): void {\n const s = this.slots.get(key);\n if (!s) return;\n if (mode === \"initial\" && (s.entry.loaded || s.ctl)) return;\n if (mode === \"refresh\" && s.ctl) return;\n s.ctl?.abort();\n clearTimeout(s.timer);\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, error: undefined });\n this.fetch(s, ctl.signal).then(\n (read) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const before = s.entry.read;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n if (s.options.editor && finished(before, read)) this.reloadViewers(s.ref);\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { read: null, loading: false, loaded: true, error: toContentKitError(e) });\n },\n );\n }\n\n /** Replaces the read for every hook showing it, without refetching. */\n set(key: string, read: ReadResult): void {\n const s = this.slots.get(key);\n if (!s) return;\n s.ctl?.abort();\n s.ctl = undefined;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n }\n\n // The client retries transport faults; a read rate limit waits it out here.\n private async fetch(s: Slot, signal: AbortSignal): Promise<ReadResult> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.client.media.read(s.ref, { ...s.options, signal });\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"rate_limited\" || attempt >= RATE_LIMIT_RETRIES || signal.aborted) throw err;\n await sleep(Math.min(30, err.retryAfter ?? 1) * 1000, signal);\n }\n }\n }\n\n // One timer per slot: the soonest of a poll (while uploads process) and the refresh before expiry.\n private schedule(s: Slot): void {\n clearTimeout(s.timer);\n s.timer = undefined;\n const read = s.entry.read;\n if (!read || s.ctl || !s.listeners.size) return;\n let delay: number | null = null;\n const polls = [...s.listeners.values()].map((p) => p.poll).filter((p): p is number => typeof p === \"number\" && p > 0);\n if (polls.length && processing(read)) delay = Math.min(...polls);\n if ([...s.listeners.values()].some((p) => p.refresh)) {\n const d = expiryDelay(read.expires);\n if (d !== null) delay = delay === null ? d : Math.min(delay, d);\n }\n if (delay !== null) s.timer = setTimeout(() => this.load(s.key, \"refresh\"), delay);\n }\n\n // Uploads finished processing: the item's viewer reads list new files and URLs.\n private reloadViewers(ref: RefBody): void {\n for (const [key, s] of this.slots) {\n if (!s.options.editor && sameItem(s.ref, ref) && (s.entry.loaded || s.ctl)) this.load(key, \"reload\");\n }\n }\n\n private apply(change: ContentKitChange, scope: string | null): void {\n if (change.type !== \"media.committed\" && change.type !== \"media.processed\") return;\n const { ref } = change;\n for (const [key, s] of this.slots) {\n if (!sameItem(s.ref, ref)) continue;\n if (change.type === \"media.committed\") {\n if (s.entry.loaded || s.ctl) this.load(key, \"reload\");\n continue;\n }\n const read = s.entry.read;\n const { file } = change;\n if (!read) continue;\n // Viewer URLs and responses from an earlier scope require a fresh read.\n if (!s.options.editor || s.scope !== scope) {\n this.load(key, \"reload\");\n continue;\n }\n if (s.options.prefix && !file.path.startsWith(s.options.prefix)) continue;\n this.update(s, { read: withUpload(read, file.path, file) });\n // A read in flight may predate the processing.\n if (s.ctl) this.load(key, \"reload\");\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<ReadEntry>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners.keys()) l();\n }\n}\n\n/** Some upload processing in before is done (or failed) in after. */\nfunction finished(before: ReadResult | null, after: ReadResult): boolean {\n if (!before) return false;\n const now = new Map(after.files.filter((f) => f.upload).map((f) => [f.path, f]));\n return before.files.some((f) => isProcessing(f) && now.has(f.path) && !isProcessing(now.get(f.path)!));\n}\n\nconst stores = new WeakMap<ContentKitClient, ReadStore>();\n\n/** The client's shared read store. */\nexport function storeFor(client: ContentKitClient): ReadStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ReadStore(client)));\n return s;\n}\n"],"mappings":"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"}
|