@adobe/helix-html-pipeline 1.0.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.
Files changed (60) hide show
  1. package/.eslintrc.cjs +33 -0
  2. package/.husky/pre-commit +4 -0
  3. package/.mocha-multi.json +6 -0
  4. package/.nycrc.json +10 -0
  5. package/.releaserc.cjs +16 -0
  6. package/CHANGELOG.md +6 -0
  7. package/CODE_OF_CONDUCT.md +74 -0
  8. package/CONTRIBUTING.md +74 -0
  9. package/LICENSE.txt +264 -0
  10. package/README.md +45 -0
  11. package/docs/API.md +12 -0
  12. package/package.json +101 -0
  13. package/src/PipelineContent.d.ts +69 -0
  14. package/src/PipelineContent.js +26 -0
  15. package/src/PipelineRequest.d.ts +26 -0
  16. package/src/PipelineRequest.js +36 -0
  17. package/src/PipelineResponse.d.ts +32 -0
  18. package/src/PipelineResponse.js +44 -0
  19. package/src/PipelineState.d.ts +72 -0
  20. package/src/PipelineState.js +42 -0
  21. package/src/PipelineStatusError.d.ts +14 -0
  22. package/src/PipelineStatusError.js +17 -0
  23. package/src/html-pipe.js +100 -0
  24. package/src/index.d.ts +98 -0
  25. package/src/index.js +18 -0
  26. package/src/json-pipe.js +87 -0
  27. package/src/steps/add-heading-ids.js +32 -0
  28. package/src/steps/create-page-blocks.js +78 -0
  29. package/src/steps/create-pictures.js +35 -0
  30. package/src/steps/extract-metadata.js +257 -0
  31. package/src/steps/fetch-config.js +42 -0
  32. package/src/steps/fetch-content.js +83 -0
  33. package/src/steps/fetch-metadata.js +53 -0
  34. package/src/steps/fix-sections.js +36 -0
  35. package/src/steps/folder-mapping.js +61 -0
  36. package/src/steps/get-metadata.js +170 -0
  37. package/src/steps/make-html.js +34 -0
  38. package/src/steps/parse-markdown.js +42 -0
  39. package/src/steps/removeHlxProps.js +34 -0
  40. package/src/steps/render-code.js +25 -0
  41. package/src/steps/render.js +158 -0
  42. package/src/steps/rewrite-blob-images.js +44 -0
  43. package/src/steps/rewrite-icons.js +93 -0
  44. package/src/steps/set-custom-response-headers.js +41 -0
  45. package/src/steps/set-x-surrogate-key-header.js +35 -0
  46. package/src/steps/split-sections.js +57 -0
  47. package/src/steps/stringify-response.js +39 -0
  48. package/src/steps/utils.js +107 -0
  49. package/src/utils/hast-util-to-dom.js +190 -0
  50. package/src/utils/heading-handler.js +42 -0
  51. package/src/utils/icon-handler.js +40 -0
  52. package/src/utils/json-filter.js +143 -0
  53. package/src/utils/last-modified.js +48 -0
  54. package/src/utils/link-handler.js +25 -0
  55. package/src/utils/mdast-to-vdom.js +323 -0
  56. package/src/utils/mdast-util-gfm-nolink.js +93 -0
  57. package/src/utils/path.js +103 -0
  58. package/src/utils/remark-gfm-nolink.js +128 -0
  59. package/src/utils/section-handler.js +69 -0
  60. package/src/utils/table-handler.js +27 -0
@@ -0,0 +1,93 @@
1
+ /*
2
+ * Copyright 2021 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
+ /* eslint-disable no-param-reassign */
14
+
15
+ const REGEXP_ICON = /:(#?[a-zA-Z_-]+[a-zA-Z0-9]*):/g;
16
+
17
+ /**
18
+ * Create a <img> or <svg> icon dom element eg:
19
+ * `<img class="icon icon-smile" src="/icons/smile.svg"/>` or
20
+ * `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-smile"><use href="/icons.svg#smile"></use></svg>`
21
+ * @param {Document} document the dom document
22
+ * @param {string} value the identifier of the icon
23
+ */
24
+ function createIcon(document, value) {
25
+ value = encodeURIComponent(value);
26
+
27
+ // icon starts with #
28
+ if (value.startsWith('%23')) {
29
+ value = value.substring(3);
30
+ const $el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
31
+ $el.classList.add('icon', `icon-${value}`);
32
+ const $use = document.createElement('use');
33
+ $use.setAttribute('href', `/icons.svg#${value}`);
34
+ $el.appendChild($use);
35
+ return $el;
36
+ }
37
+
38
+ // create normal image
39
+ const $el = document.createElement('img');
40
+ $el.classList.add('icon', `icon-${value}`);
41
+ $el.setAttribute('src', `/icons/${value}.svg`);
42
+ $el.setAttribute('alt', `${value} icon`);
43
+ return $el;
44
+ }
45
+
46
+ /**
47
+ * Rewrite :icons:
48
+ *
49
+ * @param {Document} document The (vdom) document
50
+ */
51
+ function rewriteIcons(document) {
52
+ const { NodeFilter } = document.window;
53
+ const nodeIterator = document.createNodeIterator(
54
+ document.body,
55
+ NodeFilter.SHOW_TEXT,
56
+ );
57
+
58
+ let textNode;
59
+ // eslint-disable-next-line no-cond-assign
60
+ while (textNode = nodeIterator.nextNode()) {
61
+ const text = textNode.data;
62
+ let lastIdx = 0;
63
+ for (const match of text.matchAll(REGEXP_ICON)) {
64
+ const [matched, icon] = match;
65
+ const before = text.substring(lastIdx, match.index);
66
+ if (before) {
67
+ textNode.parentNode.insertBefore(document.createTextNode(before), textNode);
68
+ }
69
+ textNode.parentNode.insertBefore(createIcon(document, icon), textNode);
70
+ lastIdx = match.index + matched.length;
71
+ }
72
+
73
+ if (lastIdx && lastIdx <= text.length) {
74
+ // there is still some text left
75
+ const after = text.substring(lastIdx);
76
+ if (after) {
77
+ textNode.data = after;
78
+ } else {
79
+ textNode.remove();
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @type PipelineStep
87
+ * @param content
88
+ */
89
+ export default function rewrite({ content }) {
90
+ if (content.document) {
91
+ rewriteIcons(content.document);
92
+ }
93
+ }
@@ -0,0 +1,41 @@
1
+ /*
2
+ * Copyright 2022 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 { cleanupHeaderValue } from '@adobe/helix-shared-utils';
13
+ import { filterGlobalMetadata } from './extract-metadata.js';
14
+
15
+ /**
16
+ * Array of headers allowed in the metadata.json file.
17
+ */
18
+ const allowList = [
19
+ 'content-security-policy',
20
+ 'content-security-policy-report-only',
21
+ 'access-control-allow-origin',
22
+ 'access-control-allow-methods',
23
+ 'link',
24
+ ];
25
+
26
+ /**
27
+ * Decorates the pipeline response object with the headers defined in metadata.json.
28
+ * @type PipelineStep
29
+ * @param {PipelineState} state
30
+ * @param {PipelineRequest} req
31
+ * @param {PipelineResponse} res
32
+ * @returns {Promise<void>}
33
+ */
34
+ export default function setCustomResponseHeaders(state, req, res) {
35
+ const meta = filterGlobalMetadata(state.metadata, state.info.path);
36
+ Object.entries(meta).forEach(([name, value]) => {
37
+ if (allowList.includes(name)) {
38
+ res.headers.set(name, cleanupHeaderValue(value));
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Copyright 2021 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 { computeSurrogateKey } from '@adobe/helix-shared-utils';
13
+
14
+ /**
15
+ * @type PipelineStep
16
+ * @param {PipelineState} state
17
+ * @param {PipelineRequest} req
18
+ * @param {PipelineResponse} res
19
+ * @returns {Promise<void>}
20
+ */
21
+ export default function setXSurrogateKeyHeader(state, req, res) {
22
+ const {
23
+ content, contentBusId, info, owner, repo, ref,
24
+ } = state;
25
+
26
+ const keys = [];
27
+ if (content.sourceLocation) {
28
+ keys.push(computeSurrogateKey(content.sourceLocation));
29
+ }
30
+ if (info.selector !== 'plain') {
31
+ keys.push(`${contentBusId}_metadata`);
32
+ keys.push(`${ref}--${repo}--${owner}_head`);
33
+ }
34
+ res.headers.set('x-surrogate-key', keys.join(' '));
35
+ }
@@ -0,0 +1,57 @@
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 { selectAll } from 'unist-util-select';
13
+
14
+ // Compute the meta information for the section
15
+ function computeMeta(section) {
16
+ return selectAll('yaml', section).reduce((prev, { payload }) => Object.assign(prev, payload), {});
17
+ }
18
+
19
+ /**
20
+ * Splits the sections in the mdast tree
21
+ * @type PipelineStep
22
+ * @param {PipelineState} state
23
+ */
24
+ export default function split(state) {
25
+ const { content: { mdast } } = state;
26
+
27
+ // filter all children that are either yaml or break blocks
28
+ const dividers = mdast.children.filter((node) => node.type === 'yaml' || node.type === 'thematicBreak')
29
+ // then get their index in the list of children
30
+ .map((node) => mdast.children.indexOf(node));
31
+
32
+ // find pairwise permutations of spaces between blocks
33
+ // include the very start and end of the document
34
+ const starts = [0, ...dividers];
35
+ const ends = [...dividers, mdast.children.length];
36
+
37
+ // content.mdast.children = _.zip(starts, ends)
38
+ mdast.children = starts.map((k, i) => [k, ends[i]])
39
+ // but filter out empty section
40
+ .filter(([start, end]) => start !== end)
41
+ // then return all nodes that are in between
42
+ .map(([start, end]) => {
43
+ // skip 'thematicBreak' nodes
44
+ const index = mdast.children[start].type === 'thematicBreak' ? start + 1 : start;
45
+ const section = {
46
+ type: 'section',
47
+ children: mdast.children.slice(index, end),
48
+ };
49
+ section.meta = computeMeta(section);
50
+ return section;
51
+ });
52
+
53
+ // unwrap sole section directly on the root
54
+ if (mdast.children.length === 1 && mdast.children[0].type === 'section') {
55
+ mdast.children = mdast.children[0].children;
56
+ }
57
+ }
@@ -0,0 +1,39 @@
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
+ * Serializes the response document to HTML
14
+ * @param {PipelineState} state
15
+ * @param {PipelineRequest} req
16
+ * @param {PipelineResponse} res
17
+ */
18
+ export default function stringify(state, req, res) {
19
+ const { log } = state;
20
+ if (res.body) {
21
+ log.debug('stringify: ignoring already defined context.response.body');
22
+ return;
23
+ }
24
+ const doc = res.document;
25
+ if (!doc) {
26
+ throw Error('no response document');
27
+ }
28
+ if (doc.serialize) {
29
+ res.body = doc.serialize();
30
+ } else if (doc.doctype) {
31
+ res.body = `<!DOCTYPE ${doc.doctype.name}>${doc.documentElement.outerHTML}`;
32
+ } else if (doc.documentElement) {
33
+ res.body = doc.documentElement.outerHTML;
34
+ } else if (doc.innerHTML) {
35
+ res.body = doc.innerHTML;
36
+ } else {
37
+ throw Error(`unexpected context.response.document: ${doc}`);
38
+ }
39
+ }
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Copyright 2020 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 { parse, resolve } from 'url';
13
+
14
+ /**
15
+ * Returns the original host name from the request to the outer CDN.
16
+ * @param {object} headers The request headers
17
+ * @returns {string} The original host
18
+ */
19
+ export function getOriginalHost(headers) {
20
+ const xfh = headers.get('x-forwarded-host');
21
+ if (xfh) {
22
+ return xfh.split(',')[0].trim();
23
+ }
24
+ return headers.get('host');
25
+ }
26
+
27
+ /**
28
+ * Turns a relative into an absolute URL.
29
+ * @param {object} headers The request headers
30
+ * @param {string} url The relative or absolute URL
31
+ * @returns {string} The absolute URL or <code>null</code>
32
+ * if <code>url</code> is not a string
33
+ */
34
+ export function getAbsoluteUrl(headers, url) {
35
+ if (typeof url !== 'string') {
36
+ return null;
37
+ }
38
+ return resolve(`https://${getOriginalHost(headers)}/`, url);
39
+ }
40
+
41
+ /**
42
+ * Returns the canonical HTML url for the give one by
43
+ *
44
+ * - removing .html extension
45
+ * - removing index
46
+ *
47
+ * @param {string} url
48
+ * @return {string} canonical url
49
+ */
50
+ export function makeCanonicalHtmlUrl(url) {
51
+ if (typeof url !== 'string') {
52
+ return null;
53
+ }
54
+ const queryIdx = url.indexOf('?');
55
+ const query = queryIdx > 0 ? url.substring(queryIdx) : '';
56
+ let base = queryIdx > 0 ? url.substring(0, queryIdx) : url;
57
+ if (base.endsWith('.html')) {
58
+ base = base.substring(0, base.length - 5);
59
+ }
60
+ if (base.endsWith('index')) {
61
+ base = base.substring(0, base.length - 5);
62
+ }
63
+ return `${base}${query}`;
64
+ }
65
+
66
+ /**
67
+ * Wraps the content of $node with a new $parent node and then appends the new parent to the node.
68
+ *
69
+ * @param {DOMNode} $node The content of the node to wrap
70
+ * @param {DOMNode} $parent The new parent node
71
+ */
72
+ export function wrapContent($parent, $node) {
73
+ $parent.append(...$node.childNodes);
74
+ $node.append($parent);
75
+ }
76
+
77
+ /**
78
+ * Converts all non-valid-css-classname characters to `-`.
79
+ * @param {string} text input text
80
+ * @returns {string} the css class name
81
+ */
82
+ export function toClassName(text) {
83
+ return text
84
+ .trim()
85
+ .toLowerCase()
86
+ .replace(/[^0-9a-z]/gi, '-');
87
+ }
88
+
89
+ /**
90
+ * Adds the fastly-image-optimization url params to the given image src.
91
+ * @param {string} src The image source.
92
+ * @param {number} [width = 0] optional 'width' parameter
93
+ * @param {string} [format = 'webply'] image format.
94
+ * @param {string} [optimize = 'medium'] optimization.
95
+ * @returns {string}
96
+ */
97
+ export function optimizeImageURL(src, width, format = 'webply', optimize = 'medium') {
98
+ // use deprecated api to avoid complexity with non absolute paths
99
+ const url = parse(src, true);
100
+ delete url.search;
101
+ if (width) {
102
+ url.query.width = String(width);
103
+ }
104
+ url.query.format = format;
105
+ url.query.optimize = optimize;
106
+ return url.format();
107
+ }
@@ -0,0 +1,190 @@
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
+ }
@@ -0,0 +1,42 @@
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
+ import { heading as fallback } from 'mdast-util-to-hast/lib/handlers/heading.js';
13
+ import { toString } from 'mdast-util-to-string';
14
+ import strip from 'strip-markdown';
15
+
16
+ /**
17
+ * Utility class injects heading identifiers during the MDAST to VDOM transformation.
18
+ */
19
+ export default class HeadingHandler {
20
+ /**
21
+ * Initializes the handler
22
+ */
23
+ constructor(slugger) {
24
+ this.slugger = slugger;
25
+ }
26
+
27
+ /**
28
+ * Returns the handler function
29
+ */
30
+ handler() {
31
+ return (h, node) => {
32
+ // Prepare the heading id
33
+ const headingIdentifier = this.slugger.slug(toString(strip()(node)));
34
+
35
+ // Inject the id after transformation
36
+ const n = { ...node };
37
+ const el = fallback(h, n);
38
+ el.properties.id = el.properties.id || headingIdentifier;
39
+ return el;
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,40 @@
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
+ }