@lobehub/editor
Version:
A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.
286 lines (272 loc) • 14 kB
JavaScript
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
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; }
import { $createRangeSelection, $getSelection, $isLineBreakNode, $isRangeSelection, $isRootOrShadowRoot, $isTextNode, $setSelection } from 'lexical';
import { PUNCTUATION_OR_SPACE, getOpenTagStartIndex, isEqualSubString } from "../utils";
export function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
var grandParentNode = parentNode.getParent();
if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
return false;
}
var textContent = anchorNode.getTextContent();
// Checking for anchorOffset position to prevent any checks for cases when caret is too far
// from a line start to be a part of block-level markdown trigger.
//
// TODO:
// Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
// since otherwise it won't be a markdown shortcut, but tables are exception
if (fromTrigger !== 'enter' && textContent[anchorOffset - 1] !== ' ') {
return false;
}
var _iterator = _createForOfIteratorHelper(elementTransformers),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _step$value = _step.value,
regExp = _step$value.regExp,
trigger = _step$value.trigger;
var _match = textContent.match(regExp);
if (fromTrigger === trigger && _match && _match[0].length === (fromTrigger === 'enter' || _match[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) {
return true;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return false;
}
export function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
var grandParentNode = parentNode.getParent();
if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
return false;
}
var textContent = anchorNode.getTextContent();
// Checking for anchorOffset position to prevent any checks for cases when caret is too far
// from a line start to be a part of block-level markdown trigger.
//
// TODO:
// Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
// since otherwise it won't be a markdown shortcut, but tables are exception
if (fromTrigger !== 'enter' && textContent[anchorOffset - 1] !== ' ') {
return false;
}
var _iterator2 = _createForOfIteratorHelper(elementTransformers),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _step2$value = _step2.value,
regExp = _step2$value.regExp,
replace = _step2$value.replace,
trigger = _step2$value.trigger;
var _match2 = textContent.match(regExp);
if (fromTrigger === trigger && _match2 && _match2[0].length === (fromTrigger === 'enter' || _match2[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) {
var nextSiblings = anchorNode.getNextSiblings();
var _anchorNode$splitText = anchorNode.splitText(anchorOffset),
_anchorNode$splitText2 = _slicedToArray(_anchorNode$splitText, 2),
leadingNode = _anchorNode$splitText2[0],
remainderNode = _anchorNode$splitText2[1];
var siblings = remainderNode ? [remainderNode].concat(_toConsumableArray(nextSiblings)) : nextSiblings;
if (replace(parentNode, siblings, _match2, false) !== false) {
leadingNode.remove();
return true;
}
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return false;
}
export function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) {
var textContent = anchorNode.getTextContent();
var lastChar = textContent[anchorOffset - 1];
var transformers = transformersByTrigger[lastChar];
if (!transformers) {
return false;
}
// If typing in the middle of content, remove the tail to do
// reg exp match up to a string end (caret position)
if (anchorOffset < textContent.length) {
textContent = textContent.slice(0, anchorOffset);
}
var _iterator3 = _createForOfIteratorHelper(transformers),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var transformer = _step3.value;
if (!transformer.replace || !transformer.regExp) {
continue;
}
var _match3 = textContent.match(transformer.regExp);
if (_match3 === null) {
continue;
}
var startIndex = _match3.index || 0;
var endIndex = startIndex + _match3[0].length;
var replaceNode = void 0;
if (startIndex === 0) {
var _anchorNode$splitText3 = anchorNode.splitText(endIndex);
var _anchorNode$splitText4 = _slicedToArray(_anchorNode$splitText3, 1);
replaceNode = _anchorNode$splitText4[0];
} else {
var _anchorNode$splitText5 = anchorNode.splitText(startIndex, endIndex);
var _anchorNode$splitText6 = _slicedToArray(_anchorNode$splitText5, 2);
replaceNode = _anchorNode$splitText6[1];
}
replaceNode.selectNext(0, 0);
transformer.replace(replaceNode, _match3);
return true;
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
return false;
}
export function $runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformers) {
var textContent = anchorNode.getTextContent();
var closeTagEndIndex = anchorOffset - 1;
var closeChar = textContent[closeTagEndIndex];
// Quick check if we're possibly at the end of inline markdown style
var matchers = textFormatTransformers[closeChar];
if (!matchers) {
return false;
}
var _iterator4 = _createForOfIteratorHelper(matchers),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var matcher = _step4.value;
var tag = matcher.tag;
var tagLength = tag.length;
var closeTagStartIndex = closeTagEndIndex - tagLength + 1;
// If tag is not single char check if rest of it matches with text content
if (tagLength > 1 && !isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) {
continue;
}
// Space before closing tag cancels inline markdown
if (textContent[closeTagStartIndex - 1] === ' ') {
continue;
}
// Some tags can not be used within words, hence should have newline/space/punctuation after it
var afterCloseTagChar = textContent[closeTagEndIndex + 1];
if (matcher.intraword === false && afterCloseTagChar && !PUNCTUATION_OR_SPACE.test(afterCloseTagChar)) {
continue;
}
var closeNode = anchorNode;
var openNode = closeNode;
var openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag);
// Go through text node siblings and search for opening tag
// if haven't found it within the same text node as closing tag
var sibling = openNode;
while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) {
if ($isLineBreakNode(sibling)) {
break;
}
if ($isTextNode(sibling)) {
if (sibling.hasFormat('code')) {
continue;
}
var siblingTextContent = sibling.getTextContent();
openNode = sibling;
openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag);
}
}
// Opening tag is not found
if (openTagStartIndex < 0) {
continue;
}
// No content between opening and closing tag
if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) {
continue;
}
// Checking longer tags for repeating chars (e.g. *** vs **)
var prevOpenNodeText = openNode.getTextContent();
if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) {
continue;
}
// Some tags can not be used within words, hence should have newline/space/punctuation before it
var beforeOpenTagChar = prevOpenNodeText[openTagStartIndex - 1];
if (matcher.intraword === false && beforeOpenTagChar && !PUNCTUATION_OR_SPACE.test(beforeOpenTagChar)) {
continue;
}
// Clean text from opening and closing tags (starting from closing tag
// to prevent any offset shifts if we start from opening one)
var prevCloseNodeText = closeNode.getTextContent();
var closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1);
closeNode.setTextContent(closeNodeText);
var openNodeText = openNode === closeNode ? closeNodeText : prevOpenNodeText;
openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength));
var _selection = $getSelection();
var nextSelection = $createRangeSelection();
$setSelection(nextSelection);
// Adjust offset based on deleted chars
var newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1;
nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text');
nextSelection.focus.set(closeNode.__key, newOffset, 'text');
if (matcher.process) {
matcher.process(nextSelection);
return true;
} else if (matcher.format) {
// Apply formatting to selected text
var _iterator5 = _createForOfIteratorHelper(matcher.format),
_step5;
try {
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
var format = _step5.value;
if (!nextSelection.hasFormat(format)) {
nextSelection.formatText(format);
}
}
// Collapse selection up to the focus point
} catch (err) {
_iterator5.e(err);
} finally {
_iterator5.f();
}
nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type);
// Remove formatting from collapsed selection
var _iterator6 = _createForOfIteratorHelper(matcher.format),
_step6;
try {
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
var _format = _step6.value;
if (nextSelection.hasFormat(_format)) {
nextSelection.toggleFormat(_format);
}
}
} catch (err) {
_iterator6.e(err);
} finally {
_iterator6.f();
}
if ($isRangeSelection(_selection)) {
nextSelection.format = _selection.format;
}
} else {
// No format or process specified, nothing to do
$setSelection(_selection);
continue;
}
return true;
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return false;
}