@adobe/helix-html-pipeline 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/package.json +17 -15
- package/src/PipelineContent.d.ts +8 -7
- package/src/PipelineContent.js +2 -0
- package/src/PipelineResponse.d.ts +6 -1
- package/src/html-pipe.js +14 -0
- package/src/steps/add-heading-ids.js +14 -13
- package/src/steps/create-page-blocks.js +28 -27
- package/src/steps/create-pictures.js +16 -12
- package/src/steps/extract-metadata.js +61 -44
- package/src/steps/fix-sections.js +8 -9
- package/src/steps/get-metadata.js +5 -4
- package/src/steps/make-html.js +3 -14
- package/src/steps/removeHlxProps.js +9 -10
- package/src/steps/render.js +68 -116
- package/src/steps/rewrite-blob-images.js +6 -24
- package/src/steps/rewrite-icons.js +30 -44
- package/src/steps/stringify-response.js +11 -11
- package/src/steps/utils.js +26 -4
- package/src/utils/{table-handler.js → hast-utils.js} +13 -15
- package/src/utils/heading-handler.js +11 -24
- package/src/utils/mdast-to-hast.js +60 -0
- package/src/utils/section-handler.js +6 -4
- package/src/utils/hast-util-to-dom.js +0 -190
- package/src/utils/icon-handler.js +0 -40
- package/src/utils/link-handler.js +0 -25
- package/src/utils/mdast-to-vdom.js +0 -323
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2018 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import { toHast as mdast2hast, defaultHandlers } from 'mdast-util-to-hast';
|
|
13
|
+
import { raw } from 'hast-util-raw';
|
|
14
|
+
import { visit, CONTINUE } from 'unist-util-visit';
|
|
15
|
+
|
|
16
|
+
import section from './section-handler.js';
|
|
17
|
+
import heading from './heading-handler.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Turns the MDAST into a HAST structure
|
|
21
|
+
* @param {Node} mdast mdast tree
|
|
22
|
+
* @param {GithubSlugger} slugger github slugger for the heading ids
|
|
23
|
+
* @returns {Root} the HAST document
|
|
24
|
+
*/
|
|
25
|
+
export default function getHast(mdast, slugger) {
|
|
26
|
+
const hast = mdast2hast(mdast, {
|
|
27
|
+
handlers: {
|
|
28
|
+
...defaultHandlers,
|
|
29
|
+
section: section(),
|
|
30
|
+
heading: heading(slugger),
|
|
31
|
+
},
|
|
32
|
+
allowDangerousHtml: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// TODO: remove for cleanup
|
|
36
|
+
// the following recreates a bug with the old vdom transformer that would create a
|
|
37
|
+
// <p></p> for all raw `<p>` before a block with void elements.
|
|
38
|
+
visit(hast, (node, idx, parent) => {
|
|
39
|
+
if (node.type !== 'raw' || node.value !== '<p>') {
|
|
40
|
+
return CONTINUE;
|
|
41
|
+
}
|
|
42
|
+
// check if any other raw empty nodes follow until the </p>
|
|
43
|
+
for (let i = idx + 1; i < parent.children.length; i += 1) {
|
|
44
|
+
const next = parent.children[i];
|
|
45
|
+
if (next.type === 'raw') {
|
|
46
|
+
if (next.value === '</p>') {
|
|
47
|
+
return i + 1;
|
|
48
|
+
}
|
|
49
|
+
if (next.value === '<br>' || next.value.startsWith('<img ')) {
|
|
50
|
+
node.value = '<p></p>';
|
|
51
|
+
return i + 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/* c8 ignore next */
|
|
56
|
+
return CONTINUE;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return raw(hast);
|
|
60
|
+
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { all } from 'mdast-util-to-hast';
|
|
13
|
-
import { wrap } from 'mdast-util-to-hast/lib/wrap.js';
|
|
14
13
|
|
|
15
14
|
const HELIX_NAMESPACE = 'hlx-';
|
|
16
15
|
const DEFAULT_SECTION_TAG = 'div';
|
|
@@ -61,9 +60,12 @@ export default function sectionHandler() {
|
|
|
61
60
|
|
|
62
61
|
const tagName = getTagName(n);
|
|
63
62
|
const props = getAttributes(n);
|
|
64
|
-
props.
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
props.className = [DEFAULT_SECTION_CLASS];
|
|
64
|
+
if (props.class) {
|
|
65
|
+
props.className.push(...props.class.split(/\s+/));
|
|
66
|
+
}
|
|
67
|
+
delete props.class;
|
|
68
|
+
const children = all(h, n);
|
|
67
69
|
return h(node, tagName, props, children);
|
|
68
70
|
};
|
|
69
71
|
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* (ISC License)
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) 2018 Keith McKnight <keith@mcknig.ht>
|
|
5
|
-
*
|
|
6
|
-
* Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
7
|
-
* with or without fee is hereby granted, provided that the above copyright notice
|
|
8
|
-
* and this permission notice appear in all copies.
|
|
9
|
-
*
|
|
10
|
-
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
11
|
-
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
12
|
-
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
13
|
-
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
14
|
-
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
15
|
-
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
16
|
-
* THIS SOFTWARE.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// This was copied from https://github.com/syntax-tree/hast-util-to-dom/blob/master/src/index.js
|
|
20
|
-
// and adapted so that it can be used with JSDOM
|
|
21
|
-
// TODO: contribute back to original
|
|
22
|
-
|
|
23
|
-
/* eslint-disable header/header */
|
|
24
|
-
import { find as infoFind, html as infoHtml } from 'property-information';
|
|
25
|
-
|
|
26
|
-
const ns = {
|
|
27
|
-
html: 'http://www.w3.org/1999/xhtml',
|
|
28
|
-
};
|
|
29
|
-
/* istanbul ignore next */
|
|
30
|
-
const wrap = (document) => {
|
|
31
|
-
// Add all children.
|
|
32
|
-
function appendAll(node, children, options) {
|
|
33
|
-
const childrenLength = children.length;
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < childrenLength; i += 1) {
|
|
36
|
-
// eslint-disable-next-line no-use-before-define
|
|
37
|
-
node.appendChild(transform(children[i], options));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return node;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Create a document.
|
|
44
|
-
function root(node, options) {
|
|
45
|
-
const { fragment, namespace: optionsNamespace } = options;
|
|
46
|
-
const { children = [] } = node;
|
|
47
|
-
const { length: childrenLength } = children;
|
|
48
|
-
|
|
49
|
-
let namespace = optionsNamespace;
|
|
50
|
-
let rootIsDocument = childrenLength === 0;
|
|
51
|
-
|
|
52
|
-
for (let i = 0; i < childrenLength; i += 1) {
|
|
53
|
-
const { tagName, properties = {} } = children[i];
|
|
54
|
-
|
|
55
|
-
/* c8 ignore start */
|
|
56
|
-
if (tagName === 'html') {
|
|
57
|
-
// If we have a root HTML node, we don’t need to render as a fragment.
|
|
58
|
-
rootIsDocument = true;
|
|
59
|
-
|
|
60
|
-
// Take namespace of the first child.
|
|
61
|
-
if (typeof optionsNamespace === 'undefined') {
|
|
62
|
-
namespace = properties.xmlns || ns.html;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/* c8 ignore end */
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// The root node will be a Document, DocumentFragment, or HTMLElement.
|
|
69
|
-
let el;
|
|
70
|
-
|
|
71
|
-
if (rootIsDocument) {
|
|
72
|
-
el = document.implementation.createDocument(namespace, '', null);
|
|
73
|
-
} else if (fragment) {
|
|
74
|
-
el = document.createDocumentFragment();
|
|
75
|
-
} else {
|
|
76
|
-
/* c8 ignore next */
|
|
77
|
-
el = document.createElement('html');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return appendAll(el, children, { fragment, namespace, ...options });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Create a `doctype`.
|
|
84
|
-
function doctype(node) {
|
|
85
|
-
return document.implementation.createDocumentType(
|
|
86
|
-
node.name || 'html',
|
|
87
|
-
node.public || '',
|
|
88
|
-
node.system || '',
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Create a `text`.
|
|
93
|
-
function text(node) {
|
|
94
|
-
return document.createTextNode(node.value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Create a `comment`.
|
|
98
|
-
function comment(node) {
|
|
99
|
-
return document.createComment(node.value);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Create an `element`.
|
|
103
|
-
function element(node, options) {
|
|
104
|
-
const { namespace } = options;
|
|
105
|
-
// TODO: use `g` in SVG space.
|
|
106
|
-
const { tagName = 'div', properties = {}, children = [] } = node;
|
|
107
|
-
const el = typeof namespace !== 'undefined'
|
|
108
|
-
? document.createElementNS(namespace, tagName)
|
|
109
|
-
: document.createElement(tagName);
|
|
110
|
-
|
|
111
|
-
// Add HTML attributes.
|
|
112
|
-
const props = Object.keys(properties);
|
|
113
|
-
const { length } = props;
|
|
114
|
-
|
|
115
|
-
for (let i = 0; i < length; i += 1) {
|
|
116
|
-
const key = props[i];
|
|
117
|
-
|
|
118
|
-
const {
|
|
119
|
-
attribute,
|
|
120
|
-
property,
|
|
121
|
-
// `mustUseAttribute`,
|
|
122
|
-
mustUseProperty,
|
|
123
|
-
boolean,
|
|
124
|
-
booleanish,
|
|
125
|
-
overloadedBoolean,
|
|
126
|
-
// `number`,
|
|
127
|
-
// `defined`,
|
|
128
|
-
commaSeparated,
|
|
129
|
-
// `spaceSeparated`,
|
|
130
|
-
// `commaOrSpaceSeparated`,
|
|
131
|
-
} = infoFind(infoHtml, key);
|
|
132
|
-
|
|
133
|
-
let value = properties[key];
|
|
134
|
-
|
|
135
|
-
if (Array.isArray(value)) {
|
|
136
|
-
value = value.join(commaSeparated ? ', ' : ' ');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (mustUseProperty) {
|
|
140
|
-
el[property] = value;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (boolean || (overloadedBoolean && typeof value === 'boolean')) {
|
|
144
|
-
if (value) {
|
|
145
|
-
el.setAttribute(attribute, '');
|
|
146
|
-
} else {
|
|
147
|
-
el.removeAttribute(attribute);
|
|
148
|
-
}
|
|
149
|
-
} else if (booleanish) {
|
|
150
|
-
el.setAttribute(attribute, value);
|
|
151
|
-
} else if (value === true) {
|
|
152
|
-
el.setAttribute(attribute, '');
|
|
153
|
-
} else if (value || value === 0 || value === '') {
|
|
154
|
-
el.setAttribute(attribute, value);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return appendAll(el, children, options);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// the raw node is stored in the value directly
|
|
162
|
-
function raw(node) {
|
|
163
|
-
return node.frag;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function transform(node, options) {
|
|
167
|
-
switch (node.type) {
|
|
168
|
-
case 'root':
|
|
169
|
-
return root(node, options);
|
|
170
|
-
case 'text':
|
|
171
|
-
return text(node);
|
|
172
|
-
case 'element':
|
|
173
|
-
return element(node, options);
|
|
174
|
-
case 'doctype':
|
|
175
|
-
return doctype(node);
|
|
176
|
-
case 'comment':
|
|
177
|
-
return comment(node);
|
|
178
|
-
case 'raw':
|
|
179
|
-
return raw(node);
|
|
180
|
-
default:
|
|
181
|
-
return element(node, options);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return transform;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
export default function toDOM(document, hast, options = {}) {
|
|
189
|
-
return wrap(document)(hast, options);
|
|
190
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2019 Adobe. All rights reserved.
|
|
3
|
-
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*
|
|
7
|
-
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
* governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Handles `icon` MDAST nodes by converting them into `img` or `<svg>` tags, e.g.
|
|
15
|
-
* `<img class="icon icon-smile" src="/icons/smile.svg"/>` or
|
|
16
|
-
* `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-smile"><use href="/icons.svg#smile"></use></svg>`
|
|
17
|
-
* @param {string} id the identifier of the icon
|
|
18
|
-
*/
|
|
19
|
-
export default function icon() {
|
|
20
|
-
return function handler(h, node) {
|
|
21
|
-
let { value } = node;
|
|
22
|
-
value = encodeURIComponent(value);
|
|
23
|
-
if (value.startsWith('%23')) {
|
|
24
|
-
// icon starts with #
|
|
25
|
-
value = value.substring(3);
|
|
26
|
-
return [h(node, 'svg', {
|
|
27
|
-
xmlns: 'http://www.w3.org/2000/svg',
|
|
28
|
-
className: `icon icon-${value}`,
|
|
29
|
-
}, [h(node, 'use', {
|
|
30
|
-
href: `/icons.svg#${value}`,
|
|
31
|
-
})])];
|
|
32
|
-
} else {
|
|
33
|
-
return [h(node, 'img', {
|
|
34
|
-
className: `icon icon-${value}`,
|
|
35
|
-
src: `/icons/${value}.svg`,
|
|
36
|
-
alt: `${value} icon`,
|
|
37
|
-
})];
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2018 Adobe. All rights reserved.
|
|
3
|
-
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*
|
|
7
|
-
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
* governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
import { link as fallback } from 'mdast-util-to-hast/lib/handlers/link.js';
|
|
13
|
-
import { parse, serialize } from 'uri-js';
|
|
14
|
-
|
|
15
|
-
export default function link({ extension = 'html' } = {}) {
|
|
16
|
-
return function handler(h, node) {
|
|
17
|
-
const n = { ...node };
|
|
18
|
-
const uriParts = parse(n.url);
|
|
19
|
-
if (!uriParts.scheme && uriParts.path) {
|
|
20
|
-
uriParts.path = uriParts.path.replace(/\.md$/, `.${extension}`);
|
|
21
|
-
n.url = serialize(uriParts, { absolutePath: true });
|
|
22
|
-
}
|
|
23
|
-
return fallback(h, n);
|
|
24
|
-
};
|
|
25
|
-
}
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2018 Adobe. All rights reserved.
|
|
3
|
-
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*
|
|
7
|
-
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
* governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { selectAll } from 'unist-util-select';
|
|
14
|
-
import { toHast as mdast2hast, defaultHandlers } from 'mdast-util-to-hast';
|
|
15
|
-
import { toHtml as hast2html } from 'hast-util-to-html';
|
|
16
|
-
import { JSDOM } from 'jsdom';
|
|
17
|
-
|
|
18
|
-
import toDOM from './hast-util-to-dom.js';
|
|
19
|
-
import HeadingHandler from './heading-handler.js';
|
|
20
|
-
import link from './link-handler.js';
|
|
21
|
-
import table from './table-handler.js';
|
|
22
|
-
import icon from './icon-handler.js';
|
|
23
|
-
import section from './section-handler.js';
|
|
24
|
-
|
|
25
|
-
const types = [
|
|
26
|
-
'root',
|
|
27
|
-
'paragraph',
|
|
28
|
-
'text',
|
|
29
|
-
'heading',
|
|
30
|
-
'thematicBreak',
|
|
31
|
-
'blockquote',
|
|
32
|
-
'list',
|
|
33
|
-
'listItem',
|
|
34
|
-
'table',
|
|
35
|
-
'tableRow',
|
|
36
|
-
'tableCell',
|
|
37
|
-
'html',
|
|
38
|
-
'code',
|
|
39
|
-
'yaml',
|
|
40
|
-
'definition',
|
|
41
|
-
'footnoteDefinition',
|
|
42
|
-
'emphasis',
|
|
43
|
-
'strong',
|
|
44
|
-
'delete',
|
|
45
|
-
'inlineCode',
|
|
46
|
-
'break',
|
|
47
|
-
'link',
|
|
48
|
-
'image',
|
|
49
|
-
'linkReference',
|
|
50
|
-
'imageReference',
|
|
51
|
-
'footnote',
|
|
52
|
-
'footnoteReference',
|
|
53
|
-
'embed',
|
|
54
|
-
'dataEmbed',
|
|
55
|
-
'section',
|
|
56
|
-
'icon',
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @typedef {function(parent, tagName, attributes, children)} handlerFunction
|
|
61
|
-
* @param {Node} parent the root node to append the new dom node to
|
|
62
|
-
* @param {string} tagName name of the new tag
|
|
63
|
-
* @param {object} attributes HTML attributes as key-value pairs
|
|
64
|
-
* @param {Node[]} children list of children
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Utility class that transforms an MDAST (Markdown) node into a (virtual) DOM
|
|
69
|
-
* representation of the same content.
|
|
70
|
-
*/
|
|
71
|
-
export default class VDOMTransformer {
|
|
72
|
-
/**
|
|
73
|
-
* Initializes the transformer with a Markdown document or fragment of a document
|
|
74
|
-
* @param {Node} mdast the markdown AST node to start the transformation from.
|
|
75
|
-
* @param {object} options options for custom transformers
|
|
76
|
-
*/
|
|
77
|
-
constructor(mdast, options) {
|
|
78
|
-
this._matchers = [];
|
|
79
|
-
this._handlers = {};
|
|
80
|
-
this._options = {};
|
|
81
|
-
|
|
82
|
-
// go over all handlers that have been defined
|
|
83
|
-
|
|
84
|
-
const that = this;
|
|
85
|
-
// use our own handle function for every known node type
|
|
86
|
-
types.map((type) => {
|
|
87
|
-
this._handlers[type] = (cb, node, parent) => VDOMTransformer.handle(cb, node, parent, that);
|
|
88
|
-
return true;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (mdast) {
|
|
92
|
-
this.withMdast(mdast);
|
|
93
|
-
}
|
|
94
|
-
this.withOptions(options);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
withOptions(options = {}) {
|
|
98
|
-
this._options = Object.assign(this._options, options);
|
|
99
|
-
|
|
100
|
-
this._headingHandler = new HeadingHandler(options.slugger);
|
|
101
|
-
this.match('heading', this._headingHandler.handler());
|
|
102
|
-
this.match('link', link(this._options));
|
|
103
|
-
this.match('icon', icon());
|
|
104
|
-
this.match('table', table());
|
|
105
|
-
this.match('section', section());
|
|
106
|
-
this.match('html', (h, node) => {
|
|
107
|
-
if (node.value.startsWith('<!--')) {
|
|
108
|
-
return h.augment(node, {
|
|
109
|
-
type: 'comment',
|
|
110
|
-
value: node.value.substring(4, node.value.length - 3),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
this._hasRaw = true;
|
|
114
|
-
const frag = JSDOM.fragment(node.value);
|
|
115
|
-
return h.augment(node, {
|
|
116
|
-
type: 'raw',
|
|
117
|
-
value: '', // we ignore the value here and treat it later in hast-util-to-dom
|
|
118
|
-
frag,
|
|
119
|
-
html: node.value,
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
withMdast(mdast) {
|
|
127
|
-
this._root = mdast;
|
|
128
|
-
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* A mdast-util-to-hast handler function that applies matchers and
|
|
134
|
-
* falls back to the default mdast-util-to-hast handlers if no matchers
|
|
135
|
-
* apply
|
|
136
|
-
* @private
|
|
137
|
-
* @param {handlerFunction} cb
|
|
138
|
-
* @param {Node} node the MDAST node to transofrm
|
|
139
|
-
* @param {Node} parent the MDAST parent or root node for select expressions
|
|
140
|
-
* @param {VDOMTransformer} that the MDAST to VDOM transformer
|
|
141
|
-
* @returns {Node} a HTAST representation of the `node` input
|
|
142
|
-
*/
|
|
143
|
-
static handle(cb, node, parent, that) {
|
|
144
|
-
// get the function that handles this node type
|
|
145
|
-
// this will fall back to the default if none matches
|
|
146
|
-
const handlefn = that.matches(node);
|
|
147
|
-
|
|
148
|
-
// process the node
|
|
149
|
-
|
|
150
|
-
const result = handlefn(cb, node, parent);
|
|
151
|
-
if (result && typeof result === 'string') {
|
|
152
|
-
throw new Error('returning string from a handler is not supported yet.');
|
|
153
|
-
} else if (result && typeof result === 'object' && result.outerHTML) {
|
|
154
|
-
throw new Error('returning a DOM element from a handler is not supported yet.');
|
|
155
|
-
}
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Returns the default handler for a given node type
|
|
161
|
-
* @param {Node} node an MDAST node
|
|
162
|
-
* @returns {handlerFunction} the default handler function
|
|
163
|
-
*/
|
|
164
|
-
static default(node) {
|
|
165
|
-
// use the default handler from mdast-util-to-hast
|
|
166
|
-
return defaultHandlers[node.type];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* A predicate function that filters MDAST nodes
|
|
171
|
-
* @typedef {function(node)} matcherFunction
|
|
172
|
-
* @param {Node} node an MDAST node
|
|
173
|
-
* @returns {boolean} true for matching nodes
|
|
174
|
-
*/
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Registers a handler function for nodes that match either a select expression
|
|
178
|
-
* or a matcher predicate function. The `matcher` will be evaluated against every
|
|
179
|
-
* node in the MDAST. In cases where the `matcher` matches (returns true), the
|
|
180
|
-
* processor will be called with the current node.
|
|
181
|
-
* @param {(string|matcherFunction)} matcher either an unist-util-select expression
|
|
182
|
-
* or a predicate function
|
|
183
|
-
* @param {handlerFunction} processor the appropriate handler function to handle matching types.
|
|
184
|
-
* @returns {VDOMTransformer} this, enabling chaining
|
|
185
|
-
*/
|
|
186
|
-
match(matcher, processor) {
|
|
187
|
-
const matchfn = typeof matcher === 'function' ? matcher : VDOMTransformer.matchfn(this._root, matcher);
|
|
188
|
-
this._matchers.push([matchfn, processor]);
|
|
189
|
-
return this;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Finds an appropriate handler for a given MDAST node
|
|
194
|
-
* @private
|
|
195
|
-
* @param {Node} node an MDAST node
|
|
196
|
-
* @returns {handlerFunction} a handler function to process the node with
|
|
197
|
-
*/
|
|
198
|
-
matches(node) {
|
|
199
|
-
// go through all matchers to find processors where matchfn matches
|
|
200
|
-
// start with most recently added processors
|
|
201
|
-
for (let i = this._matchers.length - 1; i >= 0; i -= 1) {
|
|
202
|
-
const [matchfn, processor] = this._matchers[i];
|
|
203
|
-
if (matchfn(node, this._root)) {
|
|
204
|
-
// return the first processor that matches
|
|
205
|
-
return processor;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// add the fallback processors
|
|
209
|
-
return VDOMTransformer.default(node);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Turns an unist-util-select expression into a matcher predicate function
|
|
214
|
-
* @private
|
|
215
|
-
* @param {Node} ast the MDAST root node to evaluated expressions against
|
|
216
|
-
* @param {string} pattern a CSS-like unist-util-select expression
|
|
217
|
-
* @returns {matcherFunction} a corresponding matcher function that returns true
|
|
218
|
-
* for nodes matching the pattern
|
|
219
|
-
*/
|
|
220
|
-
static matchfn(ast, pattern) {
|
|
221
|
-
// evaluating selectAll on a large tree for each handler is very expensive.
|
|
222
|
-
// use node name for simple element selectors
|
|
223
|
-
if (/^\w+$/.test(pattern)) {
|
|
224
|
-
return function match(node) {
|
|
225
|
-
return node.type === pattern;
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return function match(node, myast = ast) {
|
|
230
|
-
return selectAll(pattern, myast).indexOf(node) >= 0;
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Tries to sanitize inline HTML elements. The remark parser creates `raw` nodes for html.
|
|
236
|
-
* In case of inline html, those are not closed elements. since we generated the DOM already
|
|
237
|
-
* in the HTML handler, we get incomplete fragments, missing the inner HTML. This method tries
|
|
238
|
-
* to fix this. It doesn't support inner markdown-elements, though.
|
|
239
|
-
*
|
|
240
|
-
* @param {object} node HAST node
|
|
241
|
-
*/
|
|
242
|
-
static sanitizeInlineHTML(node) {
|
|
243
|
-
const stack = [];
|
|
244
|
-
for (let i = 0; i < node.children.length; i += 1) {
|
|
245
|
-
const child = node.children[i];
|
|
246
|
-
if (child.type === 'raw') {
|
|
247
|
-
if (child.frag.firstElementChild === null) {
|
|
248
|
-
if (stack.length === 0) {
|
|
249
|
-
// ignore unmatched inline elements
|
|
250
|
-
} else {
|
|
251
|
-
const last = stack.pop();
|
|
252
|
-
let html = '';
|
|
253
|
-
for (let j = last; j <= i; j += 1) {
|
|
254
|
-
const innerChild = node.children[j];
|
|
255
|
-
if (innerChild.type === 'raw') {
|
|
256
|
-
html += innerChild.html;
|
|
257
|
-
} else {
|
|
258
|
-
html += hast2html(innerChild);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
node.children[last].frag = JSDOM.fragment(html);
|
|
262
|
-
node.children[last].html = html;
|
|
263
|
-
node.children.splice(last + 1, i - last);
|
|
264
|
-
i = last;
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
stack.push(i);
|
|
268
|
-
}
|
|
269
|
-
} else if (child.children && child.children.length) {
|
|
270
|
-
VDOMTransformer.sanitizeInlineHTML(child);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Turns the MDAST into a full DOM-like structure using JSDOM
|
|
277
|
-
* @returns {Document} a full DOM document
|
|
278
|
-
*/
|
|
279
|
-
getDocument() {
|
|
280
|
-
// mdast -> hast; hast -> DOM using JSDOM
|
|
281
|
-
const hast = mdast2hast(this._root, {
|
|
282
|
-
handlers: this._handlers,
|
|
283
|
-
allowDangerousHtml: true,
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
if (this._hasRaw) {
|
|
287
|
-
VDOMTransformer.sanitizeInlineHTML(hast);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const dom = new JSDOM();
|
|
291
|
-
const doc = dom.window.document;
|
|
292
|
-
const frag = toDOM(doc, hast, { fragment: true });
|
|
293
|
-
|
|
294
|
-
if (frag.nodeName === '#document') {
|
|
295
|
-
// this only happens if it's an empty markdown document, so just ignore
|
|
296
|
-
} else {
|
|
297
|
-
doc.body.appendChild(frag);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// add convenience function to serialize entire document. this is to make it similar to the
|
|
301
|
-
// document created in html-to-vdom.
|
|
302
|
-
doc.serialize = dom.serialize.bind(dom);
|
|
303
|
-
|
|
304
|
-
// this is a bit a hack to pass the JSDOM instance along, so that other module can use it.
|
|
305
|
-
// this ensures that other modules can parse documents and fragments that are compatible
|
|
306
|
-
// with this document
|
|
307
|
-
Object.defineProperty(doc, 'JSDOM', {
|
|
308
|
-
enumerable: false,
|
|
309
|
-
writable: false,
|
|
310
|
-
value: JSDOM,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// this is another hack to pass the respective window instance along. this is to ensure that
|
|
314
|
-
// the shared prototypes can be used to check node instances (see jsdom 16.x release)
|
|
315
|
-
Object.defineProperty(doc, 'window', {
|
|
316
|
-
enumerable: false,
|
|
317
|
-
writable: false,
|
|
318
|
-
value: dom.window,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
return doc;
|
|
322
|
-
}
|
|
323
|
-
}
|