demarches-normaliennes/app/javascript/shared/tiptap/editor.ts
simon lehericey 393db312c2
a little js
2024-07-26 13:34:50 +02:00

146 lines
3.6 KiB
TypeScript

import Document from '@tiptap/extension-document';
import Hystory from '@tiptap/extension-history';
import TextAlign from '@tiptap/extension-text-align';
import Gapcursor from '@tiptap/extension-gapcursor';
import Paragraph from '@tiptap/extension-paragraph';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';
import ListItem from '@tiptap/extension-list-item';
import Text from '@tiptap/extension-text';
import Highlight from '@tiptap/extension-highlight';
import Underline from '@tiptap/extension-underline';
import Bold from '@tiptap/extension-bold';
import Italic from '@tiptap/extension-italic';
import Strike from '@tiptap/extension-strike';
import Mention from '@tiptap/extension-mention';
import Typography from '@tiptap/extension-typography';
import Heading from '@tiptap/extension-heading';
import {
Editor,
type EditorOptions,
type JSONContent,
type Extensions
} from '@tiptap/core';
import { DocumentWithHeader, Title, Header, HeaderColumn } from './nodes';
import { createSuggestionMenu, type TagSchema } from './tags';
export function createEditor({
editorElement,
content,
tags,
buttons,
attributes,
onChange
}: {
editorElement: Element;
content?: JSONContent;
tags: TagSchema[];
buttons: string[];
onChange(change: { editor: Editor }): void;
attributes?: Record<string, string>;
}): Editor {
const options = getEditorOptions(
editorElement,
tags,
buttons,
content,
attributes
);
const editor = new Editor(options);
editor.on('transaction', onChange);
return editor;
}
function getEditorOptions(
element: Element,
tags: TagSchema[],
actions: string[],
content?: JSONContent,
attributes?: Record<string, string>
): Partial<EditorOptions> {
const extensions: Extensions = [];
for (const action of actions) {
switch (action) {
case 'bold':
extensions.push(Bold);
break;
case 'italic':
extensions.push(Italic);
break;
case 'underline':
extensions.push(Underline);
break;
case 'strike':
extensions.push(Strike);
break;
case 'highlight':
extensions.push(Highlight);
break;
case 'bulletList':
extensions.push(BulletList);
break;
case 'orderedList':
extensions.push(OrderedList);
break;
case 'title':
extensions.push(Header, HeaderColumn, Title);
break;
}
}
if (actions.includes('bulletList') || actions.includes('orderedList')) {
extensions.push(ListItem);
}
if (actions.includes('heading2') || actions.includes('heading3')) {
extensions.push(Heading.configure({ levels: [2, 3] }));
}
if (
actions.includes('left') ||
actions.includes('center') ||
actions.includes('right') ||
actions.includes('justify')
) {
extensions.push(
TextAlign.configure({
types: actions.includes('title')
? ['headerColumn', 'title', 'heading', 'paragraph']
: ['heading', 'paragraph']
})
);
}
if (tags.length > 0) {
extensions.push(
Mention.configure({
renderLabel({ node }) {
return node.attrs.label;
},
HTMLAttributes: {
class: 'fr-tag fr-tag--sm'
},
suggestion: createSuggestionMenu(tags, element)
})
);
}
return {
element,
content,
editorProps: { attributes },
extensions: [
actions.includes('title') ? DocumentWithHeader : Document,
Hystory,
Typography,
Gapcursor,
Paragraph,
Text,
...extensions
]
};
}