@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
290 lines (289 loc) • 8.94 kB
JavaScript
// 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