@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.
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { select, selectAll } from 'unist-util-select';
13
13
  import { toString as plain } from 'mdast-util-to-string';
14
+ import { rewriteBlobLink } from './utils.js';
14
15
 
15
16
  function yaml(section) {
16
17
  section.meta = selectAll('yaml', section)
@@ -39,13 +40,13 @@ function image(section) {
39
40
  // TODO: get a better measure of prominence than "first"
40
41
  const img = select('image', section);
41
42
  if (img) {
42
- section.image = img.url;
43
+ section.image = rewriteBlobLink(img.url);
43
44
  }
44
45
  }
45
46
 
46
47
  /**
47
- * Construct the strings corresponding to the number of occurences per type.
48
- * @param {Object} typecounter Type as a key, number of occurences as value
48
+ * Construct the strings corresponding to the number of occurrences per type.
49
+ * @param {Object} typecounter Type as a key, number of occurrences as value
49
50
  */
50
51
  function constructTypes(typecounter) {
51
52
  const types = Object.keys(typecounter).map((type) => `has-${type}`); // has-{type}
@@ -71,7 +72,7 @@ function constructTypes(typecounter) {
71
72
  * 1. has-<type> for every type of content found in the section
72
73
  * 2. is-<type>-only for sections that have only content of type
73
74
  * 3. is-<type1>-<type2>-<type3> ranks the top three most common types of content
74
- * 4. nb-<type>-<nb_occurences> is the number of occurences per type
75
+ * 4. nb-<type>-<nb_occurrences> is the number of occurrences per type
75
76
  * @param {*} section
76
77
  */
77
78
  function sectiontype(section) {
@@ -9,9 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
-
13
- import GithubSlugger from 'github-slugger';
14
- import VDOMTransformer from '../utils/mdast-to-vdom.js';
12
+ import mdast2hast from '../utils/mdast-to-hast.js';
15
13
 
16
14
  /**
17
15
  * Converts the markdown to a jsdom dom and stores it in `content.document`
@@ -19,16 +17,7 @@ import VDOMTransformer from '../utils/mdast-to-vdom.js';
19
17
  * @param {PipelineState} state
20
18
  */
21
19
  export default function html(state) {
22
- const { log, content } = state;
20
+ const { content } = state;
23
21
  const { mdast } = content;
24
- log.debug(`Turning Markdown into HTML from ${typeof mdast}`);
25
- // initialize transformer
26
- content.slugger = new GithubSlugger();
27
- const transformer = new VDOMTransformer()
28
- .withOptions({
29
- slugger: content.slugger,
30
- });
31
- content.document = transformer
32
- .withMdast(mdast)
33
- .getDocument();
22
+ content.hast = mdast2hast(mdast, content.slugger);
34
23
  }
@@ -9,6 +9,8 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
+ import { selectAll } from 'hast-util-select';
13
+
12
14
  /**
13
15
  * Cleans the response document by removing `hlx-` stuff
14
16
  * @param {PipelineState} state
@@ -17,18 +19,15 @@
17
19
  */
18
20
  export default function clean(state, req, res) {
19
21
  const { document } = res;
20
- document.querySelectorAll('[class]').forEach((el) => {
21
- // Remove all `hlx-*` classes on the elements
22
- el.classList.value.split(' ')
23
- .filter((cls) => cls.indexOf('hlx-') === 0)
24
- .forEach((cls) => el.classList.remove(cls));
25
- if (!el.classList.length) {
26
- el.removeAttribute('class');
22
+ selectAll('[class]', document).forEach(({ properties }) => {
23
+ properties.className = properties.className.filter((name) => !name.startsWith('hlx-'));
24
+ if (properties.className.length === 0) {
25
+ delete properties.className;
27
26
  }
28
27
 
29
28
  // Remove all `data-hlx-*` attributes on these elements
30
- Object.keys(el.dataset)
31
- .filter((key) => key.match(/^hlx[A-Z]/))
32
- .forEach((key) => delete el.dataset[key]);
29
+ Object.keys(properties)
30
+ .filter((key) => key.match(/^dataHlx[A-Z].*/))
31
+ .forEach((key) => delete properties[key]);
33
32
  });
34
33
  }
@@ -11,70 +11,27 @@
11
11
  */
12
12
 
13
13
  /* eslint-disable max-len */
14
-
15
- import { JSDOM } from 'jsdom';
16
-
17
- /*
18
- <!DOCTYPE html>
19
- <html data-sly-attribute="${content.document.documentElement.attributesMap}">
20
- <head>
21
- <title>${content.meta.title}</title>
22
- <link data-sly-test="${content.meta.url}" rel="canonical" href="${content.meta.url}"/>
23
- <meta data-sly-test="${content.meta.description}" name="description" content="${content.meta.description}"/>
24
- <meta data-sly-test="${content.meta.keywords}" name="keywords" content="${content.meta.keywords}"/>
25
- <meta data-sly-test="${content.meta.title}" property="og:title" content="${content.meta.title}"/>
26
- <meta data-sly-test="${content.meta.description}" property="og:description" content="${content.meta.description}"/>
27
- <meta data-sly-test="${content.meta.url}" property="og:url" content="${content.meta.url}"/>
28
- <meta data-sly-test="${content.meta.image}" property="og:image" content="${content.meta.image}"/>
29
- <meta data-sly-test="${content.meta.image}" property="og:image:secure_url" content="${content.meta.image}"/>
30
- <sly data-sly-test="${content.meta.imageAlt}">
31
- <meta data-sly-test="${content.meta.imageAlt}" property="og:image:alt" content="${content.meta.imageAlt}"/>
32
- </sly>
33
- <meta data-sly-test="${content.meta.modifiedTime}" property="og:updated_time" content="${content.meta.modified_time}"/>
34
- <sly data-sly-test="${content.meta.tags}" data-sly-list.tag="${content.meta.tags}">
35
- <meta property="article:tag" content="${tag}"/>
36
- </sly>
37
- <meta data-sly-test="${content.meta.section}" property="article:section" content="${section}"/>
38
- <meta data-sly-test="${content.meta.published_time}" property="article:published_time" content="${content.meta.published_time}"/>
39
- <meta data-sly-test="${content.meta.modified_time}" property="article:modified_time" content="${content.meta.modified_time}"/>
40
- <meta data-sly-test="${content.meta.title}" name="twitter:title" content="${content.meta.title}"/>
41
- <meta data-sly-test="${content.meta.description}" name="twitter:description" content="${content.meta.description}"/>
42
- <meta data-sly-test="${content.meta.image}" name="twitter:image" content="${content.meta.image}"/>
43
- <sly data-sly-test="${content.meta.custom}" data-sly-list="${content.meta.custom}">
44
- <meta data-sly-test="${item.property}" property="${item.name}" content="${item.value}">
45
- <meta data-sly-test="${!item.property}" name="${item.name}" content="${item.value}">
46
- </sly>
47
- <esi:include src="/head.html" onerror="continue"/>
48
- </head>
49
- <body data-sly-attribute="${content.document.body.attributesMap}">
50
- <!-- header -->
51
- <header><esi:include src="/header.plain.html" onerror="continue"/></header>
52
- <!-- main content -->
53
- <main>${content.document.body}</main>
54
- <!-- footer -->
55
- <footer><esi:include src="/footer.plain.html" onerror="continue"/></footer>
56
- </body>
57
- </html>
58
- */
14
+ import { h } from 'hastscript';
15
+ import { unified } from 'unified';
16
+ import rehypeParse from 'rehype-parse';
59
17
 
60
18
  function appendElement($parent, $el) {
61
19
  if ($el) {
62
- $parent.append($el);
20
+ $parent.children.push($el);
63
21
  }
64
22
  }
65
23
 
66
- function createElement(doc, name, ...attrs) {
24
+ function createElement(name, ...attrs) {
67
25
  // check for empty values
26
+ const properties = {};
68
27
  for (let i = 0; i < attrs.length; i += 2) {
69
- if (!attrs[i + 1]) {
28
+ const value = attrs[i + 1];
29
+ if (!value) {
70
30
  return null;
71
31
  }
32
+ properties[attrs[i]] = value;
72
33
  }
73
- const $el = doc.createElement(name);
74
- for (let i = 0; i < attrs.length; i += 2) {
75
- $el.setAttribute(attrs[i], attrs[i + 1]);
76
- }
77
- return $el;
34
+ return h(name, properties);
78
35
  }
79
36
 
80
37
  /**
@@ -86,73 +43,68 @@ function createElement(doc, name, ...attrs) {
86
43
  */
87
44
  export default async function render(state, req, res) {
88
45
  const { content } = state;
89
- const srcDoc = content.document;
46
+ const { hast, meta } = content;
47
+
90
48
  if (state.info.selector === 'plain') {
91
49
  // just return body
92
- res.document = srcDoc.body;
93
- } else {
94
- // create document like HTL used to do
95
- const dom = new JSDOM('<!DOCTYPE html>'
96
- + '<html>'
97
- + '<head></head>'
98
- + '<body>'
99
- + '<header></header>' // todo: are those still required ?
100
- + '<main></main>'
101
- + '<footer></footer>' // todo: are those still required ?
102
- + '</body>'
103
- + '</html>');
104
- const doc = dom.window.document;
105
-
106
- // add title
107
- const $head = doc.head;
108
- const { meta } = content;
109
- const $title = doc.createElement('title');
110
- $title.innerHTML = meta.title;
111
- $head.append($title);
112
-
113
- // add meta
114
- appendElement($head, createElement(doc, 'link', 'rel', 'canonical', 'href', content.meta.canonical));
115
-
116
- appendElement($head, createElement(doc, 'meta', 'name', 'description', 'content', content.meta.description));
117
- appendElement($head, createElement(doc, 'meta', 'name', 'keywords', 'content', content.meta.keywords));
118
- appendElement($head, createElement(doc, 'meta', 'property', 'og:title', 'content', content.meta.title));
119
- appendElement($head, createElement(doc, 'meta', 'property', 'og:description', 'content', content.meta.description));
120
- appendElement($head, createElement(doc, 'meta', 'property', 'og:url', 'content', content.meta.url));
121
- appendElement($head, createElement(doc, 'meta', 'property', 'og:image', 'content', content.meta.image));
122
- appendElement($head, createElement(doc, 'meta', 'property', 'og:image:secure_url', 'content', content.meta.image));
123
- if (content.meta.imageAlt) {
124
- appendElement($head, createElement(doc, 'meta', 'property', 'og:image:alt', 'content', content.meta.imageAlt));
125
- }
126
- appendElement($head, createElement(doc, 'meta', 'property', 'og:updated_time', 'content', content.meta.modified_time));
127
- for (const tag of (meta.tags || [])) {
128
- appendElement($head, createElement(doc, 'meta', 'property', 'article:tag', 'content', tag));
129
- }
130
- appendElement($head, createElement(doc, 'meta', 'property', 'article:section', 'content', content.meta.section));
131
- appendElement($head, createElement(doc, 'meta', 'property', 'article:published_time', 'content', content.meta.published_time));
132
- appendElement($head, createElement(doc, 'meta', 'property', 'article:modified_time', 'content', content.meta.modified_time));
133
-
134
- appendElement($head, createElement(doc, 'meta', 'name', 'twitter:title', 'content', content.meta.title));
135
- appendElement($head, createElement(doc, 'meta', 'name', 'twitter:description', 'content', content.meta.description));
136
- appendElement($head, createElement(doc, 'meta', 'name', 'twitter:image', 'content', content.meta.image));
50
+ res.document = hast;
51
+ return;
52
+ }
53
+ const $head = h('head', [
54
+ h('title', meta.title),
55
+ ]);
137
56
 
138
- for (const custom of (meta.custom || [])) {
139
- appendElement($head, createElement(doc, 'meta', custom.property ? 'property' : 'name', custom.name, 'content', custom.value));
140
- }
141
- if (meta.feed) {
142
- appendElement($head, createElement(doc, 'link', 'rel', 'alternate', 'type', 'application/xml+atom', 'href', meta.feed, 'title', `${meta.title} feed`));
143
- }
144
- // inject head.html
145
- const $headHtml = doc.createElement('template');
146
- $headHtml.innerHTML = state.helixConfig?.head?.html ?? `
147
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
148
- <script src="/scripts.js" type="module" crossorigin="use-credentials"></script>
149
- <link rel="stylesheet" href="/styles.css"/>`;
150
- $head.appendChild($headHtml.content);
57
+ // add meta
58
+ appendElement($head, createElement('link', 'rel', 'canonical', 'href', content.meta.canonical));
59
+ appendElement($head, createElement('meta', 'name', 'description', 'content', content.meta.description));
60
+ appendElement($head, createElement('meta', 'name', 'keywords', 'content', content.meta.keywords));
61
+ appendElement($head, createElement('meta', 'property', 'og:title', 'content', content.meta.title));
62
+ appendElement($head, createElement('meta', 'property', 'og:description', 'content', content.meta.description));
63
+ appendElement($head, createElement('meta', 'property', 'og:url', 'content', content.meta.url));
64
+ appendElement($head, createElement('meta', 'property', 'og:image', 'content', content.meta.image));
65
+ appendElement($head, createElement('meta', 'property', 'og:image:secure_url', 'content', content.meta.image));
66
+ appendElement($head, createElement('meta', 'property', 'og:image:alt', 'content', content.meta.imageAlt));
67
+ appendElement($head, createElement('meta', 'property', 'og:updated_time', 'content', content.meta.modified_time));
68
+ for (const tag of (meta.tags || [])) {
69
+ appendElement($head, createElement('meta', 'property', 'article:tag', 'content', tag));
70
+ }
71
+ appendElement($head, createElement('meta', 'property', 'article:section', 'content', content.meta.section));
72
+ appendElement($head, createElement('meta', 'property', 'article:published_time', 'content', content.meta.published_time));
73
+ appendElement($head, createElement('meta', 'property', 'article:modified_time', 'content', content.meta.modified_time));
74
+ appendElement($head, createElement('meta', 'name', 'twitter:title', 'content', content.meta.title));
75
+ appendElement($head, createElement('meta', 'name', 'twitter:description', 'content', content.meta.description));
76
+ appendElement($head, createElement('meta', 'name', 'twitter:image', 'content', content.meta.image));
151
77
 
152
- // add body to main
153
- const $main = doc.querySelector('main');
78
+ for (const custom of (meta.custom || [])) {
79
+ appendElement($head, createElement('meta', custom.property ? 'property' : 'name', custom.name, 'content', custom.value));
80
+ }
81
+ appendElement($head, createElement('link', 'rel', 'alternate', 'type', 'application/xml+atom', 'href', meta.feed, 'title', `${meta.title} feed`));
154
82
 
155
- $main.append(...srcDoc.body.childNodes);
156
- res.document = doc;
83
+ // inject head.html
84
+ const headHtml = state.helixConfig?.head?.html;
85
+ if (headHtml) {
86
+ const $headHtml = await unified()
87
+ .use(rehypeParse, { fragment: true })
88
+ .parse(headHtml);
89
+ $head.children.push(...$headHtml.children);
90
+ } else {
91
+ appendElement($head, createElement('meta', 'name', 'viewport', 'content', 'width=device-width, initial-scale=1'));
92
+ appendElement($head, createElement('script', 'src', '/scripts.js', 'type', 'module', 'crossorigin', 'use-credentials'));
93
+ appendElement($head, createElement('link', 'rel', 'stylesheet', 'href', '/styles.css'));
157
94
  }
95
+
96
+ res.document = {
97
+ type: 'root',
98
+ children: [
99
+ { type: 'doctype' },
100
+ h('html', [
101
+ $head,
102
+ h('body', [
103
+ h('header', []), // todo: are those still required ?
104
+ h('main', hast),
105
+ h('footer', []), // todo: are those still required ?
106
+ ]),
107
+ ]),
108
+ ],
109
+ };
158
110
  }
@@ -9,36 +9,18 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
-
13
- const AZURE_BLOB_REGEXP = /^https:\/\/hlx\.blob\.core\.windows\.net\/external\//;
14
-
15
- const MEDIA_BLOB_REGEXP = /^https:\/\/.*\.hlx3?\.(live|page)\/media_.*/;
12
+ import { selectAll } from 'hast-util-select';
13
+ import { rewriteBlobLink } from './utils.js';
16
14
 
17
15
  /**
18
16
  * Rewrite blob store image URLs to /hlx_* URLs
19
17
  *
20
- * @param {Document} document The (vdom) document
21
- */
22
- function images(document) {
23
- document.querySelectorAll('img').forEach((img) => {
24
- if (AZURE_BLOB_REGEXP.test(img.src)) {
25
- const { pathname, hash } = new URL(img.src);
26
- const filename = pathname.split('/').pop();
27
- const extension = hash.split('?').shift().split('.').pop() || 'jpg';
28
- img.src = `./media_${filename}.${extension}`;
29
- } else if (MEDIA_BLOB_REGEXP.test(img.src)) {
30
- const { pathname } = new URL(img.src);
31
- img.src = `.${pathname}`; // don't append fragment until picture tag supports width/height
32
- }
33
- });
34
- }
35
-
36
- /**
37
18
  * @type PipelineStep
38
19
  * @param content
39
20
  */
40
21
  export default function rewrite({ content }) {
41
- if (content.document) {
42
- images(content.document);
43
- }
22
+ const { hast } = content;
23
+ selectAll('img', hast).forEach((img) => {
24
+ img.properties.src = rewriteBlobLink(img.properties.src);
25
+ });
44
26
  }
@@ -9,8 +9,9 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
-
13
12
  /* eslint-disable no-param-reassign */
13
+ import { h, s } from 'hastscript';
14
+ import { CONTINUE, visit } from 'unist-util-visit';
14
15
 
15
16
  const REGEXP_ICON = /:(#?[a-zA-Z_-]+[a-zA-Z0-9]*):/g;
16
17
 
@@ -18,55 +19,48 @@ const REGEXP_ICON = /:(#?[a-zA-Z_-]+[a-zA-Z0-9]*):/g;
18
19
  * Create a <img> or <svg> icon dom element eg:
19
20
  * `<img class="icon icon-smile" src="/icons/smile.svg"/>` or
20
21
  * `<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
22
  * @param {string} value the identifier of the icon
23
23
  */
24
- function createIcon(document, value) {
25
- value = encodeURIComponent(value);
24
+ function createIcon(value) {
25
+ let name = encodeURIComponent(value);
26
26
 
27
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;
28
+ if (name.startsWith('%23')) {
29
+ name = name.substring(3);
30
+ return s('svg', { class: `icon icon-${name}` }, [
31
+ s('use', { href: `/icons.svg#${name}` }),
32
+ ]);
36
33
  }
37
34
 
38
35
  // 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;
36
+ return h('img', { class: `icon icon-${name}`, src: `/icons/${name}.svg`, alt: `${name} icon` });
44
37
  }
45
38
 
46
39
  /**
47
40
  * Rewrite :icons:
48
41
  *
49
- * @param {Document} document The (vdom) document
42
+ * @type PipelineStep
43
+ * @param content
50
44
  */
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;
45
+ export default function rewrite({ content }) {
46
+ const { hast } = content;
47
+ visit(hast, (node, idx, parent) => {
48
+ if (node.type !== 'text') {
49
+ return CONTINUE;
50
+ }
51
+ const text = node.value;
62
52
  let lastIdx = 0;
63
53
  for (const match of text.matchAll(REGEXP_ICON)) {
64
54
  const [matched, icon] = match;
65
55
  const before = text.substring(lastIdx, match.index);
66
56
  if (before) {
67
- textNode.parentNode.insertBefore(document.createTextNode(before), textNode);
57
+ // textNode.parentNode.insertBefore(document.createTextNode(before), textNode);
58
+ parent.children.splice(idx, 0, { type: 'text', value: before });
59
+ idx += 1;
68
60
  }
69
- textNode.parentNode.insertBefore(createIcon(document, icon), textNode);
61
+ // textNode.parentNode.insertBefore(createIcon(document, icon), textNode);
62
+ parent.children.splice(idx, 0, createIcon(icon));
63
+ idx += 1;
70
64
  lastIdx = match.index + matched.length;
71
65
  }
72
66
 
@@ -74,20 +68,12 @@ function rewriteIcons(document) {
74
68
  // there is still some text left
75
69
  const after = text.substring(lastIdx);
76
70
  if (after) {
77
- textNode.data = after;
71
+ node.value = after;
78
72
  } else {
79
- textNode.remove();
73
+ parent.children.splice(idx, 1);
74
+ idx -= 1;
80
75
  }
81
76
  }
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
- }
77
+ return idx + 1;
78
+ });
93
79
  }
@@ -9,6 +9,9 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
+ import { toHtml } from 'hast-util-to-html';
13
+ // import rehypeFormat from 'rehype-format';
14
+ import rehypeMinifyWhitespace from 'rehype-minify-whitespace';
12
15
  /**
13
16
  * Serializes the response document to HTML
14
17
  * @param {PipelineState} state
@@ -25,15 +28,12 @@ export default function stringify(state, req, res) {
25
28
  if (!doc) {
26
29
  throw Error('no response document');
27
30
  }
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
- }
31
+
32
+ // TODO: for the next breaking release, pretty print the HTML with rehypeFormat.
33
+ // TODO: but for backward compatibility, output all on 1 line.
34
+ // rehypeFormat()(doc);
35
+ rehypeMinifyWhitespace()(doc);
36
+ res.body = toHtml(doc, {
37
+ upperDoctype: true,
38
+ });
39
39
  }
@@ -10,6 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ const AZURE_BLOB_REGEXP = /^https:\/\/hlx\.blob\.core\.windows\.net\/external\//;
14
+
15
+ const MEDIA_BLOB_REGEXP = /^https:\/\/.*\.hlx3?\.(live|page)\/media_.*/;
16
+
13
17
  /**
14
18
  * Returns the original host name from the request to the outer CDN.
15
19
  * @param {object} headers The request headers
@@ -51,12 +55,12 @@ export function makeCanonicalHtmlUrl(url) {
51
55
  /**
52
56
  * Wraps the content of $node with a new $parent node and then appends the new parent to the node.
53
57
  *
54
- * @param {DOMNode} $node The content of the node to wrap
55
- * @param {DOMNode} $parent The new parent node
58
+ * @param {Element} $node The content of the node to wrap
59
+ * @param {Element} $parent The new parent node
56
60
  */
57
61
  export function wrapContent($parent, $node) {
58
- $parent.append(...$node.childNodes);
59
- $node.append($parent);
62
+ $parent.children.push(...$node.children);
63
+ $node.children = [$parent];
60
64
  }
61
65
 
62
66
  /**
@@ -143,3 +147,21 @@ export function getAbsoluteUrl(headers, url) {
143
147
  }
144
148
  return resolveUrl(`https://${getOriginalHost(headers)}/`, url);
145
149
  }
150
+
151
+ /**
152
+ * Rewrite a blog image link. if the link is not a blog image link, it is returned as-is.
153
+ * @param {string} src the image source
154
+ * @returns {string} the new source
155
+ */
156
+ export function rewriteBlobLink(src) {
157
+ if (AZURE_BLOB_REGEXP.test(src)) {
158
+ const { pathname, hash } = new URL(src);
159
+ const filename = pathname.split('/').pop();
160
+ const extension = hash.split('?').shift().split('.').pop() || 'jpg';
161
+ return `./media_${filename}.${extension}`;
162
+ } else if (MEDIA_BLOB_REGEXP.test(src)) {
163
+ const { pathname } = new URL(src);
164
+ return `.${pathname}`; // don't append fragment until picture tag supports width/height
165
+ }
166
+ return src;
167
+ }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2018 Adobe. All rights reserved.
2
+ * Copyright 2022 Adobe. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -9,19 +9,17 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { table as fallback } from 'mdast-util-to-hast/lib/handlers/table.js';
12
+ import { EXIT, visit } from 'unist-util-visit';
13
13
 
14
- export default function table() {
15
- return function handler(h, node) {
16
- // remove 'none' from align (doesn't occur anymore, maybe leftover from prior versions)
17
- if (node.align) {
18
- for (let i = 0; i < node.align.length; i += 1) {
19
- /* c8 ignore next 3 */
20
- if (node.align[i] === 'none') {
21
- node.align[i] = null;
22
- }
23
- }
24
- }
25
- return fallback(h, node);
26
- };
14
+ export function replace(tree, oldNode, newNode) {
15
+ // $table.parentNode.replaceChild($div, $table);
16
+ // replace child in parent
17
+ visit(tree, oldNode, (node, idx, parent) => {
18
+ parent.children[idx] = newNode;
19
+ return EXIT;
20
+ });
21
+ }
22
+
23
+ export function childNodes(node) {
24
+ return node.children.filter((n) => n.type === 'element');
27
25
  }
@@ -9,34 +9,21 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { heading as fallback } from 'mdast-util-to-hast/lib/handlers/heading.js';
12
+ import { defaultHandlers } from 'mdast-util-to-hast';
13
13
  import { toString } from 'mdast-util-to-string';
14
14
  import strip from 'strip-markdown';
15
15
 
16
16
  /**
17
- * Utility class injects heading identifiers during the MDAST to VDOM transformation.
17
+ *Injects heading identifiers during the MDAST to VDOM transformation.
18
18
  */
19
- export default class HeadingHandler {
20
- /**
21
- * Initializes the handler
22
- */
23
- constructor(slugger) {
24
- this.slugger = slugger;
25
- }
19
+ export default function heading(slugger) {
20
+ return function handler(h, node) {
21
+ // Prepare the heading id
22
+ const headingIdentifier = slugger.slug(toString(strip()(node)).trim());
26
23
 
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
- }
24
+ // Inject the id after transformation
25
+ const el = defaultHandlers.heading(h, node);
26
+ el.properties.id = el.properties.id || headingIdentifier;
27
+ return el;
28
+ };
42
29
  }