@vivliostyle/vfm
Version:
Custom Markdown syntax specialized in book authoring.
236 lines (235 loc) • 8.1 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 __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readMetadata = void 0;
var js_yaml_1 = require("js-yaml");
var mdast_util_to_string_1 = __importDefault(require("mdast-util-to-string"));
var rehype_stringify_1 = __importDefault(require("rehype-stringify"));
var remark_frontmatter_1 = __importDefault(require("remark-frontmatter"));
var remark_parse_1 = __importDefault(require("remark-parse"));
var remark_rehype_1 = __importDefault(require("remark-rehype"));
var unified_1 = __importDefault(require("unified"));
var unist_util_select_1 = require("unist-util-select");
var unist_util_visit_1 = __importDefault(require("unist-util-visit"));
var attr_1 = require("./attr");
var footnotes_1 = require("./footnotes");
/**
* Read the title from heading without footnotes.
* @param tree Tree of Markdown AST.
* @returns Title text or `undefined`.
*/
var readTitleFromHeading = function (tree) {
var heading = (0, unist_util_select_1.select)('heading', tree);
if (!heading) {
return;
}
// Create title string with footnotes removed
var children = __spreadArray([], heading.children, true);
heading.children = heading.children.filter(function (child) { return child.type !== 'footnote'; });
// Remove ruby text and HTML tags
var text = (0, mdast_util_to_string_1.default)(heading)
.replace(/{(.+?)(?<=[^\\|])\|(.+?)}/g, '$1')
.replace(/<[^<>]*>/g, '');
heading.children = children;
return text;
};
/**
* Parse Markdown's Frontmatter to metadate (`VFile.data`).
* @returns Handler.
* @see https://github.com/Symbitic/remark-plugins/blob/master/packages/remark-meta/src/index.js
*/
var mdast = function () { return function (tree, file) {
(0, unist_util_visit_1.default)(tree, ['yaml'], function (node) {
var value = (0, js_yaml_1.load)(node.value, { schema: js_yaml_1.JSON_SCHEMA });
if (typeof value === 'object') {
file.data = __assign(__assign({}, file.data), value);
}
});
// If title is undefined in frontmatter, read from heading
if (!file.data.title) {
var title = readTitleFromHeading(tree);
if (title) {
file.data.title = title;
}
}
(0, unist_util_visit_1.default)(tree, ['shortcode'], function (node) {
if (node.identifier !== 'toc') {
return;
}
if (file.data.vfm) {
file.data.vfm.toc = true;
}
else {
file.data.vfm = { math: true, toc: true };
}
});
}; };
/**
* Parse Markdown frontmatter.
* @param md Markdown.
* @returns Key/Value pair.
*/
var parseMarkdown = function (md) {
var processor = (0, unified_1.default)()
.use([
[remark_parse_1.default, { gfm: true, commonmark: true }],
// Remove footnotes when reading title from heading
footnotes_1.mdast,
attr_1.mdast,
remark_frontmatter_1.default,
mdast,
])
.data('settings', { position: false })
.use(remark_rehype_1.default)
.use(rehype_stringify_1.default);
return processor.processSync(md).data;
};
/**
* Read the string or null value in the YAML parse result.
* If the value is null, it will be an empty string
* @param value Value of YAML parse result.
* @returns Text.
*/
var readStringOrNullValue = function (value) {
return value === null ? '' : "".concat(value);
};
/**
* Read an attributes from data object.
* @param data Data object.
* @returns Attributes of HTML tag.
*/
var readAttributes = function (data) {
if (data === null || typeof data !== 'object') {
return;
}
var result = [];
for (var _i = 0, _a = Object.keys(data); _i < _a.length; _i++) {
var key = _a[_i];
result.push({ name: key, value: readStringOrNullValue(data[key]) });
}
return result;
};
/**
* Read an attributes collection from data object.
* @param data Data object.
* @returns Attributes collection of HTML tag.
*/
var readAttributesCollection = function (data) {
if (!Array.isArray(data)) {
return;
}
var result = [];
data.forEach(function (value) {
var attributes = readAttributes(value);
if (attributes) {
result.push(attributes);
}
});
return result;
};
/**
* Read VFM settings from data object.
* @param data Data object.
* @returns Settings.
*/
var readSettings = function (data) {
if (data === null || typeof data !== 'object') {
return { toc: false };
}
return {
math: typeof data.math === 'boolean' ? data.math : undefined,
partial: typeof data.partial === 'boolean' ? data.partial : undefined,
hardLineBreaks: typeof data.hardLineBreaks === 'boolean'
? data.hardLineBreaks
: undefined,
disableFormatHtml: typeof data.disableFormatHtml === 'boolean'
? data.disableFormatHtml
: undefined,
theme: typeof data.theme === 'string' ? data.theme : undefined,
toc: typeof data.toc === 'boolean' ? data.toc : false,
};
};
/**
* Read metadata from Markdown frontmatter.
*
* Keys that are not defined as VFM are treated as `meta`. If you specify a key name in `customKeys`, the key and its data type will be preserved and stored in `custom` instead of `meta`.
* @param md Markdown.
* @param customKeys A collection of key names to be ignored by meta processing.
* @returns Metadata.
*/
var readMetadata = function (md, customKeys) {
if (customKeys === void 0) { customKeys = []; }
var metadata = {};
var data = parseMarkdown(md);
var others = [];
for (var _i = 0, _a = Object.keys(data); _i < _a.length; _i++) {
var key = _a[_i];
if (customKeys.includes(key)) {
if (!metadata.custom) {
metadata.custom = {};
}
metadata.custom[key] = data[key];
continue;
}
switch (key) {
case 'id':
case 'lang':
case 'dir':
case 'class':
case 'title':
metadata[key] = readStringOrNullValue(data[key]);
break;
case 'html':
case 'body':
case 'base':
metadata[key] = readAttributes(data[key]);
break;
case 'meta':
case 'link':
case 'script':
metadata[key] = readAttributesCollection(data[key]);
break;
case 'vfm':
metadata[key] = readSettings(data[key]);
break;
case 'style':
case 'head':
// Reserved for future use.
break;
default:
others.push([
{ name: 'name', value: key },
{ name: 'content', value: readStringOrNullValue(data[key]) },
]);
break;
}
}
// Other properties should be `<meta>`
if (0 < others.length) {
metadata.meta = metadata.meta ? metadata.meta.concat(others) : others;
}
return metadata;
};
exports.readMetadata = readMetadata;