@lobehub/editor
Version:
A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.
459 lines (444 loc) • 23 kB
JavaScript
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 ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
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 _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 _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 _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 _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 _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/* eslint-disable @typescript-eslint/no-unused-vars */
import { DOMParser } from '@xmldom/xmldom';
import { $getRoot, $getSelection, $isElementNode, $isRangeSelection } from 'lexical';
import { DataSource } from "../../../editor-kernel";
import { INodeHelper } from "../../../editor-kernel/inode/helper";
import { INodeService } from "../../inode";
import { createDebugLogger } from "../../../utils/debug";
import { LitexmlService } from "../service/litexml-service";
import { $parseSerializedNodeImpl, charToId, idToChar } from "../utils";
var logger = createDebugLogger('plugin', 'litexml');
var IXmlWriterContext = /*#__PURE__*/function () {
function IXmlWriterContext() {
_classCallCheck(this, IXmlWriterContext);
}
_createClass(IXmlWriterContext, [{
key: "createXmlNode",
value: function createXmlNode(tagName, attributes, textContent) {
return {
attributes: attributes || {},
children: [],
tagName: tagName,
textContent: textContent
};
}
}]);
return IXmlWriterContext;
}();
/**
* LitexmlDataSource - Handles conversion between Lexical editor state and XML format
* Provides read (parse XML to Lexical) and write (export Lexical to XML) capabilities
*/
var LitexmlDataSource = /*#__PURE__*/function (_DataSource) {
_inherits(LitexmlDataSource, _DataSource);
var _super = _createSuper(LitexmlDataSource);
function LitexmlDataSource() {
var _this;
var dataType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'litexml';
var getService = arguments.length > 1 ? arguments[1] : undefined;
var service = arguments.length > 2 ? arguments[2] : undefined;
_classCallCheck(this, LitexmlDataSource);
_this = _super.call(this, dataType);
_defineProperty(_assertThisInitialized(_this), "litexmlService", void 0);
_defineProperty(_assertThisInitialized(_this), "ctx", new IXmlWriterContext());
_this.dataType = dataType;
_this.getService = getService;
_this.litexmlService = service || new LitexmlService();
return _this;
}
_createClass(LitexmlDataSource, [{
key: "readLiteXMLToInode",
value: function readLiteXMLToInode(litexml) {
var _this$getService;
if (typeof litexml !== 'string') {
throw new Error('Invalid data type. Expected string, got ' + _typeof(litexml));
}
var xml = this.parseXMLString(litexml);
var inode = this.xmlToLexical(xml);
(_this$getService = this.getService) === null || _this$getService === void 0 || (_this$getService = _this$getService.call(this, INodeService)) === null || _this$getService === void 0 || _this$getService.processNodeTree(inode);
logger.debug('Parsed XML to Lexical State:', inode);
return inode;
}
/**
* Parse XML string and set it to the editor
* @param editor - The Lexical editor instance
* @param data - XML string to parse
*/
}, {
key: "read",
value: function read(editor, data) {
try {
var inode = this.readLiteXMLToInode(data);
var newState = editor.parseEditorState({
root: INodeHelper.createRootNode()
}, function (state) {
try {
var root = $parseSerializedNodeImpl(inode.root, editor, true, state);
state._nodeMap.set(root.getKey(), root);
} catch (error) {
console.error(error);
}
});
editor.setEditorState(newState);
} catch (error) {
logger.error('Failed to parse XML:', error);
throw error;
}
}
/**
* Export editor content to XML format
* @param editor - The Lexical editor instance
* @param options - Write options (e.g., selection flag)
* @returns XML string representation of the editor content
*/
}, {
key: "write",
value: function write(editor, options) {
var _this2 = this;
try {
if (options !== null && options !== void 0 && options.selection) {
return editor.getEditorState().read(function () {
var selection = $getSelection();
if (!selection) {
return null;
}
if ($isRangeSelection(selection)) {
var selectedNodes = selection.getNodes();
var rootNode = INodeHelper.createRootNode();
// Wrap inline nodes in a paragraph
if (selectedNodes.some(function (node) {
return node.isInline();
})) {
var p = INodeHelper.createParagraph();
INodeHelper.appendChild.apply(INodeHelper, [p].concat(_toConsumableArray(selectedNodes.map(function (node) {
return node.exportJSON();
}))));
INodeHelper.appendChild(rootNode, p);
} else {
INodeHelper.appendChild.apply(INodeHelper, [rootNode].concat(_toConsumableArray(selectedNodes.map(function (node) {
return node.exportJSON();
}))));
}
var editorState = editor.parseEditorState({
root: rootNode
});
return editorState.read(function () {
var lexicalRootNode = $getRoot();
return _this2.lexicalToXML(lexicalRootNode);
});
}
return null;
});
}
return editor.getEditorState().read(function () {
var rootNode = $getRoot();
return _this2.lexicalToXML(rootNode);
});
} catch (error) {
logger.error('Failed to export to XML:', error);
throw error;
}
}
/**
* Parse XML string using browser's built-in parser
*/
}, {
key: "parseXMLString",
value: function parseXMLString(xmlString) {
var parser = new DOMParser();
var doc = parser.parseFromString(xmlString, 'text/xml');
if (doc.getElementsByTagName('parsererror').length > 0) {
throw new Error('Invalid XML: ' + xmlString);
}
return doc;
}
/**
* Convert XML document to Lexical node structure
*/
}, {
key: "xmlToLexical",
value: function xmlToLexical(xml) {
var rootNode = INodeHelper.createRootNode();
// Process XML root element's children
var xmlRoot = xml.documentElement;
if (xmlRoot) {
this.processXMLElement(xmlRoot, rootNode);
}
return {
root: rootNode
};
}
/**
* Recursively process XML elements and convert to Lexical nodes
*/
}, {
key: "processXMLElement",
value: function processXMLElement(xmlElement, parentNode) {
var _this3 = this;
var tagName = xmlElement.tagName.toLowerCase();
var customReaders = this.litexmlService.getXMLReaders();
// Check if there's a custom reader for this tag
if (customReaders[tagName]) {
var readerOrReaders = customReaders[tagName];
var readers = Array.isArray(readerOrReaders) ? readerOrReaders : [readerOrReaders];
var _iterator = _createForOfIteratorHelper(readers),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var reader = _step.value;
var children = [];
this.processXMLChildren(xmlElement, {
children: children
});
var result = reader(xmlElement, children);
if (result !== false) {
if (Array.isArray(result)) {
if (result.length > 0) {
var attrId = xmlElement.getAttribute('id');
result[0].id = attrId ? charToId(attrId) : undefined;
}
INodeHelper.appendChild.apply(INodeHelper, [parentNode].concat(_toConsumableArray(result)));
} else if (result) {
var _attrId = xmlElement.getAttribute('id');
result.id = _attrId ? charToId(_attrId) : undefined;
INodeHelper.appendChild(parentNode, result);
}
return; // Custom reader handled it
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
// Fall back to built-in handlers
switch (tagName) {
case 'p':
case 'paragraph':
{
var paragraph = INodeHelper.createParagraph();
this.processXMLChildren(xmlElement, paragraph);
INodeHelper.appendChild(parentNode, paragraph);
break;
}
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
{
var level = parseInt(tagName.charAt(1));
var heading = INodeHelper.createElementNode('heading', {
children: [],
tag: "h".concat(level)
});
this.processXMLChildren(xmlElement, heading);
INodeHelper.appendChild(parentNode, heading);
break;
}
case 'ul':
case 'ol':
{
xmlElement.querySelectorAll(':scope > li').forEach(function (li) {
var listItem = INodeHelper.createElementNode('listitem', {
children: [],
value: 1
});
_this3.processXMLChildren(li, listItem);
INodeHelper.appendChild(parentNode, listItem);
});
break;
}
case 'blockquote':
{
var quote = INodeHelper.createElementNode('quote', {
children: []
});
this.processXMLChildren(xmlElement, quote);
INodeHelper.appendChild(parentNode, quote);
break;
}
case 'code':
{
var codeNode = INodeHelper.createElementNode('codeInline', {
children: [INodeHelper.createTextNode(xmlElement.textContent || '')]
});
INodeHelper.appendChild(parentNode, codeNode);
break;
}
case 'text':
{
var textContent = xmlElement.textContent || '';
if (textContent) {
var textNode = INodeHelper.createTextNode(textContent);
INodeHelper.appendChild(parentNode, textNode);
}
break;
}
default:
{
// Treat unknown tags as generic containers
this.processXMLChildren(xmlElement, parentNode);
}
}
}
/**
* Process XML element's children
*/
}, {
key: "processXMLChildren",
value: function processXMLChildren(xmlElement, parentNode) {
var _this4 = this;
Array.from(xmlElement.childNodes).forEach(function (child) {
if (child.nodeType === 1) {
// Element node
_this4.processXMLElement(child, parentNode);
} else if (child.nodeType === 3) {
// Text node
var text = child.textContent || '';
if (text.trim()) {
var textNode = INodeHelper.createTextNode(text);
INodeHelper.appendChild(parentNode, textNode);
}
}
});
}
/**
* Convert Lexical node structure to XML string
*/
}, {
key: "lexicalToXML",
value: function lexicalToXML(rootNode) {
var _this5 = this;
var xmlLines = ['<?xml version="1.0" encoding="UTF-8"?>'];
xmlLines.push('<root>');
var children = rootNode.getChildren();
children.forEach(function (child) {
_this5.nodesToXML(child, xmlLines);
});
xmlLines.push('</root>');
return xmlLines.join('\n');
}
/**
* Recursively convert Lexical nodes to XML elements
*/
}, {
key: "nodesToXML",
value: function nodesToXML(node, lines) {
var _this6 = this;
var indent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
var indentStr = ' '.repeat(indent * 2);
var type = node.getType();
var customWriters = this.litexmlService.getXMLWriters();
var childLines = [];
// Check if there's a custom writer for this node type
if (customWriters[type]) {
var writerOrWriters = customWriters[type];
var writers = Array.isArray(writerOrWriters) ? writerOrWriters : [writerOrWriters];
var _iterator2 = _createForOfIteratorHelper(writers),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var writer = _step2.value;
var handled = writer(node, this.ctx, indent, this.nodesToXML.bind(this));
if (handled) {
if ('lines' in handled) {
lines.push.apply(lines, _toConsumableArray(handled.lines));
return;
}
var attrs = this.buildXMLAttributes(_objectSpread({
id: idToChar(node.getKey())
}, handled.attributes));
var openTag = "".concat(indentStr, "<").concat(handled.tagName).concat(attrs, ">");
var closeTag = "</".concat(handled.tagName, ">");
if (handled.textContent) {
lines.push("".concat(openTag).concat(handled.textContent).concat(closeTag));
} else if ($isElementNode(node)) {
var children = node.getChildren();
children.forEach(function (child) {
_this6.nodesToXML(child, childLines, indent + 1);
});
lines.push.apply(lines, [openTag].concat(childLines, ["".concat(indentStr).concat(closeTag)]));
} else {
lines.push(openTag, "".concat(indentStr).concat(closeTag));
}
return; // Custom writer handled it
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
if ($isElementNode(node)) {
var _children = node.getChildren();
_children.forEach(function (child) {
_this6.nodesToXML(child, childLines, indent);
});
}
lines.push.apply(lines, childLines);
}
/**
* Build XML attribute string from attributes object
*/
}, {
key: "buildXMLAttributes",
value: function buildXMLAttributes(attributes) {
var _this7 = this;
if (!attributes || _typeof(attributes) !== 'object') {
return '';
}
return Object.entries(attributes).filter(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
value = _ref2[1];
return value !== undefined && value !== null && value !== '';
}).map(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
key = _ref4[0],
value = _ref4[1];
var escapedValue = _this7.escapeXML(String(value));
return " ".concat(key, "=\"").concat(escapedValue, "\"");
}).join('');
}
/**
* Escape XML special characters
*/
}, {
key: "escapeXML",
value: function escapeXML(text) {
return text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
}
}]);
return LitexmlDataSource;
}(DataSource);
export { LitexmlDataSource as default };