@openrails/contentkit-ui 0.68.0 → 0.69.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 +55 -10
- package/dist/client/client.d.ts +4 -2
- package/dist/client/client.js +1 -0
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/bans.js +7 -7
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.js +16 -14
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.js +5 -5
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.js +5 -5
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/polls.js +34 -31
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.js +21 -20
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/content/reactions.js +4 -4
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/http.d.ts +6 -2
- package/dist/client/http.js +11 -3
- package/dist/client/http.js.map +1 -1
- package/dist/client/media/client.js +24 -24
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -0
- package/dist/client/media/rules.js +6 -2
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.js +2 -2
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/media-folder-editor.d.ts +17 -3
- package/dist/components/media-folder-editor.js +146 -99
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/slot-upload.js +102 -100
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/canonical.d.ts +2 -2
- package/dist/react/canonical.js +2 -12
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.js +35 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -0
- package/dist/react/context.js +20 -15
- package/dist/react/context.js.map +1 -1
- package/dist/react/engagement.js +2 -2
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +29 -9
- package/dist/react/folder.js +170 -113
- package/dist/react/folder.js.map +1 -1
- package/dist/react/polls.js +3 -3
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +2 -2
- package/dist/react/posts.js.map +1 -1
- package/dist/react/provider.d.ts +7 -3
- package/dist/react/provider.js +34 -20
- package/dist/react/provider.js.map +1 -1
- package/dist/react/public.js +11 -11
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +93 -92
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +6 -3
- package/dist/react/resources.js +40 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/slot.d.ts +4 -2
- package/dist/react/slot.js +96 -92
- package/dist/react/slot.js.map +1 -1
- package/dist/react/store.d.ts +1 -1
- package/dist/react/store.js +21 -20
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.d.ts +3 -2
- package/dist/react/upload.js +20 -15
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -0
- package/dist/react/use-resource.js +38 -30
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +15 -15
- package/package.json +7 -5
package/dist/react/posts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posts.js","names":[],"sources":["../../src/react/posts.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Sort } from \"../client/content/types.js\";\nimport type { Post, PostInput } from \"../client/generated/wire.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface PostFilter {\n language?: string;\n sort?: Sort;\n /** Staff: every post, drafts and scheduled ones included (PostWrite). */\n admin?: boolean;\n /** With admin: true only drafts, false no drafts. */\n draft?: boolean;\n /** With admin: the deleted posts instead of the live ones. */\n deleted?: boolean;\n /** With admin: only posts whose title, excerpt or body contains this text. */\n q?: string;\n pageSize?: number;\n}\n\n/** Posts, a page at a time: the published ones, or every one for staff. */\nexport function usePosts(f: PostFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Post> {\n const { client, store, viewer } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n return useList(\n store,\n keyOf(\"posts\", viewer, !!f.admin, f.language, f.sort, f.draft, f.deleted, f.q, size),\n { type: \"posts\", scope: f.admin && f.deleted ? \"deleted\" : undefined },\n offsetPages(size, (q, signal) =>\n f.admin\n ? client.posts.adminList({ ...q, language: f.language, draft: f.draft, deleted: f.deleted, q: f.q }, signal)\n : client.posts.list({ ...q, language: f.language, sort: f.sort }, signal),\n ),\n );\n}\n\nexport interface UsePost {\n /** null before create (or while loading). */\n post: Post | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the post; the hook then edits it. */\n create: (input: PostInput) => Promise<Post>;\n /** Updates the given fields; a body from the editor is stored with its images' public URLs (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (PostWrite). */\n restore: () => Promise<Post>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the post's images now (the cover, a body image):\n * an unpublished post's images are not public yet, so its editors see them\n * through signed URLs of an editor read; a fresh upload shows from the\n * file itself. Any other URL is returned as is.\n */\n imageSrc: (url: string | null | undefined) => string | undefined;\n /** post.body with its images through imageSrc: what a rich-text editor shows. */\n editorBody: string | undefined;\n /** HTML from the editor with the URLs imageSrc gave back to the images' public URLs. */\n storedBody: (html: string) => string;\n}\n\nconst NAME = \"i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\";\nconst nameIn = (url: string) => url.match(new RegExp(NAME))?.[0];\n// An image URL in HTML: everything around a name up to a quote, a space or a bracket.\nconst imageURLs = new RegExp(`[^\\\\s\"'()<>]*${NAME}[^\\\\s\"'()<>]*`, \"g\");\n\n/** Whether readers see the post, and so its images' public files exist. */\nexport function isPublished(post: Post, now = Date.now()): boolean {\n return !post.is_draft && !post.moderation && (!post.live_at || Date.parse(post.live_at) <= now);\n}\n\n/** One post: read, and for staff (PostWrite) create, update, delete, restore, cover and body images. null creates a new post. */\nexport function usePost(id: string | null | undefined, o: { initial?: Post; client?: ContentKitClient } = {}): UsePost {\n const { client, store, viewer } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"post\", viewer, pid) : null;\n const r = useResource<Post | null>(store, key, { type: \"post\", id: pid ?? undefined }, (s) => client.posts.get(pid!, s), { initial: o.initial });\n const post = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await fn();\n } finally {\n setInflight((n) => n - 1);\n }\n }, []);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the post first\");\n return pid;\n }, [pid]);\n\n // Images: fresh uploads show from their file; an unpublished post's from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const given = useRef(new Map<string, string>()); // each URL imageSrc gave -> the image's public URL\n useEffect(() => {\n const urls = given.current;\n return () => {\n for (const u of urls.keys()) if (u.startsWith(\"blob:\")) URL.revokeObjectURL(u);\n };\n }, []);\n const names = useMemo(() => {\n const out = new Map<string, string>();\n for (const u of [...(post?.body.match(imageURLs) ?? []), post?.cover_url ?? \"\"]) {\n const n = nameIn(u);\n if (n && !out.has(n)) out.set(n, u);\n }\n return out;\n }, [post?.body, post?.cover_url]);\n const unread = !!post && !isPublished(post) && [...names.keys()].some((n) => !previews.has(n));\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: unread ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) {\n const n = f.upload && f.editor_url ? nameIn(f.path) : undefined;\n if (n) out.set(n, f.editor_url!);\n }\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const missing = unread && !!read.read && [...names.keys()].some((n) => !previews.has(n) && !views.has(n) && !read.read!.files.some((f) => f.failed && nameIn(f.path) === n));\n const tries = useRef(0);\n const { reload } = read;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, read.read, reload]);\n\n const imageSrc = useCallback(\n (url: string | null | undefined) => {\n if (!url) return undefined;\n const n = nameIn(url);\n const shown = n ? (previews.get(n) ?? views.get(n)) : undefined;\n if (!shown) return url;\n given.current.set(shown, url);\n return shown;\n },\n [previews, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageURLs, (u) => imageSrc(u) ?? u), [post?.body, imageSrc]);\n const storedBody = useCallback((html: string) => {\n let out = html;\n for (const [shown, url] of given.current) out = out.split(shown).join(url).split(shown.replaceAll(\"&\", \"&\")).join(url);\n return out;\n }, []);\n const preview = useCallback((name: string, url: string, file: Blob) => {\n if (typeof URL.createObjectURL !== \"function\") return url;\n const shown = URL.createObjectURL(file);\n given.current.set(shown, url);\n setPreviews((m) => new Map(m).set(name, shown));\n return shown;\n }, []);\n\n const create = useCallback(\n (input: PostInput) =>\n run(async () => {\n const post = await client.posts.create(input);\n setCreated(post.id);\n return post;\n }),\n [client, run],\n );\n const update = useCallback(\n (patch: PostInput) => run(() => client.posts.update(need(), patch.body === undefined ? patch : { ...patch, body: storedBody(patch.body) })),\n [client, run, need, storedBody],\n );\n const remove = useCallback(() => run(() => client.posts.delete(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.posts.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) return client.posts.setCover(need(), null);\n const up = await client.media.uploadNamed(client.media.postRef(need()), file);\n const url = await client.posts.setCover(need(), up.name);\n return url && preview(up.name, url, file);\n }),\n [client, run, need, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline(need(), file);\n return preview(up.name, up.url, file);\n }),\n [client, run, need, preview],\n );\n return {\n post,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n editorBody,\n storedBody,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"posts.js","names":[],"sources":["../../src/react/posts.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Sort } from \"../client/content/types.js\";\nimport type { Post, PostInput } from \"../client/generated/wire.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface PostFilter {\n language?: string;\n sort?: Sort;\n /** Staff: every post, drafts and scheduled ones included (PostWrite). */\n admin?: boolean;\n /** With admin: true only drafts, false no drafts. */\n draft?: boolean;\n /** With admin: the deleted posts instead of the live ones. */\n deleted?: boolean;\n /** With admin: only posts whose title, excerpt or body contains this text. */\n q?: string;\n pageSize?: number;\n}\n\n/** Posts, a page at a time: the published ones, or every one for staff. */\nexport function usePosts(f: PostFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Post> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n return useList(\n store,\n keyOf(\"posts\", scope, !!f.admin, f.language, f.sort, f.draft, f.deleted, f.q, size),\n { type: \"posts\", scope: f.admin && f.deleted ? \"deleted\" : undefined },\n offsetPages(size, (q, signal) =>\n f.admin\n ? client.posts.adminList({ ...q, language: f.language, draft: f.draft, deleted: f.deleted, q: f.q }, signal)\n : client.posts.list({ ...q, language: f.language, sort: f.sort }, signal),\n ),\n );\n}\n\nexport interface UsePost {\n /** null before create (or while loading). */\n post: Post | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the post; the hook then edits it. */\n create: (input: PostInput) => Promise<Post>;\n /** Updates the given fields; a body from the editor is stored with its images' public URLs (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (PostWrite). */\n restore: () => Promise<Post>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the post's images now (the cover, a body image):\n * an unpublished post's images are not public yet, so its editors see them\n * through signed URLs of an editor read; a fresh upload shows from the\n * file itself. Any other URL is returned as is.\n */\n imageSrc: (url: string | null | undefined) => string | undefined;\n /** post.body with its images through imageSrc: what a rich-text editor shows. */\n editorBody: string | undefined;\n /** HTML from the editor with the URLs imageSrc gave back to the images' public URLs. */\n storedBody: (html: string) => string;\n}\n\nconst NAME = \"i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\";\nconst nameIn = (url: string) => url.match(new RegExp(NAME))?.[0];\n// An image URL in HTML: everything around a name up to a quote, a space or a bracket.\nconst imageURLs = new RegExp(`[^\\\\s\"'()<>]*${NAME}[^\\\\s\"'()<>]*`, \"g\");\n\n/** Whether readers see the post, and so its images' public files exist. */\nexport function isPublished(post: Post, now = Date.now()): boolean {\n return !post.is_draft && !post.moderation && (!post.live_at || Date.parse(post.live_at) <= now);\n}\n\n/** One post: read, and for staff (PostWrite) create, update, delete, restore, cover and body images. null creates a new post. */\nexport function usePost(id: string | null | undefined, o: { initial?: Post; client?: ContentKitClient } = {}): UsePost {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"post\", scope, pid) : null;\n const r = useResource<Post | null>(store, key, { type: \"post\", id: pid ?? undefined }, (s) => client.posts.get(pid!, s), { initial: o.initial });\n const post = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await fn();\n } finally {\n setInflight((n) => n - 1);\n }\n }, []);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the post first\");\n return pid;\n }, [pid]);\n\n // Images: fresh uploads show from their file; an unpublished post's from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const given = useRef(new Map<string, string>()); // each URL imageSrc gave -> the image's public URL\n useEffect(() => {\n const urls = given.current;\n return () => {\n for (const u of urls.keys()) if (u.startsWith(\"blob:\")) URL.revokeObjectURL(u);\n };\n }, []);\n const names = useMemo(() => {\n const out = new Map<string, string>();\n for (const u of [...(post?.body.match(imageURLs) ?? []), post?.cover_url ?? \"\"]) {\n const n = nameIn(u);\n if (n && !out.has(n)) out.set(n, u);\n }\n return out;\n }, [post?.body, post?.cover_url]);\n const unread = !!post && !isPublished(post) && [...names.keys()].some((n) => !previews.has(n));\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: unread ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) {\n const n = f.upload && f.editor_url ? nameIn(f.path) : undefined;\n if (n) out.set(n, f.editor_url!);\n }\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const missing = unread && !!read.read && [...names.keys()].some((n) => !previews.has(n) && !views.has(n) && !read.read!.files.some((f) => f.failed && nameIn(f.path) === n));\n const tries = useRef(0);\n const { reload } = read;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, read.read, reload]);\n\n const imageSrc = useCallback(\n (url: string | null | undefined) => {\n if (!url) return undefined;\n const n = nameIn(url);\n const shown = n ? (previews.get(n) ?? views.get(n)) : undefined;\n if (!shown) return url;\n given.current.set(shown, url);\n return shown;\n },\n [previews, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageURLs, (u) => imageSrc(u) ?? u), [post?.body, imageSrc]);\n const storedBody = useCallback((html: string) => {\n let out = html;\n for (const [shown, url] of given.current) out = out.split(shown).join(url).split(shown.replaceAll(\"&\", \"&\")).join(url);\n return out;\n }, []);\n const preview = useCallback((name: string, url: string, file: Blob) => {\n if (typeof URL.createObjectURL !== \"function\") return url;\n const shown = URL.createObjectURL(file);\n given.current.set(shown, url);\n setPreviews((m) => new Map(m).set(name, shown));\n return shown;\n }, []);\n\n const create = useCallback(\n (input: PostInput) =>\n run(async () => {\n const post = await client.posts.create(input);\n setCreated(post.id);\n return post;\n }),\n [client, run],\n );\n const update = useCallback(\n (patch: PostInput) => run(() => client.posts.update(need(), patch.body === undefined ? patch : { ...patch, body: storedBody(patch.body) })),\n [client, run, need, storedBody],\n );\n const remove = useCallback(() => run(() => client.posts.delete(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.posts.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) return client.posts.setCover(need(), null);\n const up = await client.media.uploadNamed(client.media.postRef(need()), file);\n const url = await client.posts.setCover(need(), up.name);\n return url && preview(up.name, url, file);\n }),\n [client, run, need, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline(need(), file);\n return preview(up.name, up.url, file);\n }),\n [client, run, need, preview],\n );\n return {\n post,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n editorBody,\n storedBody,\n };\n}\n"],"mappings":";;;;AAuBA,SAAgB,EAAS,IAAgB,CAAC,GAAG,IAAmC,CAAC,GAAkB;CACjG,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,YAAY;CAC3B,OAAO,EACL,GACA,EAAM,SAAS,GAAO,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAI,GAClF;EAAE,MAAM;EAAS,OAAO,EAAE,SAAS,EAAE,UAAU,YAAY,KAAA;CAAU,GACrE,EAAY,IAAO,GAAG,MACpB,EAAE,QACE,EAAO,MAAM,UAAU;EAAE,GAAG;EAAG,UAAU,EAAE;EAAU,OAAO,EAAE;EAAO,SAAS,EAAE;EAAS,GAAG,EAAE;CAAE,GAAG,CAAM,IACzG,EAAO,MAAM,KAAK;EAAE,GAAG;EAAG,UAAU,EAAE;EAAU,MAAM,EAAE;CAAK,GAAG,CAAM,CAC5E,CACF;AACF;AAiCA,IAAM,IAAO,kEACP,KAAU,MAAgB,EAAI,MAAM,IAAI,OAAO,CAAI,CAAC,CAAC,GAAG,IAExD,IAAgB,OAAO,gBAAgB,EAAK,gBAAgB,GAAG;AAGrE,SAAgB,EAAY,GAAY,IAAM,KAAK,IAAI,GAAY;CACjE,OAAO,CAAC,EAAK,YAAY,CAAC,EAAK,eAAe,CAAC,EAAK,WAAW,KAAK,MAAM,EAAK,OAAO,KAAK;AAC7F;AAGA,SAAgB,EAAQ,GAA+B,IAAmD,CAAC,GAAY;CACrH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,CAAC,GAAS,KAAc,EAAwB,IAAI,GACpD,IAAM,KAAM,GACZ,IAAM,IAAM,EAAM,QAAQ,GAAO,CAAG,IAAI,MACxC,IAAI,EAAyB,GAAO,GAAK;EAAE,MAAM;EAAQ,IAAI,KAAO,KAAA;CAAU,IAAI,MAAM,EAAO,MAAM,IAAI,GAAM,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC,GACzI,IAAO,EAAE,QAAQ,MACjB,CAAC,GAAU,KAAe,EAAS,CAAC,GACpC,IAAM,EAAY,OAAU,MAAqC;EACrE,GAAa,MAAM,IAAI,CAAC;EACxB,IAAI;GACF,OAAO,MAAM,EAAG;EAClB,UAAU;GACR,GAAa,MAAM,IAAI,CAAC;EAC1B;CACF,GAAG,CAAC,CAAC,GACC,IAAO,QAAkB;EAC7B,IAAI,CAAC,GAAK,MAAU,MAAM,sCAAsC;EAChE,OAAO;CACT,GAAG,CAAC,CAAG,CAAC,GAGF,CAAC,GAAU,KAAe,kBAAsC,IAAI,IAAI,CAAC,GACzE,IAAQ,kBAAO,IAAI,IAAoB,CAAC;CAC9C,QAAgB;EACd,IAAM,IAAO,EAAM;EACnB,aAAa;GACX,KAAK,IAAM,KAAK,EAAK,KAAK,GAAG,AAAI,EAAE,WAAW,OAAO,KAAG,IAAI,gBAAgB,CAAC;EAC/E;CACF,GAAG,CAAC,CAAC;CACL,IAAM,IAAQ,QAAc;EAC1B,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAK,CAAC,GAAI,GAAM,KAAK,MAAM,CAAS,KAAK,CAAC,GAAI,GAAM,aAAa,EAAE,GAAG;GAC/E,IAAM,IAAI,EAAO,CAAC;GAClB,AAAI,KAAK,CAAC,EAAI,IAAI,CAAC,KAAG,EAAI,IAAI,GAAG,CAAC;EACpC;EACA,OAAO;CACT,GAAG,CAAC,GAAM,MAAM,GAAM,SAAS,CAAC,GAC1B,IAAS,CAAC,CAAC,KAAQ,CAAC,EAAY,CAAI,KAAK,CAAC,GAAG,EAAM,KAAK,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,EAAS,IAAI,CAAC,CAAC,GACvF,IAAS,QAAc,EAAO,MAAM,QAAQ,KAAO,MAAM,GAAG,CAAC,GAAQ,CAAG,CAAC,GACzE,IAAO,EAAa,GAAQ;EAAE,QAAQ;EAAM,MAAM,IAAS,KAAA,IAAY;EAAM;CAAO,CAAC,GACrF,IAAQ,QAAc;EAC1B,IAAM,oBAAM,IAAI,IAAoB;EACpC,KAAK,IAAM,KAAK,EAAK,MAAM,SAAS,CAAC,GAAG;GACtC,IAAM,IAAI,EAAE,UAAU,EAAE,aAAa,EAAO,EAAE,IAAI,IAAI,KAAA;GACtD,AAAI,KAAG,EAAI,IAAI,GAAG,EAAE,UAAW;EACjC;EACA,OAAO;CACT,GAAG,CAAC,EAAK,IAAI,CAAC,GAER,IAAU,KAAU,CAAC,CAAC,EAAK,QAAQ,CAAC,GAAG,EAAM,KAAK,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,EAAS,IAAI,CAAC,KAAK,CAAC,EAAM,IAAI,CAAC,KAAK,CAAC,EAAK,KAAM,MAAM,MAAM,MAAM,EAAE,UAAU,EAAO,EAAE,IAAI,MAAM,CAAC,CAAC,GACrK,IAAQ,EAAO,CAAC,GAChB,EAAE,cAAW;CACnB,QAAgB;EACd,IAAI,CAAC,GAAS;GACZ,EAAM,UAAU;GAChB;EACF;EACA,IAAI,EAAM,WAAW,GAAG;EACxB,IAAM,IAAI,iBAAiB;GAEzB,AADA,EAAM,WACN,EAAO;EACT,GAAG,MAAO,KAAK,EAAM,OAAO;EAC5B,aAAa,aAAa,CAAC;CAC7B,GAAG;EAAC;EAAS,EAAK;EAAM;CAAM,CAAC;CAE/B,IAAM,IAAW,GACd,MAAmC;EAClC,IAAI,CAAC,GAAK;EACV,IAAM,IAAI,EAAO,CAAG,GACd,IAAQ,IAAK,EAAS,IAAI,CAAC,KAAK,EAAM,IAAI,CAAC,IAAK,KAAA;EAGtD,OAFK,KACL,EAAM,QAAQ,IAAI,GAAO,CAAG,GACrB,KAFY;CAGrB,GACA,CAAC,GAAU,CAAK,CAClB,GACM,IAAa,QAAc,GAAM,KAAK,QAAQ,IAAY,MAAM,EAAS,CAAC,KAAK,CAAC,GAAG,CAAC,GAAM,MAAM,CAAQ,CAAC,GACzG,IAAa,GAAa,MAAiB;EAC/C,IAAI,IAAM;EACV,KAAK,IAAM,CAAC,GAAO,MAAQ,EAAM,SAAS,IAAM,EAAI,MAAM,CAAK,CAAC,CAAC,KAAK,CAAG,CAAC,CAAC,MAAM,EAAM,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAG;EACzH,OAAO;CACT,GAAG,CAAC,CAAC,GACC,IAAU,GAAa,GAAc,GAAa,MAAe;EACrE,IAAI,OAAO,IAAI,mBAAoB,YAAY,OAAO;EACtD,IAAM,IAAQ,IAAI,gBAAgB,CAAI;EAGtC,OAFA,EAAM,QAAQ,IAAI,GAAO,CAAG,GAC5B,GAAa,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,GAAM,CAAK,CAAC,GACvC;CACT,GAAG,CAAC,CAAC,GAEC,IAAS,GACZ,MACC,EAAI,YAAY;EACd,IAAM,IAAO,MAAM,EAAO,MAAM,OAAO,CAAK;EAE5C,OADA,EAAW,EAAK,EAAE,GACX;CACT,CAAC,GACH,CAAC,GAAQ,CAAG,CACd,GACM,IAAS,GACZ,MAAqB,QAAU,EAAO,MAAM,OAAO,EAAK,GAAG,EAAM,SAAS,KAAA,IAAY,IAAQ;EAAE,GAAG;EAAO,MAAM,EAAW,EAAM,IAAI;CAAE,CAAC,CAAC,GAC1I;EAAC;EAAQ;EAAK;EAAM;CAAU,CAChC,GACM,IAAS,QAAkB,QAAU,EAAO,MAAM,OAAO,EAAK,CAAC,CAAC,GAAG;EAAC;EAAQ;EAAK;CAAI,CAAC,GACtF,IAAU,QAAkB,QAAU,EAAO,MAAM,QAAQ,EAAK,CAAC,CAAC,GAAG;EAAC;EAAQ;EAAK;CAAI,CAAC,GACxF,IAAW,GACd,MACC,EAAI,YAAY;EACd,IAAI,CAAC,GAAM,OAAO,EAAO,MAAM,SAAS,EAAK,GAAG,IAAI;EACpD,IAAM,IAAK,MAAM,EAAO,MAAM,YAAY,EAAO,MAAM,QAAQ,EAAK,CAAC,GAAG,CAAI,GACtE,IAAM,MAAM,EAAO,MAAM,SAAS,EAAK,GAAG,EAAG,IAAI;EACvD,OAAO,KAAO,EAAQ,EAAG,MAAM,GAAK,CAAI;CAC1C,CAAC,GACH;EAAC;EAAQ;EAAK;EAAM;CAAO,CAC7B,GACM,IAAc,GACjB,MACC,EAAI,YAAY;EACd,IAAM,IAAK,MAAM,EAAO,MAAM,aAAa,EAAK,GAAG,CAAI;EACvD,OAAO,EAAQ,EAAG,MAAM,EAAG,KAAK,CAAI;CACtC,CAAC,GACH;EAAC;EAAQ;EAAK;EAAM;CAAO,CAC7B;CACA,OAAO;EACL;EACA,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAY,CAAC,EAAE,UAAU,EAAE,YAAY,KAAA;EAC5D,OAAO,EAAE;EACT,QAAQ,IAAW;EACnB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
package/dist/react/provider.d.ts
CHANGED
|
@@ -7,17 +7,21 @@ export interface ContentKitProviderProps {
|
|
|
7
7
|
client: ContentKitClient;
|
|
8
8
|
/** The signed-in user's id as ContentKit sees it (the actor id), or null when signed out. */
|
|
9
9
|
viewer?: string | null;
|
|
10
|
+
/** Request language; keep it in sync with the client's language callback. */
|
|
11
|
+
language?: string;
|
|
12
|
+
/** Change when the current viewer's permissions or entitlements change. */
|
|
13
|
+
accessRevision?: string | number;
|
|
10
14
|
/** Asks the visitor to sign in: comments, reactions, favorites and votes prompt with it rather than act anonymously. */
|
|
11
15
|
onSignIn?: () => void;
|
|
12
16
|
/** Content URL helpers (createContentURLs from `/urls`). */
|
|
13
17
|
urls?: ContentURLs;
|
|
14
18
|
/** Host router for in-app navigation (a canonical replace). */
|
|
15
19
|
navigate?: Navigate;
|
|
16
|
-
/**
|
|
17
|
-
onChange?: (change: ContentKitChange) => void;
|
|
20
|
+
/** Every successful mutation; null scope means refetch instead of applying its data. */
|
|
21
|
+
onChange?: (change: ContentKitChange, scope: string | null) => void;
|
|
18
22
|
/** Receives every failure the components show (a component's own `onError` wins). */
|
|
19
23
|
onError?: ContentKitErrorHandler;
|
|
20
24
|
children?: ReactNode;
|
|
21
25
|
}
|
|
22
26
|
/** Client, URL config, callbacks and the shared read store for every hook below. Renders no DOM. */
|
|
23
|
-
export declare function ContentKitProvider({ client, viewer, onSignIn, urls, navigate, onChange, onError, children }: ContentKitProviderProps): import('react').JSX.Element;
|
|
27
|
+
export declare function ContentKitProvider({ client, viewer, language, accessRevision, onSignIn, urls, navigate, onChange, onError, children }: ContentKitProviderProps): import('react').JSX.Element;
|
package/dist/react/provider.js
CHANGED
|
@@ -1,35 +1,49 @@
|
|
|
1
1
|
import { ContentKitContext as e } from "./context.js";
|
|
2
2
|
import { storeFor as t } from "./store.js";
|
|
3
|
-
import { useEffect as n,
|
|
4
|
-
import { jsx as
|
|
3
|
+
import { useEffect as n, useLayoutEffect as r, useMemo as i, useRef as a } from "react";
|
|
4
|
+
import { jsx as o } from "react/jsx-runtime";
|
|
5
5
|
//#region src/react/provider.tsx
|
|
6
|
-
function
|
|
7
|
-
let
|
|
6
|
+
function s({ client: s, viewer: c, language: l, accessRevision: u, onSignIn: d, urls: f, navigate: p, onChange: m, onError: h, children: g }) {
|
|
7
|
+
let _ = JSON.stringify({
|
|
8
|
+
viewer: c,
|
|
9
|
+
language: l,
|
|
10
|
+
accessRevision: u
|
|
11
|
+
});
|
|
12
|
+
r(() => {
|
|
13
|
+
s.setReadScope(_);
|
|
14
|
+
}, [s, _]);
|
|
15
|
+
let v = a(m);
|
|
8
16
|
n(() => {
|
|
9
|
-
|
|
10
|
-
}), n(() =>
|
|
11
|
-
let
|
|
12
|
-
client:
|
|
13
|
-
viewer:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
v.current = m;
|
|
18
|
+
}), n(() => s.subscribe((e, t) => v.current?.(e, t)), [s]);
|
|
19
|
+
let y = i(() => ({
|
|
20
|
+
client: s,
|
|
21
|
+
viewer: c,
|
|
22
|
+
language: l,
|
|
23
|
+
accessRevision: u,
|
|
24
|
+
readScope: _,
|
|
25
|
+
onSignIn: d,
|
|
26
|
+
urls: f,
|
|
27
|
+
navigate: p,
|
|
28
|
+
onError: h,
|
|
29
|
+
store: t(s)
|
|
19
30
|
}), [
|
|
20
|
-
o,
|
|
21
31
|
s,
|
|
22
32
|
c,
|
|
23
33
|
l,
|
|
24
34
|
u,
|
|
25
|
-
|
|
35
|
+
_,
|
|
36
|
+
d,
|
|
37
|
+
f,
|
|
38
|
+
p,
|
|
39
|
+
h
|
|
26
40
|
]);
|
|
27
|
-
return /* @__PURE__ */
|
|
28
|
-
value:
|
|
29
|
-
children:
|
|
41
|
+
return /* @__PURE__ */ o(e.Provider, {
|
|
42
|
+
value: y,
|
|
43
|
+
children: g
|
|
30
44
|
});
|
|
31
45
|
}
|
|
32
46
|
//#endregion
|
|
33
|
-
export {
|
|
47
|
+
export { s as ContentKitProvider };
|
|
34
48
|
|
|
35
49
|
//# sourceMappingURL=provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":[],"sources":["../../src/react/provider.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport { ContentKitContext, type ContentKitErrorHandler, type Navigate } from \"./context.js\";\nimport { storeFor } from \"./store.js\";\n\nexport interface ContentKitProviderProps {\n client: ContentKitClient;\n /** The signed-in user's id as ContentKit sees it (the actor id), or null when signed out. */\n viewer?: string | null;\n /** Asks the visitor to sign in: comments, reactions, favorites and votes prompt with it rather than act anonymously. */\n onSignIn?: () => void;\n /** Content URL helpers (createContentURLs from `/urls`). */\n urls?: ContentURLs;\n /** Host router for in-app navigation (a canonical replace). */\n navigate?: Navigate;\n /**
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../../src/react/provider.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport { ContentKitContext, type ContentKitErrorHandler, type Navigate } from \"./context.js\";\nimport { storeFor } from \"./store.js\";\n\nexport interface ContentKitProviderProps {\n client: ContentKitClient;\n /** The signed-in user's id as ContentKit sees it (the actor id), or null when signed out. */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n /** Asks the visitor to sign in: comments, reactions, favorites and votes prompt with it rather than act anonymously. */\n onSignIn?: () => void;\n /** Content URL helpers (createContentURLs from `/urls`). */\n urls?: ContentURLs;\n /** Host router for in-app navigation (a canonical replace). */\n navigate?: Navigate;\n /** Every successful mutation; null scope means refetch instead of applying its data. */\n onChange?: (change: ContentKitChange, scope: string | null) => void;\n /** Receives every failure the components show (a component's own `onError` wins). */\n onError?: ContentKitErrorHandler;\n children?: ReactNode;\n}\n\n/** Client, URL config, callbacks and the shared read store for every hook below. Renders no DOM. */\nexport function ContentKitProvider({ client, viewer, language, accessRevision, onSignIn, urls, navigate, onChange, onError, children }: ContentKitProviderProps) {\n const readScope = JSON.stringify({ viewer, language, accessRevision });\n useLayoutEffect(() => { client.setReadScope(readScope); }, [client, readScope]);\n const changed = useRef(onChange);\n useEffect(() => {\n changed.current = onChange;\n });\n useEffect(() => client.subscribe((c, scope) => changed.current?.(c, scope)), [client]);\n const value = useMemo(\n () => ({ client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError, store: storeFor(client) }),\n [client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError],\n );\n return <ContentKitContext.Provider value={value}>{children}</ContentKitContext.Provider>;\n}\n"],"mappings":";;;;;AA6BA,SAAgB,EAAmB,EAAE,WAAQ,WAAQ,aAAU,mBAAgB,aAAU,SAAM,aAAU,aAAU,YAAS,eAAqC;CAC/J,IAAM,IAAY,KAAK,UAAU;EAAE;EAAQ;EAAU;CAAe,CAAC;CACrE,QAAsB;EAAE,EAAO,aAAa,CAAS;CAAG,GAAG,CAAC,GAAQ,CAAS,CAAC;CAC9E,IAAM,IAAU,EAAO,CAAQ;CAI/B,AAHA,QAAgB;EACd,EAAQ,UAAU;CACpB,CAAC,GACD,QAAgB,EAAO,WAAW,GAAG,MAAU,EAAQ,UAAU,GAAG,CAAK,CAAC,GAAG,CAAC,CAAM,CAAC;CACrF,IAAM,IAAQ,SACL;EAAE;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;EAAS,OAAO,EAAS,CAAM;CAAE,IACzH;EAAC;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;CAAO,CACzF;CACA,OAAO,kBAAC,EAAkB,UAAnB;EAAmC;EAAQ;CAAqC,CAAA;AACzF"}
|
package/dist/react/public.js
CHANGED
|
@@ -3,16 +3,16 @@ import { useOptionalContentKitClient as r } from "./context.js";
|
|
|
3
3
|
import { useMediaRead as i } from "./read.js";
|
|
4
4
|
import { useEffect as a, useMemo as o, useState as s } from "react";
|
|
5
5
|
//#region src/react/public.ts
|
|
6
|
-
var c = /* @__PURE__ */ new WeakMap();
|
|
7
|
-
function
|
|
6
|
+
var c = /* @__PURE__ */ new WeakMap(), l = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function u(e = {}) {
|
|
8
8
|
let t = r(e.client), [, n] = s(0), i = t ? c.get(t) : void 0;
|
|
9
9
|
return a(() => {
|
|
10
10
|
if (!t || c.has(t)) return;
|
|
11
|
-
let e = !0;
|
|
12
|
-
return t.media.presets().then((e) => c.set(t, { presets: e }), (e) => c.set(t, {
|
|
11
|
+
let e = !0, r = l.get(t);
|
|
12
|
+
return r || (r = t.media.presets().then((e) => void c.set(t, { presets: e }), (e) => void c.set(t, {
|
|
13
13
|
presets: [],
|
|
14
14
|
error: e
|
|
15
|
-
})).finally(() => e && n((e) => e + 1)), () => {
|
|
15
|
+
})).finally(() => l.delete(t)), l.set(t, r)), r.then(() => e && n((e) => e + 1)), () => {
|
|
16
16
|
e = !1;
|
|
17
17
|
};
|
|
18
18
|
}, [t]), {
|
|
@@ -21,18 +21,18 @@ function l(e = {}) {
|
|
|
21
21
|
error: i?.error
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
function
|
|
25
|
-
let d = r(
|
|
24
|
+
function d(a, s, c, l = {}) {
|
|
25
|
+
let d = r(l.client), { presets: f, loaded: p } = u({ client: d }), m = f.find((e) => e.kind === a && e.name === c), h = l.image !== void 0, g = m ? t(m.from) ? n(m.from) : m.from : void 0, _ = i({
|
|
26
26
|
kind: a,
|
|
27
27
|
id: s
|
|
28
28
|
}, {
|
|
29
29
|
prefix: g,
|
|
30
|
-
read: h || !p ? null :
|
|
30
|
+
read: h || !p ? null : l.read,
|
|
31
31
|
client: d
|
|
32
32
|
});
|
|
33
33
|
return {
|
|
34
34
|
...o(() => {
|
|
35
|
-
let t = h ?
|
|
35
|
+
let t = h ? l.image : _.read?.public?.find((e) => e.preset === c);
|
|
36
36
|
return t && t.renditions.length ? {
|
|
37
37
|
image: {
|
|
38
38
|
preset: c,
|
|
@@ -52,7 +52,7 @@ function u(a, s, c, u = {}) {
|
|
|
52
52
|
};
|
|
53
53
|
}, [
|
|
54
54
|
h,
|
|
55
|
-
|
|
55
|
+
l.image,
|
|
56
56
|
_.read,
|
|
57
57
|
c,
|
|
58
58
|
m,
|
|
@@ -65,6 +65,6 @@ function u(a, s, c, u = {}) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
//#endregion
|
|
68
|
-
export {
|
|
68
|
+
export { u as usePresets, d as usePublicImage };
|
|
69
69
|
|
|
70
70
|
//# sourceMappingURL=public.js.map
|
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 } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.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 }>();\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 client.media.presets().then(\n
|
|
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 } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.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; the kind's default image when it has none; null while unknown. */\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\"): the exact\n * renditions from a read's `public` (or the host's listing), else the kind's\n * default image. Reads refresh after commits through the same client.\n */\nexport function usePublicImage(kind: string, id: string, preset: string, o: PublicImageOptions = {}): UsePublicImage {\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({ kind, id }, { 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 return rule ? { image: defaultImage(rule, id), 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"],"mappings":";;;;;AAiBA,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;AA4BA,SAAgB,EAAe,GAAc,GAAY,GAAgB,IAAwB,CAAC,GAAmB;CACnH,IAAM,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;EAAE;EAAM;CAAG,GAAG;EAAE;EAAQ,MAAM,KAAU,CAAC,IAAS,OAAO,EAAE;EAAM;CAAO,CAAC;CAOhG,OAAO;EAAE,GANK,QAAkE;GAC9E,IAAM,IAAM,IAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,MAAM,MAAM,EAAE,WAAW,CAAM;GAG9E,OAFI,KAAO,EAAI,WAAW,SAAe;IAAE,OAAO;KAAE;KAAQ,YAAY,EAAI;KAAY,QAAS,YAAY,KAAO,EAAI,UAAW,GAAM;IAAO;IAAG,WAAW;GAAM,IAChK,CAAC,KAAU,CAAC,EAAE,OAAa;IAAE,OAAO;IAAM,WAAW;GAAM,IACxD,IAAO;IAAE,OAAO,EAAa,GAAM,CAAE;IAAG,WAAW;GAAK,IAAI;IAAE,OAAO;IAAM,WAAW;GAAM;EACrG,GAAG;GAAC;GAAQ,EAAE;GAAO,EAAE;GAAM;GAAQ;GAAM;EAAE,CACjC;EAAO;EAAM,SAAS,CAAC,KAAU,EAAE;EAAS,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;AAC3F"}
|
package/dist/react/read.js
CHANGED
|
@@ -1,140 +1,141 @@
|
|
|
1
|
-
import { useOptionalContentKitClient as e } from "./context.js";
|
|
2
|
-
import { chunkOffsets as
|
|
3
|
-
import { readKey as
|
|
4
|
-
import { useCallback as
|
|
1
|
+
import { useOptionalContentKitClient as e, useReadScope as t } from "./context.js";
|
|
2
|
+
import { chunkOffsets as n, mergeReads as ee, processing as r } from "../client/media/windows.js";
|
|
3
|
+
import { readKey as i, storeFor as a } from "./store.js";
|
|
4
|
+
import { useCallback as o, useEffect as s, useMemo as c, useRef as l, useState as u, useSyncExternalStore as d } from "react";
|
|
5
5
|
//#region src/react/read.ts
|
|
6
|
-
var
|
|
6
|
+
var te = 2500, f = [];
|
|
7
7
|
function p(p, m = {}) {
|
|
8
|
-
let { read: h, client:
|
|
9
|
-
prefix:
|
|
10
|
-
download:
|
|
11
|
-
editor:
|
|
12
|
-
}) + (
|
|
13
|
-
base:
|
|
8
|
+
let { read: h, client: g, prefix: _, offset: v, limit: y, download: b, editor: x, window: S, refresh: C = !0 } = m, w = m.poll ?? (x ? te : !1), T = e(g), E = t(), D = T ? a(T) : null, O = S?.chunk ?? 50, k = E + i(p, {
|
|
9
|
+
prefix: _,
|
|
10
|
+
download: b,
|
|
11
|
+
editor: x
|
|
12
|
+
}) + (S ? `#${O}` : `#${v ?? 0}:${y ?? 0}`), [A, j] = u({ base: k }), M = A.base === k ? A.n : void 0, N = S ? n(S.start, S.end, M ?? S.start + 1, O) : [], [P, F] = u({
|
|
13
|
+
base: k,
|
|
14
14
|
offsets: []
|
|
15
|
-
}),
|
|
16
|
-
|
|
17
|
-
base:
|
|
18
|
-
offsets: [.../* @__PURE__ */ new Set([...
|
|
15
|
+
}), I = P.base === k ? P.offsets : [];
|
|
16
|
+
S && (P.base !== k || N.some((e) => !I.includes(e))) && F({
|
|
17
|
+
base: k,
|
|
18
|
+
offsets: [.../* @__PURE__ */ new Set([...I, ...N])].sort((e, t) => e - t)
|
|
19
19
|
});
|
|
20
|
-
let
|
|
20
|
+
let L = S ? I.length ? I : N.length ? N : [0] : [v ?? 0], R = c(() => L.map((e) => ({
|
|
21
21
|
offset: e,
|
|
22
|
-
limit:
|
|
23
|
-
})), [
|
|
24
|
-
prefix:
|
|
25
|
-
download:
|
|
26
|
-
editor:
|
|
22
|
+
limit: S ? O : y
|
|
23
|
+
})), [L.join(","), S ? O : y]), z = c(() => R.map((e) => E + i(p, {
|
|
24
|
+
prefix: _,
|
|
25
|
+
download: b,
|
|
26
|
+
editor: x,
|
|
27
27
|
offset: e.offset,
|
|
28
28
|
limit: e.limit
|
|
29
29
|
})), [
|
|
30
|
-
|
|
30
|
+
E,
|
|
31
|
+
R,
|
|
31
32
|
p.kind,
|
|
32
33
|
p.id,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
]), [
|
|
37
|
-
if (!
|
|
38
|
-
let t =
|
|
34
|
+
_,
|
|
35
|
+
b,
|
|
36
|
+
x
|
|
37
|
+
]), [B, V] = u(null), [H, U] = u(null), W = B?.base === k && B.given === h ? B : null, G = !!D && (h === void 0 || !!W), K = o((e) => {
|
|
38
|
+
if (!D || !G) return () => {};
|
|
39
|
+
let t = z.map((t, n) => D.subscribe(t, {
|
|
39
40
|
kind: p.kind,
|
|
40
41
|
id: p.id
|
|
41
42
|
}, {
|
|
42
|
-
prefix:
|
|
43
|
-
download:
|
|
44
|
-
editor:
|
|
45
|
-
...
|
|
43
|
+
prefix: _,
|
|
44
|
+
download: b,
|
|
45
|
+
editor: x,
|
|
46
|
+
...R[n]
|
|
46
47
|
}, e, {
|
|
47
|
-
poll:
|
|
48
|
-
refresh:
|
|
49
|
-
}));
|
|
48
|
+
poll: w,
|
|
49
|
+
refresh: C
|
|
50
|
+
}, E));
|
|
50
51
|
return () => t.forEach((e) => e());
|
|
51
52
|
}, [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
]),
|
|
58
|
-
if (!
|
|
59
|
-
let e =
|
|
60
|
-
return t.length === e.length && t.every((t, n) => t === e[n]) ? t :
|
|
53
|
+
D,
|
|
54
|
+
G,
|
|
55
|
+
z,
|
|
56
|
+
w,
|
|
57
|
+
C
|
|
58
|
+
]), q = l(f), J = o(() => {
|
|
59
|
+
if (!D || !G) return f;
|
|
60
|
+
let e = z.map((e) => D.snapshot(e)), t = q.current;
|
|
61
|
+
return t.length === e.length && t.every((t, n) => t === e[n]) ? t : q.current = e;
|
|
61
62
|
}, [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
]),
|
|
66
|
-
|
|
67
|
-
if (
|
|
63
|
+
D,
|
|
64
|
+
G,
|
|
65
|
+
z
|
|
66
|
+
]), Y = d(K, J, J);
|
|
67
|
+
s(() => {
|
|
68
|
+
if (D && G) for (let e of z) D.load(e, W ? "reload" : "initial");
|
|
68
69
|
}, [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
D,
|
|
71
|
+
G,
|
|
72
|
+
z,
|
|
73
|
+
W
|
|
73
74
|
]);
|
|
74
|
-
let
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
base:
|
|
78
|
-
n:
|
|
75
|
+
let X = c(() => ee(Y.map((e) => e.read)), [Y]);
|
|
76
|
+
s(() => {
|
|
77
|
+
S && X && X.total !== M && j({
|
|
78
|
+
base: k,
|
|
79
|
+
n: X.total
|
|
79
80
|
});
|
|
80
81
|
}, [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
S,
|
|
83
|
+
X,
|
|
84
|
+
M,
|
|
85
|
+
k
|
|
85
86
|
]);
|
|
86
|
-
let
|
|
87
|
-
if (h !== void 0)
|
|
88
|
-
base:
|
|
87
|
+
let ne = o(() => {
|
|
88
|
+
if (h !== void 0) V({
|
|
89
|
+
base: k,
|
|
89
90
|
given: h
|
|
90
91
|
});
|
|
91
|
-
else if (
|
|
92
|
-
|
|
92
|
+
else if (D) for (let e of z) D.load(e, "reload");
|
|
93
|
+
U(null);
|
|
93
94
|
}, [
|
|
94
|
-
T,
|
|
95
|
-
I,
|
|
96
95
|
D,
|
|
96
|
+
z,
|
|
97
|
+
k,
|
|
97
98
|
h
|
|
98
|
-
]),
|
|
99
|
-
if (h !== void 0 && !
|
|
100
|
-
base:
|
|
99
|
+
]), re = o(() => {
|
|
100
|
+
if (h !== void 0 && !W) V({
|
|
101
|
+
base: k,
|
|
101
102
|
given: h
|
|
102
103
|
});
|
|
103
|
-
else if (
|
|
104
|
+
else if (D) for (let e of z) D.load(e, "refresh");
|
|
104
105
|
}, [
|
|
105
|
-
T,
|
|
106
|
-
I,
|
|
107
106
|
D,
|
|
107
|
+
z,
|
|
108
|
+
k,
|
|
108
109
|
h,
|
|
109
|
-
|
|
110
|
-
]),
|
|
111
|
-
if (
|
|
112
|
-
else
|
|
113
|
-
base:
|
|
110
|
+
W
|
|
111
|
+
]), ie = o((e) => {
|
|
112
|
+
if (D && G) for (let t of z) D.set(t, e);
|
|
113
|
+
else U({
|
|
114
|
+
base: k,
|
|
114
115
|
given: h,
|
|
115
116
|
read: e
|
|
116
117
|
});
|
|
117
118
|
}, [
|
|
118
|
-
T,
|
|
119
|
-
H,
|
|
120
|
-
I,
|
|
121
119
|
D,
|
|
120
|
+
G,
|
|
121
|
+
z,
|
|
122
|
+
k,
|
|
122
123
|
h
|
|
123
124
|
]), Z, Q = !1, $;
|
|
124
|
-
if (
|
|
125
|
-
else if (!
|
|
125
|
+
if (H && H.base === k && H.given === h) Z = H.read;
|
|
126
|
+
else if (!Y.length) Z = h ?? null;
|
|
126
127
|
else {
|
|
127
|
-
let e =
|
|
128
|
-
Z =
|
|
128
|
+
let e = Y.every((e) => e.loaded);
|
|
129
|
+
Z = X ?? (e ? null : h ?? null), Q = Y.some((e) => e.loading || !e.loaded), $ = Y.find((e) => e.error)?.error;
|
|
129
130
|
}
|
|
130
131
|
return {
|
|
131
132
|
read: Z,
|
|
132
133
|
loading: Q,
|
|
133
134
|
error: $,
|
|
134
|
-
processing:
|
|
135
|
-
reload:
|
|
136
|
-
refresh:
|
|
137
|
-
set:
|
|
135
|
+
processing: r(Z),
|
|
136
|
+
reload: ne,
|
|
137
|
+
refresh: re,
|
|
138
|
+
set: ie
|
|
138
139
|
};
|
|
139
140
|
}
|
|
140
141
|
//#endregion
|
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 } 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 store = client ? storeFor(client) : null;\n const chunk = window?.chunk ?? READ_CHUNK;\n const base = 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) => readKey(ref, { prefix, download, editor, offset: w.offset, limit: w.limit })), [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 }));\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 { 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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"}
|
|
@@ -20,6 +20,7 @@ export type Fetcher<T> = (signal: AbortSignal) => Promise<T>;
|
|
|
20
20
|
/** What a slot holds, for matching changes: its resource type and the ids it depends on. */
|
|
21
21
|
export interface Tag {
|
|
22
22
|
type: string;
|
|
23
|
+
readScope?: string;
|
|
23
24
|
ref?: RefBody;
|
|
24
25
|
id?: string;
|
|
25
26
|
scope?: string;
|
|
@@ -34,6 +35,8 @@ interface Slot {
|
|
|
34
35
|
moreCtl?: AbortController;
|
|
35
36
|
/** Bumped by mark(): an optimistic write rolls back only if no newer one started. */
|
|
36
37
|
version: number;
|
|
38
|
+
/** Identifies the full read; item patches and appended pages retain it. */
|
|
39
|
+
readVersion: number;
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* The content reads hooks share, per client: one request per key while any
|
|
@@ -42,6 +45,7 @@ interface Slot {
|
|
|
42
45
|
*/
|
|
43
46
|
export declare class ResourceStore {
|
|
44
47
|
private readonly slots;
|
|
48
|
+
private version;
|
|
45
49
|
constructor(client: ContentKitClient);
|
|
46
50
|
subscribe(key: string, tag: Tag, listener: () => void): () => void;
|
|
47
51
|
snapshot<T>(key: string): Resource<T>;
|
|
@@ -57,12 +61,11 @@ export declare class ResourceStore {
|
|
|
57
61
|
mark(key: string): number;
|
|
58
62
|
/** Restores data unless a newer optimistic write started since mark(). */
|
|
59
63
|
rollback<T>(key: string, token: number, data: T): void;
|
|
60
|
-
/** Applies fn to
|
|
61
|
-
patch<T>(match: (tag: Tag) => boolean, fn: (data: T) => T): void;
|
|
64
|
+
/** Applies fn to matching slots, with the read identity for scoped rollback. */
|
|
65
|
+
patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void;
|
|
62
66
|
/** Refetches every shown slot whose tag matches. */
|
|
63
67
|
invalidate(match: (tag: Tag) => boolean): void;
|
|
64
68
|
private apply;
|
|
65
|
-
private postTotals;
|
|
66
69
|
private update;
|
|
67
70
|
}
|
|
68
71
|
/** The client's shared content store. */
|