@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
|
@@ -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
|
|
48
|
-
* @param {Object} typecounter Type as a key, number of
|
|
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>-<
|
|
75
|
+
* 4. nb-<type>-<nb_occurrences> is the number of occurrences per type
|
|
75
76
|
* @param {*} section
|
|
76
77
|
*/
|
|
77
78
|
function sectiontype(section) {
|
package/src/steps/make-html.js
CHANGED
|
@@ -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 {
|
|
20
|
+
const { content } = state;
|
|
23
21
|
const { mdast } = content;
|
|
24
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
31
|
-
.filter((key) => key.match(/^
|
|
32
|
-
.forEach((key) => delete
|
|
29
|
+
Object.keys(properties)
|
|
30
|
+
.filter((key) => key.match(/^dataHlx[A-Z].*/))
|
|
31
|
+
.forEach((key) => delete properties[key]);
|
|
33
32
|
});
|
|
34
33
|
}
|
package/src/steps/render.js
CHANGED
|
@@ -11,70 +11,27 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/* eslint-disable max-len */
|
|
14
|
-
|
|
15
|
-
import {
|
|
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.
|
|
20
|
+
$parent.children.push($el);
|
|
63
21
|
}
|
|
64
22
|
}
|
|
65
23
|
|
|
66
|
-
function createElement(
|
|
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
|
-
|
|
28
|
+
const value = attrs[i + 1];
|
|
29
|
+
if (!value) {
|
|
70
30
|
return null;
|
|
71
31
|
}
|
|
32
|
+
properties[attrs[i]] = value;
|
|
72
33
|
}
|
|
73
|
-
|
|
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
|
|
46
|
+
const { hast, meta } = content;
|
|
47
|
+
|
|
90
48
|
if (state.info.selector === 'plain') {
|
|
91
49
|
// just return body
|
|
92
|
-
res.document =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
$head
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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(
|
|
25
|
-
|
|
24
|
+
function createIcon(value) {
|
|
25
|
+
let name = encodeURIComponent(value);
|
|
26
26
|
|
|
27
27
|
// icon starts with #
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
42
|
+
* @type PipelineStep
|
|
43
|
+
* @param content
|
|
50
44
|
*/
|
|
51
|
-
function
|
|
52
|
-
const {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
71
|
+
node.value = after;
|
|
78
72
|
} else {
|
|
79
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
}
|
package/src/steps/utils.js
CHANGED
|
@@ -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 {
|
|
55
|
-
* @param {
|
|
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.
|
|
59
|
-
$node.
|
|
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
|
|
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 {
|
|
12
|
+
import { EXIT, visit } from 'unist-util-visit';
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 {
|
|
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
|
-
*
|
|
17
|
+
*Injects heading identifiers during the MDAST to VDOM transformation.
|
|
18
18
|
*/
|
|
19
|
-
export default
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
}
|