@plait/text-plugins
Version:
#### Dependence - `@plait/core`
439 lines (423 loc) • 14.7 kB
JavaScript
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