import type { NodeWithPos } from '@tiptap/core' import type { EditorState, Transaction } from '@tiptap/pm/state' import { getSelectedNodesOfType } from '@admin/utils/editor/get-selected-nodes-of-type' import { updateNodesAttr } from '@admin/utils/editor/update-nodes-attr' import { Extension } from '@tiptap/core' declare module '@tiptap/core' { interface Commands { nodeBackground: { setNodeBackgroundColor: (backgroundColor: string) => ReturnType unsetNodeBackgroundColor: () => ReturnType toggleNodeBackgroundColor: (backgroundColor: string) => ReturnType } } } export interface NodeBackgroundOptions { types: string[] useStyle?: boolean } function getToggleColor(targets: NodeWithPos[], inputColor: string): string | null { if (targets.length === 0) return null for (const target of targets) { const currentColor = target.node.attrs?.backgroundColor ?? null if (currentColor !== inputColor) { return inputColor } } return null } export const NodeBackground = Extension.create({ name: 'nodeBackground', addOptions() { return { types: [ 'paragraph', 'heading', 'blockquote', 'taskList', 'bulletList', 'orderedList', 'tableCell', 'tableHeader' ], useStyle: true } }, addGlobalAttributes() { return [ { types: this.options.types, attributes: { backgroundColor: { default: null as string | null, parseHTML: (element: HTMLElement) => { const styleColor = element.style?.backgroundColor if (styleColor) return styleColor const dataColor = element.getAttribute('data-background-color') return dataColor || null }, renderHTML: (attributes) => { const color = attributes.backgroundColor as string | null if (!color) return {} if (this.options.useStyle) { return { style: `background-color: ${color}` } } else { return { 'data-background-color': color } } } } } } ] }, addCommands() { const executeBackgroundCommand = ( getTargetColor: (targets: NodeWithPos[], inputColor?: string) => string | null ) => { return (inputColor?: string) => ({ state, tr }: { state: EditorState; tr: Transaction }) => { const targets = getSelectedNodesOfType(state.selection, this.options.types) if (targets.length === 0) return false const targetColor = getTargetColor(targets, inputColor) return updateNodesAttr(tr, targets, 'backgroundColor', targetColor) } } return { setNodeBackgroundColor: executeBackgroundCommand((_, inputColor) => inputColor || null), unsetNodeBackgroundColor: executeBackgroundCommand(() => null), toggleNodeBackgroundColor: executeBackgroundCommand((targets, inputColor) => getToggleColor(targets, inputColor || '') ) } } })