UNPKG

@nuxt/ui

Version:

A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.

365 lines (364 loc) 13.6 kB
import { flip, shift, offset, size, autoPlacement, hide, inline } from "@floating-ui/dom"; import { isArrayOfArray } from "./index.js"; export function isMarkInSchema(mark, editor) { if (!editor?.schema) { return false; } const markName = typeof mark === "string" ? mark : mark.name; return editor.schema.spec.marks.get(markName) !== void 0; } export function isNodeTypeSelected(editor, nodeTypes) { if (!editor) { return false; } const { selection } = editor.state; const { $from, to } = selection; return nodeTypes.some((nodeType) => { return editor.state.doc.nodesBetween($from.pos, to, (node) => { return node.type.name === nodeType; }); }); } export function isExtensionAvailable(editor, extensionName) { if (!editor) { return false; } return editor.extensionManager.extensions.some((ext) => ext.name === extensionName); } export function createToggleHandler(name) { const fnName = `toggle${name.charAt(0).toUpperCase()}${name.slice(1)}`; return { canExecute: (editor) => editor.can()[fnName](), execute: (editor) => editor.chain().focus()[fnName](), isActive: (editor) => editor.isActive(name), isDisabled: (editor) => isNodeTypeSelected(editor, ["image"]) || editor.isActive("code") }; } export function createSetHandler(name) { const fnName = `set${name.charAt(0).toUpperCase()}${name.slice(1)}`; return { canExecute: (editor) => editor.can()[fnName](), execute: (editor) => editor.chain().focus()[fnName](), isActive: (editor) => editor.isActive(name), isDisabled: (editor) => isNodeTypeSelected(editor, ["image"]) || editor.isActive("code") }; } export function createSimpleHandler(name) { return { canExecute: (editor) => editor.can()[name](), execute: (editor) => editor.chain()[name](), isActive: () => false, isDisabled: void 0 }; } export function createMarkHandler() { return { canExecute: (editor, cmd) => editor.can().toggleMark(cmd.mark), execute: (editor, cmd) => editor.chain().focus().toggleMark(cmd.mark), isActive: (editor, cmd) => editor.isActive(cmd.mark), isDisabled: (editor, cmd) => !isMarkInSchema(cmd.mark, editor) || isNodeTypeSelected(editor, ["image"]) }; } export function createTextAlignHandler() { return { canExecute: (editor, cmd) => editor.can().setTextAlign(cmd.align), execute: (editor, cmd) => editor.chain().focus().setTextAlign(cmd.align), isActive: (editor, cmd) => editor.isActive({ textAlign: cmd.align }), isDisabled: (editor) => !isExtensionAvailable(editor, "textAlign") || isNodeTypeSelected(editor, ["image", "horizontalRule"]) }; } export function createHeadingHandler() { return { canExecute: (editor, cmd) => editor.can().toggleHeading({ level: cmd.level }), execute: (editor, cmd) => editor.chain().focus().toggleHeading({ level: cmd.level }), isActive: (editor, cmd) => editor.isActive("heading", { level: cmd.level }), isDisabled: (editor) => isNodeTypeSelected(editor, ["image"]) || editor.isActive("code") }; } export function createLinkHandler() { return { canExecute: (editor) => { return editor.can().setLink({ href: "" }) || editor.can().unsetLink(); }, execute: (editor, cmd) => { const chain = editor.chain(); const previousUrl = editor.getAttributes("link").href; if (previousUrl) { return chain.focus().unsetLink(); } if (cmd?.href) { return chain.focus().setLink({ href: cmd.href }); } const href = prompt("Enter the URL:"); if (href) { return chain.focus().setLink({ href }); } return chain; }, isActive: (editor) => editor.isActive("link"), isDisabled: (editor) => { if (!isExtensionAvailable(editor, "link") || isNodeTypeSelected(editor, ["image"])) { return true; } const { selection } = editor.state; return selection.empty && !editor.isActive("link"); } }; } export function createImageHandler() { return { canExecute: (editor) => { return editor.can().setImage({ src: "" }); }, execute: (editor, cmd) => { const chain = editor.chain().focus(); if (cmd?.src) { return chain.setImage({ src: cmd.src }); } const src = prompt("Enter the image URL:"); if (src) { return chain.setImage({ src }); } return chain; }, isActive: (editor) => editor.isActive("image"), isDisabled: (editor) => { return !isExtensionAvailable(editor, "image"); } }; } export function createListHandler(listType) { const fnName = listType === "bulletList" ? "toggleBulletList" : "toggleOrderedList"; return { canExecute: (editor) => { return editor.can()[fnName]() || editor.isActive("bulletList") || editor.isActive("orderedList"); }, execute: (editor) => { const { state } = editor; const { selection } = state; let chain = editor.chain().focus(); if (selection.node) { const node = selection.node; const firstChild = node.firstChild?.firstChild; const lastChild = node.lastChild?.lastChild; const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1; const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1; chain = chain.setTextSelection({ from, to }).clearNodes(); } if (editor.isActive(listType)) { return chain.liftListItem("listItem").lift("bulletList").lift("orderedList").selectTextblockEnd(); } return chain[fnName]().selectTextblockEnd(); }, isActive: (editor) => editor.isActive(listType), isDisabled: (editor) => isNodeTypeSelected(editor, ["image"]) || editor.isActive("code") }; } export function createMoveHandler(direction) { return { canExecute: (editor, cmd) => { if (cmd?.pos == null) return false; const node = editor.state.doc.nodeAt(cmd.pos); if (!node) return false; const $pos = editor.state.doc.resolve(cmd.pos); const parent = $pos.parent; const index = $pos.index(); return direction === "up" ? index > 0 : index < parent.childCount - 1; }, execute: (editor, cmd) => { if (cmd?.pos == null) return editor.chain(); const node = editor.state.doc.nodeAt(cmd.pos); if (!node) return editor.chain(); const $pos = editor.state.doc.resolve(cmd.pos); const parent = $pos.parent; const index = $pos.index(); if (direction === "up" && index > 0) { const prevNode = parent.child(index - 1); const targetPos = cmd.pos - prevNode.nodeSize; return editor.chain().focus().deleteRange({ from: cmd.pos, to: cmd.pos + node.nodeSize }).insertContentAt(targetPos, node.toJSON()); } if (direction === "down" && index < parent.childCount - 1) { const nextNode = parent.child(index + 1); const targetPos = cmd.pos + nextNode.nodeSize; return editor.chain().focus().deleteRange({ from: cmd.pos, to: cmd.pos + node.nodeSize }).insertContentAt(targetPos, node.toJSON()); } return editor.chain(); }, isActive: () => false, isDisabled: void 0 }; } export function createHandlers() { return { mark: createMarkHandler(), textAlign: createTextAlignHandler(), heading: createHeadingHandler(), link: createLinkHandler(), image: createImageHandler(), blockquote: createToggleHandler("blockquote"), bulletList: createListHandler("bulletList"), orderedList: createListHandler("orderedList"), codeBlock: createToggleHandler("codeBlock"), horizontalRule: createSetHandler("horizontalRule"), paragraph: createSetHandler("paragraph"), undo: createSimpleHandler("undo"), redo: createSimpleHandler("redo"), clearFormatting: { canExecute: (editor, cmd) => { if (cmd?.pos != null) { const node = editor.state.doc.nodeAt(cmd.pos); return !!node; } return editor.can().clearNodes() || editor.can().unsetAllMarks(); }, execute: (editor, cmd) => { if (cmd?.pos != null) { const node = editor.state.doc.nodeAt(cmd.pos); if (!node) return editor.chain(); const from = cmd.pos + 1; const to = cmd.pos + node.nodeSize - 1; return editor.chain().focus().setTextSelection({ from, to }).clearNodes().unsetAllMarks(); } return editor.chain().focus().clearNodes().unsetAllMarks(); }, isActive: () => false, isDisabled: void 0 }, duplicate: { canExecute: (editor, cmd) => { if (cmd?.pos == null) return false; const node = editor.state.doc.nodeAt(cmd.pos); return !!node; }, execute: (editor, cmd) => { if (cmd?.pos == null) return editor.chain(); const node = editor.state.doc.nodeAt(cmd.pos); if (!node) return editor.chain(); return editor.chain().focus().insertContentAt(cmd.pos + node.nodeSize, node.toJSON()); }, isActive: () => false, isDisabled: void 0 }, delete: { canExecute: (editor, cmd) => { if (cmd?.pos == null) return false; const node = editor.state.doc.nodeAt(cmd.pos); return !!node; }, execute: (editor, cmd) => { if (cmd?.pos == null) return editor.chain(); const node = editor.state.doc.nodeAt(cmd.pos); if (!node) return editor.chain(); return editor.chain().focus().deleteRange({ from: cmd.pos, to: cmd.pos + node.nodeSize }); }, isActive: () => false, isDisabled: void 0 }, moveUp: createMoveHandler("up"), moveDown: createMoveHandler("down"), suggestion: { canExecute: () => true, execute: (editor, cmd) => { const { state } = editor; const { selection } = state; const { $from } = selection; if (cmd?.pos !== void 0) { const node = state.doc.nodeAt(cmd.pos); if (node) { const insertPos2 = cmd.pos + node.nodeSize; return editor.chain().focus().insertContentAt(insertPos2, { type: "paragraph", content: [{ type: "text", text: "/" }] }); } } const currentNode = $from.node($from.depth); const currentNodePos = $from.before($from.depth); const insertPos = currentNodePos + currentNode.nodeSize; return editor.chain().focus().insertContentAt(insertPos, { type: "paragraph", content: [{ type: "text", text: "/" }] }); }, isActive: () => false, isDisabled: void 0 }, mention: { canExecute: () => true, execute: (editor) => { const { state } = editor; const { selection } = state; const { $from } = selection; const textBefore = $from.parent.textBetween(Math.max(0, $from.parentOffset - 1), $from.parentOffset, void 0, " "); const needsSpace = textBefore && textBefore !== " "; return editor.chain().focus().insertContent(needsSpace ? " @" : "@"); }, isActive: () => false, isDisabled: void 0 }, emoji: { canExecute: () => true, execute: (editor) => { const { state } = editor; const { selection } = state; const { $from } = selection; const textBefore = $from.parent.textBetween(Math.max(0, $from.parentOffset - 1), $from.parentOffset, void 0, " "); const needsSpace = textBefore && textBefore !== " "; return editor.chain().focus().insertContent(needsSpace ? " :" : ":"); }, isActive: () => false, isDisabled: void 0 } }; } export function mapEditorItems(editor, items, customHandlers) { const handlers = { ...createHandlers(), ...customHandlers }; if (isArrayOfArray(items)) { return items.map( (group) => mapEditorItems(editor, group, customHandlers) ); } return items.filter(Boolean).map((item) => { if ("type" in item) { return item; } const { kind, children, ...rest } = item; const processedChildren = children?.length ? mapEditorItems(editor, children, customHandlers) : void 0; if (kind) { const handler = handlers[kind]; if (!handler) { return { ...rest, children: processedChildren }; } return { ...rest, children: processedChildren, disabled: handler.isDisabled?.(editor, item) || !handler.canExecute(editor, item), active: handler.isActive(editor, item), onSelect: () => handler.execute(editor, item).run() }; } return { ...rest, children: processedChildren }; }); } export function buildFloatingUIMiddleware(options) { const middleware = []; if (options.offset) { middleware.push(offset(typeof options.offset !== "boolean" ? options.offset : void 0)); } if (options.flip) { middleware.push(flip(typeof options.flip !== "boolean" ? options.flip : void 0)); } if (options.shift) { middleware.push(shift(typeof options.shift !== "boolean" ? options.shift : void 0)); } if (options.size) { middleware.push(size(typeof options.size !== "boolean" ? options.size : void 0)); } if (options.autoPlacement) { middleware.push(autoPlacement(typeof options.autoPlacement !== "boolean" ? options.autoPlacement : void 0)); } if (options.hide) { middleware.push(hide(typeof options.hide !== "boolean" ? options.hide : void 0)); } if (options.inline) { middleware.push(inline(typeof options.inline !== "boolean" ? options.inline : void 0)); } return middleware; }