@adobe/helix-html-pipeline 1.1.2 → 1.2.1

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.
@@ -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
+ }
package/src/utils/path.js CHANGED
@@ -20,7 +20,10 @@ export function getPathInfo(path) {
20
20
  // eslint-disable-next-line no-param-reassign
21
21
  path = '/';
22
22
  }
23
- const segs = path.split(/\/+/);
23
+ if (path.match(/\/\/+/)) {
24
+ return null;
25
+ }
26
+ const segs = path.split('/');
24
27
  segs.shift(); // remove _emptyness_ before first slash
25
28
  if (segs.length < 1) {
26
29
  return null;
@@ -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.class = props.class ? `${DEFAULT_SECTION_CLASS} ${props.class}` : DEFAULT_SECTION_CLASS;
65
- const children = wrap(all(h, n), true);
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
- }