UNPKG

@edtr-io/plugin-text

Version:
1,721 lines (1,527 loc) 97.4 kB
import { serializedScalar } from '@edtr-io/plugin'; import { Data, Range, Value } from 'slate'; import Html from 'slate-html-serializer'; import { createElement, Fragment, Component, forwardRef, useContext, createContext, useRef, useState, useEffect, useCallback, useMemo } from 'react'; import { HotKeys, useScopedStore } from '@edtr-io/core'; import { wrap, getParent, getDocument, unwrap, replace, mayRemoveChild, insertChildAfter, removeChild, mayInsertChild, getPlugins, getFocusTree, findPreviousNode, focusPrevious, change, findNextNode, focusNext, getFocusPath, getPlugin } from '@edtr-io/store'; import isHotkey$1, { isHotkey } from 'is-hotkey'; import { find, times, findIndex, init, findLastIndex } from 'ramda'; import { getEventTransfer, Editor } from 'slate-react'; import { RegistryContext } from '@edtr-io/plugin-rows/internal'; import { styled, EdtrIcon, edtrClose, edtrColorText, Icon, faTrashAlt, faExternalLinkAlt, edtrBold, edtrItalic, edtrLink, edtrText, edtrListNumbered, edtrListBullets, edtrQuote, edtrFormula, faCode, merge, useTheme } from '@edtr-io/ui'; import { HoverOverlay, styled as styled$1, EditorBottomToolbar } from '@edtr-io/editor-ui'; import { setDefaultPreference, PreferenceContext } from '@edtr-io/core/beta'; import { MathEditor } from '@edtr-io/math'; import List from '@convertkit/slate-lists'; var _inlines, _blocks; var paragraphNode = 'paragraph'; var orderedListNode = 'ordered-list'; var unorderedListNode = 'unordered-list'; var listItemNode = 'list-item'; var listItemChildNode = 'list-item-child'; var linkNode = '@splish-me/a'; var colorMark = '@splish-me/color'; var strongMark = '@splish-me/strong'; var emphasizeMark = '@splish-me/em'; var katexInlineNode = '@splish-me/katex-inline'; var codeMark = 'code'; var katexBlockNode = '@splish-me/katex-block'; var createHeadingNode = function createHeadingNode(level) { return "@splish-me/h" + level; }; var defaultNode = paragraphNode; var slateSchema = { inlines: (_inlines = {}, _inlines[katexInlineNode] = { isVoid: true }, _inlines[linkNode] = { text: /.+/ }, _inlines), blocks: (_blocks = {}, _blocks[katexBlockNode] = { isVoid: true }, _blocks) }; var emptyDocument = { document: { nodes: [{ object: 'block', type: defaultNode, nodes: [{ object: 'text' }] }] } }; var rules = [// text { serialize: function serialize(obj, children) { if (obj.object === 'string') { // our state should contain no newline characters return children.replace(new RegExp('[\\r\\n]', 'g'), ''); } }, deserialize: function deserialize(el) { if (el.tagName && el.tagName.toLowerCase() === 'br') { return null; } if (el.nodeName === '#text') { if (el.nodeValue && /<!--(.|\n)*?-->/.exec(el.nodeValue)) return; var text = el.nodeValue ? el.nodeValue : ''; // sanitize spurious newlines (and whitespace?) return { object: 'text', text: text.replace(new RegExp('[\\r\\n\\t]', 'g'), ''), marks: [] }; } } }, // paragraph { serialize: function serialize(obj, children) { var block = obj; if (block.object === 'block' && (block.type === paragraphNode || block.type === '@splish-me/p')) { return createElement("p", null, children); } }, deserialize: function deserialize(el, next) { if (el.tagName.toLowerCase() === 'p') { return { object: 'block', type: paragraphNode, nodes: next(el.childNodes) }; } } }, // rich text { serialize: function serialize(obj, children) { var mark = obj; if (mark.object === 'mark') { switch (mark.type) { case strongMark: return createElement("strong", null, children); case emphasizeMark: return createElement("em", null, children); default: return createElement(Fragment, null, children); } } }, deserialize: function deserialize(el, next) { switch (el.tagName.toLowerCase()) { case 'strong': case 'b': return { object: 'mark', type: strongMark, nodes: next(el.childNodes) }; case 'em': case 'i': return { object: 'mark', type: emphasizeMark, nodes: next(el.childNodes) }; default: return; } } }, // link { serialize: function serialize(obj, children) { var block = obj; if (block.object === 'inline' && block.type === linkNode) { var data = block.data; if (!data) { return null; } return createElement("a", { href: data.get('href') }, children); } }, deserialize: function deserialize(el, next) { if (el.tagName.toLowerCase() === 'a') { var href = el.getAttribute('href'); return { object: 'inline', type: linkNode, nodes: next(el.childNodes), data: Data.create({ href: href ? href : '' }) }; } } }, // headings { serialize: function serialize(obj, children) { var block = obj; if (block.object === 'block') { for (var i = 1; i <= 6; i++) { var headingNode = createHeadingNode(i); if (block.type === headingNode) { return createElement("h" + i, {}, children); } } } }, deserialize: function deserialize(el, next) { var match = /h([1-6])/.exec(el.tagName.toLowerCase()); if (match) { var level = parseInt(match[1], 10); console.log('create heading', level); return { object: 'block', type: createHeadingNode(level), nodes: next(el.childNodes) }; } } }, // lists { serialize: function serialize(obj, children) { var block = obj; if (block.object === 'block') { switch (block.type) { case unorderedListNode: return createElement("ul", null, children); case orderedListNode: return createElement("ol", null, children); case listItemNode: return createElement("li", null, children); case listItemChildNode: return createElement(Fragment, null, children); } } }, deserialize: function deserialize(el, next) { switch (el.tagName.toLowerCase()) { case 'ol': return { object: 'block', type: orderedListNode, nodes: next(el.childNodes) }; case 'ul': return { object: 'block', type: unorderedListNode, nodes: next(el.childNodes) }; case 'li': return { object: 'block', type: listItemNode, nodes: [{ object: 'block', type: listItemChildNode, nodes: next(el.childNodes) }] }; } } }, // edtr-io specific: katex { serialize: function serialize(obj, _children) { var block = obj; if (block.object === 'block' && block.type === katexBlockNode) { // @ts-expect-error, custom tag return createElement("katexblock", null, block.data.get('formula')); } var inline = obj; if (inline.object === 'inline' && inline.type === katexInlineNode) { // @ts-expect-error, custom tag return createElement("katexinline", null, block.data.get('formula')); } }, deserialize: function deserialize(el, next) { switch (el.tagName.toLowerCase()) { case 'katexblock': return { object: 'block', type: katexBlockNode, data: { formula: el.childNodes[0].nodeValue, inline: false }, nodes: next(el.childNodes) }; case 'katexinline': return { object: 'inline', type: katexInlineNode, data: { formula: el.childNodes[0].nodeValue, inline: true }, nodes: next(el.childNodes) }; default: return; } } }, // edtr-io specific: color { serialize: function serialize(obj, children) { var mark = obj; if (mark.object === 'mark' && mark.type === colorMark) { var colorIndex = mark.data.get('colorIndex'); // @ts-expect-error, custom tag return createElement("color", { index: colorIndex }, children); } }, deserialize: function deserialize(el, next) { if (el.tagName.toLowerCase() === 'color') { var colorIndex = el.getAttribute('index'); return { object: 'mark', type: linkNode, nodes: next(el.childNodes), data: Data.create({ colorIndex: parseInt(colorIndex ? colorIndex : '') }) }; } } }]; var serializer = /*#__PURE__*/new Html({ rules: rules, defaultBlock: { type: defaultNode } }); /** * @param html - The HTML string that should be deserialized to a Slate value * @internal */ function htmlToSlateValue(html) { return serializer.deserialize(html, { toJSON: false }); } /** * @param value - The Slate value that should be serialized as HTML * @internal */ function slateValueToHtml(value) { return serializer.serialize(value, { render: true }); } /** * @param value - Current {@link https://docs.slatejs.org/v/v0.47/slate-core/value | value} * @public */ function isValueEmpty(value) { // check if there is no content and only one node var block = value.document.nodes.get(0); if (value.document.text !== '' || value.document.nodes.size !== 1 || value.document.getTexts().size !== 1 || block === undefined) { return false; } // check if the node is the default node return block.object !== 'text' && block.type === defaultNode; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function getActiveMarks(editor) { return editor.value.document.getActiveMarksAtRange(getTrimmedSelectionRange(editor)); } function trimSelection(editor) { // Trim selection before applying transformation var selection = document.getSelection(); if (selection) { var str = selection.toString(); while (str.startsWith(' ')) { editor.moveStartForward(1); str = str.substring(1); } while (str.endsWith(' ')) { editor.moveEndBackward(1); str = str.substring(0, str.length - 1); } } } function getTrimmedSelectionRange(editor) { // get trimmed selection, without changing editor (e.g. for checking active marks) var _native = document.getSelection(); var selection = editor.value.selection.toRange(); if (_native) { var str = _native.toString(); while (str.startsWith(' ')) { selection = selection.moveStartForward(1); str = str.substring(1); } while (str.endsWith(' ')) { selection = selection.moveEndBackward(1); str = str.substring(0, str.length - 1); } } return Range.create(selection); } var getActiveMarks$1 = function getActiveMarks(editor) { return editor.value.document.getActiveMarksAtRange(getTrimmedSelectionRange(editor)); }; var createIsColor = function createIsColor(colorIndex) { return function (editor) { return getActiveMarks$1(editor).some(function (mark) { if (!mark) { return false; } if (typeof colorIndex === 'undefined') { return mark.type === colorMark; } return mark.type === colorMark && mark.data.get('colorIndex') == colorIndex; }); }; }; var removeColor = function removeColor(editor) { return editor.value.marks.toArray().filter(function (mark) { return mark.type === colorMark; }).reduce(function (changedEditor, mark) { return editor.removeMark(mark); }, editor); }; var createToggleColor = function createToggleColor(colorIndex) { return function (editor) { if (createIsColor(colorIndex)(editor)) { return removeColor(editor); } return removeColor(editor).addMark({ type: colorMark, data: { colorIndex: colorIndex } }); }; }; var getColorIndex = function getColorIndex(editor) { if (!createIsColor()(editor)) { return undefined; } else { var mark = getActiveMarks$1(editor).find(function (mark) { return mark ? mark.type === colorMark : false; }); return mark == null ? void 0 : mark.data.get('colorIndex'); } }; var Color = /*#__PURE__*/styled.span(function (_ref) { var config = _ref.config, colorIndex = _ref.colorIndex; var theme = config.theme; var colors = theme.plugins.colors.colors; return { color: colors[colorIndex % colors.length] }; }); var DefaultEditorComponent = /*#__PURE__*/function (_React$Component) { _inheritsLoose(DefaultEditorComponent, _React$Component); function DefaultEditorComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultEditorComponent.prototype; _proto.render = function render() { var _this$props = this.props, config = _this$props.config, attributes = _this$props.attributes, children = _this$props.children, colorIndex = _this$props.colorIndex; return createElement(Color, Object.assign({ config: config, colorIndex: colorIndex }, attributes), children); }; return DefaultEditorComponent; }(Component); var createColorPlugin = function createColorPlugin(_temp) { var _ref2 = _temp === void 0 ? {} : _temp, _ref2$EditorComponent = _ref2.EditorComponent, EditorComponent = _ref2$EditorComponent === void 0 ? DefaultEditorComponent : _ref2$EditorComponent; return function (pluginClosure) { // TODO: deserialize return { renderMark: function renderMark(props, _editor, next) { var config = pluginClosure.current ? pluginClosure.current.config : undefined; var mark = props.mark; if (!config) return null; if (mark.object === 'mark' && mark.type === colorMark) { var colorIndex = mark.data.get('colorIndex'); return createElement(EditorComponent, Object.assign({ config: config, colorIndex: colorIndex }, props)); } return next(); } }; }; }; var Button = /*#__PURE__*/styled.button(function (props) { var theme = props.config.theme; return { backgroundColor: props.active ? theme.active.backgroundColor : theme.backgroundColor, cursor: 'pointer', boxShadow: props.active ? 'inset 0 1px 3px 0 rgba(0,0,0,0.50)' : undefined, color: props.active ? theme.active.color : theme.color, outline: 'none', height: '25px', border: 'none', borderRadius: '4px', margin: '5px', padding: '0px', width: '25px', '&:hover': { color: theme.hoverColor } }; }); var ColorPaletteIcon = /*#__PURE__*/styled.div(function (props) { return { display: 'inline-block', backgroundColor: props.color, borderRadius: ' 100%', width: '19px', height: '19px', margin: '3px', verticalAlign: 'middle' }; }); function ColorControls(props) { var _props$config = props.config, i18n = _props$config.i18n, theme = _props$config.theme; var _theme$plugins$colors = theme.plugins.colors, colors = _theme$plugins$colors.colors, defaultColor = _theme$plugins$colors.defaultColor; return createElement(Fragment, null, createElement(Button, { active: !createIsColor()(props.editor), config: props.config, onClick: function onClick() { removeColor(props.editor).moveToEnd().focus(); props.switchControls(VisibleControls.All); props.onChange(props.editor); }, title: i18n.colors.resetColorTitle }, createElement(ColorPaletteIcon, { color: defaultColor })), colors.map(function (color, index) { return createElement(Button, { config: props.config, key: index, active: createIsColor(index)(props.editor), onClick: function onClick() { trimSelection(props.editor); createToggleColor(index)(props.editor).moveToEnd().focus(); props.switchControls(VisibleControls.All); props.onChange(props.editor); }, title: i18n.colors.setColorTitle }, createElement(ColorPaletteIcon, { color: color })); }), createElement(Button, { config: props.config, onClick: function onClick() { return props.switchControls(VisibleControls.All); }, title: i18n.colors.closeMenuTitle }, createElement(EdtrIcon, { icon: edtrClose }))); } var ColoredText = /*#__PURE__*/styled.span({ position: 'relative', verticalAlign: 'middle', display: 'inline-block' }); var FlexContainer = /*#__PURE__*/styled.span({ display: 'flex', alignItems: 'center', flexDirection: 'column' }); var Line = /*#__PURE__*/styled.span(function (props) { var theme = props.config.theme; var _theme$plugins$colors2 = theme.plugins.colors, colors = _theme$plugins$colors2.colors, defaultColor = _theme$plugins$colors2.defaultColor; return { border: "2px solid " + (props.index === undefined ? defaultColor : colors[props.index % colors.length]), borderRadius: '4px', bottom: '0', width: '80%', position: 'absolute' }; }); function ColoredTextIcon(props) { return createElement(ColoredText, null, createElement(FlexContainer, null, createElement(EdtrIcon, { icon: edtrColorText }), createElement(Line, { config: props.config, index: props.index }))); } var isBlockquote = function isBlockquote(editor, pluginClosure) { if (!pluginClosure.current) return false; var _pluginClosure$curren = pluginClosure.current, config = _pluginClosure$curren.config, id = _pluginClosure$curren.id, store = _pluginClosure$curren.store; var parent = getParent(id)(store.getState()); if (!parent) return false; var parentDocument = getDocument(parent.id)(store.getState()); if (!parentDocument) return false; return parentDocument.plugin === config.blockquote; }; var createBlockquote = function createBlockquote(editor, pluginClosure) { if (!pluginClosure.current) return; var _pluginClosure$curren2 = pluginClosure.current, config = _pluginClosure$curren2.config, id = _pluginClosure$curren2.id, store = _pluginClosure$curren2.store; if (!config.blockquote) return; var blockquote = config.blockquote; store.dispatch(wrap({ id: id, document: function document(id) { return { plugin: blockquote, state: id }; } })); }; var removeBlockquote = function removeBlockquote(editor, pluginClosure) { if (isBlockquote(editor, pluginClosure)) { if (!pluginClosure.current) return; var _pluginClosure$curren3 = pluginClosure.current, config = _pluginClosure$curren3.config, id = _pluginClosure$curren3.id, store = _pluginClosure$curren3.store; if (!config.blockquote) return; var parent = getParent(id)(store.getState()); if (!parent) return; store.dispatch(unwrap({ id: parent.id, oldId: id })); } }; var isCode = function isCode(editor) { return getActiveMarks(editor).some(function (mark) { return mark ? mark.type === codeMark : false; }); }; var toggleCode = function toggleCode(editor) { trimSelection(editor); return editor.toggleMark(codeMark); }; var DefaultEditorComponent$1 = /*#__PURE__*/function (_React$Component) { _inheritsLoose(DefaultEditorComponent, _React$Component); function DefaultEditorComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultEditorComponent.prototype; _proto.render = function render() { var _this$props = this.props, attributes = _this$props.attributes, mark = _this$props.mark, children = _this$props.children; if (mark.type === codeMark) { return createElement("code", Object.assign({}, attributes), children); } return null; }; return DefaultEditorComponent; }(Component); var createCodePlugin = function createCodePlugin(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$EditorComponent = _ref.EditorComponent, EditorComponent = _ref$EditorComponent === void 0 ? DefaultEditorComponent$1 : _ref$EditorComponent; return function () { return { onKeyDown: function onKeyDown(event, editor, next) { var e = event; if (isHotkey('mod+shift+´')(e) || isHotkey('mod+shift+`')(e)) { e.preventDefault(); return toggleCode(editor); } return next(); }, renderMark: function renderMark(props, _editor, next) { var mark = props.mark; if (mark.object === 'mark' && mark.type === codeMark) { return createElement(EditorComponent, Object.assign({}, props)); } return next(); } }; }; }; var _excluded = ["level", "children"]; var Heading = /*#__PURE__*/forwardRef(function (_ref, ref) { var level = _ref.level, children = _ref.children, props = _objectWithoutPropertiesLoose(_ref, _excluded); var headingLevel = level <= 6 && level >= 1 ? level : 6; return createElement("h" + headingLevel, _extends({}, props, { ref: ref }), children); }); Heading.displayName = 'Heading'; var DefaultEditorComponent$2 = /*#__PURE__*/function (_React$Component) { _inheritsLoose(DefaultEditorComponent, _React$Component); function DefaultEditorComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultEditorComponent.prototype; _proto.render = function render() { var _this$props = this.props, attributes = _this$props.attributes, children = _this$props.children, level = _this$props.level; return createElement(Heading, Object.assign({ level: level }, attributes), children); }; return DefaultEditorComponent; }(Component); var createIsHeading = function createIsHeading(level) { return function (editor) { var type = createHeadingNode(level); return editor.value.blocks.some(function (block) { return block ? block.type === type : false; }); }; }; var createSetHeading = function createSetHeading(level) { return function (editor) { var type = createHeadingNode(level); return editor.setBlocks(type); }; }; var getHeadingLevel = function getHeadingLevel(editor) { return find(function (level) { return createIsHeading(level)(editor); }, [1, 2, 3, 4, 5, 6]); }; var createHeadingsPlugin = function createHeadingsPlugin(_temp) { var _ref2 = _temp === void 0 ? {} : _temp, _ref2$EditorComponent = _ref2.EditorComponent, EditorComponent = _ref2$EditorComponent === void 0 ? DefaultEditorComponent$2 : _ref2$EditorComponent; return function () { return { renderBlock: function renderBlock(props, _editor, next) { var block = props.node; var match = /@splish-me\/h([1-6])/.exec(block.type); if (match) { var level = parseInt(match[1], 10); return createElement(EditorComponent, Object.assign({ level: level }, props)); } return next(); } }; }; }; var isList = function isList(type) { return function (editor) { var _editor$value = editor.value, document = _editor$value.document, startBlock = _editor$value.startBlock; if (!startBlock || startBlock.type !== listItemChildNode) return false; var listItem = document.getParent(startBlock.key); var list = document.getParent(listItem.key); return list.type === type; }; }; var toggleList = function toggleList(type) { if (type === void 0) { type = unorderedListNode; } return function (editor) { return editor.command('toggleList', { type: type }); }; }; var createListPlugin = function createListPlugin() { return function () { return List({ blocks: { ordered_list: orderedListNode, unordered_list: unorderedListNode, list_item: listItemNode, list_item_child: listItemChildNode, "default": defaultNode } }); }; }; var preferenceKey = 'katex:usevisualmath'; setDefaultPreference(preferenceKey, true); var DefaultEditorComponent$3 = function DefaultEditorComponent(props) { var attributes = props.attributes, editor = props.editor, readOnly = props.readOnly, node = props.node; var nodeKey = node.key, nodeType = node.type; var data = node.data; var formula = data.get('formula'); var inline = data.get('inline'); var preferences = useContext(PreferenceContext); return createElement(MathEditor, { state: formula, inline: data.get('inline'), readOnly: !props.isSelected || !editor.value.selection.isCollapsed || readOnly, visual: preferences.getKey(preferenceKey) === true, disableBlock: isList(orderedListNode)(editor.controller) || isList(unorderedListNode)(editor.controller) || props.config.noLinebreaks, config: { i18n: props.config.i18n.math, theme: props.config.theme }, onBlur: function onBlur() { editor.blur(); }, onEditorChange: function onEditorChange(visual) { preferences.setKey(preferenceKey, visual); }, onInlineChange: function onInlineChange(inline) { var newData = { formula: formula, inline: inline }; // remove old node, merge blocks if necessary if (node.isLeafBlock()) { var n = editor.value.document.getNextBlock(node.key); editor.removeNodeByKey(node.key); if (n) { editor.mergeNodeByKey(n.key); } } else { editor.removeNodeByKey(node.key); } if (inline) { editor.insertInline({ type: katexInlineNode, data: newData }); } else { editor.insertBlock({ type: katexBlockNode, data: newData }); } }, onChange: function onChange(formula) { editor.setNodeByKey(nodeKey, { type: nodeType, data: { formula: formula, inline: inline } }); }, onMoveOutLeft: function onMoveOutLeft() { editor.moveToStart().moveBackward(1).focus(); }, onMoveOutRight: function onMoveOutRight() { editor.moveToEnd().moveForward(1).focus(); }, onDeleteOutLeft: function onDeleteOutLeft() { editor["delete"]().focus(); }, additionalContainerProps: attributes }); }; var isKatex = function isKatex(editor) { return editor.value.blocks.some(function (block) { return block ? block.type === katexBlockNode : false; }) || editor.value.inlines.some(function (inline) { return inline ? inline.type === katexInlineNode : false; }); }; var insertKatex = function insertKatex(editor) { if (editor.value.selection.isExpanded) { trimSelection(editor); var selection = document.getSelection(); editor.wrapInline({ type: katexInlineNode, data: { formula: selection ? selection.toString() : '', inline: true } }).moveToEnd(); return editor.focus().moveBackward(1); } return editor.insertInline({ type: katexInlineNode, data: { formula: '', inline: true } }); }; var removeKatex = function removeKatex(editor) { var node = editor.value.blocks.toArray().find(function (block) { return block.type === katexBlockNode; }) || editor.value.inlines.toArray().find(function (inline) { return inline.type === katexInlineNode; }); if (!node) return editor; return editor.removeNodeByKey(node.key); }; var createKatexPlugin = function createKatexPlugin(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$EditorComponent = _ref.EditorComponent, EditorComponent = _ref$EditorComponent === void 0 ? DefaultEditorComponent$3 : _ref$EditorComponent; return function (pluginClosure) { function renderEditorComponent(props) { var config = pluginClosure.current ? pluginClosure.current.config : undefined; if (!config) return null; return createElement(EditorComponent, Object.assign({ config: config }, props)); } return { onKeyDown: function onKeyDown(event, editor, next) { var e = event; if (isHotkey('mod+m')(e)) { e.preventDefault(); return insertKatex(editor); } return next(); }, renderInline: function renderInline(props, editor, next) { var inline = props.node; if (inline.type === katexInlineNode) { return renderEditorComponent(props); } return next(); }, renderBlock: function renderBlock(props, editor, next) { var block = props.node; if (block.type === katexBlockNode) { return renderEditorComponent(props); } return next(); } }; }; }; var I18nContext = /*#__PURE__*/createContext(undefined); var InlineInputInner = /*#__PURE__*/styled.input({ backgroundColor: 'transparent', border: 'none', borderBottom: '2px solid #ffffff', color: '#ffffff', '&:focus': { outline: 'none', borderBottom: '2px solid rgb(70, 155, 255)' } }); var InlineInputRefForward = function InlineInputRefForward(props, ref) { return createElement(InlineInputInner, Object.assign({}, props, { ref: ref })); }; var InlineInput = /*#__PURE__*/forwardRef(InlineInputRefForward); var _excluded$1 = ["position"]; var InlinePreview = /*#__PURE__*/styled.span({ padding: '0px 8px' }); var ChangeButton = /*#__PURE__*/styled.div({ padding: '5px 5px 5px 10px', display: 'inline-block', borderLeft: '2px solid rgba(51,51,51,0.95)', cursor: 'pointer', margin: '2px', '&:hover': { color: 'rgb(70, 155, 255)' } }); function InlineSettings(_ref) { var _ref$position = _ref.position, position = _ref$position === void 0 ? 'below' : _ref$position, props = _objectWithoutPropertiesLoose(_ref, _excluded$1); return createElement(HoverOverlay, { position: position, anchor: props.anchor }, createElement(InlinePreview, null, props.children), createElement(ChangeButton, { onClick: props.onDelete }, createElement(Icon, { icon: faTrashAlt }))); } var OpenInNewTab = /*#__PURE__*/styled$1.span({ margin: '0 0 0 10px' }); var isLink = function isLink(editor) { return editor.value.inlines.some(function (inline) { return inline ? inline.type === linkNode : false; }); }; var unwrapLink = function unwrapLink(editor) { return editor.unwrapInline(linkNode); }; var wrapLink = function wrapLink(data) { if (data === void 0) { data = { href: '' }; } return function (editor) { if (editor.value.selection.isExpanded) { trimSelection(editor); return editor.wrapInline({ type: linkNode, data: data }).moveToEnd().focus().moveBackward(1); } return editor.insertText(' ').focus().moveFocusBackward(1).wrapInline({ type: linkNode, data: data }).moveToStart(); }; }; var DefaultEditorComponent$4 = function DefaultEditorComponent(props) { var attributes = props.attributes, children = props.children, node = props.node, isSelected = props.isSelected; var href = node.data.get('href'); return createElement("a", Object.assign({}, attributes, { href: href, style: isSelected ? { textDecoration: 'underline' } : undefined }), children); }; var DefaultControlsComponent = function DefaultControlsComponent(props) { var _lastInline$current; var i18n = useContext(I18nContext); var editor = props.editor; var inline = editor.value.inlines.find(nodeIsLink); var lastInline = useRef(inline); var _React$useState = useState(inline ? inline.data.get('href') : undefined), value = _React$useState[0], setValue = _React$useState[1]; var edit = !props.readOnly && isLink(editor) && editor.value.selection.isCollapsed; var lastEdit = useRef(edit); useEffect(function () { if (lastEdit.current !== edit) { if (inline && value !== undefined && value !== inline.data.get('href')) { handleHrefChange(value, inline, editor); } lastEdit.current = edit; } }, [edit, inline, value, editor]); if (!inline) return createElement(Fragment, null, props.children); if (value === undefined || ((_lastInline$current = lastInline.current) == null ? void 0 : _lastInline$current.key) !== inline.key) { var href = inline.data.get('href'); setValue(href); lastInline.current = inline; } function handleHrefChange(href, inline, editor) { editor.setNodeByKey(inline.key, { type: inline.type, data: { href: href } }); } function nodeIsLink(inline) { return inline ? inline.type === linkNode : false; } return createElement(Fragment, null, props.children, !props.readOnly && isLink(editor) && editor.value.selection.isCollapsed ? createElement(InlineSettings, { key: inline.key, onDelete: function onDelete() { return unwrapLink(editor).focus(); }, position: "below" }, createElement(InlineInput, { value: value, placeholder: i18n.link.placeholder, onChange: function onChange(event) { var newValue = event.target.value; setValue(newValue); handleHrefChange(newValue, inline, editor); }, onKeyDown: function onKeyDown(event) { if (event.key === 'Enter') { event.preventDefault(); if (value !== undefined) { handleHrefChange(value, inline, editor); } editor.focus(); } }, onBlur: function onBlur(event) { var value = event.target.value; if (new RegExp('^([_\\-a-zA-Z0-9.]+\\.[\\w]{2,})').test(value)) { setValue("https://" + value); } }, //@ts-expect-error FIXME ref: function ref(_ref) { if (!_ref) return; if (!lastEdit.current && !value) { setTimeout(function () { editor.blur(); setTimeout(function () { _ref.focus(); }); }); } } }), createElement("a", { target: "_blank", href: value, rel: "noopener noreferrer" }, createElement(OpenInNewTab, { title: i18n.link.openInNewTabTitle }, createElement(Icon, { icon: faExternalLinkAlt })))) : null); }; var createLinkPlugin = function createLinkPlugin(_temp) { var _ref2 = _temp === void 0 ? {} : _temp, _ref2$EditorComponent = _ref2.EditorComponent, EditorComponent = _ref2$EditorComponent === void 0 ? DefaultEditorComponent$4 : _ref2$EditorComponent, _ref2$ControlsCompone = _ref2.ControlsComponent, ControlsComponent = _ref2$ControlsCompone === void 0 ? DefaultControlsComponent : _ref2$ControlsCompone; return function () { return { onKeyDown: function onKeyDown(event, editor, next) { var e = event; if (isHotkey$1('mod+k', e)) { e.preventDefault(); return isLink(editor) ? unwrapLink(editor) : wrapLink()(editor); } return next(); }, renderInline: function renderInline(props, _editor, next) { var block = props.node; if (block.type === linkNode) { return createElement(EditorComponent, Object.assign({}, props)); } return next(); }, renderEditor: function renderEditor(props, editor, next) { var children = next(); if (props.readOnly) return children; return createElement(ControlsComponent, Object.assign({}, props, { editor: editor }), children); } }; }; }; var isStrong = function isStrong(editor) { return getActiveMarks(editor).some(function (mark) { return mark ? mark.type === strongMark : false; }); }; var isEmphasized = function isEmphasized(editor) { return getActiveMarks(editor).some(function (mark) { return mark ? mark.type === emphasizeMark : false; }); }; var toggleStrong = function toggleStrong(editor) { trimSelection(editor); return editor.toggleMark(strongMark); }; var toggleEmphasize = function toggleEmphasize(editor) { trimSelection(editor); return editor.toggleMark(emphasizeMark); }; var DefaultEditorComponent$5 = /*#__PURE__*/function (_React$Component) { _inheritsLoose(DefaultEditorComponent, _React$Component); function DefaultEditorComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultEditorComponent.prototype; _proto.render = function render() { var _this$props = this.props, attributes = _this$props.attributes, mark = _this$props.mark, children = _this$props.children; switch (mark.type) { case strongMark: return createElement("strong", Object.assign({}, attributes), children); case emphasizeMark: return createElement("em", Object.assign({}, attributes), children); default: return null; } }; return DefaultEditorComponent; }(Component); var createRichTextPlugin = function createRichTextPlugin(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$EditorComponent = _ref.EditorComponent, EditorComponent = _ref$EditorComponent === void 0 ? DefaultEditorComponent$5 : _ref$EditorComponent; return function () { return { onKeyDown: function onKeyDown(event, editor, next) { var e = event; if (isHotkey('mod+b')(e)) { e.preventDefault(); return toggleStrong(editor); } else if (isHotkey('mod+i')(e)) { e.preventDefault(); return toggleEmphasize(editor); } return next(); }, renderMark: function renderMark(props, _editor, next) { var mark = props.mark; if (mark.object === 'mark' && [strongMark, emphasizeMark].includes(mark.type)) { return createElement(EditorComponent, Object.assign({}, props)); } return next(); } }; }; }; function DefaultControls(props) { var editor = props.editor, config = props.config, pluginClosure = props.pluginClosure, plugins = props.plugins; return createElement(Fragment, null, plugins.richText ? createElement(Fragment, null, createElement(Button, { config: config, active: isStrong(editor), onClick: function onClick() { toggleStrong(editor).focus(); props.onChange(editor); }, title: config.i18n.richText.toggleStrongTitle }, createElement(EdtrIcon, { icon: edtrBold })), createElement(Button, { config: config, active: isEmphasized(editor), onClick: function onClick() { toggleEmphasize(editor).focus(); props.onChange(editor); }, title: config.i18n.richText.toggleEmphasizeTitle }, createElement(EdtrIcon, { icon: edtrItalic }))) : null, plugins.links ? createElement(Button, { config: config, active: isLink(editor), onClick: function onClick() { isLink(editor) ? unwrapLink(editor).focus() : wrapLink()(editor); props.onChange(editor); }, title: config.i18n.link.toggleTitle }, createElement(EdtrIcon, { icon: edtrLink })) : null, plugins.headings ? createElement(Button, { config: config, active: !!getHeadingLevel(props.editor), onClick: function onClick() { props.switchControls(VisibleControls.Headings); }, title: config.i18n.headings.openMenuTitle }, createElement(EdtrIcon, { icon: edtrText })) : null, plugins.colors ? createElement(Button, { config: config, onClick: function onClick() { return props.switchControls(VisibleControls.Colors); }, title: config.i18n.colors.openMenuTitle }, createElement(ColoredTextIcon, { config: props.config, index: getColorIndex(editor) })) : null, plugins.lists ? createElement(Button, { config: config, onClick: function onClick() { if (!isList(unorderedListNode)(editor) && !isList(orderedListNode)(editor)) { toggleList(unorderedListNode)(props.editor).focus(); props.onChange(editor); } props.switchControls(VisibleControls.Lists); }, title: config.i18n.list.openMenuTitle }, createElement(EdtrIcon, { icon: isList(orderedListNode)(editor) ? edtrListNumbered : edtrListBullets })) : null, config.blockquote ? createElement(Button, { config: config, active: isBlockquote(editor, pluginClosure), onClick: function onClick() { if (isBlockquote(editor, pluginClosure)) { removeBlockquote(editor, pluginClosure); props.onChange(editor); } else { createBlockquote(editor, pluginClosure); props.onChange(editor); } }, title: config.i18n.blockquote.toggleTitle }, createElement(EdtrIcon, { icon: edtrQuote })) : null, plugins.math ? createElement(Button, { config: config, active: isKatex(editor), onClick: function onClick() { isKatex(editor) ? removeKatex(editor).focus() : insertKatex(editor); props.onChange(editor); }, title: config.i18n.math.toggleTitle }, createElement(EdtrIcon, { icon: edtrFormula })) : null, plugins.code ? createElement(Button, { config: config, active: isCode(editor), onClick: function onClick() { toggleCode(editor).focus(); props.onChange(editor); }, title: config.i18n.code.toggleTitle }, createElement(Icon, { icon: faCode })) : null); } var setParagraph = function setParagraph(editor) { return editor.setBlocks(paragraphNode); }; var DefaultEditorComponent$6 = /*#__PURE__*/function (_React$Component) { _inheritsLoose(DefaultEditorComponent, _React$Component); function DefaultEditorComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultEditorComponent.prototype; _proto.render = function render() { var _this$props = this.props, attributes = _this$props.attributes, children = _this$props.children; return createElement("div", Object.assign({}, attributes), children); }; return DefaultEditorComponent; }(Component); var createParagraphPlugin = function createParagraphPlugin(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$EditorComponent = _ref.EditorComponent, EditorComponent = _ref$EditorComponent === void 0 ? DefaultEditorComponent$6 : _ref$EditorComponent; return function () { return { renderBlock: function renderBlock(props, _editor, next) { var block = props.node; if (block.type === paragraphNode || block.type === '@splish-me/p') { return createElement(EditorComponent, Object.assign({}, props)); } return next(); } }; }; }; var HeadingControls = function HeadingControls(props) { return createElement(Fragment, null, times(function (index) { var level = index + 1; var active = createIsHeading(level)(props.editor); return createElement(Button, { key: index, config: props.config, active: active, onClick: function onClick() { active ? setParagraph(props.editor) : createSetHeading(level)(props.editor); props.editor.focus(); props.onChange(props.editor); props.switchControls(VisibleControls.All); }, title: props.config.i18n.headings.setHeadingTitle(level) }, "H", level); }, 3), createElement(Button, { config: props.config, onClick: function onClick() { return props.switchControls(VisibleControls.All); }, title: props.config.i18n.headings.closeMenuTitle }, createElement(EdtrIcon, { icon: edtrClose }))); }; var ListControls = function ListControls(props) { return createElement(Fragment, null, createElement(Button, { config: props.config, active: isList(orderedListNode)(props.editor), onClick: function onClick() { toggleList(orderedListNode)(props.editor).focus(); props.onChange(props.editor); }, title: props.config.i18n.list.toggleOrderedList }, createElement(EdtrIcon, { icon: edtrListNumbered })), createElement(Button, { config: props.config, active: isList(unorderedListNode)(props.editor), onClick: function onClick() { toggleList(unorderedListNode)(props.editor).focus(); if (!isList(unorderedListNode)(props.editor)) { props.switchControls(VisibleControls.All); } props.onChange(props.editor); }, title: props.config.i18n.list.toggleUnorderedList }, createElement(EdtrIcon, { icon: edtrListBullets })), createElement(Button, { config: props.config, onClick: function onClick() { return props.switchControls(VisibleControls.All); }, title: props.config.i18n.list.closeMenuTitle }, createElement(EdtrIcon, { icon: edtrClose }))); }; var _excluded$2 = ["visibleControls", "setVisibleControls", "onChange"]; var VisibleControls; (function (VisibleControls) { VisibleControls[VisibleControls["All"] = 0] = "All"; VisibleControls[VisibleControls["Headings"] = 1] = "Headings"; VisibleControls[VisibleControls["Lists"] = 2] = "Lists"; VisibleControls[VisibleControls["Colors"] = 3] = "Colors"; })(VisibleControls || (VisibleControls = {})); function ControlsSwitch(_ref) { var visibleControls = _ref.visibleControls, setVisibleControls = _ref.setVisibleControls, onChange = _ref.onChange, props = _objectWithoutPropertiesLoose(_ref, _excluded$2); switch (visibleControls) { case VisibleControls.All: return createElement(DefaultControls, Object.assign({}, props, { switchControls: setVisibleControls, onChange: onChange })); case VisibleControls.Headings: return createElement(HeadingControls, Object.assign({}, props, { switchControls: setVisibleControls, onChange: onChange })); case VisibleControls.Lists: return createElement(ListControls, Object.assign({}, props, { switchControls: setVisibleControls, onChange: onChange })); case VisibleControls.Colors: return createElement(ColorControls, Object.assign({}, props, { switchControls: setVisibleControls, onChange: onChange })); } } var TimeoutBottomToolbar = /*#__PURE__*/styled$1(EditorBottomToolbar)(function (props) { var touchStyles = props.isTouch ? { bottom: 'unset', top: 0, transform: 'translate(-50%, 50%)' } : {}; return _extends({ opacity: props.visible ? 1 : 0, transition: '500ms opacity ease-in-out' }, touchStyles); }); function Controls(props) { var selectionCollapsed = props.editor.value.selection.isCollapsed; var _React$useState = useState(VisibleControls.All), visibleControls = _React$useState[0], setVisibleControls = _React$useState[1]; var _React$useState2 = useState(false), bottomToolbarVisible = _React$useState2[0], setBottomToolbarVisible = _React$useState2[1]; function showBottomToolbar() { setVisibleControls(VisibleControls.All); setBottomToolbarVisible(true); } var currentValue = JSON.stringify(props.editor.value.toJSON()); var memoized = useRef({ value: currentValue, selectionCollapsed: selectionCollapsed }); useEffect(function () { var debounceTimeout = setTimeout(showBottomToolbar, 2500); var valueChanged = memoized.current.value !== currentValue; if (valueChanged || memoized.current.selectionCollapsed !== selectionCollapsed) { memoized.current = { value: currentValue, selectionCollapsed: selectionCollapsed }; if (debounceTimeout) { clearTimeout(debounceTimeout); } var timeout = valueChanged ? 2500 : 1000; if (selectionCollapsed) { debounceTimeout = setTimeout(showBottomToolbar, timeout); } setBottomToolbarVisible(false); } return function () { clearTimeout(debounceTimeout); }; }, [currentValue, selectionCollapsed]); var onChange = useCallback(function (editor) { memoized.current = _extends({}, memoized.current, { value: JSON.stringify(editor.value.toJSON()) }); return editor; }, []); return createElement(Fragment, null, !selectionCollapsed && createElement(HoverOverlay, { position: isTouchDevice() ? 'below' : 'above' }, createElement(ControlsSwitch, Object.assign({}, props, { visibleControls: visibleControls, setVisibleControls: setVisibleControls, onChange: onChange }))), !props.readOnly && createElement(TimeoutBottomToolbar, { isTouch: isTouchDevice(), visible: selectionCollapsed && bottomToolbarVisible }, bottomToolbarVisible && createElement(ControlsSwitch, Object.assign({}, props, { visibleControls: visibleControls, setVisibleControls: setVisibleControls, onChange: onChange })))); } function isTouchDevice() { return 'ontouchstart' in window || navigator.maxTouchPoints > 0; } var createUiPlugin = function createUiPlugin(options) { return functio