UNPKG

@plait/text-plugins

Version:

#### Dependence - `@plait/core`

439 lines (423 loc) 14.7 kB
import { Transforms, Node, Editor, Text, Range, Element } from 'slate'; import { DEFAULT_COLOR, getSelectedElements } from '@plait/core'; import { isKeyHotkey } from 'is-hotkey'; import { getTextEditorsByElement, findFirstTextEditor, getTextEditors } from '@plait/common'; const AlignEditor = { isActive(editor, alignment) { const blockElement = Node.get(editor, defaultPath); if (blockElement) { const { align } = blockElement; return align === alignment; } return false; }, setAlign(editor, alignment) { const props = { align: alignment }; Transforms.setNodes(editor, props, { at: defaultPath }); } }; const defaultPath = [0]; var MarkTypes; (function (MarkTypes) { MarkTypes["bold"] = "bold"; MarkTypes["italic"] = "italic"; MarkTypes["underline"] = "underlined"; MarkTypes["strike"] = "strike"; MarkTypes["color"] = "color"; MarkTypes["fontSize"] = "font-size"; })(MarkTypes || (MarkTypes = {})); const MarkProps = [ MarkTypes.bold, MarkTypes.color, MarkTypes.italic, MarkTypes.strike, MarkTypes.underline, MarkTypes.fontSize ]; var FontSizes; (function (FontSizes) { FontSizes["fontSize12"] = "12"; FontSizes["fontSize13"] = "13"; FontSizes["fontSize14"] = "14"; FontSizes["fontSize15"] = "15"; FontSizes["fontSize16"] = "16"; FontSizes["fontSize18"] = "18"; FontSizes["fontSize20"] = "20"; FontSizes["fontSize24"] = "24"; FontSizes["fontSize28"] = "28"; FontSizes["fontSize32"] = "32"; FontSizes["fontSize40"] = "40"; FontSizes["fontSize48"] = "48"; })(FontSizes || (FontSizes = {})); const HOTKEYS = { 'mod+b': MarkTypes.bold, 'mod+i': MarkTypes.italic, 'mod+u': MarkTypes.underline, 'mod+shift+x': MarkTypes.strike }; const DEFAULT_FONT_SIZE = 14; const PlaitMarkEditor = { getMarks(editor) { const marks = {}; let at = []; if (editor.selection) { at = editor.selection; } else if (editor.children && editor.children.length > 0) { at = { anchor: Editor.start(editor, [0]), focus: Editor.end(editor, [0]) }; } const matchResult = Editor.nodes(editor, { match: Text.isText, at }); for (const match of matchResult) { const [node] = match; const { text, ...rest } = node; Object.assign(marks, rest); } for (const key in marks) { if (!MarkProps.includes(key)) { delete marks[key]; } } return marks; }, getMarksByElement(element) { const marks = {}; const texts = Node.texts(element); for (const match of texts) { const [node] = match; const { text, ...rest } = node; Object.assign(marks, rest); } for (const key in marks) { if (!MarkProps.includes(key)) { delete marks[key]; } } return marks; }, isMarkActive(editor, format) { if (!editor?.selection) { return; } const node = Node.get(editor, editor?.selection?.anchor?.path); if (!Text.isText(node)) { return false; } const marks = PlaitMarkEditor.getMarks(editor); return marks && marks[format] ? true : false; }, toggleMark(editor, format) { setSelection(editor); const isActive = PlaitMarkEditor.isMarkActive(editor, format); if (isActive) { Editor.removeMark(editor, format); } else { Editor.addMark(editor, format, true); } }, setFontSizeMark(editor, size, defaultSize = DEFAULT_FONT_SIZE) { setSelection(editor); // set paragraph text fontSize if (Number(size) === defaultSize) { Editor.removeMark(editor, MarkTypes.fontSize); } else { // set paragraph text fontSize Editor.addMark(editor, MarkTypes.fontSize, Number(size)); } }, setColorMark(editor, color, defaultTextColor = DEFAULT_COLOR) { setSelection(editor); if (color === defaultTextColor || color === null || color === undefined) { Editor.removeMark(editor, 'color'); } else { Editor.addMark(editor, 'color', color); } } }; function setSelection(editor) { if (!editor.selection) { Transforms.select(editor, [0]); } } const withMark = (editor) => { const e = editor; e.removeMark = (key, shouldChange = true) => { const { selection } = e; if (selection) { if (Range.isExpanded(selection)) { Transforms.unsetNodes(e, key, { match: Text.isText, split: true }); } else { const marks = { ...(Editor.marks(e) || {}) }; delete marks[key]; editor.marks = marks; const text = Editor.string(e, selection.anchor.path); if (text !== '') { Editor.setNormalizing(editor, false); e.insertText(''); editor.marks = marks; Editor.setNormalizing(editor, true); } else { Transforms.unsetNodes(e, key, { at: selection.anchor.path }); } if (shouldChange) { editor.onChange(); } } } }; e.addMark = (key, value) => { const { selection } = editor; if (selection) { if (Range.isExpanded(selection)) { Transforms.setNodes(e, { [key]: value }, { match: Text.isText, split: true }); } else { const marks = { ...(Editor.marks(e) || {}), [key]: value }; editor.marks = marks; const text = Editor.string(e, selection.anchor.path); if (text !== '') { Editor.setNormalizing(editor, false); e.insertText(''); editor.marks = marks; Editor.setNormalizing(editor, true); } else { Transforms.setNodes(e, { [key]: value }, { at: selection.anchor.path }); } } } }; return e; }; const markShortcuts = (editor, event) => { for (const hotkey in HOTKEYS) { if (isKeyHotkey(hotkey, event)) { event.preventDefault(); const mark = HOTKEYS[hotkey]; PlaitMarkEditor.toggleMark(editor, mark); } } }; const LinkEditor = { wrapLink(editor, text, url) { if (LinkEditor.isLinkActive(editor)) { LinkEditor.unwrapLink(editor); } const { selection } = editor; const isCollapsed = selection && Range.isCollapsed(selection); const link = { type: 'link', url, children: [{ text }] }; if (isCollapsed || Node.string(editor) === '') { Transforms.insertNodes(editor, link); } else if (!selection) { const at = { anchor: Editor.start(editor, [0]), focus: Editor.end(editor, [0]) }; Transforms.wrapNodes(editor, link, { split: true, at }); } else { Transforms.wrapNodes(editor, link, { split: true }); Transforms.collapse(editor, { edge: 'end' }); } }, unwrapLink(editor, at) { if (!at) { at = editor.selection; if (!at && editor.children && editor.children.length > 0) { at = { anchor: Editor.start(editor, [0]), focus: Editor.end(editor, [0]) }; } } Transforms.unwrapNodes(editor, { at, match: n => Element.isElement(n) && n.type === 'link' }); }, isLinkActive(editor) { let at = editor.selection; if (!at && editor.children && editor.children.length > 0) { at = { anchor: Editor.start(editor, [0]), focus: Editor.end(editor, [0]) }; } const [link] = Editor.nodes(editor, { match: n => Element.isElement(n) && n.type === 'link', at }); return !!link; }, getLinkElement(editor) { let at = editor.selection; if (!at && editor.children && editor.children.length > 0) { at = { anchor: Editor.start(editor, [0]), focus: Editor.end(editor, [0]) }; } const [link] = Editor.nodes(editor, { match: n => Element.isElement(n) && n.type === 'link', at }); return link; } }; const withLink = (editor) => { const { isInline, normalizeNode } = editor; editor.isInline = (element) => { return element.type === 'link' ? true : isInline(element); }; editor.normalizeNode = (nodeEntry) => { const node = nodeEntry[0]; const path = nodeEntry[1]; if (node.type && node.type === 'link' && Node.string(node) === '') { Transforms.removeNodes(editor, { at: path }); return; } normalizeNode(nodeEntry); }; return editor; }; const TEXT_DEFAULT_HEIGHT = 20; const CLIPBOARD_FORMAT_KEY = 'x-plait-text-fragment'; const getTextFromClipboard = (data) => { let plaitTextData = data?.getData(`application/${CLIPBOARD_FORMAT_KEY}`); const text = (data ? data.getData(`text/plain`) : ''); if (plaitTextData) { const decoded = decodeURIComponent(window.atob(plaitTextData)); const res = JSON.parse(decoded); if (res.length === 1 && Node.string(res[0])) { return res[0]; } } return text.trim() || ''; }; // credit: https://github.com/segmentio/is-url // support mailto: protocol function isUrl(string) { const protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/; const emailProtocolRE = /^mailto:([^\s@]+@[^\s@]+\.[^\s@]+)$/; const localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/; const nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/; if (typeof string !== 'string') { return false; } // 检查是否是 mailto: 协议 const emailMatch = string.match(emailProtocolRE); if (emailMatch) { // 简单验证 email 地址格式 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailMatch[1]); } // 原有的 URL 验证逻辑 const match = string.match(protocolAndDomainRE); if (!match) { return false; } const everythingAfterProtocol = match[1]; if (!everythingAfterProtocol) { return false; } if (localhostDomainRE.test(everythingAfterProtocol) || nonLocalhostDomainRE.test(everythingAfterProtocol)) { return true; } return false; } const getTextMarksByElement = (element) => { const editors = getTextEditorsByElement(element); const editor = editors[0]; if (!editor || editor.children.length === 0) { return {}; } const currentMarks = PlaitMarkEditor.getMarks(editor); return currentMarks; }; const setTextMarks = (board, mark, editors) => { let textEditors; if (editors?.length) { textEditors = editors; } else { const selectedElements = getSelectedElements(board); if (selectedElements.length) { const firstEditor = findFirstTextEditor(board); if (!firstEditor) { return; } const activeMarks = PlaitMarkEditor.getMarks(firstEditor); const elements = selectedElements.filter(element => { const elementEditors = getTextEditorsByElement(element); return elementEditors.some(editor => { const elementMarks = PlaitMarkEditor.getMarks(editor); return elementMarks[mark] === activeMarks[mark]; }); }); textEditors = getTextEditors(board, elements); } } if (textEditors && textEditors.length) { textEditors.forEach(editor => { PlaitMarkEditor.toggleMark(editor, mark); }); } }; const setFontSize = (board, size, defaultFontSize, editors) => { const textEditors = getHandleTextEditors(board, editors); if (textEditors && textEditors.length) { const selectedElements = getSelectedElements(board); textEditors.forEach(editor => { let finalDefaultFontSize; if (typeof defaultFontSize === 'function') { const element = selectedElements.find(element => { const textEditors = getTextEditorsByElement(element); return textEditors.includes(editor); }); finalDefaultFontSize = defaultFontSize(element); } else { finalDefaultFontSize = defaultFontSize; } PlaitMarkEditor.setFontSizeMark(editor, size, finalDefaultFontSize); }); } }; const setTextColor = (board, color, textSelection, editors) => { const textEditors = getHandleTextEditors(board, editors); if (textEditors && textEditors.length) { textEditors.forEach(editor => { if (textSelection) { Transforms.select(editor, textSelection); } if (color === 'transparent') { Editor.removeMark(editor, MarkTypes.color); } else { PlaitMarkEditor.setColorMark(editor, color); } }); } }; const setTextAlign = (board, align, editors) => { const textEditors = getHandleTextEditors(board, editors); if (textEditors && textEditors.length) { textEditors.forEach(editor => AlignEditor.setAlign(editor, align)); } }; const getHandleTextEditors = (board, editors) => { let textEditors; if (editors?.length) { textEditors = editors; } else { textEditors = getTextEditors(board); } return textEditors; }; const TextTransforms = { setTextAlign, setTextColor, setFontSize, setTextMarks }; /* * Public API Surface of utils */ /** * Generated bundle index. Do not edit. */ export { AlignEditor, CLIPBOARD_FORMAT_KEY, DEFAULT_FONT_SIZE, FontSizes, HOTKEYS, LinkEditor, MarkProps, MarkTypes, PlaitMarkEditor, TEXT_DEFAULT_HEIGHT, TextTransforms, getTextFromClipboard, getTextMarksByElement, isUrl, markShortcuts, setSelection, withLink, withMark }; //# sourceMappingURL=plait-text-plugins.mjs.map