import type { Editor } from '@tiptap/core' import '../../assets/src/blogeditor/index' import { BlogEditorElement, TAG } from '../../assets/src/blogeditor/index' export interface Mounted { el: BlogEditorElement editor: Editor changes: string[] uploads: { id: string; file: File }[] } /** mount creates a connected editor element, optionally with a value. */ export function mount(value?: string, attributes: Record = {}): Mounted { const el = document.createElement(TAG) as BlogEditorElement for (const [name, val] of Object.entries(attributes)) el.setAttribute(name, val) const changes: string[] = [] const uploads: { id: string; file: File }[] = [] el.addEventListener('summer-change', (e) => changes.push((e as CustomEvent<{ value: string }>).detail.value)) el.addEventListener('summer-upload', (e) => uploads.push((e as CustomEvent<{ id: string; file: File }>).detail)) if (value !== undefined) el.value = value document.body.append(el) return { el, editor: el.editor!, changes, uploads } } /** type inserts text at the selection, as typing does. */ export function type(editor: Editor, text: string): void { editor.view.dispatch(editor.state.tr.insertText(text)) } /** insertAtBlock types text at the start of the top-level textblock index. */ export function insertAtBlock(editor: Editor, index: number, text: string): void { let pos = -1 editor.state.doc.forEach((node, offset, i) => { if (i === index) pos = node.isTextblock ? offset + 1 : -1 }) if (pos < 0) throw new Error(`block ${index} is not a textblock`) editor.view.dispatch(editor.state.tr.insertText(text, pos)) } /** insertAtEveryTextblock types text at the start of every textblock. */ export function insertAtEveryTextblock(editor: Editor, text: string): void { const positions: number[] = [] editor.state.doc.descendants((node, pos) => { if (node.isTextblock) positions.push(pos + 1) return true }) let tr = editor.state.tr for (const pos of positions.reverse()) tr = tr.insertText(text, pos) editor.view.dispatch(tr) } /** button returns the toolbar button for a command. */ export function button(el: HTMLElement, command: string): HTMLButtonElement { const found = el.querySelector(`button[data-command="${command}"]`) if (!found) throw new Error(`no toolbar button ${command}`) return found } /** form returns the inline form of a kind, or null when it is closed. */ export function form(el: HTMLElement, kind: string): HTMLElement | null { const found = el.querySelector(`[data-form="${kind}"]`) return found && !found.hidden ? found : null } export function click(el: Element | null | undefined): void { if (!el) throw new Error('nothing to click') ;(el as HTMLElement).click() } export function fill(input: Element | null | undefined, value: string): void { if (!input) throw new Error('no input') ;(input as HTMLInputElement).value = value input.dispatchEvent(new Event('input', { bubbles: true })) } export function status(el: HTMLElement): string { return el.querySelector('[role="status"]')?.textContent ?? '' }