@edtr-io/plugin-text
Version:
1,721 lines (1,527 loc) • 97.4 kB
JavaScript
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