@focus-reactive/payload-plugin-comments 1.1.0 → 1.2.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 +8 -6
- package/dist/api/mutations/useAddCommentMutation.d.ts +18 -0
- package/dist/api/mutations/useAddCommentMutation.d.ts.map +1 -0
- package/dist/api/mutations/useAddCommentMutation.js +52 -0
- package/dist/api/mutations/useAddCommentMutation.js.map +1 -0
- package/dist/api/mutations/useDeleteCommentMutation.d.ts +11 -0
- package/dist/api/mutations/useDeleteCommentMutation.d.ts.map +1 -0
- package/dist/api/mutations/useDeleteCommentMutation.js +31 -0
- package/dist/api/mutations/useDeleteCommentMutation.js.map +1 -0
- package/dist/api/mutations/useResolveCommentMutation.d.ts +13 -0
- package/dist/api/mutations/useResolveCommentMutation.d.ts.map +1 -0
- package/dist/api/mutations/useResolveCommentMutation.js +41 -0
- package/dist/api/mutations/useResolveCommentMutation.js.map +1 -0
- package/dist/api/queries/useCommentsQuery.d.ts +3 -0
- package/dist/api/queries/useCommentsQuery.d.ts.map +1 -0
- package/dist/api/queries/useCommentsQuery.js +40 -0
- package/dist/api/queries/useCommentsQuery.js.map +1 -0
- package/dist/api/queries/useDocumentTitlesQuery.d.ts +3 -0
- package/dist/api/queries/useDocumentTitlesQuery.d.ts.map +1 -0
- package/dist/api/queries/useDocumentTitlesQuery.js +32 -0
- package/dist/api/queries/useDocumentTitlesQuery.js.map +1 -0
- package/dist/api/queries/useFieldLabelsQuery.d.ts +3 -0
- package/dist/api/queries/useFieldLabelsQuery.d.ts.map +1 -0
- package/dist/api/queries/useFieldLabelsQuery.js +23 -0
- package/dist/api/queries/useFieldLabelsQuery.js.map +1 -0
- package/dist/api/queries/useMentionableUsersQuery.d.ts +2 -0
- package/dist/api/queries/useMentionableUsersQuery.d.ts.map +1 -0
- package/dist/api/queries/useMentionableUsersQuery.js +25 -0
- package/dist/api/queries/useMentionableUsersQuery.js.map +1 -0
- package/dist/api/queryKeys.d.ts +23 -0
- package/dist/api/queryKeys.d.ts.map +1 -0
- package/dist/api/queryKeys.js +40 -0
- package/dist/api/queryKeys.js.map +1 -0
- package/dist/components/CommentEditor/ActionPanel.d.ts +1 -1
- package/dist/components/CommentEditor/ActionPanel.d.ts.map +1 -1
- package/dist/components/CommentEditor/ActionPanel.js +18 -3
- package/dist/components/CommentEditor/ActionPanel.js.map +1 -1
- package/dist/components/CommentEditor/index.d.ts.map +1 -1
- package/dist/components/CommentEditor/index.js +5 -3
- package/dist/components/CommentEditor/index.js.map +1 -1
- package/dist/components/CommentItem/StrikethoroughOverlay/clamp.d.ts +2 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/clamp.d.ts.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/clamp.js +7 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/clamp.js.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/index.d.ts +8 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/index.d.ts.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/index.js +24 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/index.js.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/measureLineRects.d.ts +3 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/measureLineRects.d.ts.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/measureLineRects.js +29 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/measureLineRects.js.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.d.ts +7 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.d.ts.map +1 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.js +114 -0
- package/dist/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.js.map +1 -0
- package/dist/components/CommentItem/index.d.ts.map +1 -1
- package/dist/components/CommentItem/index.js +37 -18
- package/dist/components/CommentItem/index.js.map +1 -1
- package/dist/components/CommentsDrawer/components/Header.d.ts.map +1 -1
- package/dist/components/CommentsDrawer/components/Header.js +4 -33
- package/dist/components/CommentsDrawer/components/Header.js.map +1 -1
- package/dist/components/CommentsHeaderButton/index.js +2 -2
- package/dist/components/CommentsHeaderButton/index.js.map +1 -1
- package/dist/components/CommentsPanel/components/CollapsibleGroup.d.ts +7 -2
- package/dist/components/CommentsPanel/components/CollapsibleGroup.d.ts.map +1 -1
- package/dist/components/CommentsPanel/components/CollapsibleGroup.js +7 -2
- package/dist/components/CommentsPanel/components/CollapsibleGroup.js.map +1 -1
- package/dist/components/CommentsPanel/components/DocumentView.d.ts.map +1 -1
- package/dist/components/CommentsPanel/components/DocumentView.js +11 -9
- package/dist/components/CommentsPanel/components/DocumentView.js.map +1 -1
- package/dist/components/CommentsPanel/components/FieldGroupSection.d.ts.map +1 -1
- package/dist/components/CommentsPanel/components/FieldGroupSection.js +44 -22
- package/dist/components/CommentsPanel/components/FieldGroupSection.js.map +1 -1
- package/dist/components/CommentsPanel/components/GlobalDocumentView.d.ts.map +1 -1
- package/dist/components/CommentsPanel/components/GlobalDocumentView.js +3 -9
- package/dist/components/CommentsPanel/components/GlobalDocumentView.js.map +1 -1
- package/dist/components/CommentsPanel/components/GlobalView.d.ts.map +1 -1
- package/dist/components/CommentsPanel/components/GlobalView.js +38 -41
- package/dist/components/CommentsPanel/components/GlobalView.js.map +1 -1
- package/dist/components/CommentsPanel/hooks/useCollapseState.d.ts +1 -1
- package/dist/components/CommentsPanel/hooks/useCollapseState.d.ts.map +1 -1
- package/dist/components/CommentsPanel/hooks/useCollapseState.js +4 -1
- package/dist/components/CommentsPanel/hooks/useCollapseState.js.map +1 -1
- package/dist/components/CommentsPanel/index.d.ts.map +1 -1
- package/dist/components/CommentsPanel/index.js +14 -11
- package/dist/components/CommentsPanel/index.js.map +1 -1
- package/dist/components/FieldCommentLabel/hooks/useFieldBreadcrumb.d.ts +2 -0
- package/dist/components/FieldCommentLabel/hooks/useFieldBreadcrumb.d.ts.map +1 -0
- package/dist/components/FieldCommentLabel/hooks/useFieldBreadcrumb.js +52 -0
- package/dist/components/FieldCommentLabel/hooks/useFieldBreadcrumb.js.map +1 -0
- package/dist/components/FieldCommentLabel/index.d.ts +1 -1
- package/dist/components/FieldCommentLabel/index.d.ts.map +1 -1
- package/dist/components/FieldCommentLabel/index.js +54 -41
- package/dist/components/FieldCommentLabel/index.js.map +1 -1
- package/dist/components/FieldCommentLabel/types.d.ts +1 -1
- package/dist/components/FieldCommentLabel/types.d.ts.map +1 -1
- package/dist/components/FieldCommentLabel/utils/exludeComments.js +2 -2
- package/dist/components/FieldCommentLabel/utils/exludeComments.js.map +1 -1
- package/dist/components/IconButton/index.d.ts +2 -2
- package/dist/components/IconButton/index.d.ts.map +1 -1
- package/dist/components/IconButton/index.js +11 -1
- package/dist/components/IconButton/index.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -4
- package/dist/constants.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +6 -2
- package/dist/plugin.js.map +1 -1
- package/dist/providers/CommentsDrawerProvider/index.d.ts +8 -0
- package/dist/providers/CommentsDrawerProvider/index.d.ts.map +1 -1
- package/dist/providers/CommentsDrawerProvider/index.js +22 -6
- package/dist/providers/CommentsDrawerProvider/index.js.map +1 -1
- package/dist/providers/CommentsProvider/index.d.ts +7 -17
- package/dist/providers/CommentsProvider/index.d.ts.map +1 -1
- package/dist/providers/CommentsProvider/index.js +105 -196
- package/dist/providers/CommentsProvider/index.js.map +1 -1
- package/dist/providers/CommentsProviderWrapper/index.d.ts +1 -1
- package/dist/providers/CommentsProviderWrapper/index.d.ts.map +1 -1
- package/dist/providers/CommentsProviderWrapper/index.js +11 -1
- package/dist/providers/CommentsProviderWrapper/index.js.map +1 -1
- package/dist/services/findAllComments.d.ts +4 -1
- package/dist/services/findAllComments.d.ts.map +1 -1
- package/dist/services/findAllComments.js +36 -10
- package/dist/services/findAllComments.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/translations/en.d.ts.map +1 -1
- package/dist/translations/en.js +3 -7
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/types.d.ts +2 -6
- package/dist/translations/types.d.ts.map +1 -1
- package/dist/types/general.d.ts +0 -2
- package/dist/types/general.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/query.d.ts +11 -0
- package/dist/types/query.d.ts.map +1 -0
- package/dist/types/query.js +1 -0
- package/dist/types/query.js.map +1 -0
- package/dist/utils/query/toQueryContext.d.ts +3 -0
- package/dist/utils/query/toQueryContext.d.ts.map +1 -0
- package/dist/utils/query/toQueryContext.js +20 -0
- package/dist/utils/query/toQueryContext.js.map +1 -0
- package/package.json +3 -6
- package/dist/components/CommentsPanel/constants.d.ts +0 -3
- package/dist/components/CommentsPanel/constants.d.ts.map +0 -1
- package/dist/components/CommentsPanel/constants.js +0 -9
- package/dist/components/CommentsPanel/constants.js.map +0 -1
- package/dist/components/CommentsPanel/utils/filterComments.d.ts +0 -9
- package/dist/components/CommentsPanel/utils/filterComments.d.ts.map +0 -1
- package/dist/components/CommentsPanel/utils/filterComments.js +0 -17
- package/dist/components/CommentsPanel/utils/filterComments.js.map +0 -1
- package/dist/components/FieldCommentLabel/AddCommentPopup.d.ts +0 -8
- package/dist/components/FieldCommentLabel/AddCommentPopup.d.ts.map +0 -1
- package/dist/components/FieldCommentLabel/AddCommentPopup.js +0 -50
- package/dist/components/FieldCommentLabel/AddCommentPopup.js.map +0 -1
- package/dist/providers/GlobalCommentsLoader/GlobalCommentsHydrator.d.ts +0 -13
- package/dist/providers/GlobalCommentsLoader/GlobalCommentsHydrator.d.ts.map +0 -1
- package/dist/providers/GlobalCommentsLoader/GlobalCommentsHydrator.js +0 -22
- package/dist/providers/GlobalCommentsLoader/GlobalCommentsHydrator.js.map +0 -1
- package/dist/providers/GlobalCommentsLoader/index.d.ts +0 -10
- package/dist/providers/GlobalCommentsLoader/index.d.ts.map +0 -1
- package/dist/providers/GlobalCommentsLoader/index.js +0 -31
- package/dist/providers/GlobalCommentsLoader/index.js.map +0 -1
- package/dist/services/syncAllCommentsData.d.ts +0 -12
- package/dist/services/syncAllCommentsData.d.ts.map +0 -1
- package/dist/services/syncAllCommentsData.js +0 -48
- package/dist/services/syncAllCommentsData.js.map +0 -1
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useRef, useImperativeHandle, startTransition } from "react";
|
|
4
4
|
import { useAuth, useLocale, useTranslation } from "@payloadcms/ui";
|
|
5
5
|
import { useComments } from "../../providers/CommentsProvider";
|
|
6
|
+
import { useMentionableUsersQuery } from "../../api/queries/useMentionableUsersQuery";
|
|
6
7
|
import { MentionDropdown } from "../MentionDropdown";
|
|
7
8
|
import { MentionLabel } from "../MentionLabel";
|
|
8
9
|
import { serializeEditor } from "../../utils/comment/serializeEditor";
|
|
@@ -25,7 +26,8 @@ function CommentEditor({
|
|
|
25
26
|
onEnterPress,
|
|
26
27
|
onEscapePress
|
|
27
28
|
}) {
|
|
28
|
-
const {
|
|
29
|
+
const { addComment } = useComments();
|
|
30
|
+
const { data: users = [] } = useMentionableUsersQuery();
|
|
29
31
|
const { code: locale } = useLocale();
|
|
30
32
|
const { user } = useAuth();
|
|
31
33
|
const [mentionQuery, setMentionQuery] = useState(null);
|
|
@@ -210,13 +212,13 @@ function CommentEditor({
|
|
|
210
212
|
"div",
|
|
211
213
|
{
|
|
212
214
|
ref: editorWrapperRef,
|
|
213
|
-
className: "relative flex-1 group px-2.5 py-2 rounded-md border border-transparent focus-within:border-(--theme-elevation-150) bg-transparent
|
|
215
|
+
className: "relative flex-1 min-w-0 group px-2.5 py-2 rounded-md border border-transparent focus-within:border-(--theme-elevation-150) bg-transparent",
|
|
214
216
|
children: [
|
|
215
217
|
/* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
|
|
216
218
|
"div",
|
|
217
219
|
{
|
|
218
220
|
className: `
|
|
219
|
-
is-empty w-full min-h-5
|
|
221
|
+
is-empty w-full min-h-5 max-h-32 overflow-y-auto whitespace-pre-wrap wrap-break-word
|
|
220
222
|
text-(--theme-text) text-[13px] outline-none box-border
|
|
221
223
|
[&.is-empty]:before:content-[attr(data-placeholder)] [&.is-empty]:before:text-(--theme-elevation-450)
|
|
222
224
|
[&.is-empty]:before:pointer-events-none [&.is-empty]:before:absolute
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/CommentEditor/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useImperativeHandle, type RefObject, startTransition } from \"react\";\nimport { useAuth, useLocale, useTranslation } from \"@payloadcms/ui\";\nimport { useComments } from \"../../providers/CommentsProvider\";\nimport { MentionDropdown } from \"../MentionDropdown\";\nimport { MentionLabel } from \"../MentionLabel\";\nimport { serializeEditor } from \"../../utils/comment/serializeEditor\";\nimport { isSelfMention } from \"../../utils/mention/isSelfMention\";\nimport { createRoot } from \"react-dom/client\";\nimport type { User } from \"../../types\";\nimport { FALLBACK_USERNAME } from \"../../constants\";\nimport { resolveUsername } from \"../../utils/user/resolveUsername\";\nimport { ActionPanel } from \"./ActionPanel\";\nimport { Avatar } from \"../Avatar\";\n\nexport interface CommentEditorHandle {\n getValue: () => string;\n clear: () => void;\n focus: () => void;\n insertAt: () => void;\n}\n\ninterface CommentEditorProps {\n disabled?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n ref?: RefObject<CommentEditorHandle | null>;\n fieldPath?: string | null;\n documentId?: number;\n collectionSlug?: string;\n globalSlug?: string;\n onSuccessAddComment?: () => void;\n onEnterPress?: () => void;\n onEscapePress?: () => void;\n}\n\nexport function CommentEditor({\n disabled,\n autoFocus,\n placeholder: placeholderProp,\n ref,\n fieldPath,\n documentId,\n collectionSlug,\n globalSlug,\n onSuccessAddComment,\n onEnterPress,\n onEscapePress,\n}: CommentEditorProps) {\n const { mentionUsers: users, addComment } = useComments();\n const { code: locale } = useLocale();\n const { user } = useAuth();\n\n const [mentionQuery, setMentionQuery] = useState<string | null>(null);\n const [triggerRange, setTriggerRange] = useState<Range | null>(null);\n const [filteredUsers, setFilteredUsers] = useState<User[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n const { usernameFieldPath } = useComments();\n const { t } = useTranslation();\n\n const editorRef = useRef<HTMLDivElement>(null);\n const editorWrapperRef = useRef<HTMLDivElement>(null);\n\n const unknownLabel = t(\"comments:unknownAuthor\" as never) ?? FALLBACK_USERNAME;\n const currentUserId = user?.id;\n const placeholder = placeholderProp ?? t(\"comments:writeComment\" as never) ?? \"Add a comment\";\n\n // Methods\n const detectMention = () => {\n const sel = window.getSelection();\n\n if (!sel || !sel.rangeCount) {\n setMentionQuery(null);\n\n return;\n }\n\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n\n if (node.nodeType !== Node.TEXT_NODE) {\n setMentionQuery(null);\n\n return;\n }\n\n const text = node.textContent ?? \"\";\n const offset = range.startOffset;\n\n let i = offset - 1;\n while (i >= 0 && text[i] !== \" \" && text[i] !== \"\\n\") {\n if (text[i] === \"@\") {\n const query = text.slice(i + 1, offset);\n if (query.includes(\" \")) {\n setMentionQuery(null);\n return;\n }\n\n const atRange = document.createRange();\n atRange.setStart(node, i);\n atRange.setEnd(node, offset);\n setTriggerRange(atRange.cloneRange());\n setMentionQuery(query);\n setFilteredUsers(\n users.filter((u) =>\n resolveUsername(u, usernameFieldPath, unknownLabel).toLowerCase().includes(query.toLowerCase()),\n ),\n );\n setSelectedIndex(0);\n\n return;\n }\n\n i--;\n }\n\n setMentionQuery(null);\n };\n\n const insertMention = (user: User) => {\n if (!triggerRange || !editorRef.current) return;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n sel.removeAllRanges();\n sel.addRange(triggerRange);\n sel.deleteFromDocument();\n\n const { id: userId } = user;\n\n const mentionLabelContainer = document.createElement(\"span\");\n const mentionLabelContainerRoot = createRoot(mentionLabelContainer);\n\n mentionLabelContainerRoot.render(\n <MentionLabel\n name={resolveUsername(user, usernameFieldPath, unknownLabel)}\n isSelf={isSelfMention(currentUserId, userId)}\n />,\n );\n\n mentionLabelContainer.contentEditable = \"false\";\n mentionLabelContainer.dataset.mentionId = String(userId);\n\n const range2 = sel.getRangeAt(0);\n range2.insertNode(mentionLabelContainer);\n\n const zws = document.createTextNode(\"\\u200b\");\n mentionLabelContainer.after(zws);\n\n const newRange = document.createRange();\n newRange.setStartAfter(zws);\n newRange.collapse(true);\n sel.removeAllRanges();\n sel.addRange(newRange);\n\n setMentionQuery(null);\n setTriggerRange(null);\n editorRef.current?.focus();\n };\n\n const insertAt = () => {\n const editor = editorRef.current;\n if (!editor) return;\n\n editor.focus();\n\n const sel = window.getSelection();\n if (!sel || !sel.rangeCount) {\n const range = document.createRange();\n range.selectNodeContents(editor);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n document.execCommand(\"insertText\", false, \"@\");\n detectMention();\n };\n\n const updateEmptyClass = () => {\n const editor = editorRef.current;\n if (!editor) return;\n\n const isEmpty = editor.innerHTML === \"\" || editor.innerHTML === \"<br>\";\n\n editor.classList.toggle(\"is-empty\", isEmpty);\n };\n\n // Editor API\n const getEditorValue = () => {\n if (!editorRef.current) return \"\";\n\n return serializeEditor(editorRef.current).trim();\n };\n\n const clearEditor = () => {\n setMentionQuery(null);\n setTriggerRange(null);\n\n if (editorRef.current) {\n editorRef.current.innerHTML = \"\";\n editorRef.current.classList.add(\"is-empty\");\n }\n };\n\n const focusEditor = () => {\n editorRef.current?.focus();\n };\n\n useImperativeHandle(ref, () => ({\n getValue: getEditorValue,\n clear: clearEditor,\n focus: focusEditor,\n insertAt,\n }));\n\n // Handlers\n const handleAddComment = () => {\n const serialized = getEditorValue();\n if (!serialized) return;\n\n clearEditor();\n\n startTransition(async () => {\n const res = await addComment(serialized, fieldPath, documentId, collectionSlug, locale, globalSlug);\n\n if (res.success) {\n onSuccessAddComment?.();\n }\n });\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (mentionQuery !== null) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n e.stopPropagation();\n setSelectedIndex((prev) => (prev + 1) % filteredUsers.length);\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n e.stopPropagation();\n setSelectedIndex((prev) => (prev - 1 + filteredUsers.length) % filteredUsers.length);\n return;\n }\n if (e.key === \"Enter\" && filteredUsers.length > 0) {\n e.preventDefault();\n const selectedUser = filteredUsers[selectedIndex];\n if (selectedUser) insertMention(selectedUser);\n return;\n }\n if (e.key === \"Escape\") {\n e.stopPropagation();\n setMentionQuery(null);\n setTriggerRange(null);\n return;\n }\n }\n\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n\n handleAddComment();\n onEnterPress?.();\n } else if (e.key === \"Escape\") {\n onEscapePress?.();\n }\n };\n\n const handleInput = () => {\n detectMention();\n updateEmptyClass();\n };\n\n return (\n <div className=\"relative\">\n <div className=\"flex gap-2.5 items-start\">\n <Avatar className=\"shrink-0\" user={user} usernameFieldPath={usernameFieldPath} fallbackName={unknownLabel} />\n\n <div\n ref={editorWrapperRef}\n className=\"relative flex-1 group px-2.5 py-2 rounded-md border border-transparent focus-within:border-(--theme-elevation-150) bg-transparent transition-colors\">\n <div className=\"relative\">\n <div\n className={`\n is-empty w-full min-h-5 leading-5\n text-(--theme-text) text-[13px] outline-none box-border\n [&.is-empty]:before:content-[attr(data-placeholder)] [&.is-empty]:before:text-(--theme-elevation-450)\n [&.is-empty]:before:pointer-events-none [&.is-empty]:before:absolute\n `}\n ref={editorRef}\n contentEditable={!disabled}\n autoFocus={autoFocus}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label={placeholder}\n data-placeholder={placeholder}\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n />\n </div>\n\n {mentionQuery !== null && (\n <MentionDropdown\n users={filteredUsers}\n selectedIndex={selectedIndex}\n onSelect={insertMention}\n onClose={() => {\n setMentionQuery(null);\n setTriggerRange(null);\n editorRef.current?.focus();\n }}\n anchorRef={editorWrapperRef}\n />\n )}\n\n <ActionPanel\n className=\"hidden group-focus-within:flex\"\n onMention={() => insertAt()}\n onAddComment={handleAddComment}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["user"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/CommentEditor/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useImperativeHandle, type RefObject, startTransition } from \"react\";\nimport { useAuth, useLocale, useTranslation } from \"@payloadcms/ui\";\nimport { useComments } from \"../../providers/CommentsProvider\";\nimport { useMentionableUsersQuery } from \"../../api/queries/useMentionableUsersQuery\";\nimport { MentionDropdown } from \"../MentionDropdown\";\nimport { MentionLabel } from \"../MentionLabel\";\nimport { serializeEditor } from \"../../utils/comment/serializeEditor\";\nimport { isSelfMention } from \"../../utils/mention/isSelfMention\";\nimport { createRoot } from \"react-dom/client\";\nimport type { User } from \"../../types\";\nimport { FALLBACK_USERNAME } from \"../../constants\";\nimport { resolveUsername } from \"../../utils/user/resolveUsername\";\nimport { ActionPanel } from \"./ActionPanel\";\nimport { Avatar } from \"../Avatar\";\n\nexport interface CommentEditorHandle {\n getValue: () => string;\n clear: () => void;\n focus: () => void;\n insertAt: () => void;\n}\n\ninterface CommentEditorProps {\n disabled?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n ref?: RefObject<CommentEditorHandle | null>;\n fieldPath?: string | null;\n documentId?: number;\n collectionSlug?: string;\n globalSlug?: string;\n onSuccessAddComment?: () => void;\n onEnterPress?: () => void;\n onEscapePress?: () => void;\n}\n\nexport function CommentEditor({\n disabled,\n autoFocus,\n placeholder: placeholderProp,\n ref,\n fieldPath,\n documentId,\n collectionSlug,\n globalSlug,\n onSuccessAddComment,\n onEnterPress,\n onEscapePress,\n}: CommentEditorProps) {\n const { addComment } = useComments();\n const { data: users = [] } = useMentionableUsersQuery();\n const { code: locale } = useLocale();\n const { user } = useAuth();\n\n const [mentionQuery, setMentionQuery] = useState<string | null>(null);\n const [triggerRange, setTriggerRange] = useState<Range | null>(null);\n const [filteredUsers, setFilteredUsers] = useState<User[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n const { usernameFieldPath } = useComments();\n const { t } = useTranslation();\n\n const editorRef = useRef<HTMLDivElement>(null);\n const editorWrapperRef = useRef<HTMLDivElement>(null);\n\n const unknownLabel = t(\"comments:unknownAuthor\" as never) ?? FALLBACK_USERNAME;\n const currentUserId = user?.id;\n const placeholder = placeholderProp ?? t(\"comments:writeComment\" as never) ?? \"Add a comment\";\n\n // Methods\n const detectMention = () => {\n const sel = window.getSelection();\n\n if (!sel || !sel.rangeCount) {\n setMentionQuery(null);\n\n return;\n }\n\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n\n if (node.nodeType !== Node.TEXT_NODE) {\n setMentionQuery(null);\n\n return;\n }\n\n const text = node.textContent ?? \"\";\n const offset = range.startOffset;\n\n let i = offset - 1;\n while (i >= 0 && text[i] !== \" \" && text[i] !== \"\\n\") {\n if (text[i] === \"@\") {\n const query = text.slice(i + 1, offset);\n if (query.includes(\" \")) {\n setMentionQuery(null);\n return;\n }\n\n const atRange = document.createRange();\n atRange.setStart(node, i);\n atRange.setEnd(node, offset);\n setTriggerRange(atRange.cloneRange());\n setMentionQuery(query);\n setFilteredUsers(\n users.filter((u) =>\n resolveUsername(u, usernameFieldPath, unknownLabel).toLowerCase().includes(query.toLowerCase()),\n ),\n );\n setSelectedIndex(0);\n\n return;\n }\n\n i--;\n }\n\n setMentionQuery(null);\n };\n\n const insertMention = (user: User) => {\n if (!triggerRange || !editorRef.current) return;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n sel.removeAllRanges();\n sel.addRange(triggerRange);\n sel.deleteFromDocument();\n\n const { id: userId } = user;\n\n const mentionLabelContainer = document.createElement(\"span\");\n const mentionLabelContainerRoot = createRoot(mentionLabelContainer);\n\n mentionLabelContainerRoot.render(\n <MentionLabel\n name={resolveUsername(user, usernameFieldPath, unknownLabel)}\n isSelf={isSelfMention(currentUserId, userId)}\n />,\n );\n\n mentionLabelContainer.contentEditable = \"false\";\n mentionLabelContainer.dataset.mentionId = String(userId);\n\n const range2 = sel.getRangeAt(0);\n range2.insertNode(mentionLabelContainer);\n\n const zws = document.createTextNode(\"\\u200b\");\n mentionLabelContainer.after(zws);\n\n const newRange = document.createRange();\n newRange.setStartAfter(zws);\n newRange.collapse(true);\n sel.removeAllRanges();\n sel.addRange(newRange);\n\n setMentionQuery(null);\n setTriggerRange(null);\n editorRef.current?.focus();\n };\n\n const insertAt = () => {\n const editor = editorRef.current;\n if (!editor) return;\n\n editor.focus();\n\n const sel = window.getSelection();\n if (!sel || !sel.rangeCount) {\n const range = document.createRange();\n range.selectNodeContents(editor);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n document.execCommand(\"insertText\", false, \"@\");\n detectMention();\n };\n\n const updateEmptyClass = () => {\n const editor = editorRef.current;\n if (!editor) return;\n\n const isEmpty = editor.innerHTML === \"\" || editor.innerHTML === \"<br>\";\n\n editor.classList.toggle(\"is-empty\", isEmpty);\n };\n\n // Editor API\n const getEditorValue = () => {\n if (!editorRef.current) return \"\";\n\n return serializeEditor(editorRef.current).trim();\n };\n\n const clearEditor = () => {\n setMentionQuery(null);\n setTriggerRange(null);\n\n if (editorRef.current) {\n editorRef.current.innerHTML = \"\";\n editorRef.current.classList.add(\"is-empty\");\n }\n };\n\n const focusEditor = () => {\n editorRef.current?.focus();\n };\n\n useImperativeHandle(ref, () => ({\n getValue: getEditorValue,\n clear: clearEditor,\n focus: focusEditor,\n insertAt,\n }));\n\n // Handlers\n const handleAddComment = () => {\n const serialized = getEditorValue();\n if (!serialized) return;\n\n clearEditor();\n\n startTransition(async () => {\n const res = await addComment(serialized, fieldPath, documentId, collectionSlug, locale, globalSlug);\n\n if (res.success) {\n onSuccessAddComment?.();\n }\n });\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (mentionQuery !== null) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n e.stopPropagation();\n setSelectedIndex((prev) => (prev + 1) % filteredUsers.length);\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n e.stopPropagation();\n setSelectedIndex((prev) => (prev - 1 + filteredUsers.length) % filteredUsers.length);\n return;\n }\n if (e.key === \"Enter\" && filteredUsers.length > 0) {\n e.preventDefault();\n const selectedUser = filteredUsers[selectedIndex];\n if (selectedUser) insertMention(selectedUser);\n return;\n }\n if (e.key === \"Escape\") {\n e.stopPropagation();\n setMentionQuery(null);\n setTriggerRange(null);\n return;\n }\n }\n\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n\n handleAddComment();\n onEnterPress?.();\n } else if (e.key === \"Escape\") {\n onEscapePress?.();\n }\n };\n\n const handleInput = () => {\n detectMention();\n updateEmptyClass();\n };\n\n return (\n <div className=\"relative\">\n <div className=\"flex gap-2.5 items-start\">\n <Avatar className=\"shrink-0\" user={user} usernameFieldPath={usernameFieldPath} fallbackName={unknownLabel} />\n\n <div\n ref={editorWrapperRef}\n className=\"relative flex-1 min-w-0 group px-2.5 py-2 rounded-md border border-transparent focus-within:border-(--theme-elevation-150) bg-transparent\">\n <div className=\"relative\">\n <div\n className={`\n is-empty w-full min-h-5 max-h-32 overflow-y-auto whitespace-pre-wrap wrap-break-word\n text-(--theme-text) text-[13px] outline-none box-border\n [&.is-empty]:before:content-[attr(data-placeholder)] [&.is-empty]:before:text-(--theme-elevation-450)\n [&.is-empty]:before:pointer-events-none [&.is-empty]:before:absolute\n `}\n ref={editorRef}\n contentEditable={!disabled}\n autoFocus={autoFocus}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label={placeholder}\n data-placeholder={placeholder}\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n />\n </div>\n\n {mentionQuery !== null && (\n <MentionDropdown\n users={filteredUsers}\n selectedIndex={selectedIndex}\n onSelect={insertMention}\n onClose={() => {\n setMentionQuery(null);\n setTriggerRange(null);\n editorRef.current?.focus();\n }}\n anchorRef={editorWrapperRef}\n />\n )}\n\n <ActionPanel\n className=\"hidden group-focus-within:flex\"\n onMention={() => insertAt()}\n onAddComment={handleAddComment}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["user"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clamp.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/clamp.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAE5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/clamp.ts"],"sourcesContent":["export function clamp(min: number, value: number, max: number) {\n return Math.min(Math.max(value, min), max);\n}\n"],"mappings":"AAAO,SAAS,MAAM,KAAa,OAAe,KAAa;AAC7D,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
isResolved: boolean;
|
|
4
|
+
contentRef: RefObject<HTMLElement | null>;
|
|
5
|
+
}
|
|
6
|
+
export declare function StrikethoroughOverlay({ isResolved, contentRef }: Props): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,UAAU,KAAK;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC3C;AAED,wBAAgB,qBAAqB,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,KAAK,kDAuBtE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useStrikethroughAnimation } from "./useStrikethroughAnimation";
|
|
3
|
+
function StrikethoroughOverlay({ isResolved, contentRef }) {
|
|
4
|
+
const { lineRects, lineContentRect, getLineByIndexRef } = useStrikethroughAnimation(isResolved, contentRef);
|
|
5
|
+
if (!lineRects.length || !lineContentRect) return null;
|
|
6
|
+
const lineHeight = lineContentRect.height / lineRects.length;
|
|
7
|
+
return /* @__PURE__ */ jsx("span", { className: "block absolute inset-0 pointer-events-none", "aria-hidden": "true", children: lineRects.map((rect, i) => /* @__PURE__ */ jsx(
|
|
8
|
+
"span",
|
|
9
|
+
{
|
|
10
|
+
className: "absolute h-px bg-current",
|
|
11
|
+
ref: getLineByIndexRef(i),
|
|
12
|
+
style: {
|
|
13
|
+
width: 0,
|
|
14
|
+
top: Math.round(lineHeight * i + lineHeight / 2) - 1,
|
|
15
|
+
left: rect.left - lineContentRect.left
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
`line-${i}`
|
|
19
|
+
)) });
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
StrikethoroughOverlay
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/index.tsx"],"sourcesContent":["import type { RefObject } from \"react\";\nimport { useStrikethroughAnimation } from \"./useStrikethroughAnimation\";\n\ninterface Props {\n isResolved: boolean;\n contentRef: RefObject<HTMLElement | null>;\n}\n\nexport function StrikethoroughOverlay({ isResolved, contentRef }: Props) {\n const { lineRects, lineContentRect, getLineByIndexRef } = useStrikethroughAnimation(isResolved, contentRef);\n\n if (!lineRects.length || !lineContentRect) return null;\n\n const lineHeight = lineContentRect.height / lineRects.length;\n\n return (\n <span className=\"block absolute inset-0 pointer-events-none\" aria-hidden=\"true\">\n {lineRects.map((rect, i) => (\n <span\n key={`line-${i}`}\n className=\"absolute h-px bg-current\"\n ref={getLineByIndexRef(i)}\n style={{\n width: 0,\n top: Math.round(lineHeight * i + lineHeight / 2) - 1,\n left: rect.left - lineContentRect.left,\n }}\n />\n ))}\n </span>\n );\n}\n"],"mappings":"AAkBQ;AAjBR,SAAS,iCAAiC;AAOnC,SAAS,sBAAsB,EAAE,YAAY,WAAW,GAAU;AACvE,QAAM,EAAE,WAAW,iBAAiB,kBAAkB,IAAI,0BAA0B,YAAY,UAAU;AAE1G,MAAI,CAAC,UAAU,UAAU,CAAC,gBAAiB,QAAO;AAElD,QAAM,aAAa,gBAAgB,SAAS,UAAU;AAEtD,SACE,oBAAC,UAAK,WAAU,8CAA6C,eAAY,QACtE,oBAAU,IAAI,CAAC,MAAM,MACpB;AAAA,IAAC;AAAA;AAAA,MAEC,WAAU;AAAA,MACV,KAAK,kBAAkB,CAAC;AAAA,MACxB,OAAO;AAAA,QACL,OAAO;AAAA,QACP,KAAK,KAAK,MAAM,aAAa,IAAI,aAAa,CAAC,IAAI;AAAA,QACnD,MAAM,KAAK,OAAO,gBAAgB;AAAA,MACpC;AAAA;AAAA,IAPK,QAAQ,CAAC;AAAA,EAQhB,CACD,GACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measureLineRects.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/measureLineRects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,aA8BzE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function measureLineRects(contentRef) {
|
|
2
|
+
const el = contentRef.current;
|
|
3
|
+
if (!el) return [];
|
|
4
|
+
const delEl = el.querySelector("del") ?? el;
|
|
5
|
+
const range = document.createRange();
|
|
6
|
+
range.selectNodeContents(delEl);
|
|
7
|
+
const rawRects = Array.from(range.getClientRects());
|
|
8
|
+
const groups = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const rect of rawRects) {
|
|
10
|
+
const key = Math.round(rect.top);
|
|
11
|
+
const group = groups.get(key);
|
|
12
|
+
if (group) {
|
|
13
|
+
group.push(rect);
|
|
14
|
+
} else {
|
|
15
|
+
groups.set(key, [rect]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return Array.from(groups.values()).map((rects) => {
|
|
19
|
+
const left = Math.min(...rects.map((r) => r.left));
|
|
20
|
+
const right = Math.max(...rects.map((r) => r.right));
|
|
21
|
+
const top = Math.min(...rects.map((r) => r.top));
|
|
22
|
+
const bottom = Math.max(...rects.map((r) => r.bottom));
|
|
23
|
+
return new DOMRect(left, top, right - left, bottom - top);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
measureLineRects
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=measureLineRects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/measureLineRects.ts"],"sourcesContent":["import type { RefObject } from \"react\";\n\nexport function measureLineRects(contentRef: RefObject<HTMLElement | null>) {\n const el = contentRef.current;\n if (!el) return [];\n\n const delEl = el.querySelector(\"del\") ?? el;\n\n const range = document.createRange();\n range.selectNodeContents(delEl);\n const rawRects = Array.from(range.getClientRects());\n\n const groups = new Map<number, DOMRect[]>();\n for (const rect of rawRects) {\n const key = Math.round(rect.top);\n const group = groups.get(key);\n\n if (group) {\n group.push(rect);\n } else {\n groups.set(key, [rect]);\n }\n }\n\n return Array.from(groups.values()).map((rects) => {\n const left = Math.min(...rects.map((r) => r.left));\n const right = Math.max(...rects.map((r) => r.right));\n const top = Math.min(...rects.map((r) => r.top));\n const bottom = Math.max(...rects.map((r) => r.bottom));\n\n return new DOMRect(left, top, right - left, bottom - top);\n });\n}\n"],"mappings":"AAEO,SAAS,iBAAiB,YAA2C;AAC1E,QAAM,KAAK,WAAW;AACtB,MAAI,CAAC,GAAI,QAAO,CAAC;AAEjB,QAAM,QAAQ,GAAG,cAAc,KAAK,KAAK;AAEzC,QAAM,QAAQ,SAAS,YAAY;AACnC,QAAM,mBAAmB,KAAK;AAC9B,QAAM,WAAW,MAAM,KAAK,MAAM,eAAe,CAAC;AAElD,QAAM,SAAS,oBAAI,IAAuB;AAC1C,aAAW,QAAQ,UAAU;AAC3B,UAAM,MAAM,KAAK,MAAM,KAAK,GAAG;AAC/B,UAAM,QAAQ,OAAO,IAAI,GAAG;AAE5B,QAAI,OAAO;AACT,YAAM,KAAK,IAAI;AAAA,IACjB,OAAO;AACL,aAAO,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,IACxB;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU;AAChD,UAAM,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACjD,UAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACnD,UAAM,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAC/C,UAAM,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAErD,WAAO,IAAI,QAAQ,MAAM,KAAK,QAAQ,MAAM,SAAS,GAAG;AAAA,EAC1D,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
export declare function useStrikethroughAnimation(isResolved: boolean, contentRect: RefObject<HTMLElement | null>): {
|
|
3
|
+
lineRects: DOMRect[];
|
|
4
|
+
lineContentRect: DOMRect | null;
|
|
5
|
+
getLineByIndexRef: (i: number) => (el: HTMLSpanElement | null) => void;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=useStrikethroughAnimation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStrikethroughAnimation.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgD,MAAM,OAAO,CAAC;AAMrF,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC;;;2BAezE,MAAM,UAAU,eAAe,GAAG,IAAI;EAqIrE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { clamp } from "./clamp";
|
|
3
|
+
import { measureLineRects } from "./measureLineRects";
|
|
4
|
+
const SPEED = 20;
|
|
5
|
+
function useStrikethroughAnimation(isResolved, contentRect) {
|
|
6
|
+
const [lineRects, setLineRects] = useState([]);
|
|
7
|
+
const [lineContentRect, setLineContentRect] = useState(null);
|
|
8
|
+
const progressRef = useRef(0);
|
|
9
|
+
const directionRef = useRef(1);
|
|
10
|
+
const animationIdRef = useRef(0);
|
|
11
|
+
const measuredRef = useRef(false);
|
|
12
|
+
const mountedRef = useRef(false);
|
|
13
|
+
const lineRefs = useRef([]);
|
|
14
|
+
const lineRectsRef = useRef([]);
|
|
15
|
+
const cumulativePixelStartPerLineRef = useRef([]);
|
|
16
|
+
const getLineByIndexRef = (i) => (el) => {
|
|
17
|
+
lineRefs.current[i] = el;
|
|
18
|
+
};
|
|
19
|
+
const getTotalLineWidth = () => {
|
|
20
|
+
return lineRectsRef.current.reduce((sum, r) => sum + r.width, 0) || 1;
|
|
21
|
+
};
|
|
22
|
+
const measureLines = () => {
|
|
23
|
+
const el = contentRect.current;
|
|
24
|
+
if (!el) return;
|
|
25
|
+
const lineRects2 = measureLineRects(contentRect);
|
|
26
|
+
const lineContentRect2 = el.getBoundingClientRect();
|
|
27
|
+
const precomputeCumulativePixelStartPerLine = () => {
|
|
28
|
+
let acc = 0;
|
|
29
|
+
return lineRects2.map((rect) => {
|
|
30
|
+
const start = acc;
|
|
31
|
+
acc += rect.width;
|
|
32
|
+
return start;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
lineRectsRef.current = lineRects2;
|
|
36
|
+
cumulativePixelStartPerLineRef.current = precomputeCumulativePixelStartPerLine();
|
|
37
|
+
setLineRects(lineRects2);
|
|
38
|
+
setLineContentRect(lineContentRect2);
|
|
39
|
+
};
|
|
40
|
+
const updateLines = (progress) => {
|
|
41
|
+
const rects = lineRectsRef.current;
|
|
42
|
+
const starts = cumulativePixelStartPerLineRef.current;
|
|
43
|
+
lineRefs.current.forEach((line, i) => {
|
|
44
|
+
if (!line || !rects[i]) return;
|
|
45
|
+
const pixelStart = starts[i] ?? 0;
|
|
46
|
+
const width = rects[i].width;
|
|
47
|
+
const currentWidth = clamp(0, (progress - pixelStart) / width, 1) * width;
|
|
48
|
+
line.style.width = `${currentWidth}px`;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const startLoop = (n) => {
|
|
52
|
+
cancelAnimationFrame(animationIdRef.current);
|
|
53
|
+
const tick = () => {
|
|
54
|
+
const direction = directionRef.current;
|
|
55
|
+
progressRef.current = clamp(0, progressRef.current + direction * SPEED, n);
|
|
56
|
+
updateLines(progressRef.current);
|
|
57
|
+
const done = direction === 1 && progressRef.current >= n || direction === -1 && progressRef.current <= 0;
|
|
58
|
+
if (!done) {
|
|
59
|
+
animationIdRef.current = requestAnimationFrame(tick);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
animationIdRef.current = requestAnimationFrame(tick);
|
|
63
|
+
};
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
updateLines(progressRef.current);
|
|
66
|
+
}, [lineRects]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const isFirstRun = !mountedRef.current;
|
|
69
|
+
mountedRef.current = true;
|
|
70
|
+
const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
71
|
+
if (isResolved && !measuredRef.current) {
|
|
72
|
+
measureLines();
|
|
73
|
+
measuredRef.current = true;
|
|
74
|
+
}
|
|
75
|
+
const totalWidth = getTotalLineWidth();
|
|
76
|
+
directionRef.current = isResolved ? 1 : -1;
|
|
77
|
+
cancelAnimationFrame(animationIdRef.current);
|
|
78
|
+
if (isFirstRun && isResolved) {
|
|
79
|
+
progressRef.current = totalWidth;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (isFirstRun) return;
|
|
83
|
+
if (prefersReducedMotion) {
|
|
84
|
+
const targetProgress = isResolved ? totalWidth : 0;
|
|
85
|
+
progressRef.current = targetProgress;
|
|
86
|
+
updateLines(targetProgress);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
startLoop(totalWidth);
|
|
90
|
+
}, [isResolved]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const el = contentRect.current;
|
|
93
|
+
if (!el) return;
|
|
94
|
+
const observer = new ResizeObserver(() => {
|
|
95
|
+
if (!measuredRef.current) return;
|
|
96
|
+
measureLines();
|
|
97
|
+
const totalWidth = getTotalLineWidth();
|
|
98
|
+
if (directionRef.current === 1 && progressRef.current >= totalWidth) {
|
|
99
|
+
progressRef.current = totalWidth;
|
|
100
|
+
updateLines(totalWidth);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
observer.observe(el);
|
|
104
|
+
return () => observer.disconnect();
|
|
105
|
+
}, [contentRect]);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
return () => cancelAnimationFrame(animationIdRef.current);
|
|
108
|
+
}, []);
|
|
109
|
+
return { lineRects, lineContentRect, getLineByIndexRef };
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
useStrikethroughAnimation
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=useStrikethroughAnimation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/CommentItem/StrikethoroughOverlay/useStrikethroughAnimation.ts"],"sourcesContent":["import { type RefObject, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { clamp } from \"./clamp\";\nimport { measureLineRects } from \"./measureLineRects\";\n\nconst SPEED = 20;\n\nexport function useStrikethroughAnimation(isResolved: boolean, contentRect: RefObject<HTMLElement | null>) {\n const [lineRects, setLineRects] = useState<DOMRect[]>([]);\n const [lineContentRect, setLineContentRect] = useState<DOMRect | null>(null);\n\n const progressRef = useRef<number>(0);\n const directionRef = useRef<1 | -1>(1);\n const animationIdRef = useRef<number>(0);\n\n const measuredRef = useRef<boolean>(false);\n const mountedRef = useRef<boolean>(false);\n\n const lineRefs = useRef<(HTMLSpanElement | null)[]>([]);\n const lineRectsRef = useRef<DOMRect[]>([]);\n const cumulativePixelStartPerLineRef = useRef<number[]>([]);\n\n const getLineByIndexRef = (i: number) => (el: HTMLSpanElement | null) => {\n lineRefs.current[i] = el;\n };\n\n const getTotalLineWidth = () => {\n return lineRectsRef.current.reduce((sum, r) => sum + r.width, 0) || 1;\n };\n\n const measureLines = () => {\n const el = contentRect.current;\n if (!el) return;\n\n const lineRects = measureLineRects(contentRect);\n const lineContentRect = el.getBoundingClientRect();\n\n const precomputeCumulativePixelStartPerLine = () => {\n let acc = 0;\n\n return lineRects.map((rect) => {\n const start = acc;\n acc += rect.width;\n return start;\n });\n };\n\n lineRectsRef.current = lineRects;\n cumulativePixelStartPerLineRef.current = precomputeCumulativePixelStartPerLine();\n\n setLineRects(lineRects);\n setLineContentRect(lineContentRect);\n };\n\n const updateLines = (progress: number) => {\n const rects = lineRectsRef.current;\n const starts = cumulativePixelStartPerLineRef.current;\n\n lineRefs.current.forEach((line, i) => {\n if (!line || !rects[i]) return;\n\n const pixelStart = starts[i] ?? 0;\n const width = rects[i].width;\n\n const currentWidth = clamp(0, (progress - pixelStart) / width, 1) * width;\n line.style.width = `${currentWidth}px`;\n });\n };\n\n const startLoop = (n: number) => {\n cancelAnimationFrame(animationIdRef.current);\n\n const tick = () => {\n const direction = directionRef.current;\n progressRef.current = clamp(0, progressRef.current + direction * SPEED, n);\n updateLines(progressRef.current);\n\n const done = (direction === 1 && progressRef.current >= n) || (direction === -1 && progressRef.current <= 0);\n\n if (!done) {\n animationIdRef.current = requestAnimationFrame(tick);\n }\n };\n\n animationIdRef.current = requestAnimationFrame(tick);\n };\n\n useLayoutEffect(() => {\n updateLines(progressRef.current);\n }, [lineRects]);\n\n useEffect(() => {\n const isFirstRun = !mountedRef.current;\n mountedRef.current = true;\n\n const prefersReducedMotion =\n typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n if (isResolved && !measuredRef.current) {\n measureLines();\n measuredRef.current = true;\n }\n\n const totalWidth = getTotalLineWidth();\n directionRef.current = isResolved ? 1 : -1;\n\n cancelAnimationFrame(animationIdRef.current);\n\n if (isFirstRun && isResolved) {\n progressRef.current = totalWidth;\n\n return;\n }\n\n if (isFirstRun) return;\n\n if (prefersReducedMotion) {\n const targetProgress = isResolved ? totalWidth : 0;\n\n progressRef.current = targetProgress;\n updateLines(targetProgress);\n\n return;\n }\n\n startLoop(totalWidth);\n }, [isResolved]);\n\n useEffect(() => {\n const el = contentRect.current;\n if (!el) return;\n\n const observer = new ResizeObserver(() => {\n if (!measuredRef.current) return;\n\n measureLines();\n\n const totalWidth = getTotalLineWidth();\n\n if (directionRef.current === 1 && progressRef.current >= totalWidth) {\n progressRef.current = totalWidth;\n updateLines(totalWidth);\n }\n });\n\n observer.observe(el);\n\n return () => observer.disconnect();\n }, [contentRect]);\n\n useEffect(() => {\n return () => cancelAnimationFrame(animationIdRef.current);\n }, []);\n\n return { lineRects, lineContentRect, getLineByIndexRef };\n}\n"],"mappings":"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","names":["lineRects","lineContentRect"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/CommentItem/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/CommentItem/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAU3C,UAAU,KAAK;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,KAAK,2CA+E5D"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { startTransition } from "react";
|
|
3
|
+
import { startTransition, useRef } from "react";
|
|
4
4
|
import { useTranslation } from "@payloadcms/ui";
|
|
5
|
+
import { CircleCheck } from "lucide-react";
|
|
5
6
|
import { cn } from "../../utils/general/cn";
|
|
6
7
|
import { useComments } from "../../providers/CommentsProvider";
|
|
7
8
|
import { renderCommentText } from "../../utils/comment/renderCommentText";
|
|
@@ -10,10 +11,12 @@ import { FALLBACK_DELETED_USERNAME, FALLBACK_USERNAME } from "../../constants";
|
|
|
10
11
|
import { ToolsPanel } from "./ToolsPanel";
|
|
11
12
|
import { useRelativeDate } from "../../hooks/useRelativeDate";
|
|
12
13
|
import { Avatar } from "../Avatar";
|
|
14
|
+
import { StrikethoroughOverlay } from "./StrikethoroughOverlay";
|
|
13
15
|
function CommentItem({ comment, currentUserId }) {
|
|
14
16
|
const { resolveComment, removeComment, usernameFieldPath } = useComments();
|
|
15
17
|
const { t } = useTranslation();
|
|
16
18
|
const createdAtRelativeDate = useRelativeDate(comment.createdAt);
|
|
19
|
+
const contentRef = useRef(null);
|
|
17
20
|
const deletedUserLabel = t("comments:deletedUser") ?? FALLBACK_DELETED_USERNAME;
|
|
18
21
|
const unknownLabel = t("comments:unknownAuthor") ?? FALLBACK_USERNAME;
|
|
19
22
|
const narrowedAuthor = typeof comment.author === "object" ? comment.author : null;
|
|
@@ -21,6 +24,13 @@ function CommentItem({ comment, currentUserId }) {
|
|
|
21
24
|
const isResolved = comment.isResolved ?? false;
|
|
22
25
|
const authorId = comment.author && typeof comment.author === "object" && "id" in comment.author ? comment.author.id : null;
|
|
23
26
|
const canDelete = currentUserId !== null && authorId === currentUserId;
|
|
27
|
+
const renderedText = renderCommentText({
|
|
28
|
+
text: comment.text,
|
|
29
|
+
mentions: comment.mentions,
|
|
30
|
+
currentUserId,
|
|
31
|
+
usernameFieldPath,
|
|
32
|
+
fallbackDeletedUsername: deletedUserLabel
|
|
33
|
+
});
|
|
24
34
|
const handleToggleResolve = () => {
|
|
25
35
|
startTransition(async () => {
|
|
26
36
|
await resolveComment(comment.id, !isResolved);
|
|
@@ -32,6 +42,31 @@ function CommentItem({ comment, currentUserId }) {
|
|
|
32
42
|
});
|
|
33
43
|
};
|
|
34
44
|
return /* @__PURE__ */ jsxs("div", { className: cn("group relative"), children: [
|
|
45
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
46
|
+
/* @__PURE__ */ jsx(Avatar, { user: narrowedAuthor, usernameFieldPath, fallbackName: unknownLabel }),
|
|
47
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-[13px] text-(--theme-text) truncate", children: authorName }),
|
|
50
|
+
isResolved && /* @__PURE__ */ jsx(CircleCheck, { size: 14, className: "text-green-500 shrink-0", "aria-label": t("comments:resolved") }),
|
|
51
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-(--theme-elevation-450) shrink-0", children: createdAtRelativeDate })
|
|
52
|
+
] }),
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
"p",
|
|
55
|
+
{
|
|
56
|
+
ref: contentRef,
|
|
57
|
+
className: cn(
|
|
58
|
+
"relative m-0 text-[13px] text-(--theme-text) leading-normal whitespace-pre-wrap wrap-break-word transition-opacity motion-reduce:transition-none",
|
|
59
|
+
isResolved && "opacity-60"
|
|
60
|
+
),
|
|
61
|
+
children: [
|
|
62
|
+
isResolved && /* @__PURE__ */ jsx("del", { style: { textDecoration: "none" }, dateTime: comment.resolvedAt ?? void 0, children: renderedText }),
|
|
63
|
+
!isResolved && renderedText,
|
|
64
|
+
/* @__PURE__ */ jsx(StrikethoroughOverlay, { isResolved, contentRef })
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
] })
|
|
69
|
+
] }),
|
|
35
70
|
/* @__PURE__ */ jsx(
|
|
36
71
|
ToolsPanel,
|
|
37
72
|
{
|
|
@@ -40,23 +75,7 @@ function CommentItem({ comment, currentUserId }) {
|
|
|
40
75
|
onDelete: handleDelete,
|
|
41
76
|
onResolve: handleToggleResolve
|
|
42
77
|
}
|
|
43
|
-
)
|
|
44
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
45
|
-
/* @__PURE__ */ jsx(Avatar, { user: narrowedAuthor, usernameFieldPath, fallbackName: unknownLabel }),
|
|
46
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
47
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
|
|
48
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-[13px] text-(--theme-text)", children: authorName }),
|
|
49
|
-
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-(--theme-elevation-450)", children: createdAtRelativeDate })
|
|
50
|
-
] }),
|
|
51
|
-
/* @__PURE__ */ jsx("p", { className: "m-0 text-[13px] leading-normal text-(--theme-text) whitespace-pre-wrap wrap-break-word", children: renderCommentText({
|
|
52
|
-
text: comment.text,
|
|
53
|
-
mentions: comment.mentions,
|
|
54
|
-
currentUserId,
|
|
55
|
-
usernameFieldPath,
|
|
56
|
-
fallbackDeletedUsername: deletedUserLabel
|
|
57
|
-
}) })
|
|
58
|
-
] })
|
|
59
|
-
] })
|
|
78
|
+
)
|
|
60
79
|
] });
|
|
61
80
|
}
|
|
62
81
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/CommentItem/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { startTransition } from \"react\";\nimport { useTranslation } from \"@payloadcms/ui\";\nimport { cn } from \"../../utils/general/cn\";\nimport type { Comment } from \"../../types\";\nimport { useComments } from \"../../providers/CommentsProvider\";\nimport { renderCommentText } from \"../../utils/comment/renderCommentText\";\nimport { resolveUsername } from \"../../utils/user/resolveUsername\";\nimport { FALLBACK_DELETED_USERNAME, FALLBACK_USERNAME } from \"../../constants\";\nimport { ToolsPanel } from \"./ToolsPanel\";\nimport { useRelativeDate } from \"../../hooks/useRelativeDate\";\nimport { Avatar } from \"../Avatar\";\n\ninterface Props {\n comment: Comment;\n currentUserId: number | null;\n}\n\nexport function CommentItem({ comment, currentUserId }: Props) {\n const { resolveComment, removeComment, usernameFieldPath } = useComments();\n const { t } = useTranslation();\n const createdAtRelativeDate = useRelativeDate(comment.createdAt);\n\n const deletedUserLabel = t(\"comments:deletedUser\" as never) ?? FALLBACK_DELETED_USERNAME;\n const unknownLabel = t(\"comments:unknownAuthor\" as never) ?? FALLBACK_USERNAME;\n const narrowedAuthor = typeof comment.author === \"object\" ? comment.author : null;\n const authorName = resolveUsername(narrowedAuthor, usernameFieldPath, unknownLabel);\n\n const isResolved = comment.isResolved ?? false;\n
|
|
1
|
+
{"version":3,"sources":["../../../src/components/CommentItem/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { startTransition, useRef } from \"react\";\nimport { useTranslation } from \"@payloadcms/ui\";\nimport { CircleCheck } from \"lucide-react\";\nimport { cn } from \"../../utils/general/cn\";\nimport type { Comment } from \"../../types\";\nimport { useComments } from \"../../providers/CommentsProvider\";\nimport { renderCommentText } from \"../../utils/comment/renderCommentText\";\nimport { resolveUsername } from \"../../utils/user/resolveUsername\";\nimport { FALLBACK_DELETED_USERNAME, FALLBACK_USERNAME } from \"../../constants\";\nimport { ToolsPanel } from \"./ToolsPanel\";\nimport { useRelativeDate } from \"../../hooks/useRelativeDate\";\nimport { Avatar } from \"../Avatar\";\nimport { StrikethoroughOverlay } from \"./StrikethoroughOverlay\";\n\ninterface Props {\n comment: Comment;\n currentUserId: number | null;\n}\n\nexport function CommentItem({ comment, currentUserId }: Props) {\n const { resolveComment, removeComment, usernameFieldPath } = useComments();\n const { t } = useTranslation();\n const createdAtRelativeDate = useRelativeDate(comment.createdAt);\n\n const contentRef = useRef<HTMLParagraphElement>(null);\n\n const deletedUserLabel = t(\"comments:deletedUser\" as never) ?? FALLBACK_DELETED_USERNAME;\n const unknownLabel = t(\"comments:unknownAuthor\" as never) ?? FALLBACK_USERNAME;\n const narrowedAuthor = typeof comment.author === \"object\" ? comment.author : null;\n const authorName = resolveUsername(narrowedAuthor, usernameFieldPath, unknownLabel);\n\n const isResolved = comment.isResolved ?? false;\n const authorId =\n comment.author && typeof comment.author === \"object\" && \"id\" in comment.author ? comment.author.id : null;\n const canDelete = currentUserId !== null && authorId === currentUserId;\n\n const renderedText = renderCommentText({\n text: comment.text,\n mentions: comment.mentions,\n currentUserId,\n usernameFieldPath,\n fallbackDeletedUsername: deletedUserLabel,\n });\n\n const handleToggleResolve = () => {\n startTransition(async () => {\n await resolveComment(comment.id, !isResolved);\n });\n };\n\n const handleDelete = () => {\n startTransition(async () => {\n await removeComment(comment.id);\n });\n };\n\n return (\n <div className={cn(\"group relative\")}>\n <div className=\"flex gap-2.5 items-start\">\n <Avatar user={narrowedAuthor} usernameFieldPath={usernameFieldPath} fallbackName={unknownLabel} />\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <span className=\"font-semibold text-[13px] text-(--theme-text) truncate\">{authorName}</span>\n\n {isResolved && (\n <CircleCheck size={14} className=\"text-green-500 shrink-0\" aria-label={t(\"comments:resolved\" as never)} />\n )}\n\n <span className=\"text-[11px] text-(--theme-elevation-450) shrink-0\">{createdAtRelativeDate}</span>\n </div>\n\n <p\n ref={contentRef}\n className={cn(\n \"relative m-0 text-[13px] text-(--theme-text) leading-normal whitespace-pre-wrap wrap-break-word transition-opacity motion-reduce:transition-none\",\n isResolved && \"opacity-60\",\n )}>\n {isResolved && (\n <del style={{ textDecoration: \"none\" }} dateTime={comment.resolvedAt ?? undefined}>\n {renderedText}\n </del>\n )}\n {!isResolved && renderedText}\n\n <StrikethoroughOverlay isResolved={isResolved} contentRef={contentRef} />\n </p>\n </div>\n </div>\n\n <ToolsPanel\n isResolved={isResolved}\n canDelete={canDelete}\n onDelete={handleDelete}\n onResolve={handleToggleResolve}\n />\n </div>\n );\n}\n"],"mappings":";AA6DQ,cAGE,YAHF;AA3DR,SAAS,iBAAiB,cAAc;AACxC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,UAAU;AAEnB,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,2BAA2B,yBAAyB;AAC7D,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,6BAA6B;AAO/B,SAAS,YAAY,EAAE,SAAS,cAAc,GAAU;AAC7D,QAAM,EAAE,gBAAgB,eAAe,kBAAkB,IAAI,YAAY;AACzE,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,wBAAwB,gBAAgB,QAAQ,SAAS;AAE/D,QAAM,aAAa,OAA6B,IAAI;AAEpD,QAAM,mBAAmB,EAAE,sBAA+B,KAAK;AAC/D,QAAM,eAAe,EAAE,wBAAiC,KAAK;AAC7D,QAAM,iBAAiB,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS;AAC7E,QAAM,aAAa,gBAAgB,gBAAgB,mBAAmB,YAAY;AAElF,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WACJ,QAAQ,UAAU,OAAO,QAAQ,WAAW,YAAY,QAAQ,QAAQ,SAAS,QAAQ,OAAO,KAAK;AACvG,QAAM,YAAY,kBAAkB,QAAQ,aAAa;AAEzD,QAAM,eAAe,kBAAkB;AAAA,IACrC,MAAM,QAAQ;AAAA,IACd,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA,yBAAyB;AAAA,EAC3B,CAAC;AAED,QAAM,sBAAsB,MAAM;AAChC,oBAAgB,YAAY;AAC1B,YAAM,eAAe,QAAQ,IAAI,CAAC,UAAU;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,MAAM;AACzB,oBAAgB,YAAY;AAC1B,YAAM,cAAc,QAAQ,EAAE;AAAA,IAChC,CAAC;AAAA,EACH;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,gBAAgB,GACjC;AAAA,yBAAC,SAAI,WAAU,4BACb;AAAA,0BAAC,UAAO,MAAM,gBAAgB,mBAAsC,cAAc,cAAc;AAAA,MAEhG,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,SAAI,WAAU,gCACb;AAAA,8BAAC,UAAK,WAAU,0DAA0D,sBAAW;AAAA,UAEpF,cACC,oBAAC,eAAY,MAAM,IAAI,WAAU,2BAA0B,cAAY,EAAE,mBAA4B,GAAG;AAAA,UAG1G,oBAAC,UAAK,WAAU,qDAAqD,iCAAsB;AAAA,WAC7F;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAW;AAAA,cACT;AAAA,cACA,cAAc;AAAA,YAChB;AAAA,YACC;AAAA,4BACC,oBAAC,SAAI,OAAO,EAAE,gBAAgB,OAAO,GAAG,UAAU,QAAQ,cAAc,QACrE,wBACH;AAAA,cAED,CAAC,cAAc;AAAA,cAEhB,oBAAC,yBAAsB,YAAwB,YAAwB;AAAA;AAAA;AAAA,QACzE;AAAA,SACF;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,WAAW;AAAA;AAAA,IACb;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentsDrawer/components/Header.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../../src/components/CommentsDrawer/components/Header.tsx"],"names":[],"mappings":"AAMA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,WAAW,2CAe3C"}
|
|
@@ -1,43 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { LoaderCircleIcon, XIcon } from "lucide-react";
|
|
3
|
+
import { useModal, useTranslation } from "@payloadcms/ui";
|
|
4
|
+
import { XIcon } from "lucide-react";
|
|
6
5
|
import { IconButton } from "../../IconButton";
|
|
7
6
|
function Header({ slug }) {
|
|
8
7
|
const { t } = useTranslation();
|
|
9
8
|
const { closeModal } = useModal();
|
|
10
|
-
|
|
11
|
-
const options = [
|
|
12
|
-
{ label: t("comments:filterOpen"), value: "open" },
|
|
13
|
-
{ label: t("comments:filterResolved"), value: "resolved" },
|
|
14
|
-
{ label: t("comments:filterMentioned"), value: "mentioned" }
|
|
15
|
-
];
|
|
16
|
-
return /* @__PURE__ */ jsxs("header", { className: "sticky top-0 flex items-center gap-3 py-5 bg-(--theme-bg)", children: [
|
|
9
|
+
return /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 flex items-center gap-3 py-5 bg-(--theme-bg)", children: [
|
|
17
10
|
/* @__PURE__ */ jsx("h2", { className: "m-0 text-2xl font-bold", children: t("comments:label") }),
|
|
18
|
-
|
|
19
|
-
LoaderCircleIcon,
|
|
20
|
-
{
|
|
21
|
-
width: 14,
|
|
22
|
-
height: 14,
|
|
23
|
-
className: "text-(--theme-elevation-450) animate-spin shrink-0",
|
|
24
|
-
"aria-label": t("comments:syncingComments")
|
|
25
|
-
}
|
|
26
|
-
),
|
|
27
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
|
|
28
|
-
/* @__PURE__ */ jsx(
|
|
29
|
-
SelectInput,
|
|
30
|
-
{
|
|
31
|
-
name: "filter",
|
|
32
|
-
path: "filter",
|
|
33
|
-
options,
|
|
34
|
-
value: filter,
|
|
35
|
-
onChange: (option) => setFilter(option.value),
|
|
36
|
-
isClearable: false
|
|
37
|
-
}
|
|
38
|
-
),
|
|
39
|
-
/* @__PURE__ */ jsx(IconButton, { onClick: () => closeModal(slug), title: t("comments:close"), children: /* @__PURE__ */ jsx(XIcon, { width: 16, height: 16 }) })
|
|
40
|
-
] })
|
|
11
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 ml-auto", children: /* @__PURE__ */ jsx(IconButton, { onClick: () => closeModal(slug), title: t("comments:close"), children: /* @__PURE__ */ jsx(XIcon, { width: 16, height: 16 }) }) })
|
|
41
12
|
] });
|
|
42
13
|
}
|
|
43
14
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/CommentsDrawer/components/Header.tsx"],"sourcesContent":["\"use client\";\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/CommentsDrawer/components/Header.tsx"],"sourcesContent":["\"use client\";\n\nimport { useModal, useTranslation } from \"@payloadcms/ui\";\nimport { XIcon } from \"lucide-react\";\nimport { IconButton } from \"../../IconButton\";\n\ninterface HeaderProps {\n slug: string;\n}\n\nexport function Header({ slug }: HeaderProps) {\n const { t } = useTranslation();\n const { closeModal } = useModal();\n\n return (\n <header className=\"sticky top-0 z-10 flex items-center gap-3 py-5 bg-(--theme-bg)\">\n <h2 className=\"m-0 text-2xl font-bold\">{t(\"comments:label\" as never)}</h2>\n\n <div className=\"flex items-center gap-2 ml-auto\">\n <IconButton onClick={() => closeModal(slug)} title={t(\"comments:close\" as never)}>\n <XIcon width={16} height={16} />\n </IconButton>\n </div>\n </header>\n );\n}\n"],"mappings":";AAeI,SACE,KADF;AAbJ,SAAS,UAAU,sBAAsB;AACzC,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAMpB,SAAS,OAAO,EAAE,KAAK,GAAgB;AAC5C,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,EAAE,WAAW,IAAI,SAAS;AAEhC,SACE,qBAAC,YAAO,WAAU,kEAChB;AAAA,wBAAC,QAAG,WAAU,0BAA0B,YAAE,gBAAyB,GAAE;AAAA,IAErE,oBAAC,SAAI,WAAU,mCACb,8BAAC,cAAW,SAAS,MAAM,WAAW,IAAI,GAAG,OAAO,EAAE,gBAAyB,GAC7E,8BAAC,SAAM,OAAO,IAAI,QAAQ,IAAI,GAChC,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslation } from "@payloadcms/ui";
|
|
4
|
-
import { MessageSquare } from "lucide-react";
|
|
5
4
|
import { CommentsDrawer } from "../CommentsDrawer";
|
|
6
5
|
import { useCommentsDrawer } from "../../providers/CommentsDrawerProvider";
|
|
7
6
|
import { IconButton } from "../IconButton";
|
|
7
|
+
import { Bell } from "lucide-react";
|
|
8
8
|
function CommentsHeaderButton() {
|
|
9
9
|
const { slug, open } = useCommentsDrawer();
|
|
10
10
|
const { t } = useTranslation();
|
|
11
11
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12
|
-
/* @__PURE__ */ jsx(IconButton, { variant: "neutralSecondary", onClick: () => open(), title: t("comments:openCommentsAria"), children: /* @__PURE__ */ jsx(
|
|
12
|
+
/* @__PURE__ */ jsx(IconButton, { variant: "neutralSecondary", onClick: () => open(), title: t("comments:openCommentsAria"), children: /* @__PURE__ */ jsx(Bell, { size: 16 }) }),
|
|
13
13
|
/* @__PURE__ */ jsx(CommentsDrawer, { slug })
|
|
14
14
|
] });
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/CommentsHeaderButton/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTranslation } from \"@payloadcms/ui\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/components/CommentsHeaderButton/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTranslation } from \"@payloadcms/ui\";\nimport { CommentsDrawer } from \"../CommentsDrawer\";\nimport { useCommentsDrawer } from \"../../providers/CommentsDrawerProvider\";\nimport { IconButton } from \"../IconButton\";\nimport { Bell } from \"lucide-react\";\n\nexport function CommentsHeaderButton() {\n const { slug, open } = useCommentsDrawer();\n const { t } = useTranslation();\n\n return (\n <>\n <IconButton variant=\"neutralSecondary\" onClick={() => open()} title={t(\"comments:openCommentsAria\" as never)}>\n <Bell size={16} />\n </IconButton>\n\n <CommentsDrawer slug={slug} />\n </>\n );\n}\n"],"mappings":";AAaI,mBAEI,KAFJ;AAXJ,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AAEd,SAAS,uBAAuB;AACrC,QAAM,EAAE,MAAM,KAAK,IAAI,kBAAkB;AACzC,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,SACE,iCACE;AAAA,wBAAC,cAAW,SAAQ,oBAAmB,SAAS,MAAM,KAAK,GAAG,OAAO,EAAE,2BAAoC,GACzG,8BAAC,QAAK,MAAM,IAAI,GAClB;AAAA,IAEA,oBAAC,kBAAe,MAAY;AAAA,KAC9B;AAEJ;","names":[]}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
|
+
export interface CollapsibleGroupHandle {
|
|
3
|
+
open: () => void;
|
|
4
|
+
toggle: () => void;
|
|
5
|
+
}
|
|
2
6
|
interface CollapsibleGroupProps {
|
|
3
7
|
groupKey: string;
|
|
4
8
|
label: string;
|
|
5
9
|
children: ReactNode;
|
|
6
10
|
level: "collection" | "document" | "field";
|
|
11
|
+
ref?: RefObject<CollapsibleGroupHandle | null>;
|
|
7
12
|
}
|
|
8
|
-
export declare function CollapsibleGroup({ groupKey, label, children, level }: CollapsibleGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function CollapsibleGroup({ groupKey, label, children, level, ref }: CollapsibleGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
9
14
|
export {};
|
|
10
15
|
//# sourceMappingURL=CollapsibleGroup.d.ts.map
|