UNPKG

@edtr-io/plugin-text

Version:
1,756 lines (1,537 loc) 85.2 kB
import { styled, usePluginTheme, createPluginTheme } from '@edtr-io/ui'; import { createElement, Component, Fragment, useRef, useState, useEffect, useContext, useMemo, useCallback, createRef } from 'react'; import { Data, Value, Range } from 'slate'; import { EdtrIcon, edtrTextControls, InlineSettings, InlineInput, Icon, faExternalLinkAlt, styled as styled$1, createIcon, faParagraph, Overlay, HoveringOverlay, InlineCheckbox, faQuestionCircle, BottomToolbar } from '@edtr-io/editor-ui'; import { find, times } from 'ramda'; import { canUseDOM } from 'exenv'; import isHotkey, { isHotkey as isHotkey$1 } from 'is-hotkey'; import { useEditorFocus, useStore, OverlayContext, selectors, StateType } from '@edtr-io/core'; import List from '@convertkit/slate-lists'; import { findNode, Editor } from 'slate-react'; import KaTeX from 'katex'; import { HotKeys } from 'react-hotkeys'; 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; subClass.__proto__ = superClass; } 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; } var colorMark = '@splish-me/color'; var getActiveMarks = function getActiveMarks(editor) { return editor.value.document.getActiveMarksAtRange(getTrimmedSelectionRange(editor)); }; var createIsColor = function createIsColor(colorIndex) { return function (editor) { return getActiveMarks(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(editor).find(function (mark) { return mark ? mark.type === colorMark : false; }); return mark.data.get('colorIndex'); } }; var Color = /*#__PURE__*/ styled.span(function (props) { var theme = createTextPluginTheme(name, props.theme); var colors = theme.plugins.colors.colors; return { color: colors[props.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, attributes = _this$props.attributes, children = _this$props.children, colorIndex = _this$props.colorIndex; return createElement(Color, Object.assign({ colorIndex: colorIndex }, attributes), children); }; return DefaultEditorComponent; }(Component); var DefaultRendererComponent = /*#__PURE__*/ function (_React$Component2) { _inheritsLoose(DefaultRendererComponent, _React$Component2); function DefaultRendererComponent() { return _React$Component2.apply(this, arguments) || this; } var _proto2 = DefaultRendererComponent.prototype; _proto2.render = function render() { var _this$props2 = this.props, children = _this$props2.children, colorIndex = _this$props2.colorIndex; return createElement(Color, { colorIndex: colorIndex }, children); }; return DefaultRendererComponent; }(Component); var createColorPlugin = function createColorPlugin(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$EditorComponent = _ref.EditorComponent, EditorComponent = _ref$EditorComponent === void 0 ? DefaultEditorComponent : _ref$EditorComponent, _ref$RenderComponent = _ref.RenderComponent, RenderComponent = _ref$RenderComponent === void 0 ? DefaultRendererComponent : _ref$RenderComponent; return function () { // TODO: deserialize return { serialize: function serialize(obj, children) { var mark = obj; if (mark.object === 'mark') { var colorIndex = mark.data.get('colorIndex'); return createElement(RenderComponent, { mark: mark, colorIndex: colorIndex }, children); } }, renderMark: function renderMark(props, _editor, next) { var mark = props.mark; if (mark.object === 'mark' && mark.type === colorMark) { var colorIndex = mark.data.get('colorIndex'); return createElement(EditorComponent, Object.assign({ colorIndex: colorIndex }, props)); } return next(); } }; }; }; var Button = /*#__PURE__*/ styled.button(function (props) { var theme = createTextPluginTheme(props.name, props.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 ColorControls = function ColorControls(props) { var theme = usePluginTheme(props.name, textPluginThemeFactory); 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), name: props.name, onClick: function onClick() { removeColor(props.editor).moveToEnd().focus(); props.switchControls(VisibleControls.All); props.onChange(props.editor); }, title: "Farbe zur\xFCcksetzen" }, createElement(ColorPaletteIcon, { color: defaultColor })), colors.map(function (color, index) { return createElement(Button, { key: index, active: createIsColor(index)(props.editor), name: props.name, onClick: function onClick() { trimSelection(props.editor); createToggleColor(index)(props.editor).moveToEnd().focus(); props.switchControls(VisibleControls.All); props.onChange(props.editor); }, title: "Einf\xE4rben" }, createElement(ColorPaletteIcon, { color: color })); }), createElement(Button, { name: props.name, onClick: function onClick() { return props.switchControls(VisibleControls.All); }, title: "Untermen\xFC schlie\xDFen" }, createElement(EdtrIcon, { icon: edtrTextControls.close }))); }; var ColorPaletteIcon = /*#__PURE__*/ styled.div(function (props) { return { display: 'inline-block', backgroundColor: props.color, borderRadius: ' 100%', width: '19px', height: '19px', margin: '3px', verticalAlign: 'middle' }; }); 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 = createTextPluginTheme(name, props.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' }; }); var ColoredTextIcon = function ColoredTextIcon(props) { return createElement(ColoredText, null, createElement(FlexContainer, null, createElement(EdtrIcon, { icon: edtrTextControls.colorText }), createElement(Line, { index: props.index }))); }; var isBlockquote = function isBlockquote(editor, pluginClosure) { return !!(pluginClosure.current && pluginClosure.current.parent && pluginClosure.current.parent.name === 'blockquote'); }; var createBlockquote = function createBlockquote(editor, name) { editor.command('replaceWithPlugin', { plugin: 'blockquote', state: { plugin: name, state: editor.value.toJSON() } }); }; var removeBlockquote = function removeBlockquote(editor, pluginClosure) { if (pluginClosure.current && isBlockquote(editor, pluginClosure)) { return editor.command('unwrapParent'); } }; var createHeadingNode = function createHeadingNode(level) { return "@splish-me/h" + level; }; var Heading = function Heading(_ref) { var level = _ref.level, children = _ref.children, props = _objectWithoutPropertiesLoose(_ref, ["level", "children"]); if (level <= 6 && level >= 1) return createElement("h" + level, props, children);else return createElement("h6", Object.assign({}, props), children); }; 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, children = _this$props.children, level = _this$props.level; return createElement(Heading, Object.assign({ level: level }, attributes), children); }; return DefaultEditorComponent; }(Component); var DefaultRenderComponent = /*#__PURE__*/ function (_React$Component2) { _inheritsLoose(DefaultRenderComponent, _React$Component2); function DefaultRenderComponent() { return _React$Component2.apply(this, arguments) || this; } var _proto2 = DefaultRenderComponent.prototype; _proto2.render = function render() { var _this$props2 = this.props, children = _this$props2.children, level = _this$props2.level; return createElement(Heading, { level: level }, children); }; return DefaultRenderComponent; }(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$1 : _ref2$EditorComponent, _ref2$RenderComponent = _ref2.RenderComponent, RenderComponent = _ref2$RenderComponent === void 0 ? DefaultRenderComponent : _ref2$RenderComponent; return function () { return { deserialize: function deserialize(el, next) { var match = el.tagName.toLowerCase().match(/h([1-6])/); if (match) { var level = parseInt(match[1], 10); return { object: 'block', type: createHeadingNode(level), nodes: next(el.childNodes) }; } }, serialize: function serialize(obj, children) { var block = obj; if (block.object === 'block') { var match = block.type.match(/@splish-me\/h([1-6])/); if (match) { var level = parseInt(match[1], 10); return createElement(RenderComponent, { level: level, node: obj }, children); } } }, renderNode: function renderNode(props, _editor, next) { var block = props.node; if (block.object === 'block') { var match = block.type.match(/@splish-me\/h([1-6])/); if (match) { var level = parseInt(match[1], 10); return createElement(EditorComponent, Object.assign({ level: level }, props)); } } return next(); } }; }; }; var Dropdown = /*#__PURE__*/ styled.select(function (props) { var theme = createTextPluginTheme(props.name, props.theme); return { backgroundColor: theme.backgroundColor, cursor: 'pointer', color: theme.color, outline: 'none', height: '25px', border: 'none', borderRadius: '4px', margin: '5px', '&:hover': { color: theme.hoverColor } }; }); var Option = /*#__PURE__*/ styled.option(function (props) { var theme = createTextPluginTheme(props.name, props.theme); return { backgroundColor: props.active ? theme.active.backgroundColor : theme.dropDown.backgroundColor, color: props.active ? theme.active.color : theme.color, cursor: 'pointer', '&:hover': { color: theme.hoverColor } }; }); var linkNode = '@splish-me/a'; 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$2 = function DefaultEditorComponent(props) { var attributes = props.attributes, children = props.children, node = props.node, isSelected = props.isSelected; var inline = node; var href = inline.data.get('href'); return createElement("a", Object.assign({}, attributes, { href: href, style: isSelected ? { textDecoration: 'underline' } : undefined }), children); }; var DefaultControlsComponent = function DefaultControlsComponent(props) { 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 !== 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.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: "inlineoverlay" + inline.key, onDelete: function onDelete() { return unwrapLink(editor).focus(); }, position: "below" }, createElement(InlineInput, { value: value, placeholder: "Hier Link einf\xFCgen", onChange: function onChange(e) { var newValue = e.target.value; setValue(newValue); handleHrefChange(newValue, inline, editor); }, onKeyDown: function onKeyDown(event) { if (event.key === 'Enter') { event.preventDefault(); handleHrefChange(value, inline, editor); editor.focus(); } }, //@ts-ignore 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: "\xD6ffne in neuem Tab" }, createElement(Icon, { icon: faExternalLinkAlt })))) : null); }; var DefaultRendererComponent$1 = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(DefaultRendererComponent, _React$Component); function DefaultRendererComponent() { return _React$Component.apply(this, arguments) || this; } var _proto = DefaultRendererComponent.prototype; _proto.render = function render() { var _this$props = this.props, children = _this$props.children, node = _this$props.node; var data = node.data; if (!data) { return null; } return createElement("a", { href: data.href }, children); }; return DefaultRendererComponent; }(Component); var createLinkPlugin = function createLinkPlugin(_temp) { var _ref2 = _temp === void 0 ? {} : _temp, _ref2$EditorComponent = _ref2.EditorComponent, EditorComponent = _ref2$EditorComponent === void 0 ? DefaultEditorComponent$2 : _ref2$EditorComponent, _ref2$RenderComponent = _ref2.RenderComponent, RenderComponent = _ref2$RenderComponent === void 0 ? DefaultRendererComponent$1 : _ref2$RenderComponent, _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('mod+k', e)) { e.preventDefault(); return isLink(editor) ? unwrapLink(editor) : wrapLink()(editor); } return next(); }, deserialize: function deserialize(el, next) { if (el.tagName.toLowerCase() === 'a') { // @ts-ignore FIXME var attr = el.attrs.find(function (_ref3) { var name = _ref3.name; return name === 'href'; }); return { object: 'inline', type: linkNode, nodes: next(el.childNodes), data: Data.create({ href: attr ? attr.value : '' }) }; } }, serialize: function serialize(obj, children) { var block = obj; if (block.object === 'inline' && block.type === linkNode) { return createElement(RenderComponent, { node: obj }, children); } }, renderNode: function renderNode(props, _editor, next) { var block = props.node; if (block.object === 'inline' && 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 createTextEditor = function createTextEditor(options) { var _inlines, _blocks; var schema = { inlines: (_inlines = {}, _inlines[katexInlineNode] = { isVoid: true }, _inlines[linkNode] = { text: /.+/ }, _inlines), blocks: (_blocks = {}, _blocks[katexBlockNode] = { isVoid: true }, _blocks) }; return function SlateEditor(props) { var _useEditorFocus = useEditorFocus(), focusPrevious = _useEditorFocus.focusPrevious, focusNext = _useEditorFocus.focusNext; var editor = useRef(); var store = useStore(); var overlayContext = useContext(OverlayContext); var plugins = selectors.getPlugins(store.getState()); var _React$useState = useState(Value.fromJSON(props.state.value)), rawState = _React$useState[0], setRawState = _React$useState[1]; var lastValue = useRef(props.state.value); useEffect(function () { if (lastValue.current !== props.state.value) { setRawState(Value.fromJSON(props.state.value)); lastValue.current = props.state.value; setTimeout(function () { if (!editor.current) return; if (props.focused) { editor.current.focus(); } }); } }, [lastValue, props.focused, props.state.value]); // PLEASE DONT FIX THIS! Closure needed because on* isn't recreated so doesnt use current props var slateClosure = useRef({ name: props.name, plugins: plugins, insert: props.insert, replace: props.replace, remove: props.remove, parent: props.parent, focusPrevious: focusPrevious, focusNext: focusNext, mergeWithNext: props.mergeWithNext, mergeWithPrevious: props.mergeWithPrevious }); slateClosure.current = { name: props.name, plugins: plugins, insert: props.insert, replace: props.replace, remove: props.remove, parent: props.parent, focusPrevious: focusPrevious, focusNext: focusNext, mergeWithNext: props.mergeWithNext, mergeWithPrevious: props.mergeWithPrevious }; useEffect(function () { if (!editor.current) return; if (props.focused) { setTimeout(editor.current.focus); } else { editor.current.blur(); } }, [props.focused]); var pluginClosure = useRef({ overlayContext: overlayContext, name: props.name, parent: props.parent, replace: props.replace, plugins: plugins }); pluginClosure.current = { overlayContext: overlayContext, name: props.name, parent: props.parent, replace: props.replace, plugins: plugins }; var slatePlugins = useRef(); if (slatePlugins.current === undefined) { slatePlugins.current = [].concat(options.plugins.map(function (slatePluginFactory) { return slatePluginFactory(pluginClosure); }), [newSlateOnEnter(slateClosure), focusNextDocumentOnArrowDown(slateClosure)]); } var onPaste = useMemo(function () { return createOnPaste(slateClosure); }, [slateClosure]); var onKeyDown = useMemo(function () { return createOnKeyDown(slateClosure); }, [slateClosure]); var onClick = useCallback(function (e, editor, next) { if (e.target) { // @ts-ignore var node = findNode(e.target, editor); if (!node) { return editor; } } next(); }, []); var onChange = useCallback(function (change) { var nextValue = change.value.toJSON(); setRawState(change.value); var withoutSelections = change.operations.filter(function (operation) { return typeof operation !== 'undefined' && operation.type !== 'set_selection'; }); if (!withoutSelections.isEmpty()) { lastValue.current = nextValue; props.state.set(nextValue); } }, [props.state]); return createElement(Editor, { ref: function ref(slateReact) { if (slateReact && !editor.current) { editor.current = slateReact; patchSlateInsertFragment(slateReact); } }, // ref={editor as React.RefObject<Editor>} onPaste: onPaste, onKeyDown: onKeyDown, onClick: onClick, onChange: onChange, placeholder: props.editable ? options.placeholder : '', plugins: slatePlugins.current, readOnly: !props.focused, value: rawState, schema: schema }); }; }; // PLEASE DONT FIX THIS! Closure needed because onPaste isn't recreated so doesnt use props function createOnPaste(slateClosure) { return function (e, editor, next) { if (!slateClosure.current) { next(); return; } var _slateClosure$current = slateClosure.current, plugins = _slateClosure$current.plugins, name = _slateClosure$current.name, insert = _slateClosure$current.insert, replace = _slateClosure$current.replace; if (typeof insert !== 'function') { next(); return; } var clipboardData = e.clipboardData; var _loop = function _loop(key) { var onPaste = plugins[key].onPaste; if (clipboardData && typeof onPaste === 'function') { var result = onPaste(clipboardData); if (result !== undefined) { if (typeof replace === 'function' && isValueEmpty(editor.value)) { // replace with pasted plugin setTimeout(function () { replace({ plugin: key, state: result.state }); }); } else { var nextSlateState = splitBlockAtSelection(editor); setTimeout(function () { // insert new text-plugin with the parts after the current cursor position if any if (nextSlateState) { insert({ plugin: name, state: nextSlateState }); } // insert the plugin that handled the pasted data. // It's inserted at the same index, so will be between the text plugins insert({ plugin: key, state: result.state }); }); } return { v: void 0 }; } } }; for (var key in plugins) { var _ret = _loop(key); if (typeof _ret === "object") return _ret.v; } var currentBlock = editor.value.blocks.first(); // no plugin handled the pasted data // continue normal then split the plugin if multiple blocks were inserted next(); var blocks = editor.value.document.getBlocks(); //blocks must be inserted in reversed order var afterSelected = blocks.reverse().takeUntil(function (block) { if (!block) { return false; } return currentBlock.key === block.key; }); if (afterSelected.size) { afterSelected.forEach(function (block) { if (!block) return; editor.removeNodeByKey(block.key); }); setTimeout(function () { afterSelected.forEach(function (block) { var isOnlyWhitespace = function isOnlyWhitespace(text) { return /^\s*$/.test(text); }; if (!block || isOnlyWhitespace(block.text)) return; insert({ plugin: name, state: createDocumentFromBlocks([block]) }); }); }); } }; } // PLEASE DONT FIX THIS! Closure needed because onKeyDown isn't recreated so doesnt use props function createOnKeyDown(slateClosure) { return function (e, editor, next) { var key = e.key; if (isHotkey('mod+z', e) || isHotkey('mod+y', e) || isHotkey('mod+shift+z', e)) { e.preventDefault(); return; } if (key === 'Backspace' && isSelectionAtStart(editor) || key === 'Delete' && isSelectionAtEnd(editor)) { if (!slateClosure.current) return; var previous = key === 'Backspace'; if (isValueEmpty(editor.value)) { // Focus previous resp. next document and remove self var remove = slateClosure.current.remove; var focus = slateClosure.current[previous ? 'focusPrevious' : 'focusNext']; if (typeof remove === 'function') { if (typeof focus === 'function') focus(); remove(); } } else { // Merge with previous resp. next document var merge = slateClosure.current[previous ? 'mergeWithPrevious' : 'mergeWithNext']; if (typeof merge !== 'function') return; merge(function (other) { var value = Value.fromJSON(other); var selection = Range.create(editor.value.selection); editor.insertFragmentAtRange(selection, value.document); if (!previous) editor.select(selection); return editor.value.toJSON(); }); } return; } return next(); }; } function isSelectionAtStart(editor) { var selection = editor.value.selection; var startNode = editor.value.document.getFirstText(); return selection.isCollapsed && startNode && editor.value.startText.key === startNode.key && selection.start.offset === 0; } function isSelectionAtEnd(editor) { var selection = editor.value.selection; var endNode = editor.value.document.getLastText(); return selection.isCollapsed && endNode && editor.value.endText.key === endNode.key && selection.end.offset === editor.value.endText.text.length; } function newSlateOnEnter(slateClosure) { return { commands: { replaceWithPlugin: function replaceWithPlugin(editor, options) { if (!slateClosure.current) return editor; var replace = slateClosure.current.replace; if (typeof replace !== 'function') return editor; replace(options); return editor; }, unwrapParent: function unwrapParent(editor) { if (!slateClosure.current) return editor; var parentWithReplace = findParentWith('replace', slateClosure.current); if (parentWithReplace && typeof parentWithReplace.replace === 'function') { parentWithReplace.replace({ plugin: slateClosure.current.name, state: editor.value.toJSON() }); } return editor; } }, onKeyDown: function onKeyDown(e, editor, next) { if (isHotkey('enter', e) && !editor.value.selection.isExpanded) { // remove text plugin and insert on parent if plugin is empty if (isValueEmpty(editor.value) && slateClosure.current) { var parentWithInsert = findParentWith('insert', slateClosure.current); if (parentWithInsert) { e.preventDefault(); setTimeout(function () { if (!slateClosure.current) return next(); var remove = slateClosure.current.remove; if (typeof remove === 'function' && typeof parentWithInsert.insert === 'function') { parentWithInsert.insert({ plugin: slateClosure.current.name }); remove(); } }); return; } } // remove block and insert plugin on parent, if block is empty if (editor.value.startText.text === '' && editor.value.startBlock.nodes.size === 1 && slateClosure.current) { var _parentWithInsert = findParentWith('insert', slateClosure.current); if (_parentWithInsert) { e.preventDefault(); if (!slateClosure.current) return next(); if (typeof _parentWithInsert.insert === 'function') { editor["delete"](); _parentWithInsert.insert({ plugin: slateClosure.current.name }); } return; } } if (slateClosure.current && typeof slateClosure.current.insert === 'function') { e.preventDefault(); var nextSlateState = splitBlockAtSelection(editor); setTimeout(function () { if (!slateClosure.current) return next(); var insert = slateClosure.current.insert; if (typeof insert !== 'function') return; insert({ plugin: slateClosure.current.name, state: nextSlateState }); }); return; } } return next(); } }; } function focusNextDocumentOnArrowDown(slateClosure) { return { onKeyDown: function onKeyDown(e, editor, next) { var key = e.key; if (key === 'ArrowDown' || key === 'ArrowUp') { var lastRange = getRange(); if (lastRange) { var lastY = lastRange.getBoundingClientRect().top; setTimeout(function () { if (!slateClosure.current) { return; } var currentRange = getRange(); if (!currentRange) { return; } var currentY = currentRange.getBoundingClientRect().top; if (lastY === currentY) { if (key === 'ArrowDown') { slateClosure.current.focusNext(); } else { slateClosure.current.focusPrevious(); } } }); } } return next(); function getRange() { var selection = window.getSelection(); if (selection && selection.rangeCount > 0) { return selection.getRangeAt(0); } return null; } } }; } // search recursively for a parent with the required function function findParentWith(funcQuery, closure) { if (!closure.parent) return; if (typeof closure.parent[funcQuery] === 'function') return closure.parent; return findParentWith(funcQuery, closure.parent); } function splitBlockAtSelection(editor) { if (isSelectionAtEnd(editor)) { return; } if (editor.value.focusBlock.type == katexBlockNode) { // If katex block node is focused, don't attempt to split it, insert empty paragraph instead editor.moveToEndOfBlock(); editor.insertBlock('paragraph'); } else { editor.splitBlock(1); } var blocks = editor.value.document.getBlocks(); var afterSelected = blocks.skipUntil(function (block) { if (!block) { return false; } return editor.value.blocks.first().key === block.key; }); afterSelected.forEach(function (block) { if (!block) return; editor.removeNodeByKey(block.key); }); return createDocumentFromBlocks(afterSelected.toArray()); } function createDocumentFromBlocks(blocks) { return { document: { nodes: [].concat(blocks.map(function (block) { return block.toJSON(); })) } }; } // TEMPORARY // Testbed for integration of slate fix // polyfilling slate editor function patchSlateInsertFragment(reacteditor) { // @ts-ignore var editor = reacteditor; // @ts-ignore editor.insertFragment = function (fragment) { if (!fragment.nodes.size) return editor; if (editor.value.selection.isExpanded) { editor["delete"](); } var value = editor.value; var _value = value, document = _value.document, selection = _value.selection; var start = selection.start, end = selection.end; var _value2 = value, startText = _value2.startText, endText = _value2.endText, startInline = _value2.startInline; var lastText = fragment.getLastText(); // @ts-ignore var lastInline = fragment.getClosestInline(lastText.key); // @ts-ignore var lastBlock = fragment.getClosestBlock(lastText.key); var firstChild = fragment.nodes.first(); var lastChild = fragment.nodes.last(); // @ts-ignore var keys = document.getTexts().map(function (text) { return text.key; }); var isAppending = !startInline || start.isAtStartOfNode(startText) || end.isAtStartOfNode(startText) || start.isAtEndOfNode(endText) || end.isAtEndOfNode(endText); var isInserting = firstChild.hasBlockChildren() || lastChild.hasBlockChildren(); // @ts-ignore editor.insertFragmentAtRange(selection, fragment); value = editor.value; document = value.document; // @ts-ignore var newTexts = document.getTexts().filter(function (n) { return !keys.includes(n.key); }); var newText = isAppending ? newTexts.last() : newTexts.takeLast(2).first(); if (newText && (lastInline || isInserting)) { editor.moveToEndOfNode(newText); } else if (newText && lastBlock) { // Changed code var lastInlineIndex = lastBlock.nodes.findLastIndex(function (node) { if (!node) return false; return node.object == 'inline'; }); var skipLength = lastBlock.nodes.takeLast(lastBlock.nodes.size - lastInlineIndex - 1).reduce(function (num, v) { if (!num) num = 0; if (v) return num + v.text.length; return num; }, 0); editor.moveToStartOfNode(newText).moveForward(skipLength); } return editor; }; } var defaultNode = 'paragraph'; var textState = /*#__PURE__*/ StateType.scalar({ document: { nodes: [{ object: 'block', type: defaultNode, nodes: [{ object: 'text', leaves: [{ object: 'leaf', text: '' }] }] }] } }); var createTextPlugin = function createTextPlugin(options) { return { Component: createTextEditor(options), state: textState, icon: createIcon(faParagraph), title: 'Text', description: 'Schreibe Text und Matheformeln und formatiere sie.', onKeyDown: function onKeyDown() { return false; }, isEmpty: function isEmpty(state) { var value = Value.fromJSON(state); return isValueEmpty(value); } }; }; function isValueEmpty(value) { return value.document.text === '' && value.document.nodes.size === 1 && value.document.nodes.get(0).type === defaultNode && value.document.getTexts().size === 1; } //@ts-ignore FIXME var orderedListNode = 'ordered-list'; var unorderedListNode = 'unordered-list'; var listItemNode = 'list-item'; var listItemChildNode = 'list-item-child'; 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 }); }; }; /* eslint-disable @typescript-eslint/camelcase */ var createListPlugin = function createListPlugin() { return function () { return List({ blocks: { ordered_list: orderedListNode, unordered_list: unorderedListNode, list_item: listItemNode, list_item_child: listItemChildNode, "default": defaultNode } }); }; }; /* eslint-enable @typescript-eslint/camelcase */ var createMathComponent = function createMathComponent(Component$1, _ref) { var displayMode = _ref.displayMode; var MathComponent = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(MathComponent, _React$Component); function MathComponent() { var _this; _this = _React$Component.apply(this, arguments) || this; _this.state = { html: '' }; return _this; } var _proto = MathComponent.prototype; _proto.render = function render() { var _this$state = this.state, error = _this$state.error, html = _this$state.html; var renderError = this.props.renderError; if (error) { return renderError ? renderError(error) : createElement(Component$1, { html: "" + error.message }); } return createElement(Component$1, { html: html }); }; _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps) { return nextProps.math !== this.props.math; }; MathComponent.getDerivedStateFromProps = function getDerivedStateFromProps(props) { try { var errorColor = props.errorColor, renderError = props.renderError; var html = KaTeX.renderToString(props.math, { displayMode: displayMode, errorColor: errorColor, throwOnError: !!renderError }); return { html: html, error: undefined }; } catch (error) { if (error instanceof KaTeX.ParseError) { return { error: error }; } throw error; } }; return MathComponent; }(Component); return MathComponent; }; var IBlockMath = function IBlockMath(_ref2) { var html = _ref2.html; return createElement("span", { style: { display: 'block' }, dangerouslySetInnerHTML: { __html: html } }); }; var BlockMath = /*#__PURE__*/ createMathComponent(IBlockMath, { displayMode: true }); var IInlineMath = function IInlineMath(_ref3) { var html = _ref3.html; return createElement("span", { dangerouslySetInnerHTML: { __html: html } }); }; var InlineMath = /*#__PURE__*/ createMathComponent(IInlineMath, { displayMode: false }); var handleError = function handleError(formula, error, inline, oldErrorPosition) { var errorStyle = { color: '#CC0000' }; if (error.position === oldErrorPosition) { return createElement("span", { style: errorStyle }, formula); } var beforeError = formula.substring(0, error.position); var afterError = formula.substring(error.position); return createElement("span", { style: { display: 'inline-block' } }, createElement(Math, { formula: beforeError, inline: inline, oldErrorPosition: error.position }), createElement("span", { style: errorStyle }, afterError), createElement("span", { style: _extends({}, errorStyle, { display: 'block' }) }, createElement("b", null, error.name, ": ", error.message))); }; var Math = /*#__PURE__*/ function (_React$Component2) { _inheritsLoose(Math, _React$Component2); function Math() { return _React$Component2.apply(this, arguments) || this; } var _proto2 = Math.prototype; _proto2.render = function render() { var _this$props = this.props, inline = _this$props.inline, formula = _this$props.formula, oldErrorPosition = _this$props.oldErrorPosition; if (!formula) { return null; } var Component = inline ? InlineMath : BlockMath; return createElement(Component, { math: formula, renderError: function renderError(error) { return handleError(formula, error, !inline, oldErrorPosition); } }); }; return Math; }(Component); var MathQuill = canUseDOM ? /*#__PURE__*/ require('react-mathquill')["default"] : function () { return null; }; var EditorWrapper = /*#__PURE__*/ styled$1.div(function (props) { return _extends({ whiteSpace: undefined, overflowWrap: undefined }, props.inline ? { display: 'inline-block' } : { display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '15px' }); }); var KeySpan = /*#__PURE__*/ styled$1.span({ background: '#ddd', padding: '2px 4px', borderRadius: 5, color: '#1d1c1d', textAlign: 'center', minWidth: 20 }); var HelpText = /*#__PURE__*/ createElement(Fragment, null, "Tastenk\xFCrzel:", /*#__PURE__*/ createElement("br", null), /*#__PURE__*/ createElement("br", null), /*#__PURE__*/ createElement("p", null, "Bruch: ", /*#__PURE__*/ createElement(KeySpan, null, "/")), /*#__PURE__*/ createElement("p", null, "Hochgestellt: ", /*#__PURE__*/ createElement(KeySpan, null, "\u2191"), " oder ", /*#__PURE__*/ createElement(KeySpan, null, "^")), /*#__PURE__*/ createElement("p", null, "Tiefgestellt: ", /*#__PURE__*/ createElement(KeySpan, null, "\u2193"), " oder ", /*#__PURE__*/ createElement(KeySpan, null, "_")), /*#__PURE__*/ createElement("p", null, "\u03C0, \u03B1, \u03B2, \u03B3: ", /*#__PURE__*/ createElement(KeySpan, null, "pi"), ", ", /*#__PURE__*/ createElement(KeySpan, null, "alpha"), ",", ' ', /*#__PURE__*/ createElement(KeySpan, null, "beta"), ",", /*#__PURE__*/ createElement(KeySpan, null, "gamma")), /*#__PURE__*/ createElement("p", null, "\u2264, \u2265: ", /*#__PURE__*/ createElement(KeySpan, null, '<='), ", ", /*#__PURE__*/ createElement(KeySpan, null, '>=')), /*#__PURE__*/ createElement("p", null, "Wurzeln: ", /*#__PURE__*/ createElement(KeySpan, null, "\\sqrt"), ", ", /*#__PURE__*/ createElement(KeySpan, null, "\\nthroot")), /*#__PURE__*/ createElement("p", null, "Mathematische Symbole: ", /*#__PURE__*/ createElement(KeySpan, null, '\\<NAME>'), ", z.B.", ' ', /*#__PURE__*/ createElement(KeySpan, null, "\\neq"), " (\u2260), ", /*#__PURE__*/ createElement(KeySpan, null, "\\pm"), " (\xB1), ..."), /*#__PURE__*/ createElement("p", null, "Funktionen: ", /*#__PURE__*/ createElement(KeySpan, null, "sin"), ", ", /*#__PURE__*/ createElement(KeySpan, null, "cos"), ",", ' ', /*#__PURE__*/ createElement(KeySpan, null, "ln"), ", ...")); function isAndroid() { return isTouchDevice() && navigator && /(android)/i.test(navigator.userAgent); } var DefaultEditorComponent$3 = function DefaultEditorComponent(props) { var attributes = props.attributes, node = props.node, editor = props.editor, readOnly = props.readOnly, name = props.name; var data = node.data; var inline = data.get('inline'); var formula = data.get('formula'); function setFormula(value) { editor.setNodeByKey(node.key, { type: node.type, data: { formula: value, inline: node.data.get('inline') } }); } var _React$useState = useState(true), useVisual = _React$useState[0], setUseVisual = _React$useState[1]; //Refs for positioning of hovering menu var mathQuillRef = createRef(); var latexInputRef = useRef(); var wrappedMathquillRef = Object.defineProperty({}, 'current', { // wrapper around Mathquill component get: function get() { return mathQuillRef.current ? mathQuillRef.current.element : null; } }); // state for math formula, because focus jumps to end of input field if updated directly var _React$useState2 = useState(formula), formulaState = _React$useState2[0], setFormulaState = _React$useState2[1]; var overlayContext = useContext(OverlayContext); // if math formula gets selected or leaves edit, update formula from state var edit = props.isSelected && editor.value.selection.isCollapsed && !readOnly; var lastEdit = useRef(edit); if (lastEdit.current !== edit) { if (formula !== formulaState) { setFormula(formulaState); } lastEdit.current = edit; } function checkLeaveLatexInput(e) { if (!latexInputRef.current) return; var _latexInputRef$curren = latexInputRef.current, selectionEnd = _latexInputRef$curren.selectionEnd, value = _latexInputRef$curren.value; if (e.key === 'ArrowLeft' && selectionEnd === 0) { // leave left editor.moveToStart().moveBackward(1).focus(); } else if (e.key === 'ArrowRight' && selectionEnd === value.length) { // leave right editor.moveToEnd().moveForward(1).focus(); } } function handleInlineToggle(checked) { var newData = { formula: formulaState, inline: !checked }; // 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 (checked) { editor.insertBlock({ type: katexBlockNode, data: newData }); } else { editor.insertInline({ type: katexInlineNode, data: newData }); } } function checkLatexError(mathquill) { if (mathquill) { if (mathquill.latex() == '' && formula != '') { // Error occured alert('Error while parsing LaTeX.'); setUseVisual(false); } setTimeout(function () { editor.blur(); setTimeout(function () { mathquill.focus(); }); }); } } function updateLatex(val) { //cant set formula directly, because otherwise focus jumps to end of input field setFormulaState(val); // but android is different ... if (isAndroid()) { setFormula(val); } } if (edit) { var mathquillConfig = _extends({ supSubsRequireOperand: true, autoComman