@openrails/contentkit-ui 0.72.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +20 -18
- package/dist/client/client.js.map +1 -1
- package/dist/client/generated/routes.d.ts +66 -1
- package/dist/client/generated/routes.js +85 -0
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +30 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/http.d.ts +8 -1
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/media/queue.d.ts +1 -1
- package/dist/client/media/queue.js +1 -1
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client/notifications.d.ts +23 -0
- package/dist/client/notifications.js +46 -0
- package/dist/client/notifications.js.map +1 -0
- package/dist/client.js +5 -4
- package/dist/components/admin/media-item-panel.d.ts +27 -0
- package/dist/components/admin/media-item-panel.js +397 -0
- package/dist/components/admin/media-item-panel.js.map +1 -0
- package/dist/components/admin/upload-queue.d.ts +23 -0
- package/dist/components/admin/upload-queue.js +365 -0
- package/dist/components/admin/upload-queue.js.map +1 -0
- package/dist/components/notifications.d.ts +28 -0
- package/dist/components/notifications.js +222 -0
- package/dist/components/notifications.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -2
- package/dist/locales/admin/de.js +51 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +51 -0
- package/dist/locales/admin/en.js +51 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +51 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +51 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +51 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +51 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/en.d.ts +74 -0
- package/dist/locales/social/de.js +23 -0
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +23 -0
- package/dist/locales/social/en.js +23 -0
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +23 -0
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +23 -0
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +23 -0
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +23 -0
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/folder.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/notifications.d.ts +29 -0
- package/dist/react/notifications.js +56 -0
- package/dist/react/notifications.js.map +1 -0
- package/dist/react/resources.d.ts +1 -0
- package/dist/react/resources.js +26 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/upload.d.ts +10 -10
- package/dist/react/upload.js +1 -1
- package/dist/react/upload.js.map +1 -1
- package/dist/react.js +4 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ko.js","names":[],"sources":["../../../src/locales/social/ko.ts"],"sourcesContent":["import type { social as en } from \"./en.js\";\n\nexport const social: typeof en = {\n comments: {\n title: \"댓글\",\n count_one: \"댓글 {count}개\",\n count_other: \"댓글 {count}개\",\n placeholder: \"댓글 쓰기…\",\n replyPlaceholder: \"답글 쓰기…\",\n name: \"이름\",\n namePlaceholder: \"댓글과 함께 표시됩니다\",\n post: \"게시\",\n posting: \"게시 중…\",\n reply: \"답글\",\n replyingTo: \"{name}님에게 답글\",\n empty: \"아직 댓글이 없습니다. 첫 댓글을 남겨 보세요!\",\n loadMore: \"댓글 더 보기\",\n loading: \"댓글을 불러오는 중…\",\n loadFailed: \"댓글을 불러오지 못했습니다.\",\n signIn: \"로그인하고 댓글 쓰기\",\n required: \"먼저 내용을 입력하세요.\",\n nameRequired: \"이름을 입력하세요.\",\n charsLeft_one: \"{count}자 남음\",\n charsLeft_other: \"{count}자 남음\",\n submitHint: \"{key}+Enter로 게시\",\n sort: \"댓글 정렬\",\n newest: \"최신순\",\n best: \"인기순\",\n showReplies_one: \"답글 {count}개 보기\",\n showReplies_other: \"답글 {count}개 보기\",\n hideReplies: \"답글 숨기기\",\n moreReplies: \"답글 더 보기\",\n loadingReplies: \"답글을 불러오는 중…\",\n anonymous: \"익명\",\n deleted: \"삭제된 댓글입니다.\",\n edit: \"수정\",\n delete: \"삭제\",\n actions: \"댓글 작업\",\n deleteTitle: \"이 댓글을 삭제할까요?\",\n deleteBody: \"삭제되었다는 표시로 바뀌며, 답글은 남습니다.\",\n held: \"검토 중입니다. 운영자가 승인할 때까지 이 댓글은 나에게만 보입니다.\",\n heldBadge: \"검토 중\",\n rejected: \"게시되지 않음: {reason}\",\n rejectedNoReason: \"게시되지 않았습니다.\",\n rejectedBadge: \"게시 안 됨\",\n like: \"좋아요\",\n dislike: \"싫어요\",\n showMore: \"더 보기\",\n showLess: \"접기\",\n restore: \"복원\",\n locked: \"여기에는 댓글을 쓸 수 없습니다.\",\n rateLimited: \"댓글을 너무 빨리 쓰고 있습니다. {seconds}초 후에 다시 시도하세요.\",\n rateLimitedSoon: \"댓글을 너무 빨리 쓰고 있습니다. 잠시 후 다시 시도하세요.\",\n ban: \"댓글 금지\",\n unban: \"댓글 금지 해제\",\n },\n bans: {\n globalNotice: \"이 사이트에서는 댓글을 쓸 수 없습니다.\",\n ownerNotice: \"이 크리에이터의 콘텐츠에는 댓글을 쓸 수 없습니다.\",\n reason: \"사유: {reason}\",\n until: \"{date}까지.\",\n untilLifted: \"해제될 때까지.\",\n title: \"{name}님의 댓글 금지\",\n ownerHelp: \"내 모든 콘텐츠에 적용됩니다. 기존 댓글은 남고 반응, 즐겨찾기, 투표는 계속할 수 있습니다.\",\n globalHelp: \"사이트 전체에 적용됩니다. 기존 댓글은 남고 반응, 즐겨찾기, 투표는 계속할 수 있습니다.\",\n scope: \"범위\",\n scopeOwner: \"내 콘텐츠\",\n scopeGlobal: \"사이트 전체\",\n duration: \"기간\",\n day: \"1일\",\n week: \"7일\",\n month: \"30일\",\n forever: \"해제할 때까지\",\n custom: \"날짜 지정\",\n endsAt: \"종료\",\n reasonLabel: \"사유(선택)\",\n reasonPlaceholder: \"사용자에게 표시됩니다\",\n save: \"금지\",\n update: \"금지 수정\",\n lift: \"금지 해제\",\n liftTitle: \"{name}님의 금지를 해제할까요?\",\n liftBody: \"다른 금지가 없다면 다시 댓글을 쓸 수 있습니다.\",\n empty: \"금지가 없습니다.\",\n active: \"활성\",\n expired: \"만료됨\",\n bannedAt: \"{date}에 금지됨\",\n add: \"사용자 금지\",\n user: \"사용자 ID\",\n userPlaceholder: \"사용자의 ID\",\n loadFailed: \"금지 목록을 불러오지 못했습니다.\",\n },\n reactions: {\n like: \"좋아요\",\n dislike: \"싫어요\",\n signIn: \"로그인하고 반응하기\",\n failed: \"반응이 저장되지 않았습니다. 다시 시도하세요.\",\n },\n favorites: {\n add: \"즐겨찾기에 추가\",\n remove: \"즐겨찾기에서 삭제\",\n added: \"즐겨찾기에 추가했습니다.\",\n removed: \"즐겨찾기에서 삭제했습니다.\",\n signIn: \"로그인하고 즐겨찾기 저장\",\n },\n poll: {\n votes_one: \"{count}표\",\n votes_other: \"{count}표\",\n yourVote: \"내 투표\",\n closes: \"{date}에 마감\",\n closed: \"마감된 투표입니다.\",\n signIn: \"로그인하고 투표하기\",\n answerPlaceholder: \"내 답변\",\n answer: \"답변 보내기\",\n updateAnswer: \"답변 수정\",\n editAnswer: \"답변 편집\",\n yourAnswer: \"내 답변\",\n answers_one: \"답변 {count}개\",\n answers_other: \"답변 {count}개\",\n loadFailed: \"투표를 불러오지 못했습니다.\",\n },\n pollEditor: {\n newTitle: \"새 투표\",\n editTitle: \"투표 수정\",\n question: \"질문\",\n questionPlaceholder: \"무엇을 물어볼까요?\",\n language: \"언어\",\n kind: \"유형\",\n multipleChoice: \"객관식\",\n freeText: \"주관식\",\n freeTextHelp: \"독자가 답변을 쓰고, 답변은 자동으로 묶입니다.\",\n liveAt: \"게시 시작\",\n closesAt: \"마감(선택)\",\n active: \"공개\",\n inactive: \"비공개\",\n image: \"질문 이미지\",\n options: \"선택지\",\n option: \"선택지 {index}\",\n addOption: \"선택지 추가\",\n newOption: \"새 선택지\",\n removeOption: \"선택지 삭제\",\n removeOptionTitle: \"이 선택지를 삭제할까요?\",\n removeOptionBody: \"해당 표도 삭제됩니다. 투표에는 선택지가 최소 2개 남습니다.\",\n optionImage: \"선택지 {index} 이미지\",\n uploadImage: \"이미지 추가\",\n replaceImage: \"이미지 교체\",\n removeImage: \"이미지 삭제\",\n create: \"투표 만들기\",\n creating: \"만드는 중…\",\n delete: \"투표 삭제\",\n deleteTitle: \"이 투표를 삭제할까요?\",\n deleteBody: \"표와 이미지가 삭제됩니다. 되돌릴 수 없습니다.\",\n saved: \"저장했습니다.\",\n created: \"투표를 만들었습니다.\",\n imageFailed: \"투표는 저장되었지만 이미지 하나가 업로드되지 않았습니다. 여기서 다시 추가하세요.\",\n minOptions: \"투표에는 선택지가 최소 2개 필요합니다.\",\n maxOptions: \"선택지는 최대 {max}개입니다.\",\n questionRequired: \"질문을 입력하세요.\",\n optionRequired: \"모든 선택지에 이름이 필요합니다.\",\n },\n moderation: {\n title: \"댓글 관리\",\n all: \"모든 댓글\",\n queue: \"검토 대기열\",\n bans: \"금지\",\n kind: \"콘텐츠\",\n allKinds: \"모든 콘텐츠\",\n on: \"대상\",\n published: \"게시됨\",\n held: \"보류\",\n rejected: \"거부됨\",\n deleted: \"삭제됨\",\n reply: \"답글\",\n delete: \"삭제\",\n restore: \"복원\",\n approve: \"승인\",\n reject: \"거부\",\n rejectReason: \"작성자에게 보여 줄 사유(선택)\",\n heldBecause: \"보류 사유: {reason}\",\n empty: \"댓글이 없습니다.\",\n queueEmpty: \"검토할 항목이 없습니다.\",\n loadFailed: \"댓글을 불러오지 못했습니다.\",\n stale: \"목록이 표시된 뒤 수정되었습니다. 다시 검토하세요.\",\n loadMore: \"더 보기\",\n queueComments: \"댓글\",\n queuePosts: \"게시물\",\n noAccess: \"댓글을 관리할 권한이 없습니다.\",\n author: \"작성자\",\n authorPlaceholder: \"사용자 ID로 필터\",\n filter: \"필터\",\n byAuthor: \"{name} 님의 댓글\",\n clearAuthor: \"전체 보기\",\n theirComments: \"이 사용자의 댓글\",\n },\n time: {\n justNow: \"방금\",\n },\n contentErrors: {\n forbidden: \"이 작업을 할 권한이 없습니다.\",\n unauthorized: \"세션이 만료되었습니다. 다시 로그인하세요.\",\n not_found: \"더 이상 사용할 수 없습니다.\",\n conflict: \"다른 곳에서 변경되었습니다. 새로고침 후 다시 시도하세요.\",\n rate_limited: \"요청이 너무 많습니다. {seconds}초 후에 다시 시도하세요.\",\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ko.js","names":[],"sources":["../../../src/locales/social/ko.ts"],"sourcesContent":["import type { social as en } from \"./en.js\";\n\nexport const social: typeof en = {\n notifications: {\n \"archived\": \"보관됨\",\n \"dismiss\": \"보관\",\n \"restore\": \"받은 알림으로 복원\",\n \"dismissed\": \"취소됨\",\n \"title\": \"알림\",\n \"filter\": \"알림 필터\",\n \"all\": \"전체\",\n \"unread\": \"읽지 않음\",\n \"refresh\": \"알림 새로고침\",\n \"signInHint\": \"알림을 보려면 로그인하세요.\",\n \"signIn\": \"로그인\",\n \"loadFailed\": \"알림을 불러오지 못했습니다.\",\n \"loading\": \"알림을 불러오는 중…\",\n \"empty\": \"아직 알림이 없습니다.\",\n \"emptyUnread\": \"모든 알림을 확인했습니다.\",\n \"loadMore\": \"더 보기\",\n \"open\": \"조치 필요\",\n \"resolved\": \"해결됨\",\n \"markRead\": \"읽음으로 표시\",\n \"markUnread\": \"읽지 않음으로 표시\",\n \"unreadLabel\": \"알림, 읽지 않은 항목 있음\"\n },\n\n comments: {\n title: \"댓글\",\n count_one: \"댓글 {count}개\",\n count_other: \"댓글 {count}개\",\n placeholder: \"댓글 쓰기…\",\n replyPlaceholder: \"답글 쓰기…\",\n name: \"이름\",\n namePlaceholder: \"댓글과 함께 표시됩니다\",\n post: \"게시\",\n posting: \"게시 중…\",\n reply: \"답글\",\n replyingTo: \"{name}님에게 답글\",\n empty: \"아직 댓글이 없습니다. 첫 댓글을 남겨 보세요!\",\n loadMore: \"댓글 더 보기\",\n loading: \"댓글을 불러오는 중…\",\n loadFailed: \"댓글을 불러오지 못했습니다.\",\n signIn: \"로그인하고 댓글 쓰기\",\n required: \"먼저 내용을 입력하세요.\",\n nameRequired: \"이름을 입력하세요.\",\n charsLeft_one: \"{count}자 남음\",\n charsLeft_other: \"{count}자 남음\",\n submitHint: \"{key}+Enter로 게시\",\n sort: \"댓글 정렬\",\n newest: \"최신순\",\n best: \"인기순\",\n showReplies_one: \"답글 {count}개 보기\",\n showReplies_other: \"답글 {count}개 보기\",\n hideReplies: \"답글 숨기기\",\n moreReplies: \"답글 더 보기\",\n loadingReplies: \"답글을 불러오는 중…\",\n anonymous: \"익명\",\n deleted: \"삭제된 댓글입니다.\",\n edit: \"수정\",\n delete: \"삭제\",\n actions: \"댓글 작업\",\n deleteTitle: \"이 댓글을 삭제할까요?\",\n deleteBody: \"삭제되었다는 표시로 바뀌며, 답글은 남습니다.\",\n held: \"검토 중입니다. 운영자가 승인할 때까지 이 댓글은 나에게만 보입니다.\",\n heldBadge: \"검토 중\",\n rejected: \"게시되지 않음: {reason}\",\n rejectedNoReason: \"게시되지 않았습니다.\",\n rejectedBadge: \"게시 안 됨\",\n like: \"좋아요\",\n dislike: \"싫어요\",\n showMore: \"더 보기\",\n showLess: \"접기\",\n restore: \"복원\",\n locked: \"여기에는 댓글을 쓸 수 없습니다.\",\n rateLimited: \"댓글을 너무 빨리 쓰고 있습니다. {seconds}초 후에 다시 시도하세요.\",\n rateLimitedSoon: \"댓글을 너무 빨리 쓰고 있습니다. 잠시 후 다시 시도하세요.\",\n ban: \"댓글 금지\",\n unban: \"댓글 금지 해제\",\n },\n bans: {\n globalNotice: \"이 사이트에서는 댓글을 쓸 수 없습니다.\",\n ownerNotice: \"이 크리에이터의 콘텐츠에는 댓글을 쓸 수 없습니다.\",\n reason: \"사유: {reason}\",\n until: \"{date}까지.\",\n untilLifted: \"해제될 때까지.\",\n title: \"{name}님의 댓글 금지\",\n ownerHelp: \"내 모든 콘텐츠에 적용됩니다. 기존 댓글은 남고 반응, 즐겨찾기, 투표는 계속할 수 있습니다.\",\n globalHelp: \"사이트 전체에 적용됩니다. 기존 댓글은 남고 반응, 즐겨찾기, 투표는 계속할 수 있습니다.\",\n scope: \"범위\",\n scopeOwner: \"내 콘텐츠\",\n scopeGlobal: \"사이트 전체\",\n duration: \"기간\",\n day: \"1일\",\n week: \"7일\",\n month: \"30일\",\n forever: \"해제할 때까지\",\n custom: \"날짜 지정\",\n endsAt: \"종료\",\n reasonLabel: \"사유(선택)\",\n reasonPlaceholder: \"사용자에게 표시됩니다\",\n save: \"금지\",\n update: \"금지 수정\",\n lift: \"금지 해제\",\n liftTitle: \"{name}님의 금지를 해제할까요?\",\n liftBody: \"다른 금지가 없다면 다시 댓글을 쓸 수 있습니다.\",\n empty: \"금지가 없습니다.\",\n active: \"활성\",\n expired: \"만료됨\",\n bannedAt: \"{date}에 금지됨\",\n add: \"사용자 금지\",\n user: \"사용자 ID\",\n userPlaceholder: \"사용자의 ID\",\n loadFailed: \"금지 목록을 불러오지 못했습니다.\",\n },\n reactions: {\n like: \"좋아요\",\n dislike: \"싫어요\",\n signIn: \"로그인하고 반응하기\",\n failed: \"반응이 저장되지 않았습니다. 다시 시도하세요.\",\n },\n favorites: {\n add: \"즐겨찾기에 추가\",\n remove: \"즐겨찾기에서 삭제\",\n added: \"즐겨찾기에 추가했습니다.\",\n removed: \"즐겨찾기에서 삭제했습니다.\",\n signIn: \"로그인하고 즐겨찾기 저장\",\n },\n poll: {\n votes_one: \"{count}표\",\n votes_other: \"{count}표\",\n yourVote: \"내 투표\",\n closes: \"{date}에 마감\",\n closed: \"마감된 투표입니다.\",\n signIn: \"로그인하고 투표하기\",\n answerPlaceholder: \"내 답변\",\n answer: \"답변 보내기\",\n updateAnswer: \"답변 수정\",\n editAnswer: \"답변 편집\",\n yourAnswer: \"내 답변\",\n answers_one: \"답변 {count}개\",\n answers_other: \"답변 {count}개\",\n loadFailed: \"투표를 불러오지 못했습니다.\",\n },\n pollEditor: {\n newTitle: \"새 투표\",\n editTitle: \"투표 수정\",\n question: \"질문\",\n questionPlaceholder: \"무엇을 물어볼까요?\",\n language: \"언어\",\n kind: \"유형\",\n multipleChoice: \"객관식\",\n freeText: \"주관식\",\n freeTextHelp: \"독자가 답변을 쓰고, 답변은 자동으로 묶입니다.\",\n liveAt: \"게시 시작\",\n closesAt: \"마감(선택)\",\n active: \"공개\",\n inactive: \"비공개\",\n image: \"질문 이미지\",\n options: \"선택지\",\n option: \"선택지 {index}\",\n addOption: \"선택지 추가\",\n newOption: \"새 선택지\",\n removeOption: \"선택지 삭제\",\n removeOptionTitle: \"이 선택지를 삭제할까요?\",\n removeOptionBody: \"해당 표도 삭제됩니다. 투표에는 선택지가 최소 2개 남습니다.\",\n optionImage: \"선택지 {index} 이미지\",\n uploadImage: \"이미지 추가\",\n replaceImage: \"이미지 교체\",\n removeImage: \"이미지 삭제\",\n create: \"투표 만들기\",\n creating: \"만드는 중…\",\n delete: \"투표 삭제\",\n deleteTitle: \"이 투표를 삭제할까요?\",\n deleteBody: \"표와 이미지가 삭제됩니다. 되돌릴 수 없습니다.\",\n saved: \"저장했습니다.\",\n created: \"투표를 만들었습니다.\",\n imageFailed: \"투표는 저장되었지만 이미지 하나가 업로드되지 않았습니다. 여기서 다시 추가하세요.\",\n minOptions: \"투표에는 선택지가 최소 2개 필요합니다.\",\n maxOptions: \"선택지는 최대 {max}개입니다.\",\n questionRequired: \"질문을 입력하세요.\",\n optionRequired: \"모든 선택지에 이름이 필요합니다.\",\n },\n moderation: {\n title: \"댓글 관리\",\n all: \"모든 댓글\",\n queue: \"검토 대기열\",\n bans: \"금지\",\n kind: \"콘텐츠\",\n allKinds: \"모든 콘텐츠\",\n on: \"대상\",\n published: \"게시됨\",\n held: \"보류\",\n rejected: \"거부됨\",\n deleted: \"삭제됨\",\n reply: \"답글\",\n delete: \"삭제\",\n restore: \"복원\",\n approve: \"승인\",\n reject: \"거부\",\n rejectReason: \"작성자에게 보여 줄 사유(선택)\",\n heldBecause: \"보류 사유: {reason}\",\n empty: \"댓글이 없습니다.\",\n queueEmpty: \"검토할 항목이 없습니다.\",\n loadFailed: \"댓글을 불러오지 못했습니다.\",\n stale: \"목록이 표시된 뒤 수정되었습니다. 다시 검토하세요.\",\n loadMore: \"더 보기\",\n queueComments: \"댓글\",\n queuePosts: \"게시물\",\n noAccess: \"댓글을 관리할 권한이 없습니다.\",\n author: \"작성자\",\n authorPlaceholder: \"사용자 ID로 필터\",\n filter: \"필터\",\n byAuthor: \"{name} 님의 댓글\",\n clearAuthor: \"전체 보기\",\n theirComments: \"이 사용자의 댓글\",\n },\n time: {\n justNow: \"방금\",\n },\n contentErrors: {\n forbidden: \"이 작업을 할 권한이 없습니다.\",\n unauthorized: \"세션이 만료되었습니다. 다시 로그인하세요.\",\n not_found: \"더 이상 사용할 수 없습니다.\",\n conflict: \"다른 곳에서 변경되었습니다. 새로고침 후 다시 시도하세요.\",\n rate_limited: \"요청이 너무 많습니다. {seconds}초 후에 다시 시도하세요.\",\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
//#region src/locales/social/zh.ts
|
|
2
2
|
var e = {
|
|
3
|
+
notifications: {
|
|
4
|
+
archived: "已归档",
|
|
5
|
+
dismiss: "归档",
|
|
6
|
+
restore: "恢复到收件箱",
|
|
7
|
+
dismissed: "已关闭",
|
|
8
|
+
title: "通知",
|
|
9
|
+
filter: "通知筛选",
|
|
10
|
+
all: "全部",
|
|
11
|
+
unread: "未读",
|
|
12
|
+
refresh: "刷新通知",
|
|
13
|
+
signInHint: "登录后可查看通知。",
|
|
14
|
+
signIn: "登录",
|
|
15
|
+
loadFailed: "无法加载通知。",
|
|
16
|
+
loading: "正在加载通知…",
|
|
17
|
+
empty: "暂无通知。",
|
|
18
|
+
emptyUnread: "没有未读通知。",
|
|
19
|
+
loadMore: "显示更多",
|
|
20
|
+
open: "需要处理",
|
|
21
|
+
resolved: "已解决",
|
|
22
|
+
markRead: "标为已读",
|
|
23
|
+
markUnread: "标为未读",
|
|
24
|
+
unreadLabel: "通知,有未读内容"
|
|
25
|
+
},
|
|
3
26
|
comments: {
|
|
4
27
|
title: "评论",
|
|
5
28
|
count_one: "{count} 条评论",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zh.js","names":[],"sources":["../../../src/locales/social/zh.ts"],"sourcesContent":["import type { social as en } from \"./en.js\";\n\nexport const social: typeof en = {\n comments: {\n title: \"评论\",\n count_one: \"{count} 条评论\",\n count_other: \"{count} 条评论\",\n placeholder: \"写评论…\",\n replyPlaceholder: \"写回复…\",\n name: \"名字\",\n namePlaceholder: \"将随评论显示\",\n post: \"发布\",\n posting: \"发布中…\",\n reply: \"回复\",\n replyingTo: \"回复 {name}\",\n empty: \"还没有评论。来发表第一条吧!\",\n loadMore: \"显示更多评论\",\n loading: \"正在加载评论…\",\n loadFailed: \"无法加载评论。\",\n signIn: \"登录后评论\",\n required: \"请先写点内容。\",\n nameRequired: \"请输入名字。\",\n charsLeft_one: \"还剩 {count} 个字符\",\n charsLeft_other: \"还剩 {count} 个字符\",\n submitHint: \"按 {key}+Enter 发布\",\n sort: \"评论排序\",\n newest: \"最新\",\n best: \"最热\",\n showReplies_one: \"显示 {count} 条回复\",\n showReplies_other: \"显示 {count} 条回复\",\n hideReplies: \"隐藏回复\",\n moreReplies: \"显示更多回复\",\n loadingReplies: \"正在加载回复…\",\n anonymous: \"匿名\",\n deleted: \"此评论已删除。\",\n edit: \"编辑\",\n delete: \"删除\",\n actions: \"评论操作\",\n deleteTitle: \"删除这条评论?\",\n deleteBody: \"它会被替换为已删除的提示,回复会保留。\",\n held: \"审核中。在管理员批准前,这条评论只有你能看到。\",\n heldBadge: \"审核中\",\n rejected: \"未发布:{reason}\",\n rejectedNoReason: \"未发布。\",\n rejectedBadge: \"未发布\",\n like: \"赞\",\n dislike: \"踩\",\n showMore: \"展开\",\n showLess: \"收起\",\n restore: \"恢复\",\n locked: \"你不能在这里评论。\",\n rateLimited: \"你评论得太快了。请在 {seconds} 秒后重试。\",\n rateLimitedSoon: \"你评论得太快了。请稍后重试。\",\n ban: \"禁止评论\",\n unban: \"解除评论禁令\",\n },\n bans: {\n globalNotice: \"你不能在本站评论。\",\n ownerNotice: \"你不能评论这位创作者的内容。\",\n reason: \"原因:{reason}\",\n until: \"截至 {date}。\",\n untilLifted: \"直到解除为止。\",\n title: \"禁止 {name} 评论\",\n ownerHelp: \"适用于你的所有内容。对方已有的评论会保留,仍可点赞、收藏和投票。\",\n globalHelp: \"适用于整个网站。对方已有的评论会保留,仍可点赞、收藏和投票。\",\n scope: \"范围\",\n scopeOwner: \"我的内容\",\n scopeGlobal: \"整个网站\",\n duration: \"时长\",\n day: \"1 天\",\n week: \"7 天\",\n month: \"30 天\",\n forever: \"直到解除\",\n custom: \"截至某个日期\",\n endsAt: \"结束\",\n reasonLabel: \"原因(可选)\",\n reasonPlaceholder: \"会显示给该用户\",\n save: \"禁止\",\n update: \"更新禁令\",\n lift: \"解除禁令\",\n liftTitle: \"解除对 {name} 的禁令?\",\n liftBody: \"如果没有其他禁令,对方可以再次评论。\",\n empty: \"没有禁令。\",\n active: \"生效中\",\n expired: \"已过期\",\n bannedAt: \"于 {date} 禁止\",\n add: \"禁止用户\",\n user: \"用户 ID\",\n userPlaceholder: \"该用户的 ID\",\n loadFailed: \"无法加载禁令。\",\n },\n reactions: {\n like: \"赞\",\n dislike: \"踩\",\n signIn: \"登录后表态\",\n failed: \"你的表态未保存。请重试。\",\n },\n favorites: {\n add: \"加入收藏\",\n remove: \"取消收藏\",\n added: \"已加入收藏。\",\n removed: \"已取消收藏。\",\n signIn: \"登录后收藏\",\n },\n poll: {\n votes_one: \"{count} 票\",\n votes_other: \"{count} 票\",\n yourVote: \"你的投票\",\n closes: \"{date} 截止\",\n closed: \"此投票已结束。\",\n signIn: \"登录后投票\",\n answerPlaceholder: \"你的回答\",\n answer: \"提交回答\",\n updateAnswer: \"更新回答\",\n editAnswer: \"编辑回答\",\n yourAnswer: \"你的回答\",\n answers_one: \"{count} 个回答\",\n answers_other: \"{count} 个回答\",\n loadFailed: \"无法加载此投票。\",\n },\n pollEditor: {\n newTitle: \"新投票\",\n editTitle: \"编辑投票\",\n question: \"问题\",\n questionPlaceholder: \"你想问什么?\",\n language: \"语言\",\n kind: \"类型\",\n multipleChoice: \"选择题\",\n freeText: \"自由回答\",\n freeTextHelp: \"读者写下回答,回答会被自动归类。\",\n liveAt: \"上线时间\",\n closesAt: \"截止时间(可选)\",\n active: \"公开可见\",\n inactive: \"对公众隐藏\",\n image: \"问题图片\",\n options: \"选项\",\n option: \"选项 {index}\",\n addOption: \"添加选项\",\n newOption: \"新选项\",\n removeOption: \"删除选项\",\n removeOptionTitle: \"删除此选项?\",\n removeOptionBody: \"它的票数也会被删除。投票至少保留两个选项。\",\n optionImage: \"选项 {index} 的图片\",\n uploadImage: \"添加图片\",\n replaceImage: \"替换图片\",\n removeImage: \"移除图片\",\n create: \"创建投票\",\n creating: \"创建中…\",\n delete: \"删除投票\",\n deleteTitle: \"删除此投票?\",\n deleteBody: \"票数和图片会被删除,且无法撤销。\",\n saved: \"已保存。\",\n created: \"投票已创建。\",\n imageFailed: \"投票已保存,但有一张图片未上传。请在这里重新添加。\",\n minOptions: \"投票至少需要两个选项。\",\n maxOptions: \"投票最多有 {max} 个选项。\",\n questionRequired: \"请填写问题。\",\n optionRequired: \"每个选项都需要文字。\",\n },\n moderation: {\n title: \"评论管理\",\n all: \"全部评论\",\n queue: \"待审队列\",\n bans: \"禁令\",\n kind: \"内容\",\n allKinds: \"全部内容\",\n on: \"位于\",\n published: \"已发布\",\n held: \"已暂扣\",\n rejected: \"已拒绝\",\n deleted: \"已删除\",\n reply: \"回复\",\n delete: \"删除\",\n restore: \"恢复\",\n approve: \"通过\",\n reject: \"拒绝\",\n rejectReason: \"告知作者的原因(可选)\",\n heldBecause: \"暂扣原因:{reason}\",\n empty: \"没有评论。\",\n queueEmpty: \"没有待审内容。\",\n loadFailed: \"无法加载评论。\",\n stale: \"列出后内容已被编辑,请重新审核。\",\n loadMore: \"显示更多\",\n queueComments: \"评论\",\n queuePosts: \"文章\",\n noAccess: \"你没有审核评论的权限。\",\n author: \"作者\",\n authorPlaceholder: \"按用户 ID 筛选\",\n filter: \"筛选\",\n byAuthor: \"{name} 的评论\",\n clearAuthor: \"显示所有人的\",\n theirComments: \"该用户的评论\",\n },\n time: {\n justNow: \"刚刚\",\n },\n contentErrors: {\n forbidden: \"你没有执行此操作的权限。\",\n unauthorized: \"会话已过期。请重新登录。\",\n not_found: \"此内容已不可用。\",\n conflict: \"此内容已在别处更改。请刷新后重试。\",\n rate_limited: \"请求过多。请在 {seconds} 秒后重试。\",\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zh.js","names":[],"sources":["../../../src/locales/social/zh.ts"],"sourcesContent":["import type { social as en } from \"./en.js\";\n\nexport const social: typeof en = {\n notifications: {\n \"archived\": \"已归档\",\n \"dismiss\": \"归档\",\n \"restore\": \"恢复到收件箱\",\n \"dismissed\": \"已关闭\",\n \"title\": \"通知\",\n \"filter\": \"通知筛选\",\n \"all\": \"全部\",\n \"unread\": \"未读\",\n \"refresh\": \"刷新通知\",\n \"signInHint\": \"登录后可查看通知。\",\n \"signIn\": \"登录\",\n \"loadFailed\": \"无法加载通知。\",\n \"loading\": \"正在加载通知…\",\n \"empty\": \"暂无通知。\",\n \"emptyUnread\": \"没有未读通知。\",\n \"loadMore\": \"显示更多\",\n \"open\": \"需要处理\",\n \"resolved\": \"已解决\",\n \"markRead\": \"标为已读\",\n \"markUnread\": \"标为未读\",\n \"unreadLabel\": \"通知,有未读内容\"\n },\n\n comments: {\n title: \"评论\",\n count_one: \"{count} 条评论\",\n count_other: \"{count} 条评论\",\n placeholder: \"写评论…\",\n replyPlaceholder: \"写回复…\",\n name: \"名字\",\n namePlaceholder: \"将随评论显示\",\n post: \"发布\",\n posting: \"发布中…\",\n reply: \"回复\",\n replyingTo: \"回复 {name}\",\n empty: \"还没有评论。来发表第一条吧!\",\n loadMore: \"显示更多评论\",\n loading: \"正在加载评论…\",\n loadFailed: \"无法加载评论。\",\n signIn: \"登录后评论\",\n required: \"请先写点内容。\",\n nameRequired: \"请输入名字。\",\n charsLeft_one: \"还剩 {count} 个字符\",\n charsLeft_other: \"还剩 {count} 个字符\",\n submitHint: \"按 {key}+Enter 发布\",\n sort: \"评论排序\",\n newest: \"最新\",\n best: \"最热\",\n showReplies_one: \"显示 {count} 条回复\",\n showReplies_other: \"显示 {count} 条回复\",\n hideReplies: \"隐藏回复\",\n moreReplies: \"显示更多回复\",\n loadingReplies: \"正在加载回复…\",\n anonymous: \"匿名\",\n deleted: \"此评论已删除。\",\n edit: \"编辑\",\n delete: \"删除\",\n actions: \"评论操作\",\n deleteTitle: \"删除这条评论?\",\n deleteBody: \"它会被替换为已删除的提示,回复会保留。\",\n held: \"审核中。在管理员批准前,这条评论只有你能看到。\",\n heldBadge: \"审核中\",\n rejected: \"未发布:{reason}\",\n rejectedNoReason: \"未发布。\",\n rejectedBadge: \"未发布\",\n like: \"赞\",\n dislike: \"踩\",\n showMore: \"展开\",\n showLess: \"收起\",\n restore: \"恢复\",\n locked: \"你不能在这里评论。\",\n rateLimited: \"你评论得太快了。请在 {seconds} 秒后重试。\",\n rateLimitedSoon: \"你评论得太快了。请稍后重试。\",\n ban: \"禁止评论\",\n unban: \"解除评论禁令\",\n },\n bans: {\n globalNotice: \"你不能在本站评论。\",\n ownerNotice: \"你不能评论这位创作者的内容。\",\n reason: \"原因:{reason}\",\n until: \"截至 {date}。\",\n untilLifted: \"直到解除为止。\",\n title: \"禁止 {name} 评论\",\n ownerHelp: \"适用于你的所有内容。对方已有的评论会保留,仍可点赞、收藏和投票。\",\n globalHelp: \"适用于整个网站。对方已有的评论会保留,仍可点赞、收藏和投票。\",\n scope: \"范围\",\n scopeOwner: \"我的内容\",\n scopeGlobal: \"整个网站\",\n duration: \"时长\",\n day: \"1 天\",\n week: \"7 天\",\n month: \"30 天\",\n forever: \"直到解除\",\n custom: \"截至某个日期\",\n endsAt: \"结束\",\n reasonLabel: \"原因(可选)\",\n reasonPlaceholder: \"会显示给该用户\",\n save: \"禁止\",\n update: \"更新禁令\",\n lift: \"解除禁令\",\n liftTitle: \"解除对 {name} 的禁令?\",\n liftBody: \"如果没有其他禁令,对方可以再次评论。\",\n empty: \"没有禁令。\",\n active: \"生效中\",\n expired: \"已过期\",\n bannedAt: \"于 {date} 禁止\",\n add: \"禁止用户\",\n user: \"用户 ID\",\n userPlaceholder: \"该用户的 ID\",\n loadFailed: \"无法加载禁令。\",\n },\n reactions: {\n like: \"赞\",\n dislike: \"踩\",\n signIn: \"登录后表态\",\n failed: \"你的表态未保存。请重试。\",\n },\n favorites: {\n add: \"加入收藏\",\n remove: \"取消收藏\",\n added: \"已加入收藏。\",\n removed: \"已取消收藏。\",\n signIn: \"登录后收藏\",\n },\n poll: {\n votes_one: \"{count} 票\",\n votes_other: \"{count} 票\",\n yourVote: \"你的投票\",\n closes: \"{date} 截止\",\n closed: \"此投票已结束。\",\n signIn: \"登录后投票\",\n answerPlaceholder: \"你的回答\",\n answer: \"提交回答\",\n updateAnswer: \"更新回答\",\n editAnswer: \"编辑回答\",\n yourAnswer: \"你的回答\",\n answers_one: \"{count} 个回答\",\n answers_other: \"{count} 个回答\",\n loadFailed: \"无法加载此投票。\",\n },\n pollEditor: {\n newTitle: \"新投票\",\n editTitle: \"编辑投票\",\n question: \"问题\",\n questionPlaceholder: \"你想问什么?\",\n language: \"语言\",\n kind: \"类型\",\n multipleChoice: \"选择题\",\n freeText: \"自由回答\",\n freeTextHelp: \"读者写下回答,回答会被自动归类。\",\n liveAt: \"上线时间\",\n closesAt: \"截止时间(可选)\",\n active: \"公开可见\",\n inactive: \"对公众隐藏\",\n image: \"问题图片\",\n options: \"选项\",\n option: \"选项 {index}\",\n addOption: \"添加选项\",\n newOption: \"新选项\",\n removeOption: \"删除选项\",\n removeOptionTitle: \"删除此选项?\",\n removeOptionBody: \"它的票数也会被删除。投票至少保留两个选项。\",\n optionImage: \"选项 {index} 的图片\",\n uploadImage: \"添加图片\",\n replaceImage: \"替换图片\",\n removeImage: \"移除图片\",\n create: \"创建投票\",\n creating: \"创建中…\",\n delete: \"删除投票\",\n deleteTitle: \"删除此投票?\",\n deleteBody: \"票数和图片会被删除,且无法撤销。\",\n saved: \"已保存。\",\n created: \"投票已创建。\",\n imageFailed: \"投票已保存,但有一张图片未上传。请在这里重新添加。\",\n minOptions: \"投票至少需要两个选项。\",\n maxOptions: \"投票最多有 {max} 个选项。\",\n questionRequired: \"请填写问题。\",\n optionRequired: \"每个选项都需要文字。\",\n },\n moderation: {\n title: \"评论管理\",\n all: \"全部评论\",\n queue: \"待审队列\",\n bans: \"禁令\",\n kind: \"内容\",\n allKinds: \"全部内容\",\n on: \"位于\",\n published: \"已发布\",\n held: \"已暂扣\",\n rejected: \"已拒绝\",\n deleted: \"已删除\",\n reply: \"回复\",\n delete: \"删除\",\n restore: \"恢复\",\n approve: \"通过\",\n reject: \"拒绝\",\n rejectReason: \"告知作者的原因(可选)\",\n heldBecause: \"暂扣原因:{reason}\",\n empty: \"没有评论。\",\n queueEmpty: \"没有待审内容。\",\n loadFailed: \"无法加载评论。\",\n stale: \"列出后内容已被编辑,请重新审核。\",\n loadMore: \"显示更多\",\n queueComments: \"评论\",\n queuePosts: \"文章\",\n noAccess: \"你没有审核评论的权限。\",\n author: \"作者\",\n authorPlaceholder: \"按用户 ID 筛选\",\n filter: \"筛选\",\n byAuthor: \"{name} 的评论\",\n clearAuthor: \"显示所有人的\",\n theirComments: \"该用户的评论\",\n },\n time: {\n justNow: \"刚刚\",\n },\n contentErrors: {\n forbidden: \"你没有执行此操作的权限。\",\n unauthorized: \"会话已过期。请重新登录。\",\n not_found: \"此内容已不可用。\",\n conflict: \"此内容已在别处更改。请刷新后重试。\",\n rate_limited: \"请求过多。请在 {seconds} 秒后重试。\",\n },\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" | "post.save" | "moderation.load" | "moderation.resolve" | "taxonomy.load" | "taxonomy.save";
|
|
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" | "post.save" | "moderation.load" | "moderation.resolve" | "taxonomy.load" | "taxonomy.save" | "media.save" | "notifications.load" | "notifications.read" | "notifications.dismiss";
|
|
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 | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"taxonomy.load\"\n | \"taxonomy.save\";\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 | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"taxonomy.load\"\n | \"taxonomy.save\"\n | \"media.save\"\n | \"notifications.load\"\n | \"notifications.read\"\n | \"notifications.dismiss\";\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":";;;AAwFA,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/folder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? `${ref.kind}/${ref.id}` : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The UploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, ready, loadError });\n live.current = { groups, ref: target, listed, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? `${ref.kind}/${ref.id}` : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The ItemUploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, ready, loadError });\n live.current = { groups, ref: target, listed, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -20,3 +20,4 @@ export { round3, useFrameStrip, useVideoFrame, useVideoImages, useVideoPoster, t
|
|
|
20
20
|
export { GALLERY_VIEW_KEY, PLAYER_QUALITY_KEY, useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type CarouselOptions, type GalleryViewOptions, type HlsPlayerOptions, type PlayerQuality, type PlayerStatus, type PlayerTracks, type QualityLevel, type UseCarousel, type UseHlsPlayer, } from './gallery.js';
|
|
21
21
|
export type { PlaybackProgress, ProgressReason } from './player.js';
|
|
22
22
|
export { HOVER_DELAY, InlinePreviewContext, useInlinePreview, useReducedMotion, type InlinePreviewOptions } from './inline-preview.js';
|
|
23
|
+
export { useNotifications, useNotificationUnread, type UseNotifications, type UseNotificationsOptions } from './notifications.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ContentKitClient } from '../client/client.js';
|
|
2
|
+
import { Item } from '../client/generated/wire.js';
|
|
3
|
+
import { UseList } from './use-resource.js';
|
|
4
|
+
/** Options for the current person's inbox; no recipient selector is accepted. */
|
|
5
|
+
export interface UseNotificationsOptions {
|
|
6
|
+
client?: ContentKitClient;
|
|
7
|
+
unread?: boolean;
|
|
8
|
+
dismissed?: boolean;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
/** Refresh while visible. Off by default so loaded pages remain stable. */
|
|
12
|
+
refreshInterval?: number | false;
|
|
13
|
+
}
|
|
14
|
+
export interface UseNotifications extends UseList<Item> {
|
|
15
|
+
/** Null viewer disables reads; undefined viewer leaves authentication to the client. */
|
|
16
|
+
signedOut: boolean;
|
|
17
|
+
setRead: (id: string, read: boolean) => Promise<Item>;
|
|
18
|
+
setDismissed: (id: string, dismissed: boolean) => Promise<Item>;
|
|
19
|
+
}
|
|
20
|
+
/** Shared, cursor-paged notification reads, isolated by viewer and access revision. */
|
|
21
|
+
export declare function useNotifications(o?: UseNotificationsOptions): UseNotifications;
|
|
22
|
+
/** A bounded unread-present indicator, not an invented total count. */
|
|
23
|
+
export declare function useNotificationUnread(o?: Omit<UseNotificationsOptions, "unread" | "dismissed" | "pageSize">): {
|
|
24
|
+
unread: boolean;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: import('../client/errors.js').ContentKitError | undefined;
|
|
27
|
+
signedOut: boolean;
|
|
28
|
+
reload: () => void;
|
|
29
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { keyOf as e, useContentScope as t, useList as n } from "./use-resource.js";
|
|
2
|
+
import { useCallback as r, useEffect as i } from "react";
|
|
3
|
+
//#region src/react/notifications.ts
|
|
4
|
+
function a(a = {}) {
|
|
5
|
+
let { client: o, store: s, scope: c, viewer: l } = t(a.client), u = a.enabled !== !1 && l !== null, d = a.unread ?? !1, f = a.pageSize ?? 20, p = a.dismissed ?? !1, m = u ? e("notifications", c, d, p, f) : null, h = n(s, m, {
|
|
6
|
+
type: "notifications",
|
|
7
|
+
scope: `${p ? "archived" : "active"}:${d ? "unread" : "all"}`
|
|
8
|
+
}, async (e, t) => {
|
|
9
|
+
let n = await o.notifications.list({
|
|
10
|
+
unread: d,
|
|
11
|
+
dismissed: p,
|
|
12
|
+
limit: f,
|
|
13
|
+
cursor: typeof e == "string" ? e : void 0,
|
|
14
|
+
signal: t
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
items: n.items,
|
|
18
|
+
next: n.next_cursor || null
|
|
19
|
+
};
|
|
20
|
+
}), g = r((e, t) => o.notifications.setRead(e, t), [o]), _ = r((e, t) => o.notifications.setDismissed(e, t), [o]), v = a.refreshInterval ?? !1, y = h.reload;
|
|
21
|
+
return i(() => {
|
|
22
|
+
if (!u || v === !1 || v <= 0) return;
|
|
23
|
+
let e = setInterval(() => {
|
|
24
|
+
document.visibilityState === "visible" && y();
|
|
25
|
+
}, Math.max(5e3, v));
|
|
26
|
+
return () => clearInterval(e);
|
|
27
|
+
}, [
|
|
28
|
+
u,
|
|
29
|
+
v,
|
|
30
|
+
y
|
|
31
|
+
]), {
|
|
32
|
+
...h,
|
|
33
|
+
signedOut: l === null,
|
|
34
|
+
setRead: g,
|
|
35
|
+
setDismissed: _
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function o(e = {}) {
|
|
39
|
+
let t = a({
|
|
40
|
+
refreshInterval: 3e4,
|
|
41
|
+
...e,
|
|
42
|
+
unread: !0,
|
|
43
|
+
pageSize: 1
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
unread: t.items.length > 0,
|
|
47
|
+
loading: t.loading,
|
|
48
|
+
error: t.error,
|
|
49
|
+
signedOut: t.signedOut,
|
|
50
|
+
reload: t.reload
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { o as useNotificationUnread, a as useNotifications };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.js","names":[],"sources":["../../src/react/notifications.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { Item } from \"../client/generated/wire.js\";\nimport { keyOf, useContentScope, useList, type UseList } from \"./use-resource.js\";\n\n/** Options for the current person's inbox; no recipient selector is accepted. */\nexport interface UseNotificationsOptions {\n client?: ContentKitClient;\n unread?: boolean;\n dismissed?: boolean;\n pageSize?: number;\n enabled?: boolean;\n /** Refresh while visible. Off by default so loaded pages remain stable. */\n refreshInterval?: number | false;\n}\n\nexport interface UseNotifications extends UseList<Item> {\n /** Null viewer disables reads; undefined viewer leaves authentication to the client. */\n signedOut: boolean;\n setRead: (id: string, read: boolean) => Promise<Item>;\n setDismissed: (id: string, dismissed: boolean) => Promise<Item>;\n}\n\n/** Shared, cursor-paged notification reads, isolated by viewer and access revision. */\nexport function useNotifications(o: UseNotificationsOptions = {}): UseNotifications {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const enabled = o.enabled !== false && viewer !== null;\n const unread = o.unread ?? false;\n const size = o.pageSize ?? 20;\n const dismissed = o.dismissed ?? false;\n const key = enabled ? keyOf(\"notifications\", scope, unread, dismissed, size) : null;\n const list = useList<Item>(store, key, { type: \"notifications\", scope: `${dismissed ? \"archived\" : \"active\"}:${unread ? \"unread\" : \"all\"}` }, async (cursor, signal) => {\n const page = await client.notifications.list({ unread, dismissed, limit: size, cursor: typeof cursor === \"string\" ? cursor : undefined, signal });\n return { items: page.items, next: page.next_cursor || null };\n });\n const setRead = useCallback((id: string, read: boolean) => client.notifications.setRead(id, read), [client]);\n const setDismissed = useCallback((id: string, dismissed: boolean) => client.notifications.setDismissed(id, dismissed), [client]);\n const refresh = o.refreshInterval ?? false;\n const reload = list.reload;\n useEffect(() => {\n if (!enabled || refresh === false || refresh <= 0) return;\n const timer = setInterval(() => {\n if (document.visibilityState === \"visible\") reload();\n }, Math.max(5_000, refresh));\n return () => clearInterval(timer);\n }, [enabled, refresh, reload]);\n return { ...list, signedOut: viewer === null, setRead, setDismissed };\n}\n\n/** A bounded unread-present indicator, not an invented total count. */\nexport function useNotificationUnread(o: Omit<UseNotificationsOptions, \"unread\" | \"dismissed\" | \"pageSize\"> = {}) {\n const inbox = useNotifications({ refreshInterval: 30_000, ...o, unread: true, pageSize: 1 });\n return { unread: inbox.items.length > 0, loading: inbox.loading, error: inbox.error, signedOut: inbox.signedOut, reload: inbox.reload };\n}\n"],"mappings":";;;AAwBA,SAAgB,EAAiB,IAA6B,CAAC,GAAqB;CAClF,IAAM,EAAE,WAAQ,UAAO,UAAO,cAAW,EAAgB,EAAE,MAAM,GAC3D,IAAU,EAAE,YAAY,MAAS,MAAW,MAC5C,IAAS,EAAE,UAAU,IACrB,IAAO,EAAE,YAAY,IACrB,IAAY,EAAE,aAAa,IAC3B,IAAM,IAAU,EAAM,iBAAiB,GAAO,GAAQ,GAAW,CAAI,IAAI,MACzE,IAAO,EAAc,GAAO,GAAK;EAAE,MAAM;EAAiB,OAAO,GAAG,IAAY,aAAa,SAAS,GAAG,IAAS,WAAW;CAAQ,GAAG,OAAO,GAAQ,MAAW;EACtK,IAAM,IAAO,MAAM,EAAO,cAAc,KAAK;GAAE;GAAQ;GAAW,OAAO;GAAM,QAAQ,OAAO,KAAW,WAAW,IAAS,KAAA;GAAW;EAAO,CAAC;EAChJ,OAAO;GAAE,OAAO,EAAK;GAAO,MAAM,EAAK,eAAe;EAAK;CAC7D,CAAC,GACK,IAAU,GAAa,GAAY,MAAkB,EAAO,cAAc,QAAQ,GAAI,CAAI,GAAG,CAAC,CAAM,CAAC,GACrG,IAAe,GAAa,GAAY,MAAuB,EAAO,cAAc,aAAa,GAAI,CAAS,GAAG,CAAC,CAAM,CAAC,GACzH,IAAU,EAAE,mBAAmB,IAC/B,IAAS,EAAK;CAQpB,OAPA,QAAgB;EACd,IAAI,CAAC,KAAW,MAAY,MAAS,KAAW,GAAG;EACnD,IAAM,IAAQ,kBAAkB;GAC9B,AAAI,SAAS,oBAAoB,aAAW,EAAO;EACrD,GAAG,KAAK,IAAI,KAAO,CAAO,CAAC;EAC3B,aAAa,cAAc,CAAK;CAClC,GAAG;EAAC;EAAS;EAAS;CAAM,CAAC,GACtB;EAAE,GAAG;EAAM,WAAW,MAAW;EAAM;EAAS;CAAa;AACtE;AAGA,SAAgB,EAAsB,IAAwE,CAAC,GAAG;CAChH,IAAM,IAAQ,EAAiB;EAAE,iBAAiB;EAAQ,GAAG;EAAG,QAAQ;EAAM,UAAU;CAAE,CAAC;CAC3F,OAAO;EAAE,QAAQ,EAAM,MAAM,SAAS;EAAG,SAAS,EAAM;EAAS,OAAO,EAAM;EAAO,WAAW,EAAM;EAAW,QAAQ,EAAM;CAAO;AACxI"}
|
package/dist/react/resources.js
CHANGED
|
@@ -133,6 +133,12 @@ var t = {
|
|
|
133
133
|
"admin-comments"
|
|
134
134
|
];
|
|
135
135
|
switch (e.type) {
|
|
136
|
+
case "notifications.updated":
|
|
137
|
+
this.updateNotification(e.item, t);
|
|
138
|
+
break;
|
|
139
|
+
case "notifications.changed":
|
|
140
|
+
this.invalidate((e) => e.type === "notifications");
|
|
141
|
+
break;
|
|
136
142
|
case "comment.created": {
|
|
137
143
|
let t = e.comment.reply_to_id, a = !e.comment.moderation;
|
|
138
144
|
r((r) => t ? r.type === "replies" && r.id === t : r.type === "comments" && n(r.ref, e.ref), (n) => n.items.some((t) => t.id === e.comment.id) ? n : {
|
|
@@ -232,6 +238,26 @@ var t = {
|
|
|
232
238
|
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
|
|
233
239
|
}
|
|
234
240
|
}
|
|
241
|
+
updateNotification(e, t) {
|
|
242
|
+
for (let [n, r] of this.slots) {
|
|
243
|
+
if (r.tag.type !== "notifications" || !r.listeners.size) continue;
|
|
244
|
+
let i = r.entry.data, a = i?.items.find((t) => t.id === e.id);
|
|
245
|
+
if ((r.tag.readScope ?? "{}") === t && a && a.revision > e.revision) continue;
|
|
246
|
+
let o = (r.tag.scope?.startsWith("archived:") ?? !1) === !!e.dismissed_at;
|
|
247
|
+
if ((r.tag.readScope ?? "{}") !== t || !i || r.tag.scope?.endsWith(":unread") || o && !i.items.some((t) => t.id === e.id)) {
|
|
248
|
+
this.load(n, !0);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
(o || i.items.some((t) => t.id === e.id)) && (r.ctl?.abort(), r.moreCtl?.abort(), r.ctl = r.moreCtl = void 0, this.update(r, {
|
|
252
|
+
loading: !1,
|
|
253
|
+
loadingMore: !1,
|
|
254
|
+
data: {
|
|
255
|
+
...i,
|
|
256
|
+
items: i.items.flatMap((t) => t.id === e.id ? o ? [e] : [] : [t])
|
|
257
|
+
}
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
235
261
|
update(e, t) {
|
|
236
262
|
e.entry = {
|
|
237
263
|
...e.entry,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") {\n const totals = (p: Post): Post => (p.id === c.ref.id ? { ...p, total_likes: c.counts.likes, total_dislikes: c.counts.dislikes } : p);\n patch<Post>((t) => t.type === \"post\" && t.id === c.ref.id, totals);\n items<Post>([\"posts\"], totals);\n }\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"taxonomy.changed\":\n this.invalidate((t) => t.type.startsWith(\"taxonomy\"));\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Item, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"notifications.updated\":\n this.updateNotification(c.item, scope);\n break;\n case \"notifications.changed\":\n this.invalidate((t) => t.type === \"notifications\");\n break;\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") {\n const totals = (p: Post): Post => (p.id === c.ref.id ? { ...p, total_likes: c.counts.likes, total_dislikes: c.counts.dislikes } : p);\n patch<Post>((t) => t.type === \"post\" && t.id === c.ref.id, totals);\n items<Post>([\"posts\"], totals);\n }\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"taxonomy.changed\":\n this.invalidate((t) => t.type.startsWith(\"taxonomy\"));\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n // Preserve loaded pages and canonical receipt revisions. Reads that started\n // before a successful mutation cannot overwrite it when they finish later.\n private updateNotification(item: Item, scope: string | null): void {\n for (const [key, slot] of this.slots) {\n if (slot.tag.type !== \"notifications\" || !slot.listeners.size) continue;\n const page = slot.entry.data as Page<Item> | undefined;\n const cached = page?.items.find((n) => n.id === item.id);\n // Mutations from separate mounted views can complete out of order.\n // In particular, an old active receipt must not evict a newer archive.\n if ((slot.tag.readScope ?? \"{}\") === scope && cached && cached.revision > item.revision) continue;\n const archived = slot.tag.scope?.startsWith(\"archived:\") ?? false;\n const matches = archived === !!item.dismissed_at;\n if ((slot.tag.readScope ?? \"{}\") !== scope || !page || slot.tag.scope?.endsWith(\":unread\") || (matches && !page.items.some((n) => n.id === item.id))) {\n this.load(key, true);\n continue;\n }\n if (!matches && !page.items.some((n) => n.id === item.id)) continue;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n slot.ctl = slot.moreCtl = undefined;\n this.update(slot, { loading: false, loadingMore: false, data: { ...page, items: page.items.flatMap((n) => n.id === item.id ? matches ? [item] : [] : [n]) } });\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|