datocms-html-to-structured-text
Version:
Convert HTML (or a `hast` syntax tree) to a valid DatoCMS Structured Text `dast` document
550 lines • 25.2 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import convert from 'hast-util-is-element/convert';
import toText from 'hast-util-to-text';
import has from 'hast-util-has-property';
import { allowedChildren, inlineNodeTypes, } from 'datocms-structured-text-utils';
import visitChildren from './visit-children';
import { wrap } from './wrap';
export var root = function root(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: 'root' }))];
case 1:
children = _a.sent();
if (Array.isArray(children) &&
children.some(function (child) { return child && !allowedChildren.root.includes(child.type); })) {
children = wrap(children);
}
if (!Array.isArray(children) || children.length === 0) {
return [2 /*return*/, null];
}
return [2 /*return*/, createNode('root', {
children: Array.isArray(children) ? children : [],
})];
}
});
});
};
export var paragraph = function paragraph(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isAllowedChild = allowedChildren[context.parentNodeType].includes('paragraph');
return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: isAllowedChild ? 'paragraph' : context.parentNodeType }))];
case 1:
children = _a.sent();
if (Array.isArray(children) && children.length) {
return [2 /*return*/, isAllowedChild ? createNode('paragraph', { children: children }) : children];
}
return [2 /*return*/, undefined];
}
});
});
};
export var thematicBreak = function thematicBreak(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild;
return __generator(this, function (_a) {
isAllowedChild = allowedChildren[context.parentNodeType].includes('thematicBreak');
return [2 /*return*/, isAllowedChild ? createNode('thematicBreak', {}) : undefined];
});
});
};
export var heading = function heading(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var level, isAllowedChild, children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
level = Number(node.tagName.charAt(1)) || 1;
isAllowedChild = allowedChildren[context.parentNodeType].includes('heading') &&
context.allowedBlocks.includes('heading') &&
context.allowedHeadingLevels.includes(level);
return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: isAllowedChild ? 'heading' : context.parentNodeType, wrapText: isAllowedChild ? false : context.wrapText }))];
case 1:
children = _a.sent();
if (Array.isArray(children) && children.length) {
return [2 /*return*/, isAllowedChild
? createNode('heading', {
level: level,
children: children,
})
: children];
}
return [2 /*return*/, undefined];
}
});
});
};
export var code = function code(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, prefix, isPre, isCode, children, index, classList, language;
return __generator(this, function (_a) {
isAllowedChild = allowedChildren[context.parentNodeType].includes('code');
if (!isAllowedChild) {
return [2 /*return*/, inlineCode(createNode, node, context)];
}
if (!context.allowedBlocks.includes('code')) {
return [2 /*return*/, visitChildren(createNode, node, context)];
}
prefix = typeof context.codePrefix === 'string' ? context.codePrefix : 'language-';
isPre = convert('pre');
isCode = convert('code');
children = node.children;
index = -1;
classList = null;
language = {};
if (isPre(node)) {
while (++index < children.length) {
if (typeof children[index] === 'object' &&
isCode(children[index]) &&
has(children[index], 'className')) {
// error TS2339: Property 'properties' does not exist on type 'HastNode'.
// Property 'properties' does not exist on type 'HastTextNode'
// isCode (convert) checks that the node is an element and therefore it'll have properties
// @ts-ignore
classList = children[index].properties.className;
break;
}
}
}
else if (isCode(node) && has(node, 'className')) {
classList = node.properties.className;
}
if (Array.isArray(classList)) {
index = -1;
while (++index < classList.length) {
if (classList[index].slice(0, prefix.length) === prefix) {
language = { language: classList[index].slice(prefix.length) };
break;
}
}
}
return [2 /*return*/, createNode('code', __assign(__assign({}, language), { code: String(wrapText(context, toText(node))).replace(/\n+$/, '') }))];
});
});
};
export var blockquote = function blockquote(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isAllowedChild = allowedChildren[context.parentNodeType].includes('blockquote') &&
context.allowedBlocks.includes('blockquote');
return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: isAllowedChild ? 'blockquote' : context.parentNodeType }))];
case 1:
children = _a.sent();
if (Array.isArray(children) && children.length) {
return [2 /*return*/, isAllowedChild
? createNode('blockquote', { children: wrap(children) })
: children];
}
return [2 /*return*/, undefined];
}
});
});
};
export var list = function list(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isAllowedChild = allowedChildren[context.parentNodeType].includes('list') &&
context.allowedBlocks.includes('list');
if (!!isAllowedChild) return [3 /*break*/, 2];
return [4 /*yield*/, visitChildren(createNode, node, context)];
case 1: return [2 /*return*/, _a.sent()];
case 2: return [4 /*yield*/, wrapListItems(createNode, node, __assign(__assign({}, context), { parentNodeType: 'list' }))];
case 3:
children = _a.sent();
if (Array.isArray(children) && children.length) {
return [2 /*return*/, createNode('list', {
children: children,
style: node.tagName === 'ol' ? 'numbered' : 'bulleted',
})];
}
return [2 /*return*/, undefined];
}
});
});
};
export var listItem = function listItem(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, children;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isAllowedChild = allowedChildren[context.parentNodeType].includes('listItem') &&
context.allowedBlocks.includes('list');
return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: isAllowedChild ? 'listItem' : context.parentNodeType }))];
case 1:
children = _a.sent();
if (Array.isArray(children) && children.length) {
return [2 /*return*/, isAllowedChild
? createNode('listItem', {
children: wrap(children),
})
: children];
}
return [2 /*return*/, undefined];
}
});
});
};
export var link = function link(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var isAllowedChild, allowedChildrenWrapped, wrapsHeadings, i_1, splitChildren_1, children, props, meta_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!context.allowedBlocks.includes('link')) {
return [2 /*return*/, visitChildren(createNode, node, context)];
}
isAllowedChild = false;
if (allowedChildren[context.parentNodeType] === 'inlineNodes') {
isAllowedChild = inlineNodeTypes.includes('link');
}
else if (Array.isArray(allowedChildren[context.parentNodeType])) {
isAllowedChild = allowedChildren[context.parentNodeType].includes('link');
}
if (!isAllowedChild) {
allowedChildrenWrapped = ['root', 'list', 'listItem'];
isAllowedChild = allowedChildrenWrapped.includes(context.parentNodeType);
}
wrapsHeadings = node.children.some(function (child) { return child.type === 'element' && child.tagName.startsWith('h'); });
if (wrapsHeadings) {
i_1 = 0;
splitChildren_1 = [];
node.children.forEach(function (child) {
if (child.type === 'element' && child.tagName.startsWith('h')) {
if (splitChildren_1.length > 0) {
i_1++;
}
splitChildren_1.push(__assign(__assign({}, child), { children: [
__assign(__assign({}, node), { children: child.children }),
] }));
i_1++;
}
else if (splitChildren_1[i_1]) {
splitChildren_1[i_1].children.push(child);
}
else {
splitChildren_1[i_1] = __assign(__assign({}, node), { children: [child] });
}
});
node.children = splitChildren_1;
isAllowedChild = false;
}
return [4 /*yield*/, visitChildren(createNode, node, __assign(__assign({}, context), { parentNodeType: isAllowedChild ? 'link' : context.parentNodeType }))];
case 1:
children = _a.sent();
if (Array.isArray(children) && children.length) {
if (!isAllowedChild) {
return [2 /*return*/, children];
}
props = {
url: resolveUrl(context, node.properties.href),
children: children,
};
meta_1 = [];
if (node.properties) {
['target', 'rel', 'title'].forEach(function (attr) {
var value = Array.isArray(node.properties[attr])
? node.properties[attr].join(' ')
: node.properties[attr];
if (value) {
meta_1.push({ id: attr, value: value });
}
});
}
if (meta_1.length > 0) {
props.meta = meta_1;
}
return [2 /*return*/, createNode('link', props)];
}
return [2 /*return*/, undefined];
}
});
});
};
export var span = function span(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var marks, allowedMarks;
return __generator(this, function (_a) {
marks = {};
if (Array.isArray(context.marks)) {
allowedMarks = context.marks.filter(function (mark) {
return context.allowedMarks.includes(mark);
});
if (allowedMarks.length > 0) {
marks.marks = allowedMarks;
}
}
return [2 /*return*/, createNode('span', __assign({ value: wrapText(context, node.value) }, marks))];
});
});
};
export var newLine = function newLine(createNode) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, createNode('span', {
value: '\n',
})];
});
});
};
export var inlineCode = withMark('code');
export var strong = withMark('strong');
export var italic = withMark('emphasis');
export var underline = withMark('underline');
export var strikethrough = withMark('strikethrough');
export var highlight = withMark('highlight');
export var head = function head(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var baseElement;
return __generator(this, function (_a) {
baseElement = node.children.find(function (child) { return child.tagName === 'base'; });
if (baseElement) {
return [2 /*return*/, context.handlers.base(createNode, baseElement, context)];
}
else {
return [2 /*return*/, undefined];
}
return [2 /*return*/];
});
});
};
export var base = function base(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!context.global.baseUrlFound &&
typeof node.properties === 'object' &&
node.properties.href) {
context.global.baseUrl = node.properties.href.replace(/\/$/, '');
context.global.baseUrlFound = true;
}
return [2 /*return*/];
});
});
};
export var extractInlineStyles = function extractInlineStyles(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var marks, newMarks_1;
return __generator(this, function (_a) {
marks = { marks: Array.isArray(context.marks) ? context.marks : [] };
if (node.properties && typeof node.properties.style === 'string') {
newMarks_1 = [];
node.properties.style.split(';').forEach(function (declaration) {
var _a = declaration.split(':'), firstChunk = _a[0], otherChunks = _a.slice(1);
var prop = firstChunk.trim();
var value = otherChunks.join(':').trim();
switch (prop) {
case 'font-weight':
if (value === 'bold' || Number(value) > 400) {
newMarks_1.push('strong');
}
break;
case 'font-style':
if (value === 'italic') {
newMarks_1.push('emphasis');
}
break;
case 'text-decoration':
if (value === 'underline') {
newMarks_1.push('underline');
}
break;
default:
break;
}
});
if (newMarks_1.length > 0) {
marks.marks = marks.marks.concat(newMarks_1.filter(function (mark) {
return !marks.marks.includes(mark) && context.allowedMarks.includes(mark);
}));
}
}
if (marks.marks.length === 0) {
marks = {};
}
return [2 /*return*/, visitChildren(createNode, node, __assign(__assign({}, context), marks))];
});
});
};
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types
export function noop() {
return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); });
}
export function withMark(type) {
return function markHandler(createNode, node, context) {
if (!context.allowedMarks.includes(type)) {
return visitChildren(createNode, node, context);
}
var marks = { marks: [type] };
if (Array.isArray(context.marks)) {
marks = {
marks: context.marks.includes(type)
? context.marks
: context.marks.concat([type]),
};
}
return visitChildren(createNode, node, __assign(__assign({}, context), marks));
};
}
export var handlers = {
root: root,
p: paragraph,
summary: paragraph,
h1: heading,
h2: heading,
h3: heading,
h4: heading,
h5: heading,
h6: heading,
ul: list,
ol: list,
dir: list,
dt: listItem,
dd: listItem,
li: listItem,
listing: code,
plaintext: code,
pre: code,
xmp: code,
blockquote: blockquote,
a: link,
code: code,
kbd: code,
samp: code,
tt: code,
var: code,
strong: strong,
b: strong,
em: italic,
i: italic,
u: underline,
strike: strikethrough,
s: strikethrough,
mark: highlight,
base: base,
span: extractInlineStyles,
text: span,
br: newLine,
hr: thematicBreak,
head: head,
comment: noop,
script: noop,
style: noop,
title: noop,
video: noop,
audio: noop,
embed: noop,
iframe: noop,
};
export var wrapListItems = function wrapListItems(createNode, node, context) {
return __awaiter(this, void 0, void 0, function () {
var children, index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, visitChildren(createNode, node, context)];
case 1:
children = _a.sent();
if (!Array.isArray(children)) {
return [2 /*return*/, []];
}
index = -1;
while (++index < children.length) {
if (typeof children[index] !== 'undefined' &&
children[index].type !== 'listItem') {
children[index] = {
type: 'listItem',
children: [
allowedChildren.listItem.includes(children[index].type)
? children[index]
: createNode('paragraph', { children: [children[index]] }),
],
};
}
}
return [2 /*return*/, children];
}
});
});
};
export function wrapText(context, value) {
return context.wrapText ? value : value.replace(/\r?\n|\r/g, ' ');
}
export function resolveUrl(context, url) {
if (url === null || url === undefined) {
return '';
}
if (context.global.baseUrl && typeof URL !== 'undefined') {
var isRelative = /^\.?\//.test(url);
var parsed = new URL(url, context.global.baseUrl);
if (isRelative) {
var parsedBase = new URL(context.global.baseUrl);
if (!parsed.pathname.startsWith(parsedBase.pathname)) {
parsed.pathname = "" + parsedBase.pathname + parsed.pathname;
}
}
return parsed.toString();
}
return url;
}
//# sourceMappingURL=handlers.js.map