All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
527 lines
16 KiB
JavaScript
527 lines
16 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.default = remarkToSlate;
|
|
exports.mergeAdjacentTexts = mergeAdjacentTexts;
|
|
var _isEqual2 = _interopRequireDefault(require("lodash/isEqual"));
|
|
var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
|
|
var _map2 = _interopRequireDefault(require("lodash/map"));
|
|
var _flatMap2 = _interopRequireDefault(require("lodash/flatMap"));
|
|
var _isArray2 = _interopRequireDefault(require("lodash/isArray"));
|
|
var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
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 _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 _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); }
|
|
/**
|
|
* Map of MDAST node types to Slate node types.
|
|
*/
|
|
const typeMap = {
|
|
root: 'root',
|
|
paragraph: 'paragraph',
|
|
blockquote: 'quote',
|
|
code: 'code-block',
|
|
listItem: 'list-item',
|
|
table: 'table',
|
|
tableRow: 'table-row',
|
|
tableCell: 'table-cell',
|
|
thematicBreak: 'thematic-break',
|
|
link: 'link',
|
|
image: 'image',
|
|
shortcode: 'shortcode'
|
|
};
|
|
|
|
/**
|
|
* Map of MDAST node types to Slate mark types.
|
|
*/
|
|
const markMap = {
|
|
strong: 'bold',
|
|
emphasis: 'italic',
|
|
delete: 'delete',
|
|
inlineCode: 'code'
|
|
};
|
|
function isText(node) {
|
|
return !!node.text;
|
|
}
|
|
function isMarksEqual(node1, node2) {
|
|
return (0, _isEqual2.default)(node1.marks, node2.marks);
|
|
}
|
|
function mergeAdjacentTexts(children) {
|
|
if (children.length <= 0) {
|
|
return children;
|
|
}
|
|
const mergedChildren = [];
|
|
let isMerging = false;
|
|
let current;
|
|
for (let i = 0; i < children.length - 1; i++) {
|
|
if (!isMerging) {
|
|
current = children[i];
|
|
}
|
|
const next = children[i + 1];
|
|
if (isText(current) && isText(next) && isMarksEqual(current, next)) {
|
|
isMerging = true;
|
|
current = _objectSpread(_objectSpread({}, current), {}, {
|
|
text: `${current.text}${next.text}`
|
|
});
|
|
} else {
|
|
mergedChildren.push(current);
|
|
isMerging = false;
|
|
}
|
|
}
|
|
if (isMerging) {
|
|
mergedChildren.push(current);
|
|
} else {
|
|
mergedChildren.push(children[children.length - 1]);
|
|
}
|
|
return mergedChildren;
|
|
}
|
|
|
|
/**
|
|
* A Remark plugin for converting an MDAST to Slate Raw AST. Remark plugins
|
|
* return a `transformNode` function that receives the MDAST as it's first argument.
|
|
*/
|
|
function remarkToSlate({
|
|
voidCodeBlock
|
|
} = {}) {
|
|
return transformNode;
|
|
function transformNode(node) {
|
|
/**
|
|
* Call `transformNode` recursively on child nodes.
|
|
*
|
|
* If a node returns a falsey value, filter it out. Some nodes do not
|
|
* translate from MDAST to Slate, such as definitions for link/image
|
|
* references or footnotes.
|
|
*/
|
|
let children = !['strong', 'emphasis', 'delete'].includes(node.type) && !(0, _isEmpty2.default)(node.children) && (0, _flatMap2.default)(node.children, transformNode).filter(val => val);
|
|
if (Array.isArray(children)) {
|
|
// Merge adjacent text nodes with the same marks to conform to slate schema
|
|
children = mergeAdjacentTexts(children);
|
|
}
|
|
|
|
/**
|
|
* Run individual nodes through the conversion factory.
|
|
*/
|
|
const output = convertNode(node, children || undefined);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Add nodes to a parent node only if `nodes` is truthy.
|
|
*/
|
|
function addNodes(parent, nodes) {
|
|
return nodes ? _objectSpread(_objectSpread({}, parent), {}, {
|
|
children: nodes
|
|
}) : parent;
|
|
}
|
|
|
|
/**
|
|
* Create a Slate Block node.
|
|
*/
|
|
function createBlock(type, nodes, props = {}) {
|
|
if (!(0, _isArray2.default)(nodes)) {
|
|
props = nodes;
|
|
nodes = undefined;
|
|
}
|
|
|
|
// Ensure block nodes have at least one text child to conform to slate schema
|
|
const children = (0, _isEmpty2.default)(nodes) ? [createText('')] : nodes;
|
|
const node = _objectSpread({
|
|
type
|
|
}, props);
|
|
return addNodes(node, children);
|
|
}
|
|
|
|
/**
|
|
* Create a Slate Inline node.
|
|
*/
|
|
function createInline(type, props = {}, nodes) {
|
|
const node = _objectSpread({
|
|
type
|
|
}, props);
|
|
|
|
// Ensure inline nodes have at least one text child to conform to slate schema
|
|
const children = (0, _isEmpty2.default)(nodes) ? [createText('')] : nodes;
|
|
return addNodes(node, children);
|
|
}
|
|
|
|
/**
|
|
* Create a Slate Raw text node.
|
|
*/
|
|
function createText(node) {
|
|
const newNode = {};
|
|
if (typeof node === 'string') {
|
|
return _objectSpread(_objectSpread({}, newNode), {}, {
|
|
text: node
|
|
});
|
|
}
|
|
const {
|
|
text,
|
|
marks
|
|
} = node;
|
|
return normalizeMarks(_objectSpread(_objectSpread({}, newNode), {}, {
|
|
text,
|
|
marks
|
|
}));
|
|
}
|
|
function processMarkChild(childNode, marks) {
|
|
switch (childNode.type) {
|
|
/**
|
|
* If a text node is a direct child of the current node, it should be
|
|
* set aside as a text, and all marks that have been collected in the
|
|
* `marks` array should apply to that specific text.
|
|
*/
|
|
case 'html':
|
|
case 'text':
|
|
return _objectSpread(_objectSpread({}, convertNode(childNode)), {}, {
|
|
marks
|
|
});
|
|
|
|
/**
|
|
* MDAST inline code nodes don't have children, just a text value, similar
|
|
* to a text node, so it receives the same treatment as a text node, but we
|
|
* first add the inline code mark to the marks array.
|
|
*/
|
|
case 'inlineCode':
|
|
{
|
|
return _objectSpread(_objectSpread({}, convertNode(childNode)), {}, {
|
|
marks: [...marks, {
|
|
type: 'code'
|
|
}]
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Process nested style nodes. The recursive results should be pushed into
|
|
* the texts array. This way, every MDAST nested text structure becomes a
|
|
* flat array of texts that can serve as the value of a single Slate Raw
|
|
* text node.
|
|
*/
|
|
case 'strong':
|
|
case 'emphasis':
|
|
case 'delete':
|
|
return processMarkNode(childNode, marks);
|
|
case 'link':
|
|
{
|
|
const nodes = (0, _map2.default)(childNode.children, child => normalizeMarks(processMarkChild(child, marks)));
|
|
const result = convertNode(childNode, (0, _flatten2.default)(nodes));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Remaining nodes simply need mark data added to them, and to then be
|
|
* added into the cumulative children array.
|
|
*/
|
|
default:
|
|
return transformNode(_objectSpread(_objectSpread({}, childNode), {}, {
|
|
data: _objectSpread(_objectSpread({}, childNode.data), {}, {
|
|
marks
|
|
})
|
|
}));
|
|
}
|
|
}
|
|
function processMarkNode(node, parentMarks = []) {
|
|
/**
|
|
* Add the current node's mark type to the marks collected from parent
|
|
* mark nodes, if any.
|
|
*/
|
|
const markType = markMap[node.type];
|
|
const marks = markType ? [...parentMarks.filter(({
|
|
type
|
|
}) => type !== markType), {
|
|
type: markType
|
|
}] : parentMarks;
|
|
const children = (0, _flatMap2.default)(node.children, child => normalizeMarks(processMarkChild(child, marks)));
|
|
return children;
|
|
}
|
|
function normalizeMarks(node) {
|
|
if (node.marks) {
|
|
node.marks.forEach(mark => {
|
|
node[mark.type] = true;
|
|
});
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Convert a single MDAST node to a Slate Raw node. Uses local node factories
|
|
* that mimic the unist-builder function utilized in the slateRemark
|
|
* transformer.
|
|
*/
|
|
function convertNode(node, nodes) {
|
|
switch (node.type) {
|
|
/**
|
|
* General
|
|
*
|
|
* Convert simple cases that only require a type and children, with no
|
|
* additional properties.
|
|
*/
|
|
case 'paragraph':
|
|
case 'blockquote':
|
|
case 'tableRow':
|
|
case 'tableCell':
|
|
{
|
|
return createBlock(typeMap[node.type], nodes);
|
|
}
|
|
|
|
/**
|
|
* Root element
|
|
* If the root node is empty, we need to add a paragraph node to it.
|
|
*/
|
|
case 'root':
|
|
{
|
|
const children = (0, _isEmpty2.default)(nodes) ? [createBlock('paragraph')] : nodes;
|
|
return createBlock(typeMap[node.type], children);
|
|
}
|
|
|
|
/**
|
|
* List Items
|
|
*
|
|
* Markdown list items can be empty, but a list item in the Slate schema
|
|
* should at least have an empty paragraph node.
|
|
*/
|
|
case 'listItem':
|
|
{
|
|
const children = (0, _isEmpty2.default)(nodes) ? [createBlock('paragraph')] : nodes;
|
|
return createBlock(typeMap[node.type], children);
|
|
}
|
|
|
|
/**
|
|
* Shortcodes
|
|
*
|
|
* Shortcode nodes are represented as "void" blocks in the Slate AST. They
|
|
* maintain the same data as MDAST shortcode nodes. Slate void blocks must
|
|
* contain a blank text node.
|
|
*/
|
|
case 'shortcode':
|
|
{
|
|
const nodes = [createText('')];
|
|
const data = _objectSpread(_objectSpread({}, node.data), {}, {
|
|
id: node.data.shortcode,
|
|
shortcodeNew: true
|
|
});
|
|
return createBlock(typeMap[node.type], nodes, {
|
|
data
|
|
});
|
|
}
|
|
case 'text':
|
|
{
|
|
const text = node.value;
|
|
return createText(text);
|
|
}
|
|
|
|
/**
|
|
* HTML
|
|
*
|
|
* HTML nodes contain plain text like text nodes, except they only contain
|
|
* HTML. Our serialization results in non-HTML being placed in HTML nodes
|
|
* sometimes to ensure that we're never escaping HTML from the rich text
|
|
* editor. We do not replace line feeds in HTML because the HTML is raw
|
|
* in the rich text editor, so the writer knows they're writing HTML, and
|
|
* should expect soft breaks to be visually absent in the rendered HTML.
|
|
*/
|
|
case 'html':
|
|
{
|
|
return createText(node.value);
|
|
}
|
|
|
|
/**
|
|
* Inline Code
|
|
*
|
|
* Inline code nodes from an MDAST are represented in our Slate schema as
|
|
* text nodes with a "code" mark. We manually create the text containing
|
|
* the inline code value and a "code" mark, and place it in an array for use
|
|
* as a Slate text node's children array.
|
|
*/
|
|
case 'inlineCode':
|
|
{
|
|
return createText({
|
|
text: node.value,
|
|
code: true,
|
|
marks: [{
|
|
type: 'code'
|
|
}]
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Marks
|
|
*
|
|
* Marks are typically decorative sub-types that apply to text nodes. In an
|
|
* MDAST, marks are nodes that can contain other nodes. This nested
|
|
* hierarchy has to be flattened and split into distinct text nodes with
|
|
* their own set of marks.
|
|
*/
|
|
case 'strong':
|
|
case 'emphasis':
|
|
case 'delete':
|
|
{
|
|
return processMarkNode(node);
|
|
}
|
|
|
|
/**
|
|
* Headings
|
|
*
|
|
* MDAST headings use a single type with a separate "depth" property to
|
|
* indicate the heading level, while the Slate schema uses a separate node
|
|
* type for each heading level. Here we get the proper Slate node name based
|
|
* on the MDAST node depth.
|
|
*/
|
|
case 'heading':
|
|
{
|
|
const depthMap = {
|
|
1: 'one',
|
|
2: 'two',
|
|
3: 'three',
|
|
4: 'four',
|
|
5: 'five',
|
|
6: 'six'
|
|
};
|
|
const slateType = `heading-${depthMap[node.depth]}`;
|
|
return createBlock(slateType, nodes);
|
|
}
|
|
|
|
/**
|
|
* Code Blocks
|
|
*
|
|
* MDAST code blocks are a distinct node type with a simple text value. We
|
|
* convert that value into a nested child text node for Slate. If a void
|
|
* node is required due to a custom code block handler, the value is
|
|
* stored in the "code" data property instead. We also carry over the "lang"
|
|
* data property if it's defined.
|
|
*/
|
|
case 'code':
|
|
{
|
|
const data = _objectSpread(_objectSpread({
|
|
lang: node.lang
|
|
}, voidCodeBlock ? {
|
|
code: node.value
|
|
} : {}), {}, {
|
|
shortcode: 'code-block',
|
|
shortcodeData: {
|
|
code: node.value,
|
|
lang: node.lang
|
|
}
|
|
});
|
|
const text = createText(voidCodeBlock ? '' : node.value);
|
|
const nodes = [text];
|
|
const block = createBlock('shortcode', nodes, {
|
|
data
|
|
});
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* Lists
|
|
*
|
|
* MDAST has a single list type and an "ordered" property. We derive that
|
|
* information into the Slate schema's distinct list node types. We also
|
|
* include the "start" property, which indicates the number an ordered list
|
|
* starts at, if defined.
|
|
*/
|
|
case 'list':
|
|
{
|
|
const slateType = node.ordered ? 'numbered-list' : 'bulleted-list';
|
|
const data = {
|
|
start: node.start
|
|
};
|
|
return createBlock(slateType, nodes, {
|
|
data
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Breaks
|
|
*
|
|
* MDAST soft break nodes represent a trailing double space or trailing
|
|
* slash from a Markdown document. In Slate, these are simply transformed to
|
|
* line breaks within a text node.
|
|
*/
|
|
case 'break':
|
|
{
|
|
const {
|
|
data
|
|
} = node;
|
|
return createInline('break', {
|
|
data
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Thematic Breaks
|
|
*
|
|
* Thematic breaks are void nodes in the Slate schema.
|
|
*/
|
|
case 'thematicBreak':
|
|
{
|
|
return createBlock(typeMap[node.type]);
|
|
}
|
|
|
|
/**
|
|
* Links
|
|
*
|
|
* MDAST stores the link attributes directly on the node, while our Slate
|
|
* schema references them in the data object.
|
|
*/
|
|
case 'link':
|
|
{
|
|
const {
|
|
title,
|
|
url,
|
|
data
|
|
} = node;
|
|
const newData = _objectSpread(_objectSpread({}, data), {}, {
|
|
title,
|
|
url
|
|
});
|
|
return createInline(typeMap[node.type], {
|
|
data: newData
|
|
}, nodes);
|
|
}
|
|
|
|
/**
|
|
* Images
|
|
*
|
|
* Identical to link nodes except for the lack of child nodes and addition
|
|
* of alt attribute data MDAST stores the link attributes directly on the
|
|
* node, while our Slate schema references them in the data object.
|
|
*/
|
|
case 'image':
|
|
{
|
|
const {
|
|
title,
|
|
url,
|
|
alt,
|
|
data
|
|
} = node;
|
|
const newData = _objectSpread(_objectSpread({}, data), {}, {
|
|
title,
|
|
alt,
|
|
url
|
|
});
|
|
return createInline(typeMap[node.type], {
|
|
data: newData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tables
|
|
*
|
|
* Tables are parsed separately because they may include an "align"
|
|
* property, which should be passed to the Slate node.
|
|
*/
|
|
case 'table':
|
|
{
|
|
const data = {
|
|
align: node.align
|
|
};
|
|
return createBlock(typeMap[node.type], nodes, {
|
|
data
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} |