@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
217 lines (213 loc) • 10.3 kB
JavaScript
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()]
};
};