@edtr-io/plugin-text
Version:
## Table of contents
1,557 lines (1,528 loc) • 55.6 kB
JavaScript
import { serializedScalar } from '@edtr-io/plugin';
import { Element, Editor, Range, Transforms, Node, createEditor } from 'slate';
import { HotKeys } from '@edtr-io/core';
import { HoverOverlay } from '@edtr-io/editor-ui/beta';
import { withLists as withLists$1, withListsReact, ListType, ListsEditor, onKeyDown } from '@prezly/slate-lists';
import React__default, { useCallback, useMemo, useState, useContext, useRef, useEffect, createElement, Fragment, forwardRef } from 'react';
import { useSlate, ReactEditor, useSelected, withReact, Slate, Editable } from 'slate-react';
import { styled, EdtrIcon, edtrColorText, edtrBold, edtrItalic, edtrLink, edtrText, edtrClose, edtrListNumbered, edtrListBullets, edtrFormula, Icon, faCode, merge, useTheme, faExternalLinkAlt, faTrashAlt } from '@edtr-io/ui';
import isHotkey from 'is-hotkey';
import { useScopedStore, PreferenceContext } from '@edtr-io/core/beta';
import { RegistryContext } from '@edtr-io/plugin-rows/internal';
import { replace } from '@edtr-io/store';
import { has } from 'ramda';
import { styled as styled$1, EditorBottomToolbar } from '@edtr-io/editor-ui';
import { MathEditor } from '@edtr-io/math';
import KaTeX from 'katex';
function _extends() {
_extends = Object.assign ? Object.assign.bind() : 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 _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (it) return (it = it.call(o)).next.bind(it);
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var HoveringToolbarColorIcon = /*#__PURE__*/styled.div(function (_ref) {
var color = _ref.color;
return {
display: 'inline-block',
backgroundColor: 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 (_ref) {
var index = _ref.index,
colorsTheme = _ref.colorsTheme;
return {
border: "2px solid " + (index === undefined ? colorsTheme.defaultColor : colorsTheme.colors[index % colorsTheme.colors.length]),
borderRadius: '4px',
bottom: '0',
width: '80%',
position: 'absolute'
};
});
var HoveringToolbarColorTextIcon = function HoveringToolbarColorTextIcon(_ref2) {
var index = _ref2.index,
colorsTheme = _ref2.colorsTheme;
return React__default.createElement(ColoredText, null, React__default.createElement(FlexContainer, null, React__default.createElement(EdtrIcon, {
icon: edtrColorText
}), React__default.createElement(Line, {
colorsTheme: colorsTheme,
index: index
})));
};
var withLinks = function withLinks(editor) {
var isInline = editor.isInline;
editor.isInline = function (element) {
return element.type === 'a' ? true : isInline(element);
};
return editor;
};
// TODO: Use enum for types here as in https://www.npmjs.com/package/@prezly/slate-lists
// TODO: Fix "as ..." parts in the functions
var withLists = function withLists(editor) {
var editorWithListsPlugin = withLists$1({
isConvertibleToListTextNode: function isConvertibleToListTextNode(node) {
return Element.isElementType(node, 'p');
},
isDefaultTextNode: function isDefaultTextNode(node) {
return Element.isElementType(node, 'p');
},
isListNode: function isListNode(node, type) {
if (type) {
return Element.isElementType(node, type);
}
return Element.isElementType(node, 'ordered-list') || Element.isElementType(node, 'unordered-list');
},
isListItemNode: function isListItemNode(node) {
return Element.isElementType(node, 'list-item');
},
isListItemTextNode: function isListItemTextNode(node) {
return Element.isElementType(node, 'list-item-child');
},
createDefaultTextNode: function createDefaultTextNode(props) {
if (props === void 0) {
props = {};
}
return _extends({
children: [{
text: ''
}]
}, props, {
type: 'p'
});
},
createListNode: function createListNode(type, props) {
if (type === void 0) {
type = ListType.UNORDERED;
}
if (props === void 0) {
props = {};
}
var nodeType = type === ListType.ORDERED ? 'ordered-list' : 'unordered-list';
return _extends({
children: [{
text: ''
}]
}, props, {
type: nodeType
});
},
createListItemNode: function createListItemNode(props) {
if (props === void 0) {
props = {};
}
return _extends({
children: [{
text: ''
}]
}, props, {
type: 'list-item'
});
},
createListItemTextNode: function createListItemTextNode(props) {
if (props === void 0) {
props = {};
}
return _extends({
children: [{
text: ''
}]
}, props, {
type: 'list-item-child'
});
}
});
return withListsReact(editorWithListsPlugin(editor));
};
var withMath = function withMath(editor) {
var isInline = editor.isInline,
isVoid = editor.isVoid;
editor.isInline = function (element) {
return element.type === 'math' ? true : isInline(element);
};
editor.isVoid = function (element) {
return element.type === 'math' ? true : isVoid(element);
};
return editor;
};
/** @public */
var TextEditorControl;
(function (TextEditorControl) {
TextEditorControl["code"] = "code";
TextEditorControl["colors"] = "colors";
TextEditorControl["headings"] = "headings";
TextEditorControl["katex"] = "katex";
TextEditorControl["links"] = "links";
TextEditorControl["lists"] = "lists";
TextEditorControl["math"] = "math";
TextEditorControl["paragraphs"] = "paragraphs";
TextEditorControl["richText"] = "richText";
})(TextEditorControl || (TextEditorControl = {}));
var isAnyColorActive = function isAnyColorActive(editor) {
var _SlateEditor$marks;
return typeof ((_SlateEditor$marks = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks.color) === 'number';
};
var isColorActive = function isColorActive(colorIndex) {
return function (editor) {
var _SlateEditor$marks2;
return ((_SlateEditor$marks2 = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks2.color) === colorIndex;
};
};
var resetColor = function resetColor(editor) {
Editor.removeMark(editor, 'color');
};
var toggleColor = function toggleColor(colorIndex) {
return function (editor) {
if (isColorActive(colorIndex)(editor)) {
Editor.removeMark(editor, 'color');
} else {
Editor.addMark(editor, 'color', colorIndex);
}
};
};
var getColorIndex = function getColorIndex(editor) {
var _SlateEditor$marks3;
return (_SlateEditor$marks3 = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks3.color;
};
function selectionHasElement(predicate, editor) {
var selection = editor.selection;
if (!selection) return false;
var _Array$from = Array.from(Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: function match(n) {
return !Editor.isEditor(n) && Element.isElement(n) && predicate(n);
}
})),
match = _Array$from[0];
return !!match;
}
function trimSelection(editor) {
var selection = editor.selection;
if (!selection) return null;
var selectedText = Editor.string(editor, selection);
var isBackwardSelection = Range.isBackward(selection);
var anchorOffset = selection.anchor.offset;
var focusOffset = selection.focus.offset;
while (selectedText.startsWith(' ')) {
isBackwardSelection ? focusOffset++ : anchorOffset++;
selectedText = selectedText.substring(1);
}
while (selectedText.endsWith(' ')) {
isBackwardSelection ? anchorOffset-- : focusOffset--;
selectedText = selectedText.substring(0, selectedText.length - 1);
}
Transforms.setSelection(editor, {
anchor: _extends({}, selection.anchor, {
offset: anchorOffset
}),
focus: _extends({}, selection.focus, {
offset: focusOffset
})
});
}
function matchLinks(node) {
return Element.isElement(node) && node.type === 'a';
}
function isLinkActive(editor) {
return selectionHasElement(function (e) {
return e.type === 'a';
}, editor);
}
function getLinkElement(editor) {
var _Array$from = Array.from(Editor.nodes(editor, {
match: matchLinks
})),
match = _Array$from[0];
return match && match[0];
}
function toggleLink(editor) {
if (isLinkActive(editor)) {
Transforms.unwrapNodes(editor, {
match: matchLinks
});
return;
}
var selection = editor.selection;
var isCollapsed = selection && Range.isCollapsed(selection);
if (isCollapsed) {
Transforms.insertNodes(editor, {
type: 'a',
href: '',
children: [{
text: ' '
}]
});
return;
}
Transforms.wrapNodes(editor, {
type: 'a',
href: '',
children: []
}, {
split: true
});
Transforms.collapse(editor, {
edge: 'end'
});
}
function isOrderedListActive(editor) {
return selectionHasElement(function (e) {
return e.type === 'ordered-list';
}, editor);
}
function isUnorderedListActive(editor) {
return selectionHasElement(function (e) {
return e.type === 'unordered-list';
}, editor);
}
function toggleOrderedList(editor) {
if (isUnorderedListActive(editor)) {
ListsEditor.unwrapList(editor);
ListsEditor.wrapInList(editor, ListType.ORDERED);
} else if (isOrderedListActive(editor)) {
ListsEditor.unwrapList(editor);
} else {
ListsEditor.wrapInList(editor, ListType.ORDERED);
}
}
function toggleUnorderedList(editor) {
if (isOrderedListActive(editor)) {
ListsEditor.unwrapList(editor);
ListsEditor.wrapInList(editor, ListType.UNORDERED);
} else if (isUnorderedListActive(editor)) {
ListsEditor.unwrapList(editor);
} else {
ListsEditor.wrapInList(editor, ListType.UNORDERED);
}
}
function isMathActive(editor) {
return selectionHasElement(function (e) {
return e.type === 'math';
}, editor);
}
function toggleMath(editor) {
if (isMathActive(editor)) {
Transforms.removeNodes(editor, {
match: function match(n) {
return Element.isElement(n) && n.type === 'math';
}
});
} else {
var selection = editor.selection;
if (!selection) return;
var isCollapsed = Range.isCollapsed(selection);
if (isCollapsed) {
Transforms.insertNodes(editor, {
type: 'math',
src: '',
inline: true,
children: [{
text: ''
}]
});
} else {
Transforms.insertNodes(editor, [{
type: 'math',
src: Editor.string(editor, selection) || '',
inline: true,
children: []
}], {
at: selection
});
Transforms.move(editor, {
distance: 1,
reverse: true
});
}
}
}
function isBoldActive(editor) {
var _SlateEditor$marks;
return ((_SlateEditor$marks = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks.strong) === true;
}
function toggleBoldMark(editor) {
trimSelection(editor);
if (isBoldActive(editor)) {
Editor.removeMark(editor, 'strong');
} else {
Editor.addMark(editor, 'strong', true);
}
}
function isItalicActive(editor) {
var _SlateEditor$marks2;
return ((_SlateEditor$marks2 = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks2.em) === true;
}
function toggleItalicMark(editor) {
trimSelection(editor);
if (isItalicActive(editor)) {
Editor.removeMark(editor, 'em');
} else {
Editor.addMark(editor, 'em', true);
}
}
function isCodeActive(editor) {
var _SlateEditor$marks3;
return ((_SlateEditor$marks3 = Editor.marks(editor)) == null ? void 0 : _SlateEditor$marks3.code) === true;
}
function toggleCode(editor) {
if (isCodeActive(editor)) {
Editor.removeMark(editor, 'code');
} else {
Editor.addMark(editor, 'code', true);
}
}
function isAnyHeadingActive(editor) {
return selectionHasElement(function (e) {
return e.type === 'h';
}, editor);
}
var isHeadingActive = function isHeadingActive(heading) {
return function (editor) {
return selectionHasElement(function (e) {
return e.type === 'h' && e.level === heading;
}, editor);
};
};
var toggleHeading = function toggleHeading(heading) {
return function (editor) {
if (isHeadingActive(heading)(editor)) {
Transforms.setNodes(editor, {
type: 'p'
});
Transforms.unsetNodes(editor, 'level');
} else {
Transforms.setNodes(editor, {
type: 'h',
level: heading
});
}
};
};
var _textPluginsMapper;
var textPluginsMapper = (_textPluginsMapper = {}, _textPluginsMapper[TextEditorControl.math] = withMath, _textPluginsMapper[TextEditorControl.links] = withLinks, _textPluginsMapper[TextEditorControl.lists] = withLists, _textPluginsMapper);
var isRegisteredTextPlugin = function isRegisteredTextPlugin(control) {
return control in textPluginsMapper;
};
var toggleLinkAndFlag = function toggleLinkAndFlag(setIsLinkNewlyCreated) {
return function (editor) {
toggleLink(editor);
setIsLinkNewlyCreated(true);
};
};
var registeredHotkeys = function registeredHotkeys(setIsLinkNewlyCreated) {
return [{
hotkey: 'mod+b',
control: TextEditorControl.richText,
handler: toggleBoldMark
}, {
hotkey: 'mod+i',
control: TextEditorControl.richText,
handler: toggleItalicMark
}, {
hotkey: 'mod+k',
control: TextEditorControl.links,
handler: toggleLinkAndFlag(setIsLinkNewlyCreated)
}, {
hotkey: 'mod+m',
control: TextEditorControl.math,
handler: toggleMath
}];
};
var useControls = function useControls(config, setIsLinkNewlyCreated) {
var controls = config.controls;
var createTextEditor = useCallback(function (baseEditor) {
return controls.reduce(function (currentEditor, currentControl) {
// If there is no control initialization function for the current control,
// return the editor as it was received
if (!isRegisteredTextPlugin(currentControl)) {
return currentEditor;
}
// Otherwise, apply the control initialization functions to the editor
return textPluginsMapper[currentControl](currentEditor);
}, baseEditor);
}, [controls]);
var toolbarControls = useMemo(function () {
return createToolbarControls(config, setIsLinkNewlyCreated);
}, [config, setIsLinkNewlyCreated]);
var handleHotkeys = useCallback(function (event, editor) {
// Go through the registered hotkeys
for (var _iterator = _createForOfIteratorHelperLoose(registeredHotkeys(setIsLinkNewlyCreated)), _step; !(_step = _iterator()).done;) {
var _step$value = _step.value,
hotkey = _step$value.hotkey,
control = _step$value.control,
handler = _step$value.handler;
// Check if their respective control is enabled
// and if the keyboard event contains the hotkey combination
if (controls.includes(control) && isHotkey(hotkey, event)) {
// If so, prevent the default event behavior,
// handle the hotkey and break out of the loop
event.preventDefault();
handler(editor);
break;
}
}
}, [controls, setIsLinkNewlyCreated]);
return {
createTextEditor: createTextEditor,
toolbarControls: toolbarControls,
handleHotkeys: handleHotkeys
};
};
function createToolbarControls(_ref, setIsLinkNewlyCreated) {
var i18n = _ref.i18n,
theme = _ref.theme,
controls = _ref.controls;
var allControls = [
// Bold
{
name: TextEditorControl.richText,
title: i18n.richText.toggleStrongTitle,
isActive: isBoldActive,
onClick: toggleBoldMark,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrBold
});
}
},
// Italic
{
name: TextEditorControl.richText,
title: i18n.richText.toggleEmphasizeTitle,
isActive: isItalicActive,
onClick: toggleItalicMark,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrItalic
});
}
},
// Link
{
name: TextEditorControl.links,
title: i18n.link.toggleTitle,
isActive: isLinkActive,
onClick: toggleLinkAndFlag(setIsLinkNewlyCreated),
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrLink
});
}
},
// Headings
{
name: TextEditorControl.headings,
title: i18n.headings.openMenuTitle,
closeMenuTitle: i18n.headings.closeMenuTitle,
isActive: isAnyHeadingActive,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrText
});
},
renderCloseMenuIcon: function renderCloseMenuIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrClose
});
},
children: theme.controls.headings.map(function (heading) {
return {
name: TextEditorControl.headings,
title: i18n.headings.setHeadingTitle(heading),
isActive: isHeadingActive(heading),
onClick: toggleHeading(heading),
renderIcon: function renderIcon() {
return React__default.createElement("span", null, "H", heading);
}
};
})
},
// Colors
{
name: TextEditorControl.colors,
title: i18n.colors.openMenuTitle,
closeMenuTitle: i18n.colors.closeMenuTitle,
isActive: function isActive() {
return false;
},
renderIcon: function renderIcon(editor) {
return React__default.createElement(HoveringToolbarColorTextIcon, {
index: getColorIndex(editor),
colorsTheme: theme.controls.colors
});
},
renderCloseMenuIcon: function renderCloseMenuIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrClose
});
},
children: [{
name: TextEditorControl.colors,
title: i18n.colors.resetColorTitle,
isActive: function isActive(editor) {
return !isAnyColorActive(editor);
},
onClick: resetColor,
renderIcon: function renderIcon() {
return React__default.createElement(HoveringToolbarColorIcon, {
color: theme.controls.colors.defaultColor
});
}
}].concat(theme.controls.colors.colors.map(function (color, colorIndex) {
return {
name: TextEditorControl.colors,
title: i18n.colors.colorNames[colorIndex],
isActive: isColorActive(colorIndex),
onClick: toggleColor(colorIndex),
renderIcon: function renderIcon() {
return React__default.createElement(HoveringToolbarColorIcon, {
color: color
});
}
};
}))
},
// Ordered list
{
name: TextEditorControl.lists,
title: i18n.list.toggleOrderedList,
isActive: isOrderedListActive,
onClick: toggleOrderedList,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrListNumbered
});
}
},
// Unordered list
{
name: TextEditorControl.lists,
title: i18n.list.toggleUnorderedList,
isActive: isUnorderedListActive,
onClick: toggleUnorderedList,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrListBullets
});
}
},
// Math
{
name: TextEditorControl.math,
title: i18n.math.toggleTitle,
isActive: isMathActive,
onClick: toggleMath,
renderIcon: function renderIcon() {
return React__default.createElement(EdtrIcon, {
icon: edtrFormula
});
}
},
// Code
{
name: TextEditorControl.code,
title: i18n.code.toggleTitle,
isActive: isCodeActive,
onClick: toggleCode,
renderIcon: function renderIcon() {
return React__default.createElement(Icon, {
icon: faCode
});
}
}];
return allControls.filter(function (control) {
return controls.includes(TextEditorControl[control.name]);
});
}
var hotKeysMap = {
SELECT_UP: 'up',
SELECT_DOWN: 'down',
INSERT: 'enter'
};
var useSuggestions = function useSuggestions(args) {
var _useState = useState(0),
selected = _useState[0],
setSelected = _useState[1];
var store = useScopedStore();
var text = args.text,
id = args.id,
editable = args.editable,
focused = args.focused;
var plugins = useContext(RegistryContext);
var allOptions = mapPlugins(plugins, text);
var showSuggestions = editable && focused && text.startsWith('/') && allOptions.length > 0;
var options = showSuggestions ? allOptions : [];
var currentValue = text.substring(1);
var closure = useRef({
showSuggestions: showSuggestions,
selected: selected,
options: options
});
closure.current = {
showSuggestions: showSuggestions,
selected: selected,
options: options
};
useEffect(function () {
if (options.length < selected) {
setSelected(0);
}
}, [options.length, selected]);
var handleSelectionChange = function handleSelectionChange(direction) {
return function () {
if (closure.current.showSuggestions) {
setSelected(function (currentSelected) {
var optionsCount = closure.current.options.length;
var value = direction === 'up' ? optionsCount - 1 : 1;
if (optionsCount === 0) return 0;
return (currentSelected + value) % optionsCount;
});
}
};
};
var handleSuggestionInsert = function handleSuggestionInsert() {
if (closure.current.showSuggestions) {
var option = closure.current.options[closure.current.selected];
if (!option) return;
setTimeout(function () {
insertPlugin(option.name);
});
}
};
var hotKeysHandlers = {
SELECT_UP: handleSelectionChange('up'),
SELECT_DOWN: handleSelectionChange('down'),
INSERT: handleSuggestionInsert
};
return {
showSuggestions: showSuggestions,
suggestionsProps: {
options: options,
currentValue: currentValue,
selected: selected,
onMouseDown: insertPlugin
},
hotKeysProps: {
keyMap: hotKeysMap,
handlers: hotKeysHandlers
},
handleHotkeys: handleHotkeys
};
function insertPlugin(plugin) {
store.dispatch(replace({
id: id,
plugin: plugin
}));
}
function handleHotkeys(event) {
if (closure.current.showSuggestions) {
if (['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) {
event.preventDefault();
return;
}
}
}
};
function mapPlugins(registry, text) {
var search = text.replace('/', '').toLowerCase();
var startingWithSearchString = registry.filter(function (_ref) {
var _title$toLowerCase;
var title = _ref.title;
if (!search.length) return true;
return title == null ? void 0 : (_title$toLowerCase = title.toLowerCase()) == null ? void 0 : _title$toLowerCase.startsWith(search);
});
var containingSearchString = registry.filter(function (_ref2) {
var title = _ref2.title;
var value = title == null ? void 0 : title.toLowerCase();
return (value == null ? void 0 : value.includes(search)) && !(value != null && value.startsWith(search));
});
return [].concat(startingWithSearchString, containingSearchString);
}
var defaultEnabledControls = [TextEditorControl.code, TextEditorControl.colors, TextEditorControl.headings, TextEditorControl.links, TextEditorControl.lists, TextEditorControl.math, TextEditorControl.richText];
var colors = [{
value: '#1794c1',
name: 'Blue'
}, {
value: '#469a40',
name: 'Green'
}, {
value: '#ff6703',
name: 'Orange'
}];
function useTextConfig(config) {
var _config$placeholder = config.placeholder,
placeholder = _config$placeholder === void 0 ? "Write something or add elements with \u2295." : _config$placeholder,
_config$i18n = config.i18n,
i18n = _config$i18n === void 0 ? {} : _config$i18n,
_config$theme = config.theme,
theme = _config$theme === void 0 ? {} : _config$theme,
blockquote = config.blockquote,
noLinebreaks = config.noLinebreaks;
var _useTheme = useTheme(),
editor = _useTheme.editor;
return {
controls: config.controls || defaultEnabledControls,
placeholder: placeholder,
i18n: merge({
fallback: {
blockquote: {
toggleTitle: 'Quote'
},
code: {
toggleTitle: 'Code'
},
colors: {
setColorTitle: 'Set color',
resetColorTitle: 'Reset color',
openMenuTitle: 'Colors',
closeMenuTitle: 'Close sub menu',
colorNames: colors.map(function (color) {
return color.name;
})
},
headings: {
setHeadingTitle: function setHeadingTitle(level) {
return "Heading " + level;
},
openMenuTitle: 'Headings',
closeMenuTitle: 'Close sub menu'
},
link: {
toggleTitle: 'Link (Strg + K)',
placeholder: 'Enter URL',
openInNewTabTitle: 'Open in new tab'
},
list: {
toggleOrderedList: 'Ordered list',
toggleUnorderedList: 'Unordered list',
openMenuTitle: 'Lists',
closeMenuTitle: 'Close sub menu'
},
math: {
toggleTitle: 'Math formula (Strg + M)',
displayBlockLabel: 'Display as block',
placeholder: '[formula]',
editors: {
visual: 'visual',
latex: 'LaTeX',
noVisualEditorAvailableMessage: 'Only LaTeX editor available'
},
helpText: function helpText(KeySpan) {
return createElement(Fragment, null, "Shortcuts:", createElement("br", null), createElement("br", null), createElement("p", null, "Fraction: ", createElement(KeySpan, null, "/")), createElement("p", null, "Superscript: ", createElement(KeySpan, null, "\u2191"), " or ", createElement(KeySpan, null, "^")), createElement("p", null, "Subscript: ", createElement(KeySpan, null, "\u2193"), " oder ", createElement(KeySpan, null, "_")), createElement("p", null, "\u03C0, \u03B1, \u03B2, \u03B3: ", createElement(KeySpan, null, "pi"), ", ", createElement(KeySpan, null, "alpha"), ",", ' ', createElement(KeySpan, null, "beta"), ",", createElement(KeySpan, null, "gamma")), createElement("p", null, "\u2264, \u2265: ", createElement(KeySpan, null, '<='), ", ", createElement(KeySpan, null, '>=')), createElement("p", null, "Root: ", createElement(KeySpan, null, "\\sqrt"), ", ", createElement(KeySpan, null, "\\nthroot")), createElement("p", null, "Math symbols: ", createElement(KeySpan, null, '\\<NAME>'), ", e.g.", ' ', createElement(KeySpan, null, "\\neq"), " (\u2260), ", createElement(KeySpan, null, "\\pm"), " (\xB1), ..."), createElement("p", null, "Functions: ", createElement(KeySpan, null, "sin"), ", ", createElement(KeySpan, null, "cos"), ",", ' ', createElement(KeySpan, null, "ln"), ", ..."));
}
},
richText: {
toggleStrongTitle: 'Bold (Strg + B)',
toggleEmphasizeTitle: 'Italic (Strg + I)'
},
suggestions: {
noResultsMessage: 'No items found'
}
},
values: i18n
}),
theme: merge({
fallback: {
backgroundColor: 'transparent',
color: editor.color,
hoverColor: editor.primary.background,
borderColor: editor.backgroundColor,
borderRadius: '4px',
active: {
backgroundColor: '#b6b6b6',
color: editor.backgroundColor
},
dropDown: {
backgroundColor: editor.backgroundColor
},
suggestions: {
background: {
"default": 'transparent',
highlight: editor.primary.background
},
text: {
"default": editor.color,
highlight: editor.danger.background
}
},
overlay: {
backgroundColor: editor.backgroundColor,
boxShadow: '0 2px 4px 0 rgba(0,0,0,0.50)',
color: editor.color
},
controls: {
colors: {
colors: colors.map(function (color) {
return color.value;
}),
defaultColor: 'black'
},
headings: [1, 2, 3]
}
},
values: theme
}),
blockquote: blockquote,
noLinebreaks: noLinebreaks
};
}
var handleMarkdown = function handleMarkdown(chars, editor) {
switch (chars) {
case '*':
case '-':
case '+':
return createUnorderedList(editor);
case '#':
return createHeading(1, editor);
case '##':
return createHeading(2, editor);
case '###':
return createHeading(3, editor);
default:
return undefined;
}
};
function createUnorderedList(editor) {
ListsEditor.wrapInList(editor, ListType.UNORDERED);
return true;
}
function createHeading(level, editor) {
Transforms.setNodes(editor, {
type: 'h',
level: level
});
return true;
}
var onSpace = function onSpace(event, editor) {
var selection = editor.selection;
if (selection) {
var nodes = Array.from(Editor.nodes(editor, {
at: selection
}));
if (nodes.length >= 2) {
var startBlock = nodes[2][0];
var text = Node.string(startBlock);
var chars = text.slice(0, selection == null ? void 0 : selection.focus.offset).replace(/\s*/g, '');
var handled = handleMarkdown(chars, editor);
if (handled) {
event.preventDefault();
editor.deleteBackward('word');
}
}
}
};
var markdownShortcuts = function markdownShortcuts() {
return {
onKeyDown: function onKeyDown(event, editor) {
switch (event.key) {
case ' ':
return onSpace(event, editor);
default:
return;
}
}
};
};
function isTouchDevice() {
return typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0);
}
var HoveringToolbarButton = /*#__PURE__*/styled.button(function (_ref) {
var active = _ref.active,
theme = _ref.theme;
return {
backgroundColor: active ? theme.active.backgroundColor : theme.backgroundColor,
cursor: 'pointer',
boxShadow: active ? 'inset 0 1px 3px 0 rgba(0,0,0,0.50)' : undefined,
color: active ? theme.active.color : theme.color,
outline: 'none',
height: '25px',
border: 'none',
borderRadius: theme.borderRadius,
margin: '5px',
padding: '0px',
width: '25px',
'&:hover': {
color: theme.hoverColor
}
};
});
function isNestedControlButton(control) {
return has('children', control);
}
function HoveringToolbarControls(props) {
var theme = props.theme,
controls = props.controls,
editor = props.editor;
var _React$useState = React__default.useState(),
subMenu = _React$useState[0],
setSubMenu = _React$useState[1];
if (typeof subMenu !== 'number') {
return React__default.createElement(React__default.Fragment, null, controls.map(function (control, index) {
return React__default.createElement(HoveringToolbarButton, {
active: control.isActive(editor),
theme: theme,
title: control.title,
onMouseDown: function onMouseDown(event) {
event.preventDefault();
isNestedControlButton(control) ? setSubMenu(index) : control.onClick(editor);
},
key: index
}, control.renderIcon(editor));
}));
}
var activeControl = controls[subMenu];
if (!isNestedControlButton(activeControl)) return null;
var closeSubMenuControl = {
isActive: function isActive() {
return false;
},
renderIcon: function renderIcon() {
return activeControl.renderCloseMenuIcon();
},
onClick: function onClick() {
setSubMenu(undefined);
},
title: activeControl.closeMenuTitle
};
var subMenuControls = [].concat(activeControl.children, [closeSubMenuControl]);
return React__default.createElement(React__default.Fragment, null, subMenuControls.map(function (control, index) {
return React__default.createElement(HoveringToolbarButton, {
active: control.isActive(editor),
theme: theme,
title: control.title,
onMouseDown: function onMouseDown(event) {
event.preventDefault();
control.onClick(editor);
},
key: index
}, control.renderIcon(editor));
}));
}
var InlineOverlayPosition;
(function (InlineOverlayPosition) {
InlineOverlayPosition["above"] = "above";
InlineOverlayPosition["below"] = "below";
})(InlineOverlayPosition || (InlineOverlayPosition = {}));
function isAbove(position) {
return position === InlineOverlayPosition.above;
}
var Wrapper = /*#__PURE__*/styled.div({
position: 'absolute',
top: '-10000px',
left: '-10000px',
opacity: 0,
transition: 'opacity 0.5s',
zIndex: 95,
whiteSpace: 'nowrap'
});
var Content = /*#__PURE__*/styled.div(function (_ref) {
var theme = _ref.theme;
return {
boxShadow: theme.overlay.boxShadow,
backgroundColor: theme.overlay.backgroundColor,
color: theme.overlay.color,
borderRadius: theme.borderRadius
};
});
var Triangle = /*#__PURE__*/styled.div(function (_ref2) {
var _ref3;
var theme = _ref2.theme,
position = _ref2.position;
var borderPosition = isAbove(position) ? 'borderTop' : 'borderBottom';
return _ref3 = {
position: 'relative',
width: 0,
height: 0,
borderLeft: '5px solid transparent',
borderRight: '5px solid transparent'
}, _ref3[borderPosition] = "10px solid " + theme.borderColor, _ref3;
});
function InlineOverlay(_ref4) {
var config = _ref4.config,
children = _ref4.children,
initialPosition = _ref4.initialPosition,
hidden = _ref4.hidden;
var editor = useSlate();
var wrapper = React__default.useRef(null);
var triangle = React__default.useRef(null);
var _React$useState = React__default.useState(initialPosition),
position = _React$useState[0],
setPosition = _React$useState[1];
// eslint-disable-next-line react-hooks/exhaustive-deps
React__default.useLayoutEffect(function () {
if (!wrapper.current || !triangle.current) return;
var selection = editor.selection;
if (!selection) return;
if (hidden) {
wrapper.current.style.top = '';
wrapper.current.style.left = '';
return;
}
var domSelection = window.getSelection();
if (!domSelection || domSelection.rangeCount === 0) return;
var domRange = domSelection.getRangeAt(0);
var rect = domRange.getBoundingClientRect();
if (!rect || rect.height === 0) return;
if (!wrapper.current.offsetParent) return;
var parentRect = wrapper.current.offsetParent.getBoundingClientRect();
wrapper.current.style.opacity = '1';
var aboveValue = rect.top - wrapper.current.offsetHeight - 6;
setPosition(initialPosition === InlineOverlayPosition.above && aboveValue >= 0 ? InlineOverlayPosition.above : InlineOverlayPosition.below);
wrapper.current.style.top = (position === InlineOverlayPosition.above ? aboveValue : rect.bottom + 6) - parentRect.top + "px";
wrapper.current.style.left = Math.min(Math.max(rect.left - parentRect.left - wrapper.current.offsetWidth / 2 + rect.width / 2, 0), parentRect.width - wrapper.current.offsetWidth - 5) + "px";
triangle.current.style.left = rect.left - wrapper.current.offsetLeft - parentRect.left - triangle.current.offsetWidth / 2 + rect.width / 2 + "px";
});
return React__default.createElement(Wrapper, {
ref: wrapper
}, !isAbove(position) && React__default.createElement(Triangle, {
ref: triangle,
theme: config.theme,
position: position
}), React__default.createElement(Content, {
theme: config.theme
}, children), isAbove(position) && React__default.createElement(Triangle, {
ref: triangle,
theme: config.theme,
position: position
}));
}
var TimeoutBottomToolbarWrapper = /*#__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);
});
var initialPosition = /*#__PURE__*/isTouchDevice() ? InlineOverlayPosition.below : InlineOverlayPosition.above;
function HoveringToolbar(props) {
var _useState = useState(false),
isBottomToolbarActive = _useState[0],
setIsBottomToolbarActive = _useState[1];
var editor = props.editor,
config = props.config,
controls = props.controls,
text = props.text,
focused = props.focused;
var selection = editor.selection;
var isSelectionCollapsed = selection && Range.isCollapsed(selection);
var memoized = useRef({
value: text,
isSelectionCollapsed: isSelectionCollapsed
});
var showBottomToolbar = function showBottomToolbar() {
return setIsBottomToolbarActive(true);
};
useEffect(function () {
var debounceTimeout = setTimeout(showBottomToolbar, 2500);
var hasValueChanged = memoized.current.value !== text;
if (hasValueChanged || memoized.current.isSelectionCollapsed !== isSelectionCollapsed) {
memoized.current = {
value: text,
isSelectionCollapsed: isSelectionCollapsed
};
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
var timeout = hasValueChanged ? 2500 : 1000;
if (isSelectionCollapsed) {
debounceTimeout = setTimeout(showBottomToolbar, timeout);
}
setIsBottomToolbarActive(false);
}
return function () {
clearTimeout(debounceTimeout);
};
}, [text, isSelectionCollapsed]);
return React__default.createElement(React__default.Fragment, null, React__default.createElement(InlineOverlay, {
config: config,
initialPosition: initialPosition,
hidden: !selection || !focused || isSelectionCollapsed || Editor.string(editor, selection) === ''
}, React__default.createElement(HoveringToolbarControls, {
theme: config.theme,
controls: controls,
editor: editor
})), React__default.createElement(TimeoutBottomToolbarWrapper, {
isTouch: isTouchDevice(),
visible: !!isSelectionCollapsed && isBottomToolbarActive
}, isBottomToolbarActive && React__default.createElement(HoveringToolbarControls, {
theme: config.theme,
controls: controls,
editor: editor
})));
}
var InputInner = /*#__PURE__*/styled.input(function (_ref) {
var theme = _ref.theme;
return {
backgroundColor: theme.backgroundColor,
border: 'none',
borderBottom: "2px solid " + theme.color,
color: theme.color,
'&:focus': {
outline: 'none',
borderBottom: "2px solid " + theme.hoverColor
}
};
});
var InputRefForward = function InputRefForward(props, ref) {
return createElement(InputInner, Object.assign({}, props, {
ref: ref
}));
};
var LinkControlsInput = /*#__PURE__*/forwardRef(InputRefForward);
var InlinePreview = /*#__PURE__*/styled.span({
padding: '0px 8px'
});
var ChangeButton = /*#__PURE__*/styled.div(function (_ref) {
var theme = _ref.theme;
return {
padding: '5px 5px 5px 10px',
display: 'inline-block',
borderLeft: "2px solid " + theme.borderColor,
cursor: 'pointer',
margin: '2px',
'&:hover': {
color: theme.hoverColor
}
};
});
function LinkControls(_ref2) {
var hasSelectionChanged = _ref2.hasSelectionChanged,
editor = _ref2.editor,
config = _ref2.config,
isLinkNewlyCreated = _ref2.isLinkNewlyCreated,
setIsLinkNewlyCreated = _ref2.setIsLinkNewlyCreated;
var _React$useState = useState(null),
element = _React$useState[0],
setElement = _React$useState[1];
var _React$useState2 = useState(''),
value = _React$useState2[0],
setValue = _React$useState2[1];
var input = useRef(null);
var selection = editor.selection;
useEffect(function () {
if (!selection) return;
var isCollapsed = selection && Range.isCollapsed(selection);
if (isCollapsed && isLinkActive(editor)) {
var _element = getLinkElement(editor) || null;
setElement(_element);
setValue(_element ? _element.href : '');
} else {
setElement(null);
}
}, [hasSelectionChanged, selection, editor]);
useEffect(function () {
if (element && isLinkNewlyCreated) {
setTimeout(function () {
var _input$current;
setIsLinkNewlyCreated(false);
(_input$current = input.current) == null ? void 0 : _input$current.focus();
});
}
}, [element, isLinkNewlyCreated, setIsLinkNewlyCreated]);
if (!element) return null;
return createElement(InlineOverlay, {
config: config,
initialPosition: InlineOverlayPosition.below
}, createElement(InlinePreview, null, createElement(LinkControlsInput, {
ref: input,
theme: config.theme,
value: value,
placeholder: config.i18n.link.placeholder,
onChange: function onChange(event) {
setValue(event.target.value);
var path = ReactEditor.findPath(editor, element);
Transforms.setNodes(editor, {
href: event.target.value
}, {
at: path
});
}
})), createElement(ChangeButton, {
theme: config.theme,
as: "a",
target: "_blank",
href: value,
rel: "noopener noreferrer"
}, createElement(Icon, {
icon: faExternalLinkAlt
})), createElement(ChangeButton, {
theme: config.theme,
onClick: function onClick() {
setElement(null);
var path = ReactEditor.findPath(editor, element);
Transforms.unwrapNodes(editor, {
at: path
});
}
}, createElement(Icon, {
icon: faTrashAlt
})));
}
var KaTeXSpan = /*#__PURE__*/styled.span(function (_ref) {
var element = _ref.element;
if (!element.inline) {
return {
display: 'block',
margin: '1em 0',
textAlign: 'center'
};
}
});
function MathFormula(_ref2) {
var element = _ref2.element;
var html = KaTeX.renderToString("" + (element.inline ? '' : '\\displaystyle ') + element.src, {
displayMode: false,
throwOnError: false
});
return React__default.createElement(KaTeXSpan, {
dangerouslySetInnerHTML: {
__html: html
},
element: element
});
}
var visualEditorPreferenceKey = 'text:math:visual-editor';
function MathElement(_ref) {
var element = _ref.element,
attributes = _ref.attributes,
focused = _ref.focused,
children = _ref.children;
var editor = useSlate();
var selected = useSelected();
var preferences = useContext(PreferenceContext);
var shouldShowMathEditor = focused && selected && editor.selection && Range.isCollapsed(editor.selection);
if (!shouldShowMathEditor) {
return React__default.createElement("span", Object.assign({}, attributes), React__default.createElement(MathFormula, {
element: element
}), children);
}
var isVisualMode = !!preferences.getKey(visualEditorPreferenceKey);
function updateElement(update) {
var path = ReactEditor.findPath(editor, element);
Transforms.setNodes(editor, update, {
at: path
});
}
function transformOutOfElement(_temp) {
var _ref2 = _temp === void 0 ? {} : _temp,
_ref2$reverse = _ref2.reverse,
reverse = _ref2$reverse === void 0 ? false : _ref2$reverse,
_ref2$shouldDelete = _ref2.shouldDelete,
shouldDelete = _ref2$shouldDelete === void 0 ? false : _ref2$shouldDelete;
var unit = 'character';
Transforms.move(editor, {
unit: unit,
reverse: reverse
});
if (shouldDelete) {
Transforms["delete"](editor, {
unit: unit,
reverse: reverse
});
}
ReactEditor.focus(editor);
}
/* TODO: We need to define
export interface MathEditorProps {
config: DeepPartial<MathEditorConfig>
}
*/
return React__default.createElement("span", Object.assign({}, attributes, {
tabIndex: -1
}), React__default.createElement(MathEditor, {
autofocus: true,
state: element.src,
inline: element.inline,
readOnly: false,
visual: isVisualMode,
disableBlock: false,
onInlineChange: function onInlineChange(inline) {
updateElement({
inline: inline
});
},
onChange: function onChange(src) {
return updateElement({
src: src
});
},
onMoveOutRight: transformOutOfElement,
onMoveOutLeft: function onMoveOutLeft() {
transformOutOfElement({
reverse: true
});
},
onDeleteOutRight: function onDeleteOutRight() {
transformOutOfElement({
shouldDelete: true
});
},
onDeleteOutLeft: function onDeleteOutLeft() {
transformOutOfElement({
shouldDelete: true,
reverse: true
});
},
config: {},
onEditorChange: function onEditorChange(visual) {
return preferences.setKey(visualEditorPreferenceKey, visual);
}
}), children);
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
var Suggestion = /*#__PURE__*/styled.div(function (_ref) {
var isActive = _ref.isActive,
theme = _ref.theme;
return {
height: '32px',
padding: '4px 8px',
cursor: 'pointer',
backgroundColor: isActive ? theme.suggestions.background.highlight : theme.suggestions.background["default"],
borderRadius: '4px',
'&:hover': {
background: theme.suggestions.background.highlight
}
};
});
var Container = /*#__PURE__*/styled.div({
padding: '10px'
});
var StyledText = /*#__PURE__*/styled.span(function (_ref2) {
var isHighlighted = _ref2.isHighlighted,
theme = _ref2.theme;
return {
color: isHighlighted ? theme.suggestions.text.highlight : theme.suggestions.text["default"]
};
});
var Suggestions = function Suggestions(props) {
var config = props.config,
options = props.options,
currentValue = props.currentValue,
selected = props.selected,
_onMouseDown = props.onMouseDown;
var i18n = config.i18n,
theme = config.theme;
if (options.length === 0) {
return React__default.createElement(Container, null, i18n.suggestions.noResultsMessage);
}
return React__default.createElement(Container, null, options.map(function (_ref3, index) {
var name = _ref3.name,
title = _ref3.title;
var fragments = (title != null ? title : name).split(new RegExp("(" + escapeRegExp(currentValue) + ")", 'i')).map(function (text) {
return {
text: text,
isHighlighted: text.toLowerCase() === currentValue.toLowerCase()
};
});
return React__default.createElement(Suggestion, {
key: index,
isActive: index === selected,
onMouseDown: function onMouseDown() {
return _onMouseDown(name);
},
theme: theme
}, fragments.map(function (fragment, fragmentIndex) {
return React__default.createElement(StyledText, {
key: fragmentIndex,
isHighlighted: fragment.isHighlighted,
theme: theme
}, fragment.text);
}));
}));
};
function TextEditor(props) {
var _useState = useState(0),
hasSelectionChanged = _useState[0],
setHasSelectionChanged = _useState[1];
var _useState2 = useState(false),
isLinkNewlyCreated = _useState2[0],
setIsLinkNewlyCreated = _useState2[1];
var state = props.state,
id = props.id,
editable = props.editable,
focused = props.focused;
var _state$value = state.value,
selection = _state$value.selection,
value = _state$value.value;
var config = useTextConfig(props.config);
var textControls = useControls(config, setIsLinkNewlyCreated);
var createTextEditor = textControls.createTextEditor,
toolbarControls = textControls.toolbarControls;
var editor = useMemo(function () {
return createTextEditor(withReact(createEditor()));
}, [createTextEditor]);
var text = Node.string(editor);
var suggestions = useSuggestions({
text: text,
id: id,
editable: editable,
focused: focused
});
var showSuggestions = suggestions.showSuggestions,
hotKeysProps = suggestions.hotKeysProps,
suggestionsProps = suggestions.suggestionsProps;
var previousValue = useRef(value);
var previousSelection = useRef(selection);
useEffect(function () {
// The selection can only be null when the text plugin is initialized
// (In this case an update of the slate editor is not necessary)
if (!selection) return;
Transforms.setSelection(editor, selectio