UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

217 lines (213 loc) 10.3 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; 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(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } import { defaultSchema } from '@atlaskit/adf-schema/schema-default'; import { DOMParser } from '@atlaskit/editor-prosemirror/model'; var domParser = DOMParser.fromSchema(defaultSchema); export var getNestingRulesFromSchema = function getNestingRulesFromSchema() { var KEYWORDS = ['inline', 'block', 'text', 'leaf', 'group', 'unsupportedBlock', 'unsupportedInline']; var rules = {}; for (var _i = 0, _Object$keys = Object.keys(defaultSchema.nodes); _i < _Object$keys.length; _i++) { var _defaultSchema$nodes$; var nodeType = _Object$keys[_i]; var contentStr = (_defaultSchema$nodes$ = defaultSchema.nodes[nodeType]) === null || _defaultSchema$nodes$ === void 0 ? void 0 : _defaultSchema$nodes$.spec.content; if (!contentStr) { continue; } var allowedChildren = // eslint-disable-next-line require-unicode-regexp (String(contentStr).match(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g) || []).filter(function (match, index, arr) { return !KEYWORDS.includes(match) && defaultSchema.nodes[match] && arr.indexOf(match) === index; }); if (allowedChildren.length > 0) { rules[nodeType] = allowedChildren; } } return rules; }; var NESTING_RULES = getNestingRulesFromSchema(); var canContainChildren = function canContainChildren(nodeType) { var _NESTING_RULES$nodeTy; return !!((_NESTING_RULES$nodeTy = NESTING_RULES[nodeType]) !== null && _NESTING_RULES$nodeTy !== void 0 && _NESTING_RULES$nodeTy.length); }; var isAllowedChild = function isAllowedChild(parent, child) { var _NESTING_RULES$parent, _NESTING_RULES$parent2; return (_NESTING_RULES$parent = (_NESTING_RULES$parent2 = NESTING_RULES[parent]) === null || _NESTING_RULES$parent2 === void 0 ? void 0 : _NESTING_RULES$parent2.includes(child)) !== null && _NESTING_RULES$parent !== void 0 ? _NESTING_RULES$parent : false; }; var shouldApplyMark = function shouldApplyMark(tag) { return domParser.rules.some(function (rule) { return 'mark' in rule && rule.tag === tag; }); }; var isBlockElement = function isBlockElement(tag) { return domParser.rules.some(function (rule) { return rule.tag === tag && rule.node; }); }; var getMarkTypes = function getMarkTypes(tags) { var seen = new Set(); var marks = []; var _iterator = _createForOfIteratorHelper(tags), _step; try { var _loop = function _loop() { var _domParser$rules$find; var tag = _step.value; var markType = (_domParser$rules$find = domParser.rules.find(function (rule) { return rule.tag === tag; })) === null || _domParser$rules$find === void 0 ? void 0 : _domParser$rules$find.mark; if (markType && !seen.has(markType)) { marks.push({ type: markType }); seen.add(markType); } }; for (_iterator.s(); !(_step = _iterator.n()).done;) { _loop(); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return marks; }; var createParagraph = function createParagraph() { var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return { type: 'paragraph', content: content }; }; var getBlockType = function getBlockType(tag) { var rule = domParser.rules.find(function (r) { return r.tag === tag; }); return rule && 'node' in rule && rule.node ? rule.node : 'paragraph'; }; var createTextNode = function createTextNode(text, marks) { var markTypes = getMarkTypes(marks); return _objectSpread({ type: 'text', text: text }, markTypes.length > 0 && { marks: markTypes }); }; var addText = function addText(text, marks, content) { if (text) { content.push(createTextNode(text, marks)); } }; var handleBlockElement = function handleBlockElement(tag, innerContent, marks, blocks, currentParagraphContent, parseNode, nestedContainer) { // Push any accumulated content as paragraph before block element if (currentParagraphContent.length > 0) { blocks.push(createParagraph(_toConsumableArray(currentParagraphContent))); currentParagraphContent.length = 0; } var blockType = getBlockType(tag); var newMarks = shouldApplyMark(tag) ? [].concat(_toConsumableArray(marks), [tag]) : marks; var parentNodeType = nestedContainer === null || nestedContainer === void 0 ? void 0 : nestedContainer.parentType; if (nestedContainer && parentNodeType && isAllowedChild(parentNodeType, blockType)) { var elementContent = []; var childNestedContainer = canContainChildren(blockType) ? { parentType: blockType, children: elementContent } : nestedContainer; parseNode(innerContent, newMarks, childNestedContainer); if (currentParagraphContent.length > 0) { elementContent.push(createParagraph(_toConsumableArray(currentParagraphContent))); currentParagraphContent.length = 0; } nestedContainer.children.push({ type: blockType, content: elementContent.length > 0 ? elementContent : [createParagraph()] }); } else if (canContainChildren(blockType)) { var children = []; parseNode(innerContent, newMarks, { parentType: blockType, children: children }); blocks.push({ type: blockType, content: children.length > 0 ? children : [createParagraph()] }); } else { // Regular block elements parseNode(innerContent, newMarks, nestedContainer); // Push content generated from block parsing if (currentParagraphContent.length > 0) { if (blockType === 'paragraph' || blockType === 'codeBlock') { blocks.push({ type: blockType, content: _toConsumableArray(currentParagraphContent) }); } currentParagraphContent.length = 0; } } }; var handleInlineElement = function handleInlineElement(tag, innerContent, marks, parseNode, nestedContainer) { var newMarks = shouldApplyMark(tag) ? [].concat(_toConsumableArray(marks), [tag]) : marks; parseNode(innerContent, newMarks, nestedContainer); }; /** * Simple SSR-compatible parser that recognises text wrapped in HTML elements * and extracts their content as ADF. * * Designed specifically for parsing i18n strings for ADF which specifically need to be * HTML strings for translation. * * Supports nested structures automatically derived from the ADF schema: * - Lists: ul/ol → li (listItem) * - Tables: table → tr (tableRow) → td/th (tableCell/tableHeader) * - Paragraphs, code blocks, and text marks * - Any other nested structures defined in the schema * * @param html - The HTML string to parse * @returns ADF DocNode containing the parsed content */ export var parseHTMLTextContent = function parseHTMLTextContent(html) { var blocks = []; var currentParagraphContent = []; // Simple regex-based parser that works in both SSR and browser var _parseNode = function parseNode(content) { var marks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var nestedContainer = arguments.length > 2 ? arguments[2] : undefined; // Match HTML tags and text content // eslint-disable-next-line require-unicode-regexp var tagRegex = /<\s*(\w+)([^>]*)>([\s\S]*?)<\s*\/\s*\1\s*>|([^<]+)/g; var match = tagRegex.exec(content); while (match !== null) { if (match[4]) { addText(match[4], marks, currentParagraphContent); } else { // HTML element var tag = match[1].toLowerCase(); var innerContent = match[3]; if (isBlockElement(tag)) { handleBlockElement(tag, innerContent, marks, blocks, currentParagraphContent, _parseNode, nestedContainer); } else { handleInlineElement(tag, innerContent, marks, _parseNode, nestedContainer); } } match = tagRegex.exec(content); } }; _parseNode(html); // Push any remaining content if (currentParagraphContent.length > 0) { blocks.push(createParagraph(currentParagraphContent)); } return { type: 'doc', version: 1, content: blocks.length > 0 ? blocks : [createParagraph()] }; };