@openrails/contentkit-ui 0.83.0 → 0.85.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/client/beacons.d.ts +8 -5
- package/dist/client/beacons.js +15 -7
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/content/moderation.d.ts +14 -2
- package/dist/client/content/moderation.js +34 -0
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/types.d.ts +13 -0
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/discovery.d.ts +6 -0
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +8 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +77 -12
- package/dist/client/generated/routes.js +106 -1
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +52 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/reader-meta.d.ts +27 -0
- package/dist/client/reader-meta.js +99 -0
- package/dist/client/reader-meta.js.map +1 -0
- package/dist/client/versions.d.ts +1 -1
- package/dist/client/versions.js +7 -2
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +2 -1
- package/dist/components/admin/reader-manifest.d.ts +18 -0
- package/dist/components/admin/reader-manifest.js +490 -0
- package/dist/components/admin/reader-manifest.js.map +1 -0
- package/dist/components/content/card.js +1 -1
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/editors.d.ts +2 -1
- package/dist/components/content/editors.js +291 -256
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/groups/banner.d.ts +1 -1
- package/dist/components/groups/banner.js +1 -1
- package/dist/components/groups/banner.js.map +1 -1
- package/dist/components/groups/editor.js +3 -3
- package/dist/components/groups/editor.js.map +1 -1
- package/dist/components/groups/parts.d.ts +2 -2
- package/dist/components/groups/parts.js +71 -72
- package/dist/components/groups/parts.js.map +1 -1
- package/dist/components/social/comments.js +59 -45
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/index.d.ts +1 -0
- package/dist/components/social/moderation.d.ts +5 -4
- package/dist/components/social/moderation.js +143 -130
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/social/parts.js +22 -14
- package/dist/components/social/parts.js.map +1 -1
- package/dist/components/social/rules.d.ts +23 -0
- package/dist/components/social/rules.js +261 -0
- package/dist/components/social/rules.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -24
- package/dist/locales/admin/de.js +78 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +78 -0
- package/dist/locales/admin/en.js +78 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +78 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +78 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +78 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +78 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/de.js +3 -1
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +1 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +1 -0
- package/dist/locales/discovery/en.js +1 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +1 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +1 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +1 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +1 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +84 -0
- package/dist/locales/en.js +3 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +3 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +3 -1
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +3 -1
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +3 -0
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +3 -0
- package/dist/locales/social/en.js +3 -0
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +3 -0
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +3 -0
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +3 -0
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +3 -0
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +3 -1
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.d.ts +1 -1
- package/dist/react/admin.js +2 -0
- package/dist/react/admin.js.map +1 -1
- package/dist/react/comments.d.ts +29 -1
- package/dist/react/comments.js +30 -2
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/discovery.js +1 -1
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/resources.js +6 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react.js +14 -14
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/locales/zh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zh.js","names":[],"sources":["../../src/locales/zh.ts"],"sourcesContent":["import type { ContentKitUiMessageBundle } from \"../i18n/messages.js\";\nimport { admin } from \"./admin/zh.js\";\nimport { social } from \"./social/zh.js\";\nimport { groups } from \"./groups/zh.js\";\nimport { discovery } from \"./discovery/zh.js\";\nimport { content } from \"./content/zh.js\";\n\nexport const zh: ContentKitUiMessageBundle = {\n ...social,\n ...admin,\n ...groups,\n ...discovery,\n ...content,\n common: {\n cancel: \"取消\",\n close: \"关闭\",\n save: \"保存\",\n saving: \"正在保存…\",\n retry: \"重试\",\n change: \"更换\",\n upload: \"上传\",\n editCrop: \"编辑裁剪\",\n remove: \"移除\",\n rename: \"重命名\",\n replace: \"替换\",\n },\n crop: {\n title: \"裁剪图片\",\n avatarTitle: \"裁剪头像\",\n coverTitle: \"裁剪封面\",\n description: \"拖动调整位置。滚动、双指缩放或使用滑块来缩放。\",\n area: \"裁剪区域。使用方向键移动图片,按 + 或 − 缩放,按 R 旋转。\",\n zoom: \"缩放\",\n zoomIn: \"放大\",\n zoomOut: \"缩小\",\n rotateLeft: \"向左旋转\",\n rotateRight: \"向右旋转\",\n reset: \"重置\",\n undersized: \"此裁剪宽 {width} 像素;在高分辨率屏幕上需要 {target} 像素或以上才清晰。\",\n tooSmall: \"此图片最宽 {width} 像素;至少需要 {min} 像素。请使用更大的图片。\",\n shape: \"比例\",\n original: \"原始比例\",\n preview: \"效果预览\",\n size: \"{width} × {height} 像素\",\n },\n sortable: {\n handle: \"调整 {name} 的顺序\",\n instructions: \"要调整顺序,请按空格键或回车键,用方向键移动,再按空格键或回车键放下。按 Esc 取消。\",\n pickedUp: \"已拿起 {name},位于第 {position} 位,共 {total} 项。\",\n over: \"{name} 将移到第 {position} 位,共 {total} 项。\",\n dropped: \"{name} 已放在第 {position} 位,共 {total} 项。\",\n droppedAway: \"{name} 已放下。\",\n canceled: \"已取消移动 {name}。\",\n },\n folder: {\n label: \"媒体\",\n drop: \"将文件拖放到此处,或点击选择\",\n add: \"添加文件\",\n commit: \"添加 {count} 个\",\n commitAgain: \"重新添加\",\n adding: \"正在添加…\",\n queued: \"等待中\",\n uploaded: \"已上传\",\n processing: \"处理中…\",\n processingPercent: \"处理中 {percent}%\",\n edited: \"已编辑\",\n failed: \"未处理\",\n failedVideo: \"无法播放\",\n crop: \"裁剪和旋转\",\n select: \"选择 {name}\",\n selected: \"已选择 {count} 个\",\n removeSelected: \"移除所选\",\n clearSelection: \"取消选择\",\n confirmRemove: \"要移除 {count} 个文件吗?此操作无法撤销。\",\n renameLabel: \"{name} 的新名称\",\n paused: \"上传已暂停\",\n resume: \"继续\",\n dismiss: \"关闭\",\n limitSize: \"{what}最大 {size}\",\n limitMax: \"最多 {max} 个\",\n limitAspect: \"视频比例为 {min} 至 {max}\",\n kinds: {\n image: \"图片\",\n video: \"视频\",\n audio: \"音频\",\n subtitle: \"字幕\",\n file: \"文件\",\n },\n },\n readiness: {\n processing: \"正在处理媒体\",\n processingHint: \"所有文件处理完成后将自动发布。\",\n failedOne: \"无法处理 {name}\",\n failedMany: \"无法处理 {count} 个文件:{names}\",\n failedHint: \"移除这些文件后将自动发布。\",\n },\n avatar: {\n label: \"头像\",\n empty: \"暂无头像\",\n hint: \"正方形图片,至少 {size} 像素。\",\n },\n cover: {\n label: \"封面图片\",\n empty: \"暂无封面\",\n hint: \"横向图片,至少 {width} × {height} 像素。\",\n drop: \"将图片拖放到此处\",\n },\n poster: {\n title: \"设置封面\",\n description: \"从视频中选取一帧,或上传一张图片。封面与视频比例相同。\",\n frame: \"视频画面\",\n upload: \"上传图片\",\n time: \"时间\",\n previousFrame: \"上一帧\",\n nextFrame: \"下一帧\",\n jumpTo: \"跳到 {time}\",\n useFrame: \"使用此帧\",\n crop: \"裁剪…\",\n cropTitle: \"裁剪封面\",\n auto: \"自动\",\n autoHint: \"在你选择之前,将使用自动选取的画面。\",\n drop: \"将图片拖到这里,或点击选择\",\n processing: \"视频仍在处理中。编码完成后即可选择封面。\",\n rendering: \"正在生成封面…\",\n },\n progress: {\n decoding: \"正在打开图片…\",\n hashing: \"正在准备…\",\n uploading: \"正在上传… {percent}%\",\n completing: \"正在完成上传…\",\n rendering: \"正在生成各尺寸…\",\n },\n encode: {\n processing: \"正在处理视频\",\n queued: \"等待处理\",\n queuedPosition: \"排队中 · 第 {position} 位\",\n stalled: \"处理已暂停,正在等待服务器…\",\n segments: \"片段 {done} / {total}\",\n higherQualities: \"更高画质\",\n remainingSeconds: \"剩余约 {seconds} 秒\",\n remainingMinutes: \"剩余约 {minutes} 分钟\",\n remainingHours: \"剩余约 {hours} 小时 {minutes} 分钟\",\n almostDone: \"即将完成\",\n estimating: \"正在估算…\",\n phase: {\n queued: \"排队中\",\n downloading: \"准备中\",\n probing: \"分析中\",\n encoding: \"编码中\",\n muxing: \"封装中\",\n uploading: \"保存中\",\n publishing: \"发布中\",\n images: \"正在生成封面\",\n },\n },\n gallery: {\n label: \"媒体\",\n view: \"布局\",\n carousel: \"轮播\",\n grid: \"网格\",\n counter: \"{current} / {total}\",\n slide: \"第 {current} 项,共 {total} 项\",\n previous: \"上一个\",\n next: \"下一个\",\n goTo: \"转到第 {index} 项\",\n open: \"打开第 {index} 项,共 {total} 项\",\n lightbox: \"媒体查看器\",\n lockedOne: \"还有 1 项已锁定\",\n lockedMany: \"还有 {count} 项已锁定\",\n processingImage: \"正在处理图片…\",\n image: \"图片 {index}\",\n video: \"视频 {index}\",\n audio: \"音频 {index}\",\n processingAudio: \"正在处理音频…\",\n download: \"下载\",\n failedAudio: \"无法处理此音频。\",\n failedImage: \"无法处理此图片。\",\n },\n player: {\n play: \"播放视频\",\n loading: \"正在加载视频…\",\n retry: \"重试\",\n code: \"代码 {code}\",\n quality: \"画质\",\n auto: \"自动\",\n autoCurrent: \"自动({quality})\",\n label: \"视频播放器\",\n resume: \"播放\",\n pause: \"暂停\",\n replay: \"重新播放\",\n seek: \"跳转\",\n time: \"{current} / {duration}\",\n seekBack: \"后退 {seconds} 秒\",\n seekForward: \"前进 {seconds} 秒\",\n seconds: \"{seconds} 秒\",\n mute: \"静音\",\n unmute: \"取消静音\",\n volume: \"音量\",\n settings: \"设置\",\n speed: \"播放速度\",\n normal: \"正常\",\n audio: \"音轨\",\n subtitles: \"字幕\",\n subtitlesOff: \"关闭\",\n forced: \"{label}(强制)\",\n track: \"轨道 {number}\",\n fullscreen: \"全屏\",\n exitFullscreen: \"退出全屏\",\n pip: \"画中画\",\n exitPip: \"退出画中画\",\n theater: \"影院模式\",\n miniPlayer: \"迷你播放器\",\n expand: \"展开\",\n close: \"关闭\",\n failed: \"无法处理此视频。\",\n errors: {\n network: \"无法连接到视频服务器。\",\n access: \"你已无权访问此视频。\",\n not_found: \"此视频不可用。\",\n rate_limited: \"请求过多,请稍后重试。\",\n unsupported: \"此浏览器无法播放此视频。\",\n stalled: \"播放已停滞。请检查网络连接后重试。\",\n unknown: \"目前无法播放此视频。\",\n },\n },\n errors: {\n generic: \"我们这边出了问题,请重试。\",\n network: \"连接已断开,请检查网络后重试。\",\n storage: \"上传中断,请重试。\",\n decode: \"无法将此文件作为图片打开。\",\n invalid_request: \"无法保存图片。\",\n unauthorized: \"会话已过期,请重新登录后上传。\",\n forbidden: \"你没有权限更改此图片。\",\n not_found: \"此项目已不存在。\",\n conflict: \"图片已在其他地方被更改,请刷新后重试。\",\n incomplete: \"上传未完成,请重试。\",\n not_uploaded: \"上传已过期,请重新选择图片。\",\n too_large: \"文件过大。\",\n quota_exceeded: \"已达到存储空间上限。\",\n type_not_allowed: \"此处不支持该文件类型。\",\n checksum_mismatch: \"上传过程中文件发生了变化,请重试。\",\n rate_limited: \"上传次数过多,请在 {seconds} 秒后重试。\",\n unavailable: \"媒体存储暂时不可用,请稍后重试。\",\n upgrade_required: \"此项目正在升级,请稍后重试。\",\n internal_error: \"我们这边出了问题,请重试。\",\n allowedTypes: \"请使用 {allowed}。\",\n tooLargeBy: \"此文件为 {size};上限为 {max}。\",\n image_too_small: \"裁剪后图片宽 {width} 像素;至少需要 {min} 像素。请选择更大的区域或使用更大的图片。\",\n image_too_large: \"此图片为 {width}×{height} 像素;图片最多 {megapixels} 百万像素。\",\n image_unreadable: \"此文件不是有效的 {format} 图片。\",\n render_timeout: \"服务器仍在处理,完成后会显示;请稍后再查看。\",\n animation_not_allowed: \"此处不允许动图。请上传静态图片。\",\n animation_unsupported: \"不支持动态 {format} 图片。请使用动态 WebP 或 GIF。\",\n animationFrames: \"动图最多 {maxFrames} 帧;此图有 {frames} 帧。\",\n animationSeconds: \"动图最长 {maxSeconds} 秒。\",\n animationTooLarge: \"此动图过大:{frames} 帧 {width}×{height} 像素。请减少帧数或缩小尺寸。\",\n too_many_files: \"此项目的文件数已达上限。请先删除一些。\",\n animation_too_long: \"此动图过长。\",\n video_too_long: \"此文件时长超出此处允许的范围。\",\n video_too_large: \"此视频的画面尺寸超出此处允许的范围。\",\n videoTooLongBy: \"此文件时长 {duration} 秒;此处上限为 {maxSeconds} 秒。\",\n videoTooLargeBy: \"此视频为 {width}×{height} 像素;视频最多 {megapixels} 百万像素。\",\n video_over_budget: \"处理此视频耗时过长。请使用更短或更小的视频。\",\n video_aspect_unsupported: \"此视频过宽或过高,无法在此使用。\",\n videoAspectBy: \"此视频的比例为 {shape};此处的视频比例须在 {minAspect} 至 {maxAspect} 之间。\",\n tooManyFilesBy: \"此项目最多可包含 {maxFiles} 个此类文件。请先移除一些。\",\n aborted: \"已取消。\",\n resume_mismatch: \"保存的上传属于另一个文件。请重新选择文件。\",\n moderation_rejected: \"已被审核拒绝。\",\n comment_banned: \"你无法在此评论。\",\n comments_closed: \"此处评论已关闭。\",\n limit_reached: \"已达到允许的上限。\",\n comment_too_long: \"评论太长了。\",\n commentTooLongBy: \"评论最多 {maxChars} 个字符。\",\n not_configured: \"此网站未提供该功能。\",\n tenant_mismatch: \"我们这边出了点问题,请重试。\",\n gone: \"此内容已不可用。\",\n },\n};\n"],"mappings":";;;;;;AAOA,IAAa,IAAgC;CAC3C,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,QAAQ;EACN,QAAQ;EACR,OAAO;EACP,MAAM;EACN,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,SAAS;CACX;CACA,MAAM;EACJ,OAAO;EACP,aAAa;EACb,YAAY;EACZ,aAAa;EACb,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS;EACT,YAAY;EACZ,aAAa;EACb,OAAO;EACP,YAAY;EACZ,UAAU;EACV,OAAO;EACP,UAAU;EACV,SAAS;EACT,MAAM;CACR;CACA,UAAU;EACR,QAAQ;EACR,cAAc;EACd,UAAU;EACV,MAAM;EACN,SAAS;EACT,aAAa;EACb,UAAU;CACZ;CACA,QAAQ;EACN,OAAO;EACP,MAAM;EACN,KAAK;EACL,QAAQ;EACR,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,UAAU;EACV,YAAY;EACZ,mBAAmB;EACnB,QAAQ;EACR,QAAQ;EACR,aAAa;EACb,MAAM;EACN,QAAQ;EACR,UAAU;EACV,gBAAgB;EAChB,gBAAgB;EAChB,eAAe;EACf,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,UAAU;EACV,aAAa;EACb,OAAO;GACL,OAAO;GACP,OAAO;GACP,OAAO;GACP,UAAU;GACV,MAAM;EACR;CACF;CACA,WAAW;EACT,YAAY;EACZ,gBAAgB;EAChB,WAAW;EACX,YAAY;EACZ,YAAY;CACd;CACA,QAAQ;EACN,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA,OAAO;EACL,OAAO;EACP,OAAO;EACP,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,OAAO;EACP,aAAa;EACb,OAAO;EACP,QAAQ;EACR,MAAM;EACN,eAAe;EACf,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;EACN,WAAW;EACX,MAAM;EACN,UAAU;EACV,MAAM;EACN,YAAY;EACZ,WAAW;CACb;CACA,UAAU;EACR,UAAU;EACV,SAAS;EACT,WAAW;EACX,YAAY;EACZ,WAAW;CACb;CACA,QAAQ;EACN,YAAY;EACZ,QAAQ;EACR,gBAAgB;EAChB,SAAS;EACT,UAAU;EACV,iBAAiB;EACjB,kBAAkB;EAClB,kBAAkB;EAClB,gBAAgB;EAChB,YAAY;EACZ,YAAY;EACZ,OAAO;GACL,QAAQ;GACR,aAAa;GACb,SAAS;GACT,UAAU;GACV,QAAQ;GACR,WAAW;GACX,YAAY;GACZ,QAAQ;EACV;CACF;CACA,SAAS;EACP,OAAO;EACP,MAAM;EACN,UAAU;EACV,MAAM;EACN,SAAS;EACT,OAAO;EACP,UAAU;EACV,MAAM;EACN,MAAM;EACN,MAAM;EACN,UAAU;EACV,WAAW;EACX,YAAY;EACZ,iBAAiB;EACjB,OAAO;EACP,OAAO;EACP,OAAO;EACP,iBAAiB;EACjB,UAAU;EACV,aAAa;EACb,aAAa;CACf;CACA,QAAQ;EACN,MAAM;EACN,SAAS;EACT,OAAO;EACP,MAAM;EACN,SAAS;EACT,MAAM;EACN,aAAa;EACb,OAAO;EACP,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,MAAM;EACN,MAAM;EACN,UAAU;EACV,aAAa;EACb,SAAS;EACT,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,UAAU;EACV,OAAO;EACP,QAAQ;EACR,OAAO;EACP,WAAW;EACX,cAAc;EACd,QAAQ;EACR,OAAO;EACP,YAAY;EACZ,gBAAgB;EAChB,KAAK;EACL,SAAS;EACT,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,QAAQ;GACN,SAAS;GACT,QAAQ;GACR,WAAW;GACX,cAAc;GACd,aAAa;GACb,SAAS;GACT,SAAS;EACX;CACF;CACA,QAAQ;EACN,SAAS;EACT,SAAS;EACT,SAAS;EACT,QAAQ;EACR,iBAAiB;EACjB,cAAc;EACd,WAAW;EACX,WAAW;EACX,UAAU;EACV,YAAY;EACZ,cAAc;EACd,WAAW;EACX,gBAAgB;EAChB,kBAAkB;EAClB,mBAAmB;EACnB,cAAc;EACd,aAAa;EACb,kBAAkB;EAClB,gBAAgB;EAChB,cAAc;EACd,YAAY;EACZ,iBAAiB;EACjB,iBAAiB;EACjB,kBAAkB;EAClB,gBAAgB;EAChB,uBAAuB;EACvB,uBAAuB;EACvB,iBAAiB;EACjB,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,oBAAoB;EACpB,gBAAgB;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,iBAAiB;EACjB,mBAAmB;EACnB,0BAA0B;EAC1B,eAAe;EACf,gBAAgB;EAChB,SAAS;EACT,iBAAiB;EACjB,qBAAqB;EACrB,gBAAgB;EAChB,iBAAiB;EACjB,eAAe;EACf,kBAAkB;EAClB,kBAAkB;EAClB,gBAAgB;EAChB,iBAAiB;EACjB,MAAM;CACR;AACF"}
|
|
1
|
+
{"version":3,"file":"zh.js","names":[],"sources":["../../src/locales/zh.ts"],"sourcesContent":["import type { ContentKitUiMessageBundle } from \"../i18n/messages.js\";\nimport { admin } from \"./admin/zh.js\";\nimport { social } from \"./social/zh.js\";\nimport { groups } from \"./groups/zh.js\";\nimport { discovery } from \"./discovery/zh.js\";\nimport { content } from \"./content/zh.js\";\n\nexport const zh: ContentKitUiMessageBundle = {\n ...social,\n ...admin,\n ...groups,\n ...discovery,\n ...content,\n common: {\n cancel: \"取消\",\n close: \"关闭\",\n save: \"保存\",\n saving: \"正在保存…\",\n retry: \"重试\",\n change: \"更换\",\n upload: \"上传\",\n editCrop: \"编辑裁剪\",\n remove: \"移除\",\n rename: \"重命名\",\n replace: \"替换\",\n },\n crop: {\n title: \"裁剪图片\",\n avatarTitle: \"裁剪头像\",\n coverTitle: \"裁剪封面\",\n description: \"拖动调整位置。滚动、双指缩放或使用滑块来缩放。\",\n area: \"裁剪区域。使用方向键移动图片,按 + 或 − 缩放,按 R 旋转。\",\n zoom: \"缩放\",\n zoomIn: \"放大\",\n zoomOut: \"缩小\",\n rotateLeft: \"向左旋转\",\n rotateRight: \"向右旋转\",\n reset: \"重置\",\n undersized: \"此裁剪宽 {width} 像素;在高分辨率屏幕上需要 {target} 像素或以上才清晰。\",\n tooSmall: \"此图片最宽 {width} 像素;至少需要 {min} 像素。请使用更大的图片。\",\n shape: \"比例\",\n original: \"原始比例\",\n preview: \"效果预览\",\n size: \"{width} × {height} 像素\",\n },\n sortable: {\n handle: \"调整 {name} 的顺序\",\n instructions: \"要调整顺序,请按空格键或回车键,用方向键移动,再按空格键或回车键放下。按 Esc 取消。\",\n pickedUp: \"已拿起 {name},位于第 {position} 位,共 {total} 项。\",\n over: \"{name} 将移到第 {position} 位,共 {total} 项。\",\n dropped: \"{name} 已放在第 {position} 位,共 {total} 项。\",\n droppedAway: \"{name} 已放下。\",\n canceled: \"已取消移动 {name}。\",\n },\n folder: {\n label: \"媒体\",\n drop: \"将文件拖放到此处,或点击选择\",\n add: \"添加文件\",\n commit: \"添加 {count} 个\",\n commitAgain: \"重新添加\",\n adding: \"正在添加…\",\n queued: \"等待中\",\n uploaded: \"已上传\",\n processing: \"处理中…\",\n processingPercent: \"处理中 {percent}%\",\n edited: \"已编辑\",\n failed: \"未处理\",\n failedVideo: \"无法播放\",\n crop: \"裁剪和旋转\",\n select: \"选择 {name}\",\n selected: \"已选择 {count} 个\",\n removeSelected: \"移除所选\",\n clearSelection: \"取消选择\",\n confirmRemove: \"要移除 {count} 个文件吗?此操作无法撤销。\",\n renameLabel: \"{name} 的新名称\",\n paused: \"上传已暂停\",\n resume: \"继续\",\n dismiss: \"关闭\",\n limitSize: \"{what}最大 {size}\",\n limitMax: \"最多 {max} 个\",\n limitAspect: \"视频比例为 {min} 至 {max}\",\n kinds: {\n image: \"图片\",\n video: \"视频\",\n audio: \"音频\",\n subtitle: \"字幕\",\n file: \"文件\",\n },\n },\n readiness: {\n processing: \"正在处理媒体\",\n processingHint: \"所有文件处理完成后将自动发布。\",\n failedOne: \"无法处理 {name}\",\n failedMany: \"无法处理 {count} 个文件:{names}\",\n failedHint: \"移除这些文件后将自动发布。\",\n },\n avatar: {\n label: \"头像\",\n empty: \"暂无头像\",\n hint: \"正方形图片,至少 {size} 像素。\",\n },\n cover: {\n label: \"封面图片\",\n empty: \"暂无封面\",\n hint: \"横向图片,至少 {width} × {height} 像素。\",\n drop: \"将图片拖放到此处\",\n },\n poster: {\n title: \"设置封面\",\n description: \"从视频中选取一帧,或上传一张图片。封面与视频比例相同。\",\n frame: \"视频画面\",\n upload: \"上传图片\",\n time: \"时间\",\n previousFrame: \"上一帧\",\n nextFrame: \"下一帧\",\n jumpTo: \"跳到 {time}\",\n useFrame: \"使用此帧\",\n crop: \"裁剪…\",\n cropTitle: \"裁剪封面\",\n auto: \"自动\",\n autoHint: \"在你选择之前,将使用自动选取的画面。\",\n drop: \"将图片拖到这里,或点击选择\",\n processing: \"视频仍在处理中。编码完成后即可选择封面。\",\n rendering: \"正在生成封面…\",\n },\n progress: {\n decoding: \"正在打开图片…\",\n hashing: \"正在准备…\",\n uploading: \"正在上传… {percent}%\",\n completing: \"正在完成上传…\",\n rendering: \"正在生成各尺寸…\",\n },\n encode: {\n processing: \"正在处理视频\",\n queued: \"等待处理\",\n queuedPosition: \"排队中 · 第 {position} 位\",\n stalled: \"处理已暂停,正在等待服务器…\",\n segments: \"片段 {done} / {total}\",\n higherQualities: \"更高画质\",\n remainingSeconds: \"剩余约 {seconds} 秒\",\n remainingMinutes: \"剩余约 {minutes} 分钟\",\n remainingHours: \"剩余约 {hours} 小时 {minutes} 分钟\",\n almostDone: \"即将完成\",\n estimating: \"正在估算…\",\n phase: {\n queued: \"排队中\",\n downloading: \"准备中\",\n probing: \"分析中\",\n encoding: \"编码中\",\n muxing: \"封装中\",\n uploading: \"保存中\",\n publishing: \"发布中\",\n images: \"正在生成封面\",\n },\n },\n gallery: {\n label: \"媒体\",\n view: \"布局\",\n carousel: \"轮播\",\n grid: \"网格\",\n counter: \"{current} / {total}\",\n slide: \"第 {current} 项,共 {total} 项\",\n previous: \"上一个\",\n next: \"下一个\",\n goTo: \"转到第 {index} 项\",\n open: \"打开第 {index} 项,共 {total} 项\",\n lightbox: \"媒体查看器\",\n lockedOne: \"还有 1 项已锁定\",\n lockedMany: \"还有 {count} 项已锁定\",\n processingImage: \"正在处理图片…\",\n image: \"图片 {index}\",\n video: \"视频 {index}\",\n audio: \"音频 {index}\",\n processingAudio: \"正在处理音频…\",\n download: \"下载\",\n failedAudio: \"无法处理此音频。\",\n failedImage: \"无法处理此图片。\",\n },\n player: {\n play: \"播放视频\",\n loading: \"正在加载视频…\",\n retry: \"重试\",\n code: \"代码 {code}\",\n quality: \"画质\",\n auto: \"自动\",\n autoCurrent: \"自动({quality})\",\n label: \"视频播放器\",\n resume: \"播放\",\n pause: \"暂停\",\n replay: \"重新播放\",\n seek: \"跳转\",\n time: \"{current} / {duration}\",\n seekBack: \"后退 {seconds} 秒\",\n seekForward: \"前进 {seconds} 秒\",\n seconds: \"{seconds} 秒\",\n mute: \"静音\",\n unmute: \"取消静音\",\n volume: \"音量\",\n settings: \"设置\",\n speed: \"播放速度\",\n normal: \"正常\",\n audio: \"音轨\",\n subtitles: \"字幕\",\n subtitlesOff: \"关闭\",\n forced: \"{label}(强制)\",\n track: \"轨道 {number}\",\n fullscreen: \"全屏\",\n exitFullscreen: \"退出全屏\",\n pip: \"画中画\",\n exitPip: \"退出画中画\",\n theater: \"影院模式\",\n miniPlayer: \"迷你播放器\",\n expand: \"展开\",\n close: \"关闭\",\n failed: \"无法处理此视频。\",\n errors: {\n network: \"无法连接到视频服务器。\",\n access: \"你已无权访问此视频。\",\n not_found: \"此视频不可用。\",\n rate_limited: \"请求过多,请稍后重试。\",\n unsupported: \"此浏览器无法播放此视频。\",\n stalled: \"播放已停滞。请检查网络连接后重试。\",\n unknown: \"目前无法播放此视频。\",\n },\n },\n errors: {\n generic: \"我们这边出了问题,请重试。\",\n network: \"连接已断开,请检查网络后重试。\",\n storage: \"上传中断,请重试。\",\n decode: \"无法将此文件作为图片打开。\",\n invalid_request: \"无法保存图片。\",\n unauthorized: \"会话已过期,请重新登录后上传。\",\n forbidden: \"你没有权限更改此图片。\",\n not_found: \"此项目已不存在。\",\n conflict: \"已在其他地方被更改,请刷新后重试。\",\n incomplete: \"上传未完成,请重试。\",\n not_uploaded: \"上传已过期,请重新选择图片。\",\n too_large: \"文件过大。\",\n quota_exceeded: \"已达到存储空间上限。\",\n type_not_allowed: \"此处不支持该文件类型。\",\n checksum_mismatch: \"上传过程中文件发生了变化,请重试。\",\n rate_limited: \"上传次数过多,请在 {seconds} 秒后重试。\",\n unavailable: \"媒体存储暂时不可用,请稍后重试。\",\n upgrade_required: \"此项目正在升级,请稍后重试。\",\n internal_error: \"我们这边出了问题,请重试。\",\n allowedTypes: \"请使用 {allowed}。\",\n tooLargeBy: \"此文件为 {size};上限为 {max}。\",\n image_too_small: \"裁剪后图片宽 {width} 像素;至少需要 {min} 像素。请选择更大的区域或使用更大的图片。\",\n image_too_large: \"此图片为 {width}×{height} 像素;图片最多 {megapixels} 百万像素。\",\n image_unreadable: \"此文件不是有效的 {format} 图片。\",\n render_timeout: \"服务器仍在处理,完成后会显示;请稍后再查看。\",\n animation_not_allowed: \"此处不允许动图。请上传静态图片。\",\n animation_unsupported: \"不支持动态 {format} 图片。请使用动态 WebP 或 GIF。\",\n animationFrames: \"动图最多 {maxFrames} 帧;此图有 {frames} 帧。\",\n animationSeconds: \"动图最长 {maxSeconds} 秒。\",\n animationTooLarge: \"此动图过大:{frames} 帧 {width}×{height} 像素。请减少帧数或缩小尺寸。\",\n too_many_files: \"此项目的文件数已达上限。请先删除一些。\",\n animation_too_long: \"此动图过长。\",\n video_too_long: \"此文件时长超出此处允许的范围。\",\n video_too_large: \"此视频的画面尺寸超出此处允许的范围。\",\n videoTooLongBy: \"此文件时长 {duration} 秒;此处上限为 {maxSeconds} 秒。\",\n videoTooLargeBy: \"此视频为 {width}×{height} 像素;视频最多 {megapixels} 百万像素。\",\n video_over_budget: \"处理此视频耗时过长。请使用更短或更小的视频。\",\n video_aspect_unsupported: \"此视频过宽或过高,无法在此使用。\",\n videoAspectBy: \"此视频的比例为 {shape};此处的视频比例须在 {minAspect} 至 {maxAspect} 之间。\",\n tooManyFilesBy: \"此项目最多可包含 {maxFiles} 个此类文件。请先移除一些。\",\n aborted: \"已取消。\",\n resume_mismatch: \"保存的上传属于另一个文件。请重新选择文件。\",\n moderation_rejected: \"已被审核拒绝。\",\n comment_banned: \"你无法在此评论。\",\n comments_closed: \"此处评论已关闭。\",\n comments_locked: \"解锁后即可查看和发表评论。\",\n not_joinable: \"目前无法加入此群组。\",\n limit_reached: \"已达到允许的上限。\",\n comment_too_long: \"评论太长了。\",\n commentTooLongBy: \"评论最多 {maxChars} 个字符。\",\n not_configured: \"此网站未提供该功能。\",\n tenant_mismatch: \"我们这边出了点问题,请重试。\",\n gone: \"此内容已不可用。\",\n },\n};\n"],"mappings":"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"}
|
package/dist/react/admin.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface AdminLocation {
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function useAdminLocation(basePath: string | undefined, navigation: AdminNavigation | undefined): AdminLocation;
|
|
41
41
|
/** A ContentKit staff area, each behind its own permission. */
|
|
42
|
-
export type StaffArea = "comments" | "queue" | "bans" | "polls" | "contents" | "groups";
|
|
42
|
+
export type StaffArea = "comments" | "queue" | "bans" | "words" | "ips" | "polls" | "contents" | "groups";
|
|
43
43
|
export interface StaffAccess<A extends StaffArea> {
|
|
44
44
|
loading: boolean;
|
|
45
45
|
/** Per area; true until known, and when the check itself failed (the area then shows its own error). */
|
package/dist/react/admin.js
CHANGED
|
@@ -67,6 +67,8 @@ var y = "00000000-0000-7000-8000-000000000000", b = {
|
|
|
67
67
|
limit: 1
|
|
68
68
|
}, t),
|
|
69
69
|
bans: (e, t) => e.bans.list("global", { limit: 1 }, t),
|
|
70
|
+
words: (e, t) => e.moderation.bannedWords(t),
|
|
71
|
+
ips: (e, t) => e.moderation.ipBans(t),
|
|
70
72
|
polls: (e, t) => e.polls.adminList({ limit: 1 }, t),
|
|
71
73
|
contents: (e, t) => e.contents.manage({ limit: 1 }, t),
|
|
72
74
|
groups: (t, n) => t.groups.members(y, { limit: 1 }, n).catch((t) => {
|
package/dist/react/admin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.js","names":[],"sources":["../../src/react/admin.ts"],"sourcesContent":["import { useCallback, useMemo, useState, useSyncExternalStore } from \"react\";\nimport { createContentKitClient, type ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { TokenSource } from \"../client/http.js\";\nimport { useContentKitClient } from \"./context.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** What a flow needs of the host's auth: an authenticating fetch, as auth-ui's AuthClient and IssuerClient have. */\nexport interface ContentKitAuth {\n authFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n /** The access token, for HLS playlists on the API origin. */\n getAccessToken?: TokenSource;\n}\n\n/** How a flow reaches ContentKit: its own client, or baseUrl with the host's auth; else the provider's client. */\nexport interface FlowClientOptions {\n client?: ContentKitClient;\n baseUrl?: string;\n auth?: ContentKitAuth;\n}\n\n/** The flow's client: `client`, else one made from `baseUrl` and `auth`, else the provider's. */\nexport function useFlowClient({ client, baseUrl, auth }: FlowClientOptions): ContentKitClient {\n const made = useMemo(\n () =>\n client || baseUrl === undefined\n ? null\n : createContentKitClient({\n baseUrl,\n fetch: auth ? (input, init) => auth.authFetch(input, init) : undefined,\n token: auth?.getAccessToken ? () => auth.getAccessToken!() : undefined,\n }),\n [client, baseUrl, auth],\n );\n return useContentKitClient(client ?? made);\n}\n\n/** The host router as a flow sees it. With React Router: `{ pathname, search }` of useLocation() and useNavigate(). */\nexport interface AdminNavigation {\n pathname: string;\n search: string;\n navigate: (to: string, options?: { replace?: boolean }) => void;\n}\n\n/** A query for a view; empty values are left out. */\nexport type AdminQuery = Record<string, string | number | null | undefined>;\n\nexport interface AdminLocation {\n /** The view: the path under basePath, \"\" at its root. */\n view: string;\n query: URLSearchParams;\n go: (view: string, query?: AdminQuery, options?: { replace?: boolean }) => void;\n}\n\nconst MOVED = \"ckui:navigate\";\nconst subscribeHistory = (cb: () => void) => {\n addEventListener(\"popstate\", cb);\n addEventListener(MOVED, cb);\n return () => {\n removeEventListener(\"popstate\", cb);\n removeEventListener(MOVED, cb);\n };\n};\nconst historySnapshot = () => location.pathname + location.search;\nconst noHistory = () => \"\";\n\nfunction encode(query: AdminQuery = {}): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) if (v !== undefined && v !== null && v !== \"\") q.set(k, String(v));\n const s = q.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * A multi-view flow's place: under basePath in the host router (navigation),\n * else in the browser history; without basePath, in memory.\n */\nexport function useAdminLocation(basePath: string | undefined, navigation: AdminNavigation | undefined): AdminLocation {\n const history = useSyncExternalStore(navigation || basePath === undefined ? subscribeNone : subscribeHistory, historySnapshot, noHistory);\n const [memory, setMemory] = useState({ view: \"\", search: \"\" });\n const base = basePath?.replace(/\\/+$/, \"\") ?? \"\";\n let view: string;\n let search: string;\n if (basePath === undefined && !navigation) {\n ({ view, search } = memory);\n } else {\n const at = navigation ? navigation.pathname + navigation.search : history;\n const [pathname, query = \"\"] = splitOnce(at, \"?\");\n search = query ? `?${query}` : \"\";\n view = pathname === base || pathname === `${base}/` ? \"\" : pathname.startsWith(`${base}/`) ? pathname.slice(base.length + 1).replace(/\\/+$/, \"\") : \"\";\n }\n const go = useCallback(\n (to: string, query?: AdminQuery, o: { replace?: boolean } = {}) => {\n const qs = encode(query);\n if (basePath === undefined && !navigation) return setMemory({ view: to, search: qs });\n const url = (to ? `${base}/${to}` : base || \"/\") + qs;\n if (navigation) return navigation.navigate(url, o);\n window.history[o.replace ? \"replaceState\" : \"pushState\"](null, \"\", url);\n dispatchEvent(new Event(MOVED));\n },\n [basePath, base, navigation],\n );\n const query = useMemo(() => new URLSearchParams(search), [search]);\n return { view, query, go };\n}\n\nconst subscribeNone = () => () => {};\n\nfunction splitOnce(s: string, sep: string): [string, string?] {\n const i = s.indexOf(sep);\n return i < 0 ? [s] : [s.slice(0, i), s.slice(i + 1)];\n}\n\n/** A ContentKit staff area, each behind its own permission. */\nexport type StaffArea = \"comments\" | \"queue\" | \"bans\" | \"polls\" | \"contents\" | \"groups\";\n\n// No group has this id: staff get not_found, others a refusal.\nconst NO_GROUP = \"00000000-0000-7000-8000-000000000000\";\n\nconst PROBES: Record<StaffArea, (c: ContentKitClient, signal: AbortSignal) => Promise<unknown>> = {\n comments: (c, signal) => c.comments.adminList({ limit: 1 }, signal),\n queue: (c, signal) => c.moderation.held({ kind: \"comment\", limit: 1 }, signal),\n bans: (c, signal) => c.bans.list(\"global\", { limit: 1 }, signal),\n polls: (c, signal) => c.polls.adminList({ limit: 1 }, signal),\n contents: (c, signal) => c.contents.manage({ limit: 1 }, signal),\n groups: (c, signal) =>\n c.groups.members(NO_GROUP, { limit: 1 }, signal).catch((e) => {\n if (toContentKitError(e).code !== \"not_found\") throw e;\n }),\n};\n\nexport interface StaffAccess<A extends StaffArea> {\n loading: boolean;\n /** Per area; true until known, and when the check itself failed (the area then shows its own error). */\n allowed: Record<A, boolean>;\n /** Some area is allowed. */\n any: boolean;\n error?: ContentKitError;\n}\n\n/**\n * What the caller may do in each area: one small read per area, refused\n * (401, 403) or not, shared by every hook asking on the client.\n */\nexport function useStaffAccess<A extends StaffArea>(areas: readonly A[], o: FlowClientOptions = {}): StaffAccess<A> {\n const client = useFlowClient(o);\n const { store, scope } = useContentScope(client);\n const r = useResource<Record<string, boolean>>(store, keyOf(\"access\", scope, ...areas), { type: \"access\" }, async (signal) => {\n const out: Record<string, boolean> = {};\n await Promise.all(\n areas.map(async (a) => {\n try {\n await PROBES[a](client, signal);\n out[a] = true;\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"forbidden\" && err.code !== \"unauthorized\") throw err;\n out[a] = false;\n }\n }),\n );\n return out;\n });\n const allowed = Object.fromEntries(areas.map((a) => [a, r.data?.[a] ?? true])) as Record<A, boolean>;\n return { loading: r.loading || !r.loaded, allowed, any: areas.some((a) => allowed[a]), error: r.error };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin.js","names":[],"sources":["../../src/react/admin.ts"],"sourcesContent":["import { useCallback, useMemo, useState, useSyncExternalStore } from \"react\";\nimport { createContentKitClient, type ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { TokenSource } from \"../client/http.js\";\nimport { useContentKitClient } from \"./context.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** What a flow needs of the host's auth: an authenticating fetch, as auth-ui's AuthClient and IssuerClient have. */\nexport interface ContentKitAuth {\n authFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n /** The access token, for HLS playlists on the API origin. */\n getAccessToken?: TokenSource;\n}\n\n/** How a flow reaches ContentKit: its own client, or baseUrl with the host's auth; else the provider's client. */\nexport interface FlowClientOptions {\n client?: ContentKitClient;\n baseUrl?: string;\n auth?: ContentKitAuth;\n}\n\n/** The flow's client: `client`, else one made from `baseUrl` and `auth`, else the provider's. */\nexport function useFlowClient({ client, baseUrl, auth }: FlowClientOptions): ContentKitClient {\n const made = useMemo(\n () =>\n client || baseUrl === undefined\n ? null\n : createContentKitClient({\n baseUrl,\n fetch: auth ? (input, init) => auth.authFetch(input, init) : undefined,\n token: auth?.getAccessToken ? () => auth.getAccessToken!() : undefined,\n }),\n [client, baseUrl, auth],\n );\n return useContentKitClient(client ?? made);\n}\n\n/** The host router as a flow sees it. With React Router: `{ pathname, search }` of useLocation() and useNavigate(). */\nexport interface AdminNavigation {\n pathname: string;\n search: string;\n navigate: (to: string, options?: { replace?: boolean }) => void;\n}\n\n/** A query for a view; empty values are left out. */\nexport type AdminQuery = Record<string, string | number | null | undefined>;\n\nexport interface AdminLocation {\n /** The view: the path under basePath, \"\" at its root. */\n view: string;\n query: URLSearchParams;\n go: (view: string, query?: AdminQuery, options?: { replace?: boolean }) => void;\n}\n\nconst MOVED = \"ckui:navigate\";\nconst subscribeHistory = (cb: () => void) => {\n addEventListener(\"popstate\", cb);\n addEventListener(MOVED, cb);\n return () => {\n removeEventListener(\"popstate\", cb);\n removeEventListener(MOVED, cb);\n };\n};\nconst historySnapshot = () => location.pathname + location.search;\nconst noHistory = () => \"\";\n\nfunction encode(query: AdminQuery = {}): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) if (v !== undefined && v !== null && v !== \"\") q.set(k, String(v));\n const s = q.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * A multi-view flow's place: under basePath in the host router (navigation),\n * else in the browser history; without basePath, in memory.\n */\nexport function useAdminLocation(basePath: string | undefined, navigation: AdminNavigation | undefined): AdminLocation {\n const history = useSyncExternalStore(navigation || basePath === undefined ? subscribeNone : subscribeHistory, historySnapshot, noHistory);\n const [memory, setMemory] = useState({ view: \"\", search: \"\" });\n const base = basePath?.replace(/\\/+$/, \"\") ?? \"\";\n let view: string;\n let search: string;\n if (basePath === undefined && !navigation) {\n ({ view, search } = memory);\n } else {\n const at = navigation ? navigation.pathname + navigation.search : history;\n const [pathname, query = \"\"] = splitOnce(at, \"?\");\n search = query ? `?${query}` : \"\";\n view = pathname === base || pathname === `${base}/` ? \"\" : pathname.startsWith(`${base}/`) ? pathname.slice(base.length + 1).replace(/\\/+$/, \"\") : \"\";\n }\n const go = useCallback(\n (to: string, query?: AdminQuery, o: { replace?: boolean } = {}) => {\n const qs = encode(query);\n if (basePath === undefined && !navigation) return setMemory({ view: to, search: qs });\n const url = (to ? `${base}/${to}` : base || \"/\") + qs;\n if (navigation) return navigation.navigate(url, o);\n window.history[o.replace ? \"replaceState\" : \"pushState\"](null, \"\", url);\n dispatchEvent(new Event(MOVED));\n },\n [basePath, base, navigation],\n );\n const query = useMemo(() => new URLSearchParams(search), [search]);\n return { view, query, go };\n}\n\nconst subscribeNone = () => () => {};\n\nfunction splitOnce(s: string, sep: string): [string, string?] {\n const i = s.indexOf(sep);\n return i < 0 ? [s] : [s.slice(0, i), s.slice(i + 1)];\n}\n\n/** A ContentKit staff area, each behind its own permission. */\nexport type StaffArea = \"comments\" | \"queue\" | \"bans\" | \"words\" | \"ips\" | \"polls\" | \"contents\" | \"groups\";\n\n// No group has this id: staff get not_found, others a refusal.\nconst NO_GROUP = \"00000000-0000-7000-8000-000000000000\";\n\nconst PROBES: Record<StaffArea, (c: ContentKitClient, signal: AbortSignal) => Promise<unknown>> = {\n comments: (c, signal) => c.comments.adminList({ limit: 1 }, signal),\n queue: (c, signal) => c.moderation.held({ kind: \"comment\", limit: 1 }, signal),\n bans: (c, signal) => c.bans.list(\"global\", { limit: 1 }, signal),\n words: (c, signal) => c.moderation.bannedWords(signal),\n ips: (c, signal) => c.moderation.ipBans(signal),\n polls: (c, signal) => c.polls.adminList({ limit: 1 }, signal),\n contents: (c, signal) => c.contents.manage({ limit: 1 }, signal),\n groups: (c, signal) =>\n c.groups.members(NO_GROUP, { limit: 1 }, signal).catch((e) => {\n if (toContentKitError(e).code !== \"not_found\") throw e;\n }),\n};\n\nexport interface StaffAccess<A extends StaffArea> {\n loading: boolean;\n /** Per area; true until known, and when the check itself failed (the area then shows its own error). */\n allowed: Record<A, boolean>;\n /** Some area is allowed. */\n any: boolean;\n error?: ContentKitError;\n}\n\n/**\n * What the caller may do in each area: one small read per area, refused\n * (401, 403) or not, shared by every hook asking on the client.\n */\nexport function useStaffAccess<A extends StaffArea>(areas: readonly A[], o: FlowClientOptions = {}): StaffAccess<A> {\n const client = useFlowClient(o);\n const { store, scope } = useContentScope(client);\n const r = useResource<Record<string, boolean>>(store, keyOf(\"access\", scope, ...areas), { type: \"access\" }, async (signal) => {\n const out: Record<string, boolean> = {};\n await Promise.all(\n areas.map(async (a) => {\n try {\n await PROBES[a](client, signal);\n out[a] = true;\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"forbidden\" && err.code !== \"unauthorized\") throw err;\n out[a] = false;\n }\n }),\n );\n return out;\n });\n const allowed = Object.fromEntries(areas.map((a) => [a, r.data?.[a] ?? true])) as Record<A, boolean>;\n return { loading: r.loading || !r.loaded, allowed, any: areas.some((a) => allowed[a]), error: r.error };\n}\n"],"mappings":"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"}
|
package/dist/react/comments.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { BanScope, Decision, HeldKind, Reaction, Sort, Target } from '../client/content/types.js';
|
|
3
3
|
import { ContentKitError } from '../client/errors.js';
|
|
4
|
-
import { AdminComment, BanInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, ReactionCounts } from '../client/generated/wire.js';
|
|
4
|
+
import { AdminComment, BanInput, BannedWord, BannedWordInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, IPBan, IPBanInput, ReactionCounts } from '../client/generated/wire.js';
|
|
5
5
|
import { UseList } from './use-resource.js';
|
|
6
6
|
/** Counts after the caller's reaction changes from counts.mine to value. */
|
|
7
7
|
export declare function withReaction(counts: ReactionCounts, value: Reaction): ReactionCounts;
|
|
@@ -84,6 +84,34 @@ export declare function useCommentBans(o?: {
|
|
|
84
84
|
enabled?: boolean;
|
|
85
85
|
client?: ContentKitClient;
|
|
86
86
|
}): UseCommentBans;
|
|
87
|
+
export interface UseBannedWords {
|
|
88
|
+
words: BannedWord[];
|
|
89
|
+
loading: boolean;
|
|
90
|
+
error?: ContentKitError;
|
|
91
|
+
reload: () => void;
|
|
92
|
+
/** Bans words, or changes what banned ones do. */
|
|
93
|
+
ban: (words: BannedWordInput[]) => Promise<BannedWord[]>;
|
|
94
|
+
unban: (words: string[]) => Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
/** Staff: the banned words every comment and answer meets (root:comments:moderate). */
|
|
97
|
+
export declare function useBannedWords(o?: {
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
client?: ContentKitClient;
|
|
100
|
+
}): UseBannedWords;
|
|
101
|
+
export interface UseIPBans {
|
|
102
|
+
bans: IPBan[];
|
|
103
|
+
loading: boolean;
|
|
104
|
+
error?: ContentKitError;
|
|
105
|
+
reload: () => void;
|
|
106
|
+
/** Bans addresses or networks, or replaces their bans. */
|
|
107
|
+
ban: (bans: IPBanInput[]) => Promise<IPBan[]>;
|
|
108
|
+
lift: (networks: string[]) => Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
/** Staff: the IP bans on commenting and answering, newest first (root:comments:moderate). */
|
|
111
|
+
export declare function useIPBans(o?: {
|
|
112
|
+
enabled?: boolean;
|
|
113
|
+
client?: ContentKitClient;
|
|
114
|
+
}): UseIPBans;
|
|
87
115
|
export interface UseGlobalCommentBan {
|
|
88
116
|
/** The user's site-wide ban, expired or not; null when none. */
|
|
89
117
|
ban: CommentBan | null;
|
package/dist/react/comments.js
CHANGED
|
@@ -165,7 +165,35 @@ function g(i = {}) {
|
|
|
165
165
|
lift: p
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
|
-
function _(t
|
|
168
|
+
function _(t = {}) {
|
|
169
|
+
let { client: r, store: o, scope: s } = n(t.client), c = i(o, t.enabled === !1 ? null : e("moderation-rules", s, "words"), {
|
|
170
|
+
type: "moderation-rules",
|
|
171
|
+
scope: "words"
|
|
172
|
+
}, (e) => r.moderation.bannedWords(e)), l = a((e) => r.moderation.banWords(e), [r]), u = a((e) => r.moderation.unbanWords(e), [r]);
|
|
173
|
+
return {
|
|
174
|
+
words: c.data ?? [],
|
|
175
|
+
loading: t.enabled !== !1 && (c.loading || !c.loaded),
|
|
176
|
+
error: c.error,
|
|
177
|
+
reload: c.reload,
|
|
178
|
+
ban: l,
|
|
179
|
+
unban: u
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function v(t = {}) {
|
|
183
|
+
let { client: r, store: o, scope: s } = n(t.client), c = i(o, t.enabled === !1 ? null : e("moderation-rules", s, "ips"), {
|
|
184
|
+
type: "moderation-rules",
|
|
185
|
+
scope: "ips"
|
|
186
|
+
}, (e) => r.moderation.ipBans(e)), l = a((e) => r.moderation.banIPs(e), [r]), u = a((e) => r.moderation.liftIPBans(e), [r]);
|
|
187
|
+
return {
|
|
188
|
+
bans: c.data ?? [],
|
|
189
|
+
loading: t.enabled !== !1 && (c.loading || !c.loaded),
|
|
190
|
+
error: c.error,
|
|
191
|
+
reload: c.reload,
|
|
192
|
+
ban: l,
|
|
193
|
+
lift: u
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function y(t, r = {}) {
|
|
169
197
|
let { client: o, store: s, scope: c } = n(r.client), l = i(s, e("ban", c, t), {
|
|
170
198
|
type: "bans",
|
|
171
199
|
scope: "global",
|
|
@@ -181,6 +209,6 @@ function _(t, r = {}) {
|
|
|
181
209
|
};
|
|
182
210
|
}
|
|
183
211
|
//#endregion
|
|
184
|
-
export { m as useAdminComments, f as useCanComment, g as useCommentBans, d as useCommentReplies, u as useComments,
|
|
212
|
+
export { m as useAdminComments, _ as useBannedWords, f as useCanComment, g as useCommentBans, d as useCommentReplies, u as useComments, y as useGlobalCommentBan, v as useIPBans, p as useLatestComments, h as useModerationQueue, o as withReaction };
|
|
185
213
|
|
|
186
214
|
//# sourceMappingURL=comments.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.js","names":[],"sources":["../../src/react/comments.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { BanScope, Decision, HeldKind, Reaction, Sort, Target } from \"../client/content/types.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { AdminComment, BanInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, ReactionCounts } from \"../client/generated/wire.js\";\nimport type { Page, ResourceStore } from \"./resources.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** Counts after the caller's reaction changes from counts.mine to value. */\nexport function withReaction(counts: ReactionCounts, value: Reaction): ReactionCounts {\n const likes = counts.likes - (counts.mine === 1 ? 1 : 0) + (value === 1 ? 1 : 0);\n const dislikes = counts.dislikes - (counts.mine === -1 ? 1 : 0) + (value === -1 ? 1 : 0);\n return { likes, dislikes, mine: value };\n}\n\nconst commentLists = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\nconst tokens = new WeakMap<ResourceStore, Map<string, number>>();\n\n/** Sets the caller's reaction to a comment in every list showing it at once; rolls back on failure unless a newer one started. */\nasync function reactToComment(client: ContentKitClient, store: ResourceStore, scope: string, c: Pick<Comment, \"id\" | \"likes\" | \"dislikes\" | \"mine\">, value: Reaction) {\n let t = tokens.get(store);\n if (!t) tokens.set(store, (t = new Map()));\n const token = (t.get(c.id) ?? 0) + 1;\n t.set(c.id, token);\n const reads = new Set<number>();\n const put = (counts: ReactionCounts, rollback = false) =>\n store.patch<Page<Comment>>(\n (tag) => commentLists.includes(tag.type) && tag.readScope === scope,\n (page, readVersion) => {\n if (rollback && !reads.has(readVersion)) return page;\n reads.add(readVersion);\n return { ...page, items: page.items.map((x) => (x.id === c.id ? { ...x, ...counts } : x)) };\n },\n );\n put(withReaction({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, value));\n try {\n await client.comments.react(c.id, value);\n } catch (e) {\n if (t.get(c.id) === token) put({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, true);\n throw e;\n }\n}\n\nexport interface UseCommentsOptions {\n sort?: Sort;\n /** One language's thread: the list shows it and new comments join it. */\n language?: string;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n client?: ContentKitClient;\n}\n\nexport interface UseComments extends UseList<Comment> {\n /** Comments on the item, or replies to a top-level comment (replyTo); anonymous callers give anonName. */\n post: (body: string, o?: { replyTo?: string; anonName?: string }) => Promise<Comment>;\n edit: (id: string, body: string) => Promise<Comment>;\n /** Leaves a tombstone. */\n remove: (id: string) => Promise<void>;\n /** Sets the caller's reaction at once, rolled back if the server refuses it. */\n react: (comment: Comment, value: Reaction) => Promise<void>;\n /** Restores a deleted comment (moderators). */\n restore: (id: string) => Promise<void>;\n}\n\n/** A target's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(target: Target, o: UseCommentsOptions = {}): UseComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n const sort = o.sort ?? \"newest\";\n const language = o.language || undefined;\n const { type, id, version } = target;\n const key = keyOf(\"comments\", scope, type, id, version, language, sort, size);\n const list = useList(\n store,\n key,\n { type: \"comments\", target: { type, id, version } },\n offsetPages(size, (q, signal) => client.comments.list({ type, id, version }, { ...q, sort, language }, signal)),\n );\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ type, id, version }, { body, reply_to_id: p.replyTo, anon_name: p.anonName, language: p.replyTo ? undefined : language }),\n [client, type, id, version, language],\n );\n const edit = useCallback((cid: string, body: string) => client.comments.edit(cid, body), [client]);\n const remove = useCallback((cid: string) => client.comments.delete(cid), [client]);\n const restore = useCallback((cid: string) => client.comments.restore(cid), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, post, edit, remove, react, restore };\n}\n\n/** A top-level comment's replies, oldest first, a page at a time; enabled false defers the first read. */\nexport function useCommentReplies(id: string, o: { pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseList<Comment> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 10;\n const key = o.enabled === false ? null : keyOf(\"replies\", scope, id, size);\n return useList(store, key, { type: \"replies\", id }, offsetPages(size, (q, signal) => client.comments.replies(id, q, signal)));\n}\n\n/** Whether the caller may comment on the target, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(target: Target, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, type, id, version), { type: \"standing\", target: { type, id, version } }, (s) =>\n client.comments.standing({ type, id, version }, s),\n );\n return { standing: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload };\n}\n\n/** The newest published comments across the site, with their items (the host shows titles). */\nexport function useLatestComments(o: { pageSize?: number; client?: ContentKitClient } = {}): UseList<FeedItem> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n return useList(store, keyOf(\"latest\", scope, size), { type: \"latest\" }, offsetPages(size, (q, signal) => client.comments.latest(q, signal), true));\n}\n\nexport interface UseAdminComments extends UseList<AdminComment> {\n remove: (id: string) => Promise<void>;\n restore: (id: string) => Promise<void>;\n react: (comment: Comment, value: Reaction) => Promise<void>;\n}\n\n/** Staff: every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate); or one content kind's, or one author's. */\nexport function useAdminComments(o: { contentKind?: string; author?: string; pageSize?: number; client?: ContentKitClient } = {}): UseAdminComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 25;\n const kind = o.contentKind || undefined;\n const author = o.author || undefined;\n const list = useList(\n store,\n keyOf(\"admin-comments\", scope, kind, author, size),\n { type: \"admin-comments\" },\n offsetPages(size, (q, signal) => client.comments.adminList({ ...q, contentKind: kind, author }, signal)),\n );\n const remove = useCallback((id: string) => client.comments.delete(id), [client]);\n const restore = useCallback((id: string) => client.comments.restore(id), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, remove, restore, react };\n}\n\nexport interface UseModerationQueue extends UseList<HeldItem> {\n /** Approves (publishes) or rejects at the revision the queue listed; the item leaves the queue. */\n resolve: (item: HeldItem, decision: Decision, reason?: string) => Promise<void>;\n}\n\n/** Staff: held comments or content awaiting review, oldest first (ModerationReview). */\nexport function useModerationQueue(o: { kind?: HeldKind; pageSize?: number; client?: ContentKitClient } = {}): UseModerationQueue {\n const { client, store, scope } = useContentScope(o.client);\n const kind = o.kind ?? \"comment\";\n const size = o.pageSize ?? 20;\n const list = useList<HeldItem>(store, keyOf(\"held\", scope, kind, size), { type: \"held\" }, async (cursor, signal) => {\n const page = await client.moderation.held({ kind, cursor: typeof cursor === \"string\" ? cursor : undefined, limit: size }, signal);\n return { items: page.items, next: page.next ?? null };\n });\n const resolve = useCallback(async (item: HeldItem, d: Decision, reason?: string) => void (await client.moderation.resolve(item, d, reason)), [client]);\n return { ...list, resolve };\n}\n\nexport interface UseCommentBans extends UseList<CommentBan & { id: string }> {\n ban: (user: string, input?: BanInput) => Promise<CommentBan>;\n lift: (user: string) => Promise<void>;\n}\n\n/** The bans of one scope: the caller's own (\"owner\") or the site's (\"global\", CommentBan), newest first. */\nexport function useCommentBans(o: { scope?: BanScope; pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseCommentBans {\n const { client, store, scope: readScope } = useContentScope(o.client);\n const scope = o.scope ?? \"global\";\n const size = o.pageSize ?? 25;\n const list = useList(\n store,\n o.enabled === false ? null : keyOf(\"bans\", readScope, scope, size),\n { type: \"bans\", scope },\n offsetPages(size, async (q, signal) => (await client.bans.list(scope, q, signal)).map((b) => ({ ...b, id: b.user_id }))),\n );\n const ban = useCallback((user: string, input?: BanInput) => client.bans.ban(scope, user, input), [client, scope]);\n const lift = useCallback((user: string) => client.bans.lift(scope, user), [client, scope]);\n return { ...list, ban, lift };\n}\n\nexport interface UseGlobalCommentBan {\n /** The user's site-wide ban, expired or not; null when none. */\n ban: CommentBan | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans them, or replaces their ban. */\n save: (input?: BanInput) => Promise<CommentBan>;\n lift: () => Promise<void>;\n}\n\n/** Staff: one user's site-wide comment ban (root:comments:ban). */\nexport function useGlobalCommentBan(user: string, o: { client?: ContentKitClient } = {}): UseGlobalCommentBan {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentBan | null>(store, keyOf(\"ban\", scope, user), { type: \"bans\", scope: \"global\", id: user }, (signal) => client.bans.get(\"global\", user, signal));\n const save = useCallback((input?: BanInput) => client.bans.ban(\"global\", user, input), [client, user]);\n const lift = useCallback(() => client.bans.lift(\"global\", user), [client, user]);\n return { ban: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload, save, lift };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"comments.js","names":[],"sources":["../../src/react/comments.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { BanScope, Decision, HeldKind, Reaction, Sort, Target } from \"../client/content/types.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { AdminComment, BanInput, BannedWord, BannedWordInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, IPBan, IPBanInput, ReactionCounts } from \"../client/generated/wire.js\";\nimport type { Page, ResourceStore } from \"./resources.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** Counts after the caller's reaction changes from counts.mine to value. */\nexport function withReaction(counts: ReactionCounts, value: Reaction): ReactionCounts {\n const likes = counts.likes - (counts.mine === 1 ? 1 : 0) + (value === 1 ? 1 : 0);\n const dislikes = counts.dislikes - (counts.mine === -1 ? 1 : 0) + (value === -1 ? 1 : 0);\n return { likes, dislikes, mine: value };\n}\n\nconst commentLists = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\nconst tokens = new WeakMap<ResourceStore, Map<string, number>>();\n\n/** Sets the caller's reaction to a comment in every list showing it at once; rolls back on failure unless a newer one started. */\nasync function reactToComment(client: ContentKitClient, store: ResourceStore, scope: string, c: Pick<Comment, \"id\" | \"likes\" | \"dislikes\" | \"mine\">, value: Reaction) {\n let t = tokens.get(store);\n if (!t) tokens.set(store, (t = new Map()));\n const token = (t.get(c.id) ?? 0) + 1;\n t.set(c.id, token);\n const reads = new Set<number>();\n const put = (counts: ReactionCounts, rollback = false) =>\n store.patch<Page<Comment>>(\n (tag) => commentLists.includes(tag.type) && tag.readScope === scope,\n (page, readVersion) => {\n if (rollback && !reads.has(readVersion)) return page;\n reads.add(readVersion);\n return { ...page, items: page.items.map((x) => (x.id === c.id ? { ...x, ...counts } : x)) };\n },\n );\n put(withReaction({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, value));\n try {\n await client.comments.react(c.id, value);\n } catch (e) {\n if (t.get(c.id) === token) put({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, true);\n throw e;\n }\n}\n\nexport interface UseCommentsOptions {\n sort?: Sort;\n /** One language's thread: the list shows it and new comments join it. */\n language?: string;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n client?: ContentKitClient;\n}\n\nexport interface UseComments extends UseList<Comment> {\n /** Comments on the item, or replies to a top-level comment (replyTo); anonymous callers give anonName. */\n post: (body: string, o?: { replyTo?: string; anonName?: string }) => Promise<Comment>;\n edit: (id: string, body: string) => Promise<Comment>;\n /** Leaves a tombstone. */\n remove: (id: string) => Promise<void>;\n /** Sets the caller's reaction at once, rolled back if the server refuses it. */\n react: (comment: Comment, value: Reaction) => Promise<void>;\n /** Restores a deleted comment (moderators). */\n restore: (id: string) => Promise<void>;\n}\n\n/** A target's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(target: Target, o: UseCommentsOptions = {}): UseComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n const sort = o.sort ?? \"newest\";\n const language = o.language || undefined;\n const { type, id, version } = target;\n const key = keyOf(\"comments\", scope, type, id, version, language, sort, size);\n const list = useList(\n store,\n key,\n { type: \"comments\", target: { type, id, version } },\n offsetPages(size, (q, signal) => client.comments.list({ type, id, version }, { ...q, sort, language }, signal)),\n );\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ type, id, version }, { body, reply_to_id: p.replyTo, anon_name: p.anonName, language: p.replyTo ? undefined : language }),\n [client, type, id, version, language],\n );\n const edit = useCallback((cid: string, body: string) => client.comments.edit(cid, body), [client]);\n const remove = useCallback((cid: string) => client.comments.delete(cid), [client]);\n const restore = useCallback((cid: string) => client.comments.restore(cid), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, post, edit, remove, react, restore };\n}\n\n/** A top-level comment's replies, oldest first, a page at a time; enabled false defers the first read. */\nexport function useCommentReplies(id: string, o: { pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseList<Comment> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 10;\n const key = o.enabled === false ? null : keyOf(\"replies\", scope, id, size);\n return useList(store, key, { type: \"replies\", id }, offsetPages(size, (q, signal) => client.comments.replies(id, q, signal)));\n}\n\n/** Whether the caller may comment on the target, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(target: Target, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, type, id, version), { type: \"standing\", target: { type, id, version } }, (s) =>\n client.comments.standing({ type, id, version }, s),\n );\n return { standing: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload };\n}\n\n/** The newest published comments across the site, with their items (the host shows titles). */\nexport function useLatestComments(o: { pageSize?: number; client?: ContentKitClient } = {}): UseList<FeedItem> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n return useList(store, keyOf(\"latest\", scope, size), { type: \"latest\" }, offsetPages(size, (q, signal) => client.comments.latest(q, signal), true));\n}\n\nexport interface UseAdminComments extends UseList<AdminComment> {\n remove: (id: string) => Promise<void>;\n restore: (id: string) => Promise<void>;\n react: (comment: Comment, value: Reaction) => Promise<void>;\n}\n\n/** Staff: every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate); or one content kind's, or one author's. */\nexport function useAdminComments(o: { contentKind?: string; author?: string; pageSize?: number; client?: ContentKitClient } = {}): UseAdminComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 25;\n const kind = o.contentKind || undefined;\n const author = o.author || undefined;\n const list = useList(\n store,\n keyOf(\"admin-comments\", scope, kind, author, size),\n { type: \"admin-comments\" },\n offsetPages(size, (q, signal) => client.comments.adminList({ ...q, contentKind: kind, author }, signal)),\n );\n const remove = useCallback((id: string) => client.comments.delete(id), [client]);\n const restore = useCallback((id: string) => client.comments.restore(id), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, remove, restore, react };\n}\n\nexport interface UseModerationQueue extends UseList<HeldItem> {\n /** Approves (publishes) or rejects at the revision the queue listed; the item leaves the queue. */\n resolve: (item: HeldItem, decision: Decision, reason?: string) => Promise<void>;\n}\n\n/** Staff: held comments or content awaiting review, oldest first (ModerationReview). */\nexport function useModerationQueue(o: { kind?: HeldKind; pageSize?: number; client?: ContentKitClient } = {}): UseModerationQueue {\n const { client, store, scope } = useContentScope(o.client);\n const kind = o.kind ?? \"comment\";\n const size = o.pageSize ?? 20;\n const list = useList<HeldItem>(store, keyOf(\"held\", scope, kind, size), { type: \"held\" }, async (cursor, signal) => {\n const page = await client.moderation.held({ kind, cursor: typeof cursor === \"string\" ? cursor : undefined, limit: size }, signal);\n return { items: page.items, next: page.next ?? null };\n });\n const resolve = useCallback(async (item: HeldItem, d: Decision, reason?: string) => void (await client.moderation.resolve(item, d, reason)), [client]);\n return { ...list, resolve };\n}\n\nexport interface UseCommentBans extends UseList<CommentBan & { id: string }> {\n ban: (user: string, input?: BanInput) => Promise<CommentBan>;\n lift: (user: string) => Promise<void>;\n}\n\n/** The bans of one scope: the caller's own (\"owner\") or the site's (\"global\", CommentBan), newest first. */\nexport function useCommentBans(o: { scope?: BanScope; pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseCommentBans {\n const { client, store, scope: readScope } = useContentScope(o.client);\n const scope = o.scope ?? \"global\";\n const size = o.pageSize ?? 25;\n const list = useList(\n store,\n o.enabled === false ? null : keyOf(\"bans\", readScope, scope, size),\n { type: \"bans\", scope },\n offsetPages(size, async (q, signal) => (await client.bans.list(scope, q, signal)).map((b) => ({ ...b, id: b.user_id }))),\n );\n const ban = useCallback((user: string, input?: BanInput) => client.bans.ban(scope, user, input), [client, scope]);\n const lift = useCallback((user: string) => client.bans.lift(scope, user), [client, scope]);\n return { ...list, ban, lift };\n}\n\nexport interface UseBannedWords {\n words: BannedWord[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans words, or changes what banned ones do. */\n ban: (words: BannedWordInput[]) => Promise<BannedWord[]>;\n unban: (words: string[]) => Promise<void>;\n}\n\n/** Staff: the banned words every comment and answer meets (root:comments:moderate). */\nexport function useBannedWords(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseBannedWords {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<BannedWord[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"words\"), { type: \"moderation-rules\", scope: \"words\" }, (signal) =>\n client.moderation.bannedWords(signal),\n );\n const ban = useCallback((words: BannedWordInput[]) => client.moderation.banWords(words), [client]);\n const unban = useCallback((words: string[]) => client.moderation.unbanWords(words), [client]);\n return { words: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, unban };\n}\n\nexport interface UseIPBans {\n bans: IPBan[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans addresses or networks, or replaces their bans. */\n ban: (bans: IPBanInput[]) => Promise<IPBan[]>;\n lift: (networks: string[]) => Promise<void>;\n}\n\n/** Staff: the IP bans on commenting and answering, newest first (root:comments:moderate). */\nexport function useIPBans(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseIPBans {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<IPBan[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"ips\"), { type: \"moderation-rules\", scope: \"ips\" }, (signal) =>\n client.moderation.ipBans(signal),\n );\n const ban = useCallback((bans: IPBanInput[]) => client.moderation.banIPs(bans), [client]);\n const lift = useCallback((networks: string[]) => client.moderation.liftIPBans(networks), [client]);\n return { bans: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, lift };\n}\n\nexport interface UseGlobalCommentBan {\n /** The user's site-wide ban, expired or not; null when none. */\n ban: CommentBan | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans them, or replaces their ban. */\n save: (input?: BanInput) => Promise<CommentBan>;\n lift: () => Promise<void>;\n}\n\n/** Staff: one user's site-wide comment ban (root:comments:ban). */\nexport function useGlobalCommentBan(user: string, o: { client?: ContentKitClient } = {}): UseGlobalCommentBan {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentBan | null>(store, keyOf(\"ban\", scope, user), { type: \"bans\", scope: \"global\", id: user }, (signal) => client.bans.get(\"global\", user, signal));\n const save = useCallback((input?: BanInput) => client.bans.ban(\"global\", user, input), [client, user]);\n const lift = useCallback(() => client.bans.lift(\"global\", user), [client, user]);\n return { ban: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload, save, lift };\n}\n"],"mappings":"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"}
|
package/dist/react/context.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ReadStore } from './store.js';
|
|
|
5
5
|
/** What failed: a component's or hook's save, load or render step. */
|
|
6
6
|
export type ContentKitOperation = "poster.load" | "poster.frame" | "poster.save" | "slot.load" | "slot.decode" | "slot.save" | "slot.remove" | "upload" | "folder.load" | "folder.commit" | "folder.update" | "folder.process" | "crop.load" | "crop.save" | SocialOperation;
|
|
7
7
|
/** What failed in a content component: a load or a write. */
|
|
8
|
-
export type SocialOperation = "comment.load" | "comment.post" | "comment.edit" | "comment.delete" | "comment.react" | "comment.restore" | "ban.save" | "ban.lift" | "reaction.save" | "favorite.save" | "poll.load" | "poll.vote" | "poll.answer" | "poll.save" | "post.load" | "content.save" | "moderation.load" | "moderation.resolve" | "media.save" | "notifications.load" | "notifications.read" | "notifications.dismiss" | "group.load" | "group.save" | "membership.save" | "history.forget" | "filters.save" | "offer.save" | "membership.join";
|
|
8
|
+
export type SocialOperation = "comment.load" | "comment.post" | "comment.edit" | "comment.delete" | "comment.react" | "comment.restore" | "ban.save" | "ban.lift" | "reaction.save" | "favorite.save" | "poll.load" | "poll.vote" | "poll.answer" | "poll.save" | "post.load" | "content.save" | "moderation.load" | "moderation.resolve" | "rules.save" | "reader.save" | "media.save" | "notifications.load" | "notifications.read" | "notifications.dismiss" | "group.load" | "group.save" | "membership.save" | "history.forget" | "filters.save" | "offer.save" | "membership.join";
|
|
9
9
|
/** The failure's context: the operation and, for one file, its name. */
|
|
10
10
|
export interface ContentKitErrorInfo {
|
|
11
11
|
operation: ContentKitOperation;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"content.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"media.save\"\n | \"notifications.load\"\n | \"notifications.read\"\n | \"notifications.dismiss\"\n | \"group.load\"\n | \"group.save\"\n | \"membership.save\"\n | \"history.forget\"\n | \"filters.save\"\n | \"offer.save\"\n | \"membership.join\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\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 readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"content.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"rules.save\"\n | \"reader.save\"\n | \"media.save\"\n | \"notifications.load\"\n | \"notifications.read\"\n | \"notifications.dismiss\"\n | \"group.load\"\n | \"group.save\"\n | \"membership.save\"\n | \"history.forget\"\n | \"filters.save\"\n | \"offer.save\"\n | \"membership.join\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\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 readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;AA+FA,IAAa,IAAoB,EAA6C,IAAI;AAGlF,SAAgB,IAAuB;CACrC,OAAO,EAAW,CAAiB,CAAC,EAAE,aAAa;AACrD;AAGA,SAAgB,IAAwC;CACtD,IAAM,IAAM,EAAW,CAAiB;CACxC,IAAI,CAAC,GAAK,MAAU,MAAM,6DAA6D;CACvF,OAAO;AACT;AAGA,SAAgB,EAAoB,GAAiD;CACnF,IAAM,IAAI,EAA4B,CAAG;CACzC,IAAI,CAAC,GAAG,MAAU,MAAM,2EAA2E;CACnG,OAAO;AACT;AAEA,SAAgB,EAA4B,GAAwD;CAClG,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,KAAO,GAAK,UAAU,MAC/B,IAAQ,EAAa;CAE3B,OADA,QAAsB;EAAE,GAAQ,aAAa,CAAK;CAAG,GAAG,CAAC,GAAQ,CAAK,CAAC,GAChE;AACT;AAGA,SAAgB,IAA8B;CAC5C,IAAM,IAAO,EAAW,CAAiB,CAAC,EAAE;CAC5C,IAAI,CAAC,GAAM,MAAU,MAAM,oDAAoD;CAC/E,OAAO;AACT;AAGA,SAAgB,EAAiB,GAAmG;CAClI,IAAM,IAAM,EAAW,CAAiB,CAAC,EAAE,SACrC,IAAU,EAAO,KAAO,CAAG;CAEjC,OADA,EAAQ,UAAU,KAAO,GAClB,GAAa,GAAY,GAAgC,MAAkB;EAChF,IAAM,IAAQ,EAAkB,CAAC;EACjC,AAAI,EAAM,SAAS,aAAW,EAAQ,UAAU,GAAO,MAAS,KAAA,IAAY,EAAE,aAAU,IAAI;GAAE;GAAW;EAAK,CAAC;CACjH,GAAG,CAAC,CAAC;AACP;AAMA,IAAa,IAAe,EAA2C,KAAA,CAAS;AAGhF,SAAgB,IAAsD;CACpE,IAAM,IAAQ,EAAW,CAAY;CACrC,OAAO,GAAiB,MAA0B,IAAQ,EAAM,CAAE,IAAI,EAAG,GAAI,CAAC,CAAK,CAAC;AACtF"}
|
package/dist/react/discovery.js
CHANGED
|
@@ -210,7 +210,7 @@ function A(t, n = {}) {
|
|
|
210
210
|
var j = /* @__PURE__ */ new WeakMap();
|
|
211
211
|
function M(e, t) {
|
|
212
212
|
let n = j.get(e);
|
|
213
|
-
return n || j.set(e, n = new s(e
|
|
213
|
+
return n || j.set(e, n = new s(e, t)), n;
|
|
214
214
|
}
|
|
215
215
|
function N(e = {}) {
|
|
216
216
|
let { client: t } = i(e.client);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** The reader's recent searches, newest first (signed out, of this tab's session); enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope } = useContentScope(o.client);\n const guarded = useGuarded();\n const key = o.enabled === false ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n const clear = useCallback((query?: string) => guarded(() => client.discovery.clearSearches(query ? { query } : {})), [client, guarded]);\n return { searches: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.group?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until read (and signed out). */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces every setting; the lists showing re-read. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** The signed-in reader's content filters: each configured group, its default and their setting. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = viewer === null ? null : keyOf(\"content-filters\", scope, language);\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const current = useRef(r.data);\n current.current = r.data;\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.discovery.setContentFilters(update, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, guarded, language, key, store],\n );\n const settings = (f: ContentFilters) => f.filters.filter((x) => x.overridden).map((x) => ({ group_id: x.group.id, visible: x.visible }));\n const setVisible = useCallback(\n (groupId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: [...settings(f).filter((x) => x.group_id !== groupId), { group_id: groupId, visible }], include_deleted: f.include_deleted });\n },\n [save],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: settings(f), include_deleted: include });\n },\n [save],\n );\n return { filters: r.data ?? null, loading: !!key && (r.loading || !r.loaded), error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n groups: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"groups\"> = { contents: [], groups: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient } = {},\n): UseTypeahead {\n const { client, language } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, ...rest } = o;\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead>({ ...NONE, query: \"\", loading: false });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false });\n return;\n }\n setState((s) => ({ ...s, loading: true }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n client.discovery.typeahead(query, ctl.signal).then(\n (r) => setState({ contents: r.contents, groups: r.groups, query: q, loading: false }),\n (e) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength]);\n return state;\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client.discovery, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const b = useBeacons(o);\n const { client: _c, ...attribution } = o;\n const session = useMemo(() => (content ? b.view(content, version, attribution) : null), [b, content, version]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => session?.update(u), [session]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string }[], o: ExposureOptions): UseExposure {\n const b = useBeacons(o);\n const renderId = useMemo(() => eventId(), [o.render]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ render_id: renderId, surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language }), [renderId, o.surface, o.ranker, o.query_id, o.language]);\n const rendered = useRef(new Set<string>());\n const visible = useRef(new Set<string>());\n useEffect(() => {\n rendered.current = new Set();\n visible.current = new Set();\n }, [renderId]);\n useEffect(() => {\n const shown = items.flatMap((it, i) => (rendered.current.has(it.id) ? [] : [{ content_id: it.id, position: i + 1 }]));\n for (const s of shown) rendered.current.add(s.content_id);\n if (shown.length) b.exposure({ ...base, stage: \"rendered\", occurred_at: new Date().toISOString(), shown });\n }, [b, base, items]);\n const pending = useRef<{ content_id: string; position: number }[]>([]);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver(\n (entries) => {\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement;\n const content = el.dataset.exposureContent!;\n if (visible.current.has(content)) continue;\n visible.current.add(content);\n pending.current.push({ content_id: content, position: Number(el.dataset.exposurePosition) });\n }\n timer.current ??= setTimeout(() => {\n timer.current = undefined;\n const shown = pending.current.splice(0);\n if (shown.length) b.exposure({ ...base, stage: \"visible\", occurred_at: new Date().toISOString(), shown });\n }, 300);\n },\n { threshold: 0.5 },\n );\n }, [b, base]);\n useEffect(() => () => observer?.disconnect(), [observer]);\n const track = useCallback(\n (content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n },\n [observer],\n );\n const click = useCallback((content: string, position: number) => b.click(content, { render_id: renderId, surface: o.surface, position }), [b, renderId, o.surface]);\n return { render_id: renderId, track, click };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** The reader's recent searches, newest first (signed out, of this tab's session); enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope } = useContentScope(o.client);\n const guarded = useGuarded();\n const key = o.enabled === false ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n const clear = useCallback((query?: string) => guarded(() => client.discovery.clearSearches(query ? { query } : {})), [client, guarded]);\n return { searches: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.group?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until read (and signed out). */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces every setting; the lists showing re-read. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** The signed-in reader's content filters: each configured group, its default and their setting. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = viewer === null ? null : keyOf(\"content-filters\", scope, language);\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const current = useRef(r.data);\n current.current = r.data;\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.discovery.setContentFilters(update, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, guarded, language, key, store],\n );\n const settings = (f: ContentFilters) => f.filters.filter((x) => x.overridden).map((x) => ({ group_id: x.group.id, visible: x.visible }));\n const setVisible = useCallback(\n (groupId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: [...settings(f).filter((x) => x.group_id !== groupId), { group_id: groupId, visible }], include_deleted: f.include_deleted });\n },\n [save],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: settings(f), include_deleted: include });\n },\n [save],\n );\n return { filters: r.data ?? null, loading: !!key && (r.loading || !r.loaded), error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n groups: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"groups\"> = { contents: [], groups: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient } = {},\n): UseTypeahead {\n const { client, language } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, ...rest } = o;\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead>({ ...NONE, query: \"\", loading: false });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false });\n return;\n }\n setState((s) => ({ ...s, loading: true }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n client.discovery.typeahead(query, ctl.signal).then(\n (r) => setState({ contents: r.contents, groups: r.groups, query: q, loading: false }),\n (e) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength]);\n return state;\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const b = useBeacons(o);\n const { client: _c, ...attribution } = o;\n const session = useMemo(() => (content ? b.view(content, version, attribution) : null), [b, content, version]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => session?.update(u), [session]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string }[], o: ExposureOptions): UseExposure {\n const b = useBeacons(o);\n const renderId = useMemo(() => eventId(), [o.render]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ render_id: renderId, surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language }), [renderId, o.surface, o.ranker, o.query_id, o.language]);\n const rendered = useRef(new Set<string>());\n const visible = useRef(new Set<string>());\n useEffect(() => {\n rendered.current = new Set();\n visible.current = new Set();\n }, [renderId]);\n useEffect(() => {\n const shown = items.flatMap((it, i) => (rendered.current.has(it.id) ? [] : [{ content_id: it.id, position: i + 1 }]));\n for (const s of shown) rendered.current.add(s.content_id);\n if (shown.length) b.exposure({ ...base, stage: \"rendered\", occurred_at: new Date().toISOString(), shown });\n }, [b, base, items]);\n const pending = useRef<{ content_id: string; position: number }[]>([]);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver(\n (entries) => {\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement;\n const content = el.dataset.exposureContent!;\n if (visible.current.has(content)) continue;\n visible.current.add(content);\n pending.current.push({ content_id: content, position: Number(el.dataset.exposurePosition) });\n }\n timer.current ??= setTimeout(() => {\n timer.current = undefined;\n const shown = pending.current.splice(0);\n if (shown.length) b.exposure({ ...base, stage: \"visible\", occurred_at: new Date().toISOString(), shown });\n }, 300);\n },\n { threshold: 0.5 },\n );\n }, [b, base]);\n useEffect(() => () => observer?.disconnect(), [observer]);\n const track = useCallback(\n (content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n },\n [observer],\n );\n const click = useCallback((content: string, position: number) => b.click(content, { render_id: renderId, surface: o.surface, position }), [b, renderId, o.surface]);\n return { render_id: renderId, track, click };\n}\n"],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { usePresets, usePublicImage, type PublicImageOptions, type UsePresets, t
|
|
|
7
7
|
export { useCanonicalContent, type CanonicalContent, type CanonicalContentOptions } from './canonical.js';
|
|
8
8
|
export { useNearViewport, type NearViewportOptions } from './viewport.js';
|
|
9
9
|
export { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type AdminNavigation, type AdminQuery, type ContentKitAuth, type FlowClientOptions, type StaffAccess, type StaffArea, } from './admin.js';
|
|
10
|
-
export { useAdminComments, useCanComment, useCommentBans, useGlobalCommentBan, useCommentReplies, useComments, useLatestComments, useModerationQueue, withReaction, type UseAdminComments, type UseCommentBans, type UseGlobalCommentBan, type UseComments, type UseCommentsOptions, type UseModerationQueue, } from './comments.js';
|
|
10
|
+
export { useAdminComments, useBannedWords, useCanComment, useCommentBans, useGlobalCommentBan, useIPBans, useCommentReplies, useComments, useLatestComments, useModerationQueue, withReaction, type UseAdminComments, type UseBannedWords, type UseIPBans, type UseCommentBans, type UseGlobalCommentBan, type UseComments, type UseCommentsOptions, type UseModerationQueue, } from './comments.js';
|
|
11
11
|
export { useContentConfig, type UseContentConfig } from './config.js';
|
|
12
12
|
export { useFavorite, useReaction, type UseFavorite, type UseReaction } from './engagement.js';
|
|
13
13
|
export { usePoll, usePollEditor, usePolls, withVote, type PollFilter, type PollImages, type UsePoll, type UsePollEditor } from './polls.js';
|
package/dist/react/resources.js
CHANGED
|
@@ -264,6 +264,12 @@ var n = {
|
|
|
264
264
|
case "searches.changed":
|
|
265
265
|
this.invalidate((e) => e.type === "searches");
|
|
266
266
|
break;
|
|
267
|
+
case "rules.changed":
|
|
268
|
+
this.invalidate((t) => t.type === "moderation-rules" && t.scope === e.rules);
|
|
269
|
+
break;
|
|
270
|
+
case "reader.changed":
|
|
271
|
+
this.invalidate((t) => t.type === "reader-plan" && t.id === e.version);
|
|
272
|
+
break;
|
|
267
273
|
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "content" && t.id === e.id || t.type === "contents");
|
|
268
274
|
}
|
|
269
275
|
}
|