@openrails/contentkit-ui 0.71.0 → 0.73.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 +122 -10
- package/dist/client/content/bans.d.ts +2 -0
- package/dist/client/content/bans.js +24 -12
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.d.ts +2 -0
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/polls.d.ts +9 -2
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/generated/routes.d.ts +28 -2
- package/dist/client/generated/routes.js +40 -0
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +4 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +3 -3
- package/dist/client/media/queue.d.ts +1 -1
- package/dist/client/media/queue.js +1 -1
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client/taxonomy.d.ts +5 -1
- package/dist/client/taxonomy.js +7 -0
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/client.js +2 -2
- package/dist/components/admin/media-item-panel.d.ts +27 -0
- package/dist/components/admin/media-item-panel.js +397 -0
- package/dist/components/admin/media-item-panel.js.map +1 -0
- package/dist/components/admin/poll-manager.d.ts +28 -0
- package/dist/components/admin/poll-manager.js +297 -0
- package/dist/components/admin/poll-manager.js.map +1 -0
- package/dist/components/admin/post-manager.d.ts +45 -0
- package/dist/components/admin/post-manager.js +602 -0
- package/dist/components/admin/post-manager.js.map +1 -0
- package/dist/components/admin/shell.d.ts +73 -0
- package/dist/components/admin/shell.js +148 -0
- package/dist/components/admin/shell.js.map +1 -0
- package/dist/components/admin/taxonomy-manager.d.ts +54 -0
- package/dist/components/admin/taxonomy-manager.js +976 -0
- package/dist/components/admin/taxonomy-manager.js.map +1 -0
- package/dist/components/admin/upload-queue.d.ts +23 -0
- package/dist/components/admin/upload-queue.js +365 -0
- package/dist/components/admin/upload-queue.js.map +1 -0
- package/dist/components/admin/user-comment-standing.d.ts +25 -0
- package/dist/components/admin/user-comment-standing.js +229 -0
- package/dist/components/admin/user-comment-standing.js.map +1 -0
- package/dist/components/social/bans.d.ts +5 -2
- package/dist/components/social/bans.js +132 -134
- package/dist/components/social/bans.js.map +1 -1
- package/dist/components/social/index.d.ts +1 -1
- package/dist/components/social/moderation.d.ts +11 -13
- package/dist/components/social/moderation.js +299 -218
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/social/parts.d.ts +3 -2
- package/dist/components/social/parts.js +74 -48
- package/dist/components/social/parts.js.map +1 -1
- package/dist/components/social/poll-editor.js +1 -0
- package/dist/components/social/poll-editor.js.map +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/rich-text-editor.d.ts +13 -0
- package/dist/editor/rich-text-editor.js +228 -0
- package/dist/editor/rich-text-editor.js.map +1 -0
- package/dist/editor.js +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +15 -8
- package/dist/locales/admin/de.d.ts +2 -0
- package/dist/locales/admin/de.js +227 -0
- package/dist/locales/admin/de.js.map +1 -0
- package/dist/locales/admin/en.d.ts +222 -0
- package/dist/locales/admin/en.js +227 -0
- package/dist/locales/admin/en.js.map +1 -0
- package/dist/locales/admin/es.d.ts +2 -0
- package/dist/locales/admin/es.js +227 -0
- package/dist/locales/admin/es.js.map +1 -0
- package/dist/locales/admin/ja.d.ts +2 -0
- package/dist/locales/admin/ja.js +227 -0
- package/dist/locales/admin/ja.js.map +1 -0
- package/dist/locales/admin/ko.d.ts +2 -0
- package/dist/locales/admin/ko.js +227 -0
- package/dist/locales/admin/ko.js.map +1 -0
- package/dist/locales/admin/zh.d.ts +2 -0
- package/dist/locales/admin/zh.js +227 -0
- package/dist/locales/admin/zh.js.map +1 -0
- package/dist/locales/de.js +5 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +227 -0
- package/dist/locales/en.js +5 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +5 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +5 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +5 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +8 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +7 -0
- package/dist/locales/social/en.js +8 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +8 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +8 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +8 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +8 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +5 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +23 -19
- package/dist/provider.js.map +1 -1
- package/dist/react/admin.d.ts +55 -0
- package/dist/react/admin.js +97 -0
- package/dist/react/admin.js.map +1 -0
- package/dist/react/comments.d.ts +18 -2
- package/dist/react/comments.js +24 -8
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -1
- package/dist/react/context.js +6 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/folder.js.map +1 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/polls.js +88 -87
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +123 -122
- package/dist/react/posts.js.map +1 -1
- package/dist/react/resources.js +3 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/upload.d.ts +10 -10
- package/dist/react/upload.js +1 -1
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +16 -0
- package/dist/react/use-resource.js +20 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +21 -20
- package/dist/styles.css +1 -1
- package/package.json +37 -2
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 { IMAGE_SCHEME, imageRef, imageRefs } from \"../client/content/posts.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 (root:posts:write). */\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 image references (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (root:posts:write). */\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, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`post.images`), else, while it has none (an unpublished post's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: 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 every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body still references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\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 (root:posts:write) 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, published ones from post.images,\n // the rest (an unpublished post's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(post?.images ?? {})), [post?.images]);\n const pending = useMemo(() => {\n const names = refNames(post?.body);\n if (post?.cover && !post.cover_url && !names.includes(post.cover)) names.push(post.cover);\n return names.filter((n) => !previews.has(n));\n }, [post?.body, post?.cover, post?.cover_url, previews]);\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: pending.length ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => !!read.read?.files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = !!read.read && pending.some((n) => !views.has(n) && !failed(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 // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageRefs(), (ref, n: string) => previews.get(n) ?? views.get(n) ?? ref), [post?.body, previews, views]);\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\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 cover = await client.posts.setCover(need(), up.name);\n const url = preview(up.name, file) ?? cover;\n if (url) shown.current.set(url, up.name);\n return url;\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 const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\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 coverSrc: imageSrc(post?.cover),\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 { IMAGE_SCHEME, imageRef, imageRefs } from \"../client/content/posts.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.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 (root:posts:write). */\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 image references (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (root:posts:write). */\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, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`post.images`), else, while it has none (an unpublished post's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: 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 every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body still references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\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 (root:posts:write) 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 guarded = useGuarded();\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n }, [guarded]);\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, published ones from post.images,\n // the rest (an unpublished post's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(post?.images ?? {})), [post?.images]);\n const pending = useMemo(() => {\n const names = refNames(post?.body);\n if (post?.cover && !post.cover_url && !names.includes(post.cover)) names.push(post.cover);\n return names.filter((n) => !previews.has(n));\n }, [post?.body, post?.cover, post?.cover_url, previews]);\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: pending.length ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => !!read.read?.files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = !!read.read && pending.some((n) => !views.has(n) && !failed(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 // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageRefs(), (ref, n: string) => previews.get(n) ?? views.get(n) ?? ref), [post?.body, previews, views]);\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\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 cover = await client.posts.setCover(need(), up.name);\n const url = preview(up.name, file) ?? cover;\n if (url) shown.current.set(url, up.name);\n return url;\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 const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\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 coverSrc: imageSrc(post?.cover),\n editorBody,\n storedBody,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/resources.js
CHANGED
|
@@ -226,6 +226,9 @@ var t = {
|
|
|
226
226
|
case "ban.lifted":
|
|
227
227
|
this.invalidate((t) => t.type === "bans" && t.scope === e.scope || t.type === "standing");
|
|
228
228
|
break;
|
|
229
|
+
case "taxonomy.changed":
|
|
230
|
+
this.invalidate((e) => e.type.startsWith("taxonomy"));
|
|
231
|
+
break;
|
|
229
232
|
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 === "post" && t.id === e.id || t.type === "posts");
|
|
230
233
|
}
|
|
231
234
|
}
|
|
@@ -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, 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 \"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 \"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 // 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 type { Comment, 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 \"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 // 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/upload.d.ts
CHANGED
|
@@ -4,17 +4,17 @@ import { Rotation, Size } from '../client/crop.js';
|
|
|
4
4
|
import { ContentKitError } from '../client/errors.js';
|
|
5
5
|
import { Crop, Edit, EncodeProgress, FileInfo } from '../client/generated/wire.js';
|
|
6
6
|
import { Progress, PutOptions, UploadedFile, UploadOptions } from '../client/media/client.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ItemUploadQueue, QueueOptions, QueueSnapshot } from '../client/media/queue.js';
|
|
8
8
|
export interface UseUploadQueue extends QueueSnapshot {
|
|
9
|
-
queue:
|
|
10
|
-
add:
|
|
11
|
-
move:
|
|
12
|
-
update:
|
|
13
|
-
remove:
|
|
14
|
-
retry:
|
|
15
|
-
start:
|
|
16
|
-
pause:
|
|
17
|
-
commit:
|
|
9
|
+
queue: ItemUploadQueue;
|
|
10
|
+
add: ItemUploadQueue["add"];
|
|
11
|
+
move: ItemUploadQueue["move"];
|
|
12
|
+
update: ItemUploadQueue["update"];
|
|
13
|
+
remove: ItemUploadQueue["remove"];
|
|
14
|
+
retry: ItemUploadQueue["retry"];
|
|
15
|
+
start: ItemUploadQueue["start"];
|
|
16
|
+
pause: ItemUploadQueue["pause"];
|
|
17
|
+
commit: ItemUploadQueue["commit"];
|
|
18
18
|
}
|
|
19
19
|
export interface UploadQueueOptions extends QueueOptions {
|
|
20
20
|
/** Overrides the provider's client. */
|
package/dist/react/upload.js
CHANGED
|
@@ -2,7 +2,7 @@ import { toContentKitError as e } from "../client/errors.js";
|
|
|
2
2
|
import { ratio as t } from "../client/aspect.js";
|
|
3
3
|
import { centeredCrop as n, constrainCrop as r, editOf as i, rotation as a, sameEdit as o, toOriginal as s } from "../client/crop.js";
|
|
4
4
|
import { encodeRemaining as c } from "../client/media/encode.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ItemUploadQueue as l } from "../client/media/queue.js";
|
|
6
6
|
import { useContentKitClient as u } from "./context.js";
|
|
7
7
|
import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h, useSyncExternalStore as g } from "react";
|
|
8
8
|
//#region src/react/upload.ts
|
package/dist/react/upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","names":[],"sources":["../../src/react/upload.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editOf, rotation, sameEdit, toOriginal, type Rotation, type Size } from \"../client/crop.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Crop, Edit, EncodeProgress, FileInfo } from \"../client/generated/wire.js\";\nimport type { Progress, PutOptions, UploadedFile, UploadOptions } from \"../client/media/client.js\";\nimport { encodeRemaining } from \"../client/media/encode.js\";\nimport { UploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: UploadQueue;\n add: UploadQueue[\"add\"];\n move: UploadQueue[\"move\"];\n update: UploadQueue[\"update\"];\n remove: UploadQueue[\"remove\"];\n retry: UploadQueue[\"retry\"];\n start: UploadQueue[\"start\"];\n pause: UploadQueue[\"pause\"];\n commit: UploadQueue[\"commit\"];\n}\n\nexport interface UploadQueueOptions extends QueueOptions {\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\n/**\n * A file queue for one item: another ref gets a fresh queue and the previous\n * one pauses (the other options are read once per queue). Unmounting pauses\n * running uploads.\n */\nexport function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue {\n const client = useContentKitClient(own);\n const key = `${options.ref.kind}/${options.ref.id}`;\n const open = () => ({ key, queue: new UploadQueue(client.media, { ...options, autoStart: false }) });\n const [current, setCurrent] = useState(open);\n if (current.key !== key) setCurrent(open());\n const { queue } = current;\n const autoStart = options.autoStart ?? true;\n useEffect(() => {\n if (autoStart) queue.start();\n return () => queue.pause();\n }, [queue, autoStart]);\n const snap = useSyncExternalStore(queue.subscribe, queue.getSnapshot, queue.getSnapshot);\n return {\n ...snap,\n queue,\n add: queue.add.bind(queue),\n move: queue.move.bind(queue),\n update: queue.update.bind(queue),\n remove: queue.remove.bind(queue),\n retry: queue.retry.bind(queue),\n start: queue.start.bind(queue),\n pause: queue.pause.bind(queue),\n commit: queue.commit.bind(queue),\n };\n}\n\nexport interface UploadStatus {\n status: \"idle\" | \"uploading\" | \"done\" | \"error\";\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile & { file?: FileInfo };\n}\n\nexport interface UseUpload extends UploadStatus {\n /**\n * Uploads one file; with put it also commits it to its path (put's edit,\n * meta and index) and waits until it is processed (result.file).\n */\n upload: (file: File, opts: Omit<UploadOptions, \"signal\" | \"onProgress\"> & { put?: PutOptions }) => Promise<UploadedFile & { file?: FileInfo }>;\n cancel: () => void;\n}\n\n/** One upload at a time (a cover, an inline image); a new upload cancels the previous. */\nexport function useUpload({ client: own }: { client?: ContentKitClient } = {}): UseUpload {\n const { media } = useContentKitClient(own);\n const [s, set] = useState<UploadStatus>({ status: \"idle\" });\n const ctl = useRef<AbortController | null>(null);\n useEffect(() => () => ctl.current?.abort(), []);\n\n const upload = useCallback<UseUpload[\"upload\"]>(\n async (file, { put, ...opts }) => {\n ctl.current?.abort();\n const c = (ctl.current = new AbortController());\n set({ status: \"uploading\" });\n const o: UploadOptions = {\n ...opts,\n signal: c.signal,\n onProgress: (progress) => c === ctl.current && set({ status: \"uploading\", progress }),\n };\n try {\n let result: UploadedFile & { file?: FileInfo };\n if (put) {\n let up: UploadedFile | undefined;\n const f = await media.put(file, { ...o, ...put, onUploaded: (u) => (up = u) });\n result = { ...up!, file: f };\n } else result = await media.upload(file, o);\n if (c === ctl.current) set((prev) => ({ status: \"done\", progress: prev.progress, result }));\n return result;\n } catch (e) {\n const error = toContentKitError(e);\n if (c === ctl.current) set(error.code === \"aborted\" ? { status: \"idle\" } : { status: \"error\", error });\n throw error;\n }\n },\n [media],\n );\n const cancel = useCallback(() => ctl.current?.abort(), []);\n return { ...s, upload, cancel };\n}\n\nexport interface UseCropOptions {\n /** The upload's size (an editor read's w and h). */\n source: Size;\n /** The edited image's \"W:H\", e.g. the public preset's aspect; \"\" or omitted is free. */\n aspect?: AspectRatio;\n /** The current edit to start from. */\n initial?: Edit | null;\n}\n\nexport interface UseCrop {\n /** In original pixels, inside the source and at the aspect. */\n crop: Crop;\n rotate: Rotation;\n /** Ready for an edit or put op; null when nothing changes. Its identity changes only with its value. */\n edit: Edit | null;\n /** A rect in original pixels. */\n setCrop: (rect: Crop) => void;\n /** A rect on the image as displayed (after the rotation) at display size: a cropper's output. */\n setFromDisplay: (rect: Crop, display: Size) => void;\n /** Turns clockwise by deg (a multiple of 90); the crop keeps the aspect. */\n rotateBy: (deg: number) => void;\n reset: () => void;\n}\n\n/** Headless crop state for any cropper UI: a rect in original pixels, a rotation and the resulting edit. */\nexport function useCrop({ source: given, aspect: shape, initial }: UseCropOptions): UseCrop {\n const aspect = ratio(shape);\n const { width, height } = given;\n const source = useMemo(() => ({ width, height }), [width, height]);\n const start = (): [Crop, Rotation] => {\n const rotate = rotation(initial?.rotate ?? 0);\n const c = initial?.crop;\n return [c ? constrainCrop(c, source, aspect, rotate) : centeredCrop(source, aspect, rotate), rotate];\n };\n const [state, setState] = useState(start);\n const [crop, rotate] = state;\n const set = useCallback(\n (next: [Crop, Rotation] | ((s: [Crop, Rotation]) => [Crop, Rotation])) =>\n setState((s) => {\n const n = typeof next === \"function\" ? next(s) : next;\n return sameEdit({ crop: n[0], rotate: n[1] }, { crop: s[0], rotate: s[1] }) ? s : n;\n }),\n [],\n );\n const setCrop = useCallback((r: Crop) => set(([, rot]) => [constrainCrop(r, source, aspect, rot), rot]), [set, source, aspect]);\n const setFromDisplay = useCallback(\n (r: Crop, display: Size) => set(([, rot]) => [constrainCrop(toOriginal(r, display, source, rot), source, aspect, rot), rot]),\n [set, source, aspect],\n );\n const rotateBy = useCallback(\n (deg: number) =>\n set(([c, rot]) => {\n const next = rotation(rot + deg);\n return [constrainCrop(c, source, aspect, next), next];\n }),\n [set, source, aspect],\n );\n const reset = useCallback(() => set([centeredCrop(source, aspect), 0]), [set, source, aspect]);\n const last = useRef<Edit | null>(null);\n const edit = useMemo(() => {\n const e = editOf(crop, source, rotate);\n return sameEdit(e, last.current) ? last.current : (last.current = e);\n }, [crop, source, rotate]);\n return { crop, rotate, edit, setCrop, setFromDisplay, rotateBy, reset };\n}\n\nexport interface UseEncodeProgress {\n progress?: EncodeProgress;\n /** Seconds left, counting down each second between reports; undefined while unknown. */\n remaining?: number;\n}\n\n/**\n * A pending video file's encode progress from the read API (`files[i].progress`),\n * with the ETA counted down locally between polls.\n */\nexport function useEncodeProgress(progress?: EncodeProgress | null): UseEncodeProgress {\n const [base, setBase] = useState(() => ({ at: progress?.at, received: Date.now() }));\n if (progress?.at !== base.at) setBase({ at: progress?.at, received: Date.now() });\n const [now, setNow] = useState(() => Date.now());\n const ticking = !!progress?.eta && !progress.stalled;\n useEffect(() => {\n if (!ticking) return;\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [ticking, base]);\n return { progress: progress ?? undefined, remaining: encodeRemaining(progress, base.received, Math.max(now, base.received)) };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"upload.js","names":[],"sources":["../../src/react/upload.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editOf, rotation, sameEdit, toOriginal, type Rotation, type Size } from \"../client/crop.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Crop, Edit, EncodeProgress, FileInfo } from \"../client/generated/wire.js\";\nimport type { Progress, PutOptions, UploadedFile, UploadOptions } from \"../client/media/client.js\";\nimport { encodeRemaining } from \"../client/media/encode.js\";\nimport { ItemUploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: ItemUploadQueue;\n add: ItemUploadQueue[\"add\"];\n move: ItemUploadQueue[\"move\"];\n update: ItemUploadQueue[\"update\"];\n remove: ItemUploadQueue[\"remove\"];\n retry: ItemUploadQueue[\"retry\"];\n start: ItemUploadQueue[\"start\"];\n pause: ItemUploadQueue[\"pause\"];\n commit: ItemUploadQueue[\"commit\"];\n}\n\nexport interface UploadQueueOptions extends QueueOptions {\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\n/**\n * A file queue for one item: another ref gets a fresh queue and the previous\n * one pauses (the other options are read once per queue). Unmounting pauses\n * running uploads.\n */\nexport function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue {\n const client = useContentKitClient(own);\n const key = `${options.ref.kind}/${options.ref.id}`;\n const open = () => ({ key, queue: new ItemUploadQueue(client.media, { ...options, autoStart: false }) });\n const [current, setCurrent] = useState(open);\n if (current.key !== key) setCurrent(open());\n const { queue } = current;\n const autoStart = options.autoStart ?? true;\n useEffect(() => {\n if (autoStart) queue.start();\n return () => queue.pause();\n }, [queue, autoStart]);\n const snap = useSyncExternalStore(queue.subscribe, queue.getSnapshot, queue.getSnapshot);\n return {\n ...snap,\n queue,\n add: queue.add.bind(queue),\n move: queue.move.bind(queue),\n update: queue.update.bind(queue),\n remove: queue.remove.bind(queue),\n retry: queue.retry.bind(queue),\n start: queue.start.bind(queue),\n pause: queue.pause.bind(queue),\n commit: queue.commit.bind(queue),\n };\n}\n\nexport interface UploadStatus {\n status: \"idle\" | \"uploading\" | \"done\" | \"error\";\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile & { file?: FileInfo };\n}\n\nexport interface UseUpload extends UploadStatus {\n /**\n * Uploads one file; with put it also commits it to its path (put's edit,\n * meta and index) and waits until it is processed (result.file).\n */\n upload: (file: File, opts: Omit<UploadOptions, \"signal\" | \"onProgress\"> & { put?: PutOptions }) => Promise<UploadedFile & { file?: FileInfo }>;\n cancel: () => void;\n}\n\n/** One upload at a time (a cover, an inline image); a new upload cancels the previous. */\nexport function useUpload({ client: own }: { client?: ContentKitClient } = {}): UseUpload {\n const { media } = useContentKitClient(own);\n const [s, set] = useState<UploadStatus>({ status: \"idle\" });\n const ctl = useRef<AbortController | null>(null);\n useEffect(() => () => ctl.current?.abort(), []);\n\n const upload = useCallback<UseUpload[\"upload\"]>(\n async (file, { put, ...opts }) => {\n ctl.current?.abort();\n const c = (ctl.current = new AbortController());\n set({ status: \"uploading\" });\n const o: UploadOptions = {\n ...opts,\n signal: c.signal,\n onProgress: (progress) => c === ctl.current && set({ status: \"uploading\", progress }),\n };\n try {\n let result: UploadedFile & { file?: FileInfo };\n if (put) {\n let up: UploadedFile | undefined;\n const f = await media.put(file, { ...o, ...put, onUploaded: (u) => (up = u) });\n result = { ...up!, file: f };\n } else result = await media.upload(file, o);\n if (c === ctl.current) set((prev) => ({ status: \"done\", progress: prev.progress, result }));\n return result;\n } catch (e) {\n const error = toContentKitError(e);\n if (c === ctl.current) set(error.code === \"aborted\" ? { status: \"idle\" } : { status: \"error\", error });\n throw error;\n }\n },\n [media],\n );\n const cancel = useCallback(() => ctl.current?.abort(), []);\n return { ...s, upload, cancel };\n}\n\nexport interface UseCropOptions {\n /** The upload's size (an editor read's w and h). */\n source: Size;\n /** The edited image's \"W:H\", e.g. the public preset's aspect; \"\" or omitted is free. */\n aspect?: AspectRatio;\n /** The current edit to start from. */\n initial?: Edit | null;\n}\n\nexport interface UseCrop {\n /** In original pixels, inside the source and at the aspect. */\n crop: Crop;\n rotate: Rotation;\n /** Ready for an edit or put op; null when nothing changes. Its identity changes only with its value. */\n edit: Edit | null;\n /** A rect in original pixels. */\n setCrop: (rect: Crop) => void;\n /** A rect on the image as displayed (after the rotation) at display size: a cropper's output. */\n setFromDisplay: (rect: Crop, display: Size) => void;\n /** Turns clockwise by deg (a multiple of 90); the crop keeps the aspect. */\n rotateBy: (deg: number) => void;\n reset: () => void;\n}\n\n/** Headless crop state for any cropper UI: a rect in original pixels, a rotation and the resulting edit. */\nexport function useCrop({ source: given, aspect: shape, initial }: UseCropOptions): UseCrop {\n const aspect = ratio(shape);\n const { width, height } = given;\n const source = useMemo(() => ({ width, height }), [width, height]);\n const start = (): [Crop, Rotation] => {\n const rotate = rotation(initial?.rotate ?? 0);\n const c = initial?.crop;\n return [c ? constrainCrop(c, source, aspect, rotate) : centeredCrop(source, aspect, rotate), rotate];\n };\n const [state, setState] = useState(start);\n const [crop, rotate] = state;\n const set = useCallback(\n (next: [Crop, Rotation] | ((s: [Crop, Rotation]) => [Crop, Rotation])) =>\n setState((s) => {\n const n = typeof next === \"function\" ? next(s) : next;\n return sameEdit({ crop: n[0], rotate: n[1] }, { crop: s[0], rotate: s[1] }) ? s : n;\n }),\n [],\n );\n const setCrop = useCallback((r: Crop) => set(([, rot]) => [constrainCrop(r, source, aspect, rot), rot]), [set, source, aspect]);\n const setFromDisplay = useCallback(\n (r: Crop, display: Size) => set(([, rot]) => [constrainCrop(toOriginal(r, display, source, rot), source, aspect, rot), rot]),\n [set, source, aspect],\n );\n const rotateBy = useCallback(\n (deg: number) =>\n set(([c, rot]) => {\n const next = rotation(rot + deg);\n return [constrainCrop(c, source, aspect, next), next];\n }),\n [set, source, aspect],\n );\n const reset = useCallback(() => set([centeredCrop(source, aspect), 0]), [set, source, aspect]);\n const last = useRef<Edit | null>(null);\n const edit = useMemo(() => {\n const e = editOf(crop, source, rotate);\n return sameEdit(e, last.current) ? last.current : (last.current = e);\n }, [crop, source, rotate]);\n return { crop, rotate, edit, setCrop, setFromDisplay, rotateBy, reset };\n}\n\nexport interface UseEncodeProgress {\n progress?: EncodeProgress;\n /** Seconds left, counting down each second between reports; undefined while unknown. */\n remaining?: number;\n}\n\n/**\n * A pending video file's encode progress from the read API (`files[i].progress`),\n * with the ETA counted down locally between polls.\n */\nexport function useEncodeProgress(progress?: EncodeProgress | null): UseEncodeProgress {\n const [base, setBase] = useState(() => ({ at: progress?.at, received: Date.now() }));\n if (progress?.at !== base.at) setBase({ at: progress?.at, received: Date.now() });\n const [now, setNow] = useState(() => Date.now());\n const ticking = !!progress?.eta && !progress.stalled;\n useEffect(() => {\n if (!ticking) return;\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [ticking, base]);\n return { progress: progress ?? undefined, remaining: encodeRemaining(progress, base.received, Math.max(now, base.received)) };\n}\n"],"mappings":"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"}
|
|
@@ -42,3 +42,19 @@ export declare function offsetPages<T>(limit: number, list: (q: {
|
|
|
42
42
|
}, signal: AbortSignal) => Promise<T[]>, underfill?: boolean): PageFetcher<T>;
|
|
43
43
|
/** A stable store key. */
|
|
44
44
|
export declare const keyOf: (...parts: unknown[]) => string;
|
|
45
|
+
/** One numbered page of a list. */
|
|
46
|
+
export interface UsePaged<T> {
|
|
47
|
+
items: T[];
|
|
48
|
+
loading: boolean;
|
|
49
|
+
error?: ContentKitError;
|
|
50
|
+
/** Another page follows. */
|
|
51
|
+
hasNext: boolean;
|
|
52
|
+
reload: () => void;
|
|
53
|
+
}
|
|
54
|
+
/** Page `page` (from 1) of a limit/offset list: one read of limit + 1 tells whether another follows. */
|
|
55
|
+
export declare function usePaged<T extends {
|
|
56
|
+
id: string;
|
|
57
|
+
}>(store: ResourceStore, key: string | null, tag: Tag, page: number, limit: number, list: (q: {
|
|
58
|
+
limit: number;
|
|
59
|
+
offset: number;
|
|
60
|
+
}, signal: AbortSignal) => Promise<T[]>): UsePaged<T>;
|
|
@@ -68,7 +68,26 @@ function m(e, t, n = !1) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
var h = (...e) => JSON.stringify(e);
|
|
71
|
+
function g(e, t, n, r, i, a) {
|
|
72
|
+
let o = (Math.max(1, r) - 1) * i, s = f(e, t === null ? null : `${t}#${o}`, n, async (e) => {
|
|
73
|
+
let t = await a({
|
|
74
|
+
limit: i + 1,
|
|
75
|
+
offset: o
|
|
76
|
+
}, e);
|
|
77
|
+
return {
|
|
78
|
+
items: t.slice(0, i),
|
|
79
|
+
next: t.length > i ? o + i : null
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
items: s.data?.items ?? [],
|
|
84
|
+
loading: s.loading || !!t && !s.loaded,
|
|
85
|
+
error: s.error,
|
|
86
|
+
hasNext: s.data ? s.data.next !== null : !1,
|
|
87
|
+
reload: s.reload
|
|
88
|
+
};
|
|
89
|
+
}
|
|
71
90
|
//#endregion
|
|
72
|
-
export { h as keyOf, m as offsetPages, l as useContentScope, p as useList, f as useResource };
|
|
91
|
+
export { h as keyOf, m as offsetPages, l as useContentScope, p as useList, g as usePaged, f as useResource };
|
|
73
92
|
|
|
74
93
|
//# sourceMappingURL=use-resource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-resource.js","names":[],"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { ContentKitContext, useContentKitClient, useReadScope } from \"./context.js\";\nimport { resourcesFor, type Fetcher, type Page, type Resource, type ResourceStore, type Tag } from \"./resources.js\";\n\n/** The client, its shared store, and the provider's viewer and sign-in. */\nexport function useContentScope(own?: ContentKitClient | null) {\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n return { client, store: resourcesFor(client), scope: useReadScope(), viewer: ctx?.viewer, onSignIn: ctx?.onSignIn };\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\nconst none = () => () => {};\n\n/** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */\nexport function useResource<T>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n fetcher: Fetcher<T>,\n o: { initial?: T; more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>> } = {},\n): Resource<T> & { reload: () => void } {\n const scope = useReadScope();\n const fetch = useRef(fetcher);\n const more = useRef(o.more);\n const initial = useRef(o.initial);\n useEffect(() => {\n fetch.current = fetcher;\n more.current = o.more;\n initial.current = o.initial;\n });\n const subscribe = useCallback(\n (l: () => void) => (key ? store.subscribe(key, { ...tag, readScope: scope }, l) : none()),\n // The tag is part of the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, key, scope],\n );\n const snapshot = useCallback(() => (key ? store.snapshot<T>(key) : IDLE), [store, key]);\n const entry = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (!key) return;\n store.bind(key, (s) => fetch.current(s), more.current && ((c, s) => more.current!(c, s)));\n if (initial.current !== undefined && !store.snapshot(key).loaded && !store.snapshot(key).loading) store.set(key, initial.current);\n else store.load(key);\n }, [store, key]);\n const reload = useCallback(() => key && store.load(key, true), [store, key]);\n if (!entry.loaded && o.initial !== undefined) return { ...entry, data: o.initial, reload };\n return { ...entry, reload };\n}\n\n/** A shared list, a page at a time. */\nexport interface UseList<T> {\n items: T[];\n /** The first page is loading (also on reload). */\n loading: boolean;\n loadingMore: boolean;\n error?: ContentKitError;\n hasMore: boolean;\n loadMore: () => Promise<void>;\n reload: () => void;\n}\n\nexport type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;\n\nexport function useList<T extends { id: string }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T> {\n const r = useResource<Page<T>>(store, key, tag, (s) => page(undefined, s), { more: (c, s) => page(c, s) });\n const loadMore = useCallback(() => (key ? store.loadMore(key) : Promise.resolve()), [store, key]);\n return {\n items: r.data?.items ?? [],\n loading: r.loading || (!!key && !r.loaded),\n loadingMore: !!r.loadingMore,\n error: r.error,\n hasMore: r.data ? r.data.next !== null : false,\n loadMore,\n reload: r.reload,\n };\n}\n\n/**\n * A limit/offset list as pages. A full page means more may follow; with\n * underfill (the latest feed drops items the caller may not see) only an\n * empty page ends it.\n */\nexport function offsetPages<T>(limit: number, list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>, underfill = false): PageFetcher<T> {\n return async (cursor, signal) => {\n const offset = typeof cursor === \"number\" ? cursor : 0;\n const items = await list({ limit, offset }, signal);\n const end = underfill ? items.length === 0 : items.length < limit;\n return { items, next: end ? null : offset + limit };\n };\n}\n\n/** A stable store key. */\nexport const keyOf = (...parts: unknown[]) => JSON.stringify(parts);\n"],"mappings":";;;;AAOA,SAAgB,EAAgB,GAA+B;CAC7D,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,EAAoB,CAAG;CACtC,OAAO;EAAE;EAAQ,OAAO,EAAa,CAAM;EAAG,OAAO,EAAa;EAAG,QAAQ,GAAK;EAAQ,UAAU,GAAK;CAAS;AACpH;AAEA,IAAM,IAAwB;CAAE,MAAM,KAAA;CAAW,SAAS;CAAO,QAAQ;AAAM,GACzE,gBAAmB,CAAC;AAG1B,SAAgB,EACd,GACA,GACA,GACA,GACA,IAAsG,CAAC,GACjE;CACtC,IAAM,IAAQ,EAAa,GACrB,IAAQ,EAAO,CAAO,GACtB,IAAO,EAAO,EAAE,IAAI,GACpB,IAAU,EAAO,EAAE,OAAO;CAChC,QAAgB;EAGd,AAFA,EAAM,UAAU,GAChB,EAAK,UAAU,EAAE,MACjB,EAAQ,UAAU,EAAE;CACtB,CAAC;CACD,IAAM,IAAY,GACf,MAAmB,IAAM,EAAM,UAAU,GAAK;EAAE,GAAG;EAAK,WAAW;CAAM,GAAG,CAAC,IAAI,EAAK,GAGvF;EAAC;EAAO;EAAK;CAAK,CACpB,GACM,IAAW,QAAmB,IAAM,EAAM,SAAY,CAAG,IAAI,GAAO,CAAC,GAAO,CAAG,CAAC,GAChF,IAAQ,EAAqB,GAAW,GAAU,CAAQ;CAChE,QAAgB;EACT,MACL,EAAM,KAAK,IAAM,MAAM,EAAM,QAAQ,CAAC,GAAG,EAAK,aAAa,GAAG,MAAM,EAAK,QAAS,GAAG,CAAC,EAAE,GACpF,EAAQ,YAAY,KAAA,KAAa,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAU,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAS,EAAM,IAAI,GAAK,EAAQ,OAAO,IAC3H,EAAM,KAAK,CAAG;CACrB,GAAG,CAAC,GAAO,CAAG,CAAC;CACf,IAAM,IAAS,QAAkB,KAAO,EAAM,KAAK,GAAK,EAAI,GAAG,CAAC,GAAO,CAAG,CAAC;CAE3E,OADI,CAAC,EAAM,UAAU,EAAE,YAAY,KAAA,IAAkB;EAAE,GAAG;EAAO,MAAM,EAAE;EAAS;CAAO,IAClF;EAAE,GAAG;EAAO;CAAO;AAC5B;AAgBA,SAAgB,EAAkC,GAAsB,GAAoB,GAAU,GAAkC;CACtI,IAAM,IAAI,EAAqB,GAAO,GAAK,IAAM,MAAM,EAAK,KAAA,GAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAK,GAAG,CAAC,EAAE,CAAC,GACnG,IAAW,QAAmB,IAAM,EAAM,SAAS,CAAG,IAAI,QAAQ,QAAQ,GAAI,CAAC,GAAO,CAAG,CAAC;CAChG,OAAO;EACL,OAAO,EAAE,MAAM,SAAS,CAAC;EACzB,SAAS,EAAE,WAAY,CAAC,CAAC,KAAO,CAAC,EAAE;EACnC,aAAa,CAAC,CAAC,EAAE;EACjB,OAAO,EAAE;EACT,SAAS,EAAE,OAAO,EAAE,KAAK,SAAS,OAAO;EACzC;EACA,QAAQ,EAAE;CACZ;AACF;AAOA,SAAgB,EAAe,GAAe,GAAmF,IAAY,IAAuB;CAClK,OAAO,OAAO,GAAQ,MAAW;EAC/B,IAAM,IAAS,OAAO,KAAW,WAAW,IAAS,GAC/C,IAAQ,MAAM,EAAK;GAAE;GAAO;EAAO,GAAG,CAAM;EAElD,OAAO;GAAE;GAAO,OADJ,IAAY,EAAM,WAAW,IAAI,EAAM,SAAS,KAChC,OAAO,IAAS;EAAM;CACpD;AACF;AAGA,IAAa,KAAS,GAAG,MAAqB,KAAK,UAAU,CAAK"}
|
|
1
|
+
{"version":3,"file":"use-resource.js","names":[],"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { ContentKitContext, useContentKitClient, useReadScope } from \"./context.js\";\nimport { resourcesFor, type Fetcher, type Page, type Resource, type ResourceStore, type Tag } from \"./resources.js\";\n\n/** The client, its shared store, and the provider's viewer and sign-in. */\nexport function useContentScope(own?: ContentKitClient | null) {\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n return { client, store: resourcesFor(client), scope: useReadScope(), viewer: ctx?.viewer, onSignIn: ctx?.onSignIn };\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\nconst none = () => () => {};\n\n/** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */\nexport function useResource<T>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n fetcher: Fetcher<T>,\n o: { initial?: T; more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>> } = {},\n): Resource<T> & { reload: () => void } {\n const scope = useReadScope();\n const fetch = useRef(fetcher);\n const more = useRef(o.more);\n const initial = useRef(o.initial);\n useEffect(() => {\n fetch.current = fetcher;\n more.current = o.more;\n initial.current = o.initial;\n });\n const subscribe = useCallback(\n (l: () => void) => (key ? store.subscribe(key, { ...tag, readScope: scope }, l) : none()),\n // The tag is part of the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, key, scope],\n );\n const snapshot = useCallback(() => (key ? store.snapshot<T>(key) : IDLE), [store, key]);\n const entry = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (!key) return;\n store.bind(key, (s) => fetch.current(s), more.current && ((c, s) => more.current!(c, s)));\n if (initial.current !== undefined && !store.snapshot(key).loaded && !store.snapshot(key).loading) store.set(key, initial.current);\n else store.load(key);\n }, [store, key]);\n const reload = useCallback(() => key && store.load(key, true), [store, key]);\n if (!entry.loaded && o.initial !== undefined) return { ...entry, data: o.initial, reload };\n return { ...entry, reload };\n}\n\n/** A shared list, a page at a time. */\nexport interface UseList<T> {\n items: T[];\n /** The first page is loading (also on reload). */\n loading: boolean;\n loadingMore: boolean;\n error?: ContentKitError;\n hasMore: boolean;\n loadMore: () => Promise<void>;\n reload: () => void;\n}\n\nexport type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;\n\nexport function useList<T extends { id: string }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T> {\n const r = useResource<Page<T>>(store, key, tag, (s) => page(undefined, s), { more: (c, s) => page(c, s) });\n const loadMore = useCallback(() => (key ? store.loadMore(key) : Promise.resolve()), [store, key]);\n return {\n items: r.data?.items ?? [],\n loading: r.loading || (!!key && !r.loaded),\n loadingMore: !!r.loadingMore,\n error: r.error,\n hasMore: r.data ? r.data.next !== null : false,\n loadMore,\n reload: r.reload,\n };\n}\n\n/**\n * A limit/offset list as pages. A full page means more may follow; with\n * underfill (the latest feed drops items the caller may not see) only an\n * empty page ends it.\n */\nexport function offsetPages<T>(limit: number, list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>, underfill = false): PageFetcher<T> {\n return async (cursor, signal) => {\n const offset = typeof cursor === \"number\" ? cursor : 0;\n const items = await list({ limit, offset }, signal);\n const end = underfill ? items.length === 0 : items.length < limit;\n return { items, next: end ? null : offset + limit };\n };\n}\n\n/** A stable store key. */\nexport const keyOf = (...parts: unknown[]) => JSON.stringify(parts);\n\n/** One numbered page of a list. */\nexport interface UsePaged<T> {\n items: T[];\n loading: boolean;\n error?: ContentKitError;\n /** Another page follows. */\n hasNext: boolean;\n reload: () => void;\n}\n\n/** Page `page` (from 1) of a limit/offset list: one read of limit + 1 tells whether another follows. */\nexport function usePaged<T extends { id: string }>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n page: number,\n limit: number,\n list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>,\n): UsePaged<T> {\n const offset = (Math.max(1, page) - 1) * limit;\n const r = useResource<Page<T>>(store, key === null ? null : `${key}#${offset}`, tag, async (signal) => {\n const items = await list({ limit: limit + 1, offset }, signal);\n return { items: items.slice(0, limit), next: items.length > limit ? offset + limit : null };\n });\n return { items: r.data?.items ?? [], loading: r.loading || (!!key && !r.loaded), error: r.error, hasNext: r.data ? r.data.next !== null : false, reload: r.reload };\n}\n"],"mappings":"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"}
|
package/dist/react.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { HOVER_DELAY as e, InlinePreviewContext as t, useInlinePreview as n, useReducedMotion as r } from "./react/inline-preview.js";
|
|
2
|
-
import {
|
|
3
|
-
import { useCrop as
|
|
4
|
-
import { useMediaRead as
|
|
5
|
-
import { usePresets as
|
|
6
|
-
import { editOutput as
|
|
7
|
-
import { round3 as
|
|
8
|
-
import { GALLERY_VIEW_KEY as
|
|
9
|
-
import { useEditorCrop as
|
|
10
|
-
import { nameIn as
|
|
11
|
-
import { useNearViewport as
|
|
12
|
-
import { ResourceStore as
|
|
13
|
-
import { useContentScope as
|
|
14
|
-
import { useAdminComments as
|
|
15
|
-
import { useContentConfig as
|
|
16
|
-
import { useFavorite as
|
|
17
|
-
import { usePoll as
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
2
|
+
import { GuardContext as i, useContentKit as a, useContentKitClient as o, useContentURLs as s, useErrorReporter as c, useGuarded as l, useOptionalContentKitClient as u } from "./react/context.js";
|
|
3
|
+
import { useCrop as d, useEncodeProgress as f, useUpload as p, useUploadQueue as m } from "./react/upload.js";
|
|
4
|
+
import { useMediaRead as h } from "./react/read.js";
|
|
5
|
+
import { usePresets as g, usePublicImage as _ } from "./react/public.js";
|
|
6
|
+
import { editOutput as v, useSlotCrop as y, useSlotImage as b } from "./react/slot.js";
|
|
7
|
+
import { round3 as x, useFrameStrip as S, useVideoFrame as C, useVideoImages as w, useVideoPoster as T } from "./react/video.js";
|
|
8
|
+
import { GALLERY_VIEW_KEY as E, PLAYER_QUALITY_KEY as D, useCarousel as O, useGalleryView as k, useHlsPlayer as A, useRefreshBeforeExpiry as j } from "./react/gallery.js";
|
|
9
|
+
import { useEditorCrop as M } from "./react/crop.js";
|
|
10
|
+
import { nameIn as N, thumbnailOf as P, useMediaFolder as F } from "./react/folder.js";
|
|
11
|
+
import { useNearViewport as I } from "./react/viewport.js";
|
|
12
|
+
import { ResourceStore as L, resourcesFor as R } from "./react/resources.js";
|
|
13
|
+
import { useContentScope as z, usePaged as B } from "./react/use-resource.js";
|
|
14
|
+
import { useAdminComments as V, useCanComment as H, useCommentBans as U, useCommentReplies as W, useComments as G, useGlobalCommentBan as K, useLatestComments as q, useModerationQueue as J, withReaction as Y } from "./react/comments.js";
|
|
15
|
+
import { useContentConfig as X } from "./react/config.js";
|
|
16
|
+
import { useFavorite as Z, useReaction as Q } from "./react/engagement.js";
|
|
17
|
+
import { usePoll as $, usePollEditor as ee, usePolls as te, withVote as ne } from "./react/polls.js";
|
|
18
|
+
import { useAdminLocation as re, useFlowClient as ie, useStaffAccess as ae } from "./react/admin.js";
|
|
19
|
+
import { isPublished as oe, usePost as se, usePosts as ce } from "./react/posts.js";
|
|
20
|
+
import { ContentKitProvider as le } from "./react/provider.js";
|
|
21
|
+
import { useCanonicalContent as ue } from "./react/canonical.js";
|
|
22
|
+
export { le as ContentKitProvider, E as GALLERY_VIEW_KEY, i as GuardContext, e as HOVER_DELAY, t as InlinePreviewContext, D as PLAYER_QUALITY_KEY, L as ResourceStore, v as editOutput, oe as isPublished, N as nameIn, R as resourcesFor, x as round3, P as thumbnailOf, V as useAdminComments, re as useAdminLocation, H as useCanComment, ue as useCanonicalContent, O as useCarousel, U as useCommentBans, W as useCommentReplies, G as useComments, X as useContentConfig, a as useContentKit, o as useContentKitClient, z as useContentScope, s as useContentURLs, d as useCrop, M as useEditorCrop, f as useEncodeProgress, c as useErrorReporter, Z as useFavorite, ie as useFlowClient, S as useFrameStrip, k as useGalleryView, K as useGlobalCommentBan, l as useGuarded, A as useHlsPlayer, n as useInlinePreview, q as useLatestComments, F as useMediaFolder, h as useMediaRead, J as useModerationQueue, I as useNearViewport, u as useOptionalContentKitClient, B as usePaged, $ as usePoll, ee as usePollEditor, te as usePolls, se as usePost, ce as usePosts, g as usePresets, _ as usePublicImage, Q as useReaction, r as useReducedMotion, j as useRefreshBeforeExpiry, y as useSlotCrop, b as useSlotImage, ae as useStaffAccess, p as useUpload, m as useUploadQueue, C as useVideoFrame, w as useVideoImages, T as useVideoPoster, Y as withReaction, ne as withVote };
|