mdast-util-caml
Version:
Converts a `micromark` token stream into an `mdast` syntax tree.
556 lines (524 loc) • 18.6 kB
JavaScript
// mdast-util-caml v0.0.9-md - https://github.com/wikibonsai/remark-caml.git
import { merge } from 'lodash-es';
import { selectAll } from 'unist-util-select';
import * as wikirefs from 'wikirefs';
import { resolve } from 'caml-mkdn';
import { safe } from 'mdast-util-to-markdown/lib/util/safe.js';
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _toPrimitive(input, hint) {
if (_typeof(input) !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (_typeof(res) !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return _typeof(key) === "symbol" ? key : String(key);
}
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 _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (_x = (_i = _i.call(arr)).next, 0 === i) {
if (Object(_i) !== _i) return;
_n = !1;
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
} catch (err) {
_d = !0, _e = err;
} finally {
try {
if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayLikeToArray$1(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _unsupportedIterableToArray$1(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest();
}
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
// required options
function initAttrBox(tree, opts) {
// opts
var defaults = {
attrs: {
render: true,
title: 'Attributes'
},
cssNames: {
attr: 'attr',
attrbox: 'attrbox',
attrboxTitle: 'attrbox-title',
// [[wikiattrs]]-related
wiki: 'wiki',
invalid: 'invalid',
// types
reftype: 'reftype__',
doctype: 'doctype__'
}
};
var fullOpts = merge(defaults, opts);
// todo: should i be making node assertions...?
// https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context
// extract attr data nodes
var attrDataNodes = selectAll('attrbox-data', tree);
// extract attr data
var attrData = attrDataNodes.reduce(function (acc, node) {
Object.entries(node.data.items).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
if (acc[key]) {
acc[key] = acc[key].concat(value);
} else {
acc[key] = value;
}
});
return acc;
}, {});
// build attrbox and return
return Object.keys(attrData).length > 0 ? buildAttrBox(attrData, fullOpts) : undefined;
}
// html properties are meant to build an element like this:
//
// <aside class='attrbox'>
// <span class='attrbox-title'>Attributes</span>
// <dl>
// <dt>key</dt>
// <dd><span class='attr type key'>val a</span></dd>
// <dd><span class='attr type key'>val b</span></dd>
// <dd><span class='attr type key'>val c</span></dd>
// ...
// </dl>
// </aside>
function buildAttrBox(attrData, fullOpts) {
// init
var attrbox = {
type: 'attrbox',
children: [],
data: {
items: {},
hName: 'aside',
hProperties: {
className: [fullOpts.cssNames.attrbox]
}
}
};
var attrboxTitle = {
type: 'attrbox-title',
children: [{
type: 'text',
value: fullOpts.attrs.title
}],
data: {
hName: 'span',
hProperties: {
className: [fullOpts.cssNames.attrboxTitle]
}
}
};
var attrBoxListNode = {
type: 'attrbox-list',
children: [],
data: {
hName: 'dl'
}
};
// construct
attrbox.children.push(attrboxTitle);
attrbox.children.push(attrBoxListNode);
// if we have attr items, process them
if (Object.keys(attrData).length > 0) {
// copy item data
attrbox.data.items = _objectSpread({}, attrData);
// build item hProperties from item data
for (var _i = 0, _Object$entries = Object.entries(attrData); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
attrtype = _Object$entries$_i[0],
items = _Object$entries$_i[1];
addAttrKey(attrbox, attrtype);
var _iterator = _createForOfIteratorHelper(items),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var item = _step.value;
if (item.type === 'wiki') {
addWikiAttrVal(attrbox, attrtype, item, fullOpts);
} else {
addAttrVal(attrbox, attrtype, item, fullOpts);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
}
return attrbox;
}
function addAttrKey(attrbox, key, fullOpts) {
var keyTxt = key ? key.trim() : undefined;
var keyNode = {
type: 'attr-key',
children: [{
type: 'text',
value: keyTxt ? keyTxt : 'key error'
}],
data: {
hName: 'dt'
}
};
// add to attrbox-list
attrbox.children[1].children.push(keyNode);
}
function addAttrVal(attrbox, key, val, fullOpts) {
var valNode = {
type: 'attr-val',
children: [{
type: val.type,
children: [{
type: 'text',
value: val.string
}],
data: {
hName: 'span',
hProperties: {
className: [fullOpts.cssNames.attr, val.type ? val.type.trim().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '') : 'valtype-error', key ? key.trim().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '') : 'key-error']
}
}
}],
data: {
hName: 'dd'
}
};
// add to attrbox-list
attrbox.children[1].children.push(valNode);
}
// todo: link to mdast-util-wikirefs instance
// this interface is to help cut down on interdependencies between caml + wikirefs
function addWikiAttrVal(attrbox, attrtype, wikiVal,
// wikiVal: WikiAttrData,
fullOpts // fullOpts: DefaultsWikiRefs & DefaultsWikiAttrs,
) {
var wikiNode; // let wikiNode: WikiAttrNode;
var valNode = {
type: 'attr-val',
children: [],
data: {
hName: 'dd'
}
};
// invalid
if (!wikiVal.htmlHref) {
wikiNode = {
type: 'wikiattr',
children: [{
type: 'text',
value: wikirefs.CONST.MARKER.OPEN + wikiVal.filename + wikirefs.CONST.MARKER.CLOSE
}],
data: {
hName: 'a',
hProperties: {
className: [fullOpts.cssNames.attr, fullOpts.cssNames.wiki, fullOpts.cssNames.invalid]
}
}
};
// valid
} else {
wikiNode = {
type: 'wikiattr',
children: [{
type: 'text',
value: wikiVal.htmlText ? wikiVal.htmlText : wikiVal.filename
}],
data: {
hName: 'a',
hProperties: {
className: wikiVal.doctype.length > 0
// with doctype
? [fullOpts.cssNames.attr, fullOpts.cssNames.wiki, attrtype ? fullOpts.cssNames.reftype + attrtype.trim().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '') : fullOpts.cssNames.reftype + 'attrtype-error', fullOpts.cssNames.doctype + wikiVal.doctype.trim().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '')]
// without doctype
: [fullOpts.cssNames.attr, fullOpts.cssNames.wiki, attrtype ? fullOpts.cssNames.reftype + attrtype.trim().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '') : fullOpts.cssNames.reftype + 'attrtype-error'],
href: wikiVal.baseUrl + wikiVal.htmlHref,
dataHref: wikiVal.baseUrl + wikiVal.htmlHref
}
}
};
}
// add wikiattr to attr-val
valNode.children.push(wikiNode);
// add to attrbox-list
attrbox.children[1].children.push(valNode);
}
// required options
function fromMarkdownCaml(opts) {
// note: enter/exit keys should match a token name
return {
enter: {
attrBox: enterAttrBox
},
exit: {
attrKey: exitAttrKey,
attrVal: exitAttrVal,
attrBox: exitAttrBox
}
};
function enterAttrBox(token) {
var attrBoxDataNode = {
type: 'attrbox-data',
data: {
items: {}
}
};
// is accessible via 'this.stack' (see below)
this.enter(attrBoxDataNode, token);
// current key
var curKey = this.getData('curKey');
if (curKey === undefined) {
this.setData('curKey', '');
}
}
function exitAttrKey(token) {
var key = this.sliceSerialize(token).trim();
var current = top(this.stack);
if (current.data && current.data.items && !Object.keys(current.data.items).includes(key)) {
current.data.items[key] = [];
}
this.setData('curKey', key);
}
function exitAttrVal(token) {
var stringValue = this.sliceSerialize(token);
var item = resolve(stringValue);
var current = top(this.stack);
var curKey = this.getData('curKey');
if (current.data && current.data.items) {
if (curKey !== undefined) {
current.data.items[curKey].push(item);
}
}
}
// note: leaving this here to close the token
function exitAttrBox(token) {
this.exit(token);
return;
}
// util
function top(stack) {
return Array.from(stack)[Array.from(stack).length - 1];
}
}
function toMarkdownCaml() {
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// opts
var defaults = {
attrs: {
enable: true,
toMarkdown: {
format: 'none',
listKind: 'mkdn',
prefixed: true
}
}
};
var fullOpts = merge(defaults, opts);
// 'defaults' ensure 'fullOpts.attrs' will be populated above
var format = fullOpts.attrs.toMarkdown.format;
var listKind = fullOpts.attrs.toMarkdown.listKind;
var prefixed = fullOpts.attrs.toMarkdown.prefixed;
return {
// TODO: I don't fully understand what this does, but I did my
// best to fill it in based on what I saw in other mdast utils
// (e.g. https://github.com/syntax-tree/mdast-util-math/blob/main/index.js#L135)
unsafe: [{
character: '[',
inConstruct: ['phrasing', 'label', 'reference']
}, {
character: ']',
inConstruct: ['label', 'reference']
}],
handlers: {
// as of (2021-05-07), the typings for Handle do not reflect
// that the handler will be passed nodes of a specific type
// note: name should match 'Node.type'
// attrbox: handleNode as Handle,
// note: 'case-conversion' <-> caseConversion seems to not be working...
'attrbox-data': handler,
attrboxData: handler
}
};
function handler(node, _, context) {
var exit = context.enter('attrbox');
var value = buildMkdn(node.data.items, context);
exit();
return value;
}
// note: this handler will generate markdown from the content's top-level attrbox
// function handleNode(
// node: (AttrBoxNode),
// _: Uni.Parent | null | undefined,
// context: Context,
// ): string {
// attrboxFound = true;
// const exit = context.enter('attrbox' as ConstructName);
// const value: string = buildMkdn(node.data.items, context);
// exit();
// return value;
// }
function buildMkdn(attrs, context) {
// init vars / build string value
var value = '';
if (prefixed) {
value += ':';
if (format === 'pad') {
value += ' ';
}
}
for (var _i = 0, _Object$entries = Object.entries(attrs); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
key = _Object$entries$_i[0],
items = _Object$entries$_i[1];
var vals = items;
// key
/* eslint-disable indent */
var nodeKey = safe(context, key, {
before: ':',
after: ':'
});
/* eslint-enable indent */
if (nodeKey) {
value += nodeKey;
if (format === 'pad') {
value += ' ';
}
value += '::';
if (format === 'pad') {
value += ' ';
}
}
// values / items
for (var i = 0; i < items.length; i++) {
// todo: link to mdast-util-wikirefs instance
// this interface is to help cut down on interdependencies between caml + wikirefs
// [[wikilink]] (taken from mdast-util-wikirefs)
if (vals[i].type === 'wiki') {
var isLastItem = i === vals.length - 1;
/* eslint-disable indent */
var nodeFileName = safe(context,
// todo: 'context' -> 'state' (see: https://github.com/syntax-tree/mdast-util-to-markdown/commit/e812c7954f8b8ea5dd68476c856cbfd7cc4c442b)
vals[i].filename, {
before: '[',
after: ']'
});
/* eslint-enable indent */
// single item or list comma-separated
if (vals.length === 1 || listKind === 'comma') {
value += "[[".concat(nodeFileName, "]]");
if (!isLastItem) {
value += ',';
if (format === 'pad') {
value += ' ';
}
}
}
// multiple items, list mkdn-separated
if (vals.length > 1 && listKind === 'mkdn') {
value += "\n- [[".concat(nodeFileName, "]]");
}
// add last newline after last item
if (isLastItem) {
value += '\n';
}
// primitive
} else {
var camlItem = vals[i];
var _isLastItem = i === vals.length - 1;
/* eslint-disable indent */
var nodeValue = safe(context, camlItem.string, {
before: '[',
after: ']'
});
/* eslint-enable indent */
// single item or list comma-separated
if (vals.length === 1 || listKind === 'comma') {
value += nodeValue;
if (!_isLastItem) {
value += ',';
if (format === 'pad') {
value += ' ';
}
}
}
// multiple items, list mkdn-separated
if (vals.length > 1 && listKind === 'mkdn') {
value += "\n- ".concat(nodeValue);
}
// add last newline after last item
if (_isLastItem) {
value += '\n';
}
}
}
}
return value;
}
}
export { fromMarkdownCaml, initAttrBox, toMarkdownCaml };
//# sourceMappingURL=index.js.map