Skip to content
File

Blob: src/client/components/editor/extensions/details-block.tsx

typescript60 lines
1import { Extension } from "@tiptap/core";
2import Details, { DetailsContent, DetailsSummary } from "@tiptap/extension-details";
3import { Plugin } from "@tiptap/pm/state";
4import { DEFAULT_DETAILS_SUMMARY, DETAILS_SUMMARY_PLACEHOLDER } from "@/shared/editor/schema";
5import { applyMoveToDetailsContent } from "../controllers/details-block";
6 
7const DetailsBlock = Details.configure({
8 persist: true,
9 HTMLAttributes: { class: "tiptap-details" },
10 renderToggleButton: ({ element, isOpen, node }) => {
11 const summary = node.firstChild?.textContent?.trim() || DEFAULT_DETAILS_SUMMARY;
12 element.className = "tiptap-details-toggle";
13 element.setAttribute("aria-label", `${isOpen ? "Collapse" : "Expand"} details: ${summary}`);
14 },
15});
16 
17const DetailsBlockSummary = DetailsSummary.configure({
18 HTMLAttributes: {
19 class: "tiptap-details-summary",
20 "data-placeholder": DETAILS_SUMMARY_PLACEHOLDER,
21 },
22});
23 
24const DetailsBlockContent = DetailsContent.configure({
25 HTMLAttributes: { class: "tiptap-details-content" },
26});
27 
28const DetailsBlockKeyboardNavigation = Extension.create({
29 name: "detailsBlockKeyboardNavigation",
30 
31 addProseMirrorPlugins() {
32 return [
33 new Plugin({
34 props: {
35 handleDOMEvents: {
36 keydown: (view, event) => {
37 if (event.key !== "Tab" || event.shiftKey) return false;
38 
39 const tr = view.state.tr;
40 if (!applyMoveToDetailsContent(tr)) return false;
41 
42 event.preventDefault();
43 view.dispatch(tr.scrollIntoView());
44 view.focus();
45 return true;
46 },
47 },
48 },
49 }),
50 ];
51 },
52});
53 
54export const DetailsBlockExtensions = [
55 DetailsBlock,
56 DetailsBlockSummary,
57 DetailsBlockContent,
58 DetailsBlockKeyboardNavigation,
59] as const;