UNPKG

@wordpress/editor

Version:
290 lines (289 loc) 8.94 kB
// packages/editor/src/components/collab-sidebar/note-thread.js import clsx from "clsx"; import { useEffect, useRef } from "@wordpress/element"; import { Button } from "@wordpress/components"; import { Stack } from "@wordpress/ui"; import { useDebounce } from "@wordpress/compose"; import { __, _n, sprintf } from "@wordpress/i18n"; import { useDispatch } from "@wordpress/data"; import { __unstableStripHTML as stripHTML } from "@wordpress/dom"; import { store as blockEditorStore, privateApis as blockEditorPrivateApis } from "@wordpress/block-editor"; import { AddNote } from "./add-note.mjs"; import { Note } from "./note.mjs"; import { NoteCard } from "./note-card.mjs"; import { NoteForm } from "./note-form.mjs"; import { FloatingContainer } from "./floating-container.mjs"; import { focusNoteThread, getNoteExcerpt } from "./utils.mjs"; import { store as editorStore } from "../../store/index.mjs"; import { unlock } from "../../lock-unlock.mjs"; import { jsx, jsxs } from "react/jsx-runtime"; var { useBlockElement } = unlock(blockEditorPrivateApis); function NoteThread({ note, onEditNote, onAddReply, onDeleteNote, isSelected, sidebarRef, floating, onKeyDown }) { const isFloating = !!floating; const { toggleBlockHighlight, selectBlock, toggleBlockSpotlight } = unlock( useDispatch(blockEditorStore) ); const { selectNote } = unlock(useDispatch(editorStore)); const relatedBlockElement = useBlockElement(note.blockClientId); const debouncedToggleBlockHighlight = useDebounce( toggleBlockHighlight, 50 ); const floatingRef = useRef(null); const isKeyboardTabbingRef = useRef(false); const registerThread = floating?.registerThread; const unregisterThread = floating?.unregisterThread; useEffect(() => { const floatingEl = floatingRef.current; if (floatingEl && registerThread) { registerThread(note.id, relatedBlockElement, floatingEl); } return () => unregisterThread?.(note.id); }, [relatedBlockElement, note.id, registerThread, unregisterThread]); const onMouseEnter = () => { debouncedToggleBlockHighlight(note.blockClientId, true); }; const onMouseLeave = () => { debouncedToggleBlockHighlight(note.blockClientId, false); }; const onFocus = () => { toggleBlockHighlight(note.blockClientId, true); }; const onBlur = (event) => { if (!document.hasFocus()) { return; } const isNoteFocused = event.relatedTarget?.closest( ".editor-collab-sidebar-panel__thread" ); const isDialogFocused = event.relatedTarget?.closest('[role="dialog"]'); const isTabbing = isKeyboardTabbingRef.current; if (isNoteFocused && !isTabbing) { return; } if (isDialogFocused) { return; } if (isTabbing && event.currentTarget.contains(event.relatedTarget)) { return; } toggleBlockHighlight(note.blockClientId, false); unselectNote(); }; const handleNoteSelect = () => { selectNote(note.id); toggleBlockSpotlight(note.blockClientId, true); if (!!note.blockClientId) { selectBlock(note.blockClientId, null); } }; const unselectNote = () => { selectNote(void 0); toggleBlockSpotlight(note.blockClientId, false); }; const handleResolve = () => { onEditNote({ id: note.id, status: "approved" }); unselectNote(); if (isFloating) { relatedBlockElement?.focus(); } else { focusNoteThread(note.id, sidebarRef.current); } }; const allReplies = note?.reply || []; const lastReply = allReplies.length > 0 ? allReplies[allReplies.length - 1] : void 0; const restReplies = allReplies.length > 0 ? allReplies.slice(0, -1) : []; const noteExcerpt = getNoteExcerpt( stripHTML(note.content?.rendered), 10 ); const ariaLabel = !!note.blockClientId ? sprintf( // translators: %s: note excerpt __("Note: %s"), noteExcerpt ) : sprintf( // translators: %s: note excerpt __("Original block deleted. Note: %s"), noteExcerpt ); if (isFloating && note.id === "new") { return /* @__PURE__ */ jsx( AddNote, { onSubmit: onAddReply, sidebarRef, floating: { y: floating.y, ref: floatingRef } } ); } return /* @__PURE__ */ jsxs( FloatingContainer, { floating: isFloating ? { y: floating.y, ref: floatingRef } : void 0, className: clsx("editor-collab-sidebar-panel__thread", { "is-selected": isSelected }), id: `note-thread-${note.id}`, gap: "md", onClick: handleNoteSelect, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyUp: (event) => { if (event.key === "Tab") { isKeyboardTabbingRef.current = false; } }, onKeyDown: (event) => { if (event.key === "Tab") { isKeyboardTabbingRef.current = true; } else { onKeyDown(event); } }, tabIndex: 0, role: "treeitem", "aria-label": ariaLabel, "aria-expanded": isSelected, children: [ /* @__PURE__ */ jsx( Button, { className: "editor-collab-sidebar-panel__skip-to-note", variant: "secondary", size: "compact", onClick: () => { focusNoteThread(note.id, sidebarRef.current, "textarea"); }, children: __("Add new reply") } ), !note.blockClientId && /* @__PURE__ */ jsx("p", { className: "editor-collab-sidebar-panel__deleted-block-notice", children: __("Original block deleted.") }), /* @__PURE__ */ jsx( Note, { note, isSelected, onEditNote, onDeleteNote, onResolve: handleResolve } ), isSelected && allReplies.map((reply) => /* @__PURE__ */ jsx( Note, { note: reply, parentNote: note, isSelected, onEditNote, onDeleteNote }, reply.id )), !isSelected && restReplies.length > 0 && /* @__PURE__ */ jsx( Stack, { direction: "row", align: "center", justify: "space-between", className: "editor-collab-sidebar-panel__more-reply-separator", children: /* @__PURE__ */ jsx( Button, { size: "compact", variant: "tertiary", className: "editor-collab-sidebar-panel__more-reply-button", onClick: () => { selectNote(note.id); focusNoteThread(note.id, sidebarRef.current); }, children: sprintf( // translators: %s: number of replies. _n( "%s more reply", "%s more replies", restReplies.length ), restReplies.length ) } ) } ), !isSelected && lastReply && /* @__PURE__ */ jsx( Note, { note: lastReply, parentNote: note, isSelected: false, onEditNote, onDeleteNote } ), isSelected && /* @__PURE__ */ jsx(NoteCard, { role: "treeitem", children: /* @__PURE__ */ jsx( NoteForm, { onSubmit: (inputComment) => { if ("approved" === note.status) { onEditNote({ id: note.id, status: "hold", content: inputComment }); } else { onAddReply({ content: inputComment, parent: note.id }); } }, onCancel: (event) => { event.stopPropagation(); unselectNote(); focusNoteThread(note.id, sidebarRef.current); }, labels: { submit: "approved" === note.status ? __("Reopen & Reply") : __("Reply"), input: sprintf( // translators: %1$s: note identifier, %2$s: author name __("Reply to note %1$s by %2$s"), note.id, note.author_name ) } } ) }), !!note.blockClientId && /* @__PURE__ */ jsx( Button, { className: "editor-collab-sidebar-panel__skip-to-block", variant: "secondary", size: "compact", onClick: (event) => { event.stopPropagation(); relatedBlockElement?.focus(); }, children: __("Back to block") } ) ] } ); } export { NoteThread }; //# sourceMappingURL=note-thread.mjs.map