@adobe/helix-html-pipeline 1.1.3 → 1.3.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 +12 -10
- package/src/PipelineContent.d.ts +8 -7
- package/src/PipelineContent.js +2 -0
- package/src/PipelineResponse.d.ts +6 -1
- package/src/PipelineState.d.ts +3 -1
- package/src/PipelineState.js +1 -0
- package/src/forms-pipe.js +160 -0
- package/src/html-pipe.js +7 -0
- package/src/index.d.ts +13 -0
- package/src/index.js +2 -0
- package/src/options-pipe.js +37 -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/path.js +4 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [1.3.0](https://github.com/adobe/helix-html-pipeline/compare/v1.2.1...v1.3.0) (2022-03-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* provide pipes for OPTIONS and POSTs ([#24](https://github.com/adobe/helix-html-pipeline/issues/24)) ([1dfc47e](https://github.com/adobe/helix-html-pipeline/commit/1dfc47e764a0b1d8acee80b51b845be2e54a16f5))
|
|
7
|
+
|
|
8
|
+
## [1.2.1](https://github.com/adobe/helix-html-pipeline/compare/v1.2.0...v1.2.1) (2022-03-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* reject double-slashes ([#22](https://github.com/adobe/helix-html-pipeline/issues/22)) ([5aee75d](https://github.com/adobe/helix-html-pipeline/commit/5aee75d4109550525d971c64d87e4f2420863c30)), closes [#20](https://github.com/adobe/helix-html-pipeline/issues/20)
|
|
14
|
+
|
|
15
|
+
# [1.2.0](https://github.com/adobe/helix-html-pipeline/compare/v1.1.3...v1.2.0) (2022-03-16)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* use hast instead of jsdom ([#12](https://github.com/adobe/helix-html-pipeline/issues/12)) ([bee0a0b](https://github.com/adobe/helix-html-pipeline/commit/bee0a0b3309919f896520bc700dd2d867be19a1c)), closes [#11](https://github.com/adobe/helix-html-pipeline/issues/11)
|
|
21
|
+
|
|
1
22
|
## [1.1.3](https://github.com/adobe/helix-html-pipeline/compare/v1.1.2...v1.1.3) (2022-03-12)
|
|
2
23
|
|
|
3
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-html-pipeline",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Helix HTML Pipeline",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -35,8 +35,11 @@
|
|
|
35
35
|
"@adobe/helix-markdown-support": "3.1.2",
|
|
36
36
|
"@adobe/helix-shared-utils": "2.0.5",
|
|
37
37
|
"github-slugger": "1.4.0",
|
|
38
|
+
"hast-util-raw": "7.2.1",
|
|
39
|
+
"hast-util-select": "5.0.1",
|
|
38
40
|
"hast-util-to-html": "8.0.3",
|
|
39
|
-
"
|
|
41
|
+
"hast-util-to-string": "2.0.0",
|
|
42
|
+
"hastscript": "7.0.2",
|
|
40
43
|
"mdast-util-gfm-footnote": "1.0.1",
|
|
41
44
|
"mdast-util-gfm-strikethrough": "1.0.1",
|
|
42
45
|
"mdast-util-gfm-table": "1.0.3",
|
|
@@ -50,15 +53,17 @@
|
|
|
50
53
|
"micromark-extension-gfm-task-list-item": "1.0.3",
|
|
51
54
|
"micromark-util-combine-extensions": "1.0.0",
|
|
52
55
|
"mime": "3.0.0",
|
|
53
|
-
"
|
|
56
|
+
"rehype-format": "4.0.1",
|
|
57
|
+
"rehype-minify-whitespace": "5.0.0",
|
|
58
|
+
"rehype-parse": "8.0.4",
|
|
54
59
|
"remark-parse": "10.0.1",
|
|
55
60
|
"strip-markdown": "5.0.0",
|
|
56
61
|
"unified": "10.1.2",
|
|
57
62
|
"unist-util-map": "3.0.0",
|
|
63
|
+
"unist-util-remove": "3.1.0",
|
|
58
64
|
"unist-util-remove-position": "4.0.1",
|
|
59
65
|
"unist-util-select": "4.0.1",
|
|
60
|
-
"unist-util-visit": "4.1.0"
|
|
61
|
-
"uri-js": "4.4.1"
|
|
66
|
+
"unist-util-visit": "4.1.0"
|
|
62
67
|
},
|
|
63
68
|
"devDependencies": {
|
|
64
69
|
"@adobe/eslint-config-helix": "1.3.2",
|
|
@@ -75,19 +80,16 @@
|
|
|
75
80
|
"eslint-plugin-header": "3.1.1",
|
|
76
81
|
"eslint-plugin-import": "2.25.4",
|
|
77
82
|
"esmock": "1.7.4",
|
|
78
|
-
"hastscript": "7.0.2",
|
|
79
83
|
"husky": "7.0.4",
|
|
80
|
-
"hyperscript": "2.0.2",
|
|
81
84
|
"js-yaml": "4.1.0",
|
|
82
85
|
"jsdoc-to-markdown": "7.1.1",
|
|
86
|
+
"jsdom": "19.0.0",
|
|
83
87
|
"junit-report-builder": "3.0.0",
|
|
84
88
|
"lint-staged": "12.3.5",
|
|
85
89
|
"mocha": "9.2.2",
|
|
86
90
|
"mocha-multi-reporters": "1.5.1",
|
|
87
91
|
"remark-gfm": "3.0.1",
|
|
88
|
-
"semantic-release": "19.0.2"
|
|
89
|
-
"sinon": "13.0.1",
|
|
90
|
-
"unist-builder": "3.0.0"
|
|
92
|
+
"semantic-release": "19.0.2"
|
|
91
93
|
},
|
|
92
94
|
"lint-staged": {
|
|
93
95
|
"*.js": "eslint",
|
package/src/PipelineContent.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import {Node} from "unist";
|
|
13
13
|
import GithubSlugger from 'github-slugger';
|
|
14
|
+
import { Root } from 'hast';
|
|
14
15
|
|
|
15
16
|
declare enum SourceType {
|
|
16
17
|
CONTENT = 'content',
|
|
@@ -50,12 +51,9 @@ declare class PipelineContent {
|
|
|
50
51
|
mdast: Node;
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* document
|
|
54
|
+
* The transformed document (hast) representation
|
|
54
55
|
*/
|
|
55
|
-
|
|
56
|
-
title: string;
|
|
57
|
-
intro: string;
|
|
58
|
-
image: string;
|
|
56
|
+
hast: Root;
|
|
59
57
|
|
|
60
58
|
/**
|
|
61
59
|
* slugger to use for heading id calculations
|
|
@@ -63,7 +61,10 @@ declare class PipelineContent {
|
|
|
63
61
|
slugger: GithubSlugger;
|
|
64
62
|
|
|
65
63
|
/**
|
|
66
|
-
*
|
|
64
|
+
* document specific metadata
|
|
67
65
|
*/
|
|
68
|
-
|
|
66
|
+
meta: object;
|
|
67
|
+
title: string;
|
|
68
|
+
intro: string;
|
|
69
|
+
image: string;
|
|
69
70
|
}
|
package/src/PipelineContent.js
CHANGED
|
@@ -9,6 +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
|
+
import GithubSlugger from 'github-slugger';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* State of the pipeline
|
|
@@ -21,6 +22,7 @@ export class PipelineContent {
|
|
|
21
22
|
constructor() {
|
|
22
23
|
Object.assign(this, {
|
|
23
24
|
sourceBus: 'content',
|
|
25
|
+
slugger: new GithubSlugger(),
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -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 { Element } from 'hast';
|
|
13
|
+
|
|
12
14
|
declare interface PipelineResponseInit {
|
|
13
15
|
status?: number;
|
|
14
16
|
headers: Map<string, string> | object;
|
|
@@ -17,7 +19,10 @@ declare interface PipelineResponseInit {
|
|
|
17
19
|
declare class PipelineResponse {
|
|
18
20
|
constructor(body?:string, init?:PipelineResponseInit);
|
|
19
21
|
status: number;
|
|
20
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The transformed document (hast) representation
|
|
24
|
+
*/
|
|
25
|
+
document: Element;
|
|
21
26
|
body: string;
|
|
22
27
|
headers: Map<string, string>;
|
|
23
28
|
error: any;
|
package/src/PipelineState.d.ts
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
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 {PathInfo, S3Loader, PipelineTimer} from "./index";
|
|
12
|
+
import {PathInfo, S3Loader, FormsMessageDispatcher, PipelineTimer} from "./index";
|
|
13
13
|
import {PipelineContent} from "./PipelineContent";
|
|
14
14
|
|
|
15
15
|
declare interface PipelineOptions {
|
|
16
16
|
log: Console;
|
|
17
17
|
s3Loader: S3Loader;
|
|
18
|
+
messageDispatcher: FormsMessageDispatcher;
|
|
18
19
|
owner: string;
|
|
19
20
|
repo: string;
|
|
20
21
|
ref: string;
|
|
@@ -31,6 +32,7 @@ declare class PipelineState {
|
|
|
31
32
|
content: PipelineContent;
|
|
32
33
|
contentBusId: string;
|
|
33
34
|
s3Loader: S3Loader;
|
|
35
|
+
messageDispatcher: FormsMessageDispatcher;
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* Content bus partition
|
package/src/PipelineState.js
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
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 { PipelineResponse } from './PipelineResponse.js';
|
|
14
|
+
import fetchMetadata from './steps/fetch-metadata.js';
|
|
15
|
+
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
|
|
16
|
+
import { getOriginalHost } from './steps/utils.js';
|
|
17
|
+
|
|
18
|
+
function error(log, msg, status, response) {
|
|
19
|
+
log.error(msg);
|
|
20
|
+
response.status = status;
|
|
21
|
+
response.headers.set('x-error', cleanupHeaderValue(msg));
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Converts URLSearchParams to an object
|
|
27
|
+
* @param {URLSearchParams} searchParams the search params object
|
|
28
|
+
* @returns {Object} The converted object
|
|
29
|
+
*/
|
|
30
|
+
function searchParamsToObject(searchParams) {
|
|
31
|
+
const result = {};
|
|
32
|
+
|
|
33
|
+
for (const key of searchParams.keys()) {
|
|
34
|
+
// get all values association with the key
|
|
35
|
+
const values = searchParams.getAll(key);
|
|
36
|
+
|
|
37
|
+
// if multiple values, convert to array
|
|
38
|
+
result[key] = (values.length === 1) ? values[0] : values;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extracts and parses the body data from the request
|
|
46
|
+
* @param {PipelineRequest} request the request object (see fetch api)
|
|
47
|
+
* @returns {Object} The body data
|
|
48
|
+
* @throws {Error} If an error occurs parsing the body
|
|
49
|
+
*/
|
|
50
|
+
export async function extractBodyData(request) {
|
|
51
|
+
let { body } = request;
|
|
52
|
+
if (!body) {
|
|
53
|
+
throw Error('missing body');
|
|
54
|
+
}
|
|
55
|
+
const type = request.headers.get('content-type');
|
|
56
|
+
|
|
57
|
+
// if content is form-urlencoded we need place the object in the body
|
|
58
|
+
// in a "data" property in the body as this is what forms-service expects.
|
|
59
|
+
if (/^application\/x-www-form-urlencoded/.test(type)) {
|
|
60
|
+
// did they pass an object in the body when a form-urlencoded body was expected?
|
|
61
|
+
if (body === '[object Object]') {
|
|
62
|
+
throw Error('invalid form-urlencoded body');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
body = {
|
|
66
|
+
data: searchParamsToObject(new URLSearchParams(body)),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// else treat the body as json
|
|
70
|
+
} else if (/^application\/json/.test(type)) {
|
|
71
|
+
body = JSON.parse(body);
|
|
72
|
+
// verify the body data is as expected
|
|
73
|
+
if (!body.data) {
|
|
74
|
+
throw Error('missing body.data');
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
throw Error(`post body content-type not supported: ${type}`);
|
|
78
|
+
}
|
|
79
|
+
return body;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle a pipeline POST request.
|
|
84
|
+
* At this point POST's only apply to json files that are backed by a workbook.
|
|
85
|
+
* @param {PipelineState} state pipeline options
|
|
86
|
+
* @param {PipelineRequest} request
|
|
87
|
+
* @returns {Promise<PipelineResponse>} a response
|
|
88
|
+
*/
|
|
89
|
+
export async function formsPipe(state, request) {
|
|
90
|
+
const { log } = state;
|
|
91
|
+
|
|
92
|
+
// todo: improve
|
|
93
|
+
const response = new PipelineResponse('', {
|
|
94
|
+
headers: {
|
|
95
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
await fetchMetadata(state, request, response);
|
|
99
|
+
await setCustomResponseHeaders(state, request, response);
|
|
100
|
+
|
|
101
|
+
const {
|
|
102
|
+
owner, repo, ref, contentBusId, partition, s3Loader,
|
|
103
|
+
} = state;
|
|
104
|
+
const { path } = state.info;
|
|
105
|
+
const resourcePath = `${path}.json`;
|
|
106
|
+
|
|
107
|
+
// block all POSTs to resources with extensions
|
|
108
|
+
if (state.info.originalExtension !== '') {
|
|
109
|
+
return error(log, 'POST to URL with extension not allowed', 405, response);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// head workbook in content bus
|
|
113
|
+
const resourceFetchResponse = await s3Loader.headObject('helix-content-bus', `${contentBusId}/${partition}${resourcePath}`);
|
|
114
|
+
if (resourceFetchResponse.status !== 200) {
|
|
115
|
+
return resourceFetchResponse;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let body;
|
|
119
|
+
try {
|
|
120
|
+
body = await extractBodyData(request);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return error(log, err.message, 400, response);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const sheets = resourceFetchResponse.headers.get('x-amz-meta-x-sheet-names');
|
|
126
|
+
if (!sheets) {
|
|
127
|
+
return error(log, `Target workbook at ${resourcePath} missing x-sheet-names header.`, 403, response);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sourceLocation = resourceFetchResponse.headers.get('x-amz-meta-x-source-location');
|
|
131
|
+
const referer = request.headers.get('referer') || 'unknown';
|
|
132
|
+
const sheetNames = sheets.split(',');
|
|
133
|
+
|
|
134
|
+
if (!sourceLocation || !sheetNames.includes('incoming')) {
|
|
135
|
+
return error(log, `Target workbook at ${resourcePath} is not setup to intake data.`, 403, response);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Send message to SQS if workbook contains and incoming
|
|
139
|
+
// sheet and the source location is not null
|
|
140
|
+
const host = getOriginalHost(request.headers);
|
|
141
|
+
|
|
142
|
+
const message = {
|
|
143
|
+
url: `https://${ref}--${repo}--${owner}.hlx.live${resourcePath}`,
|
|
144
|
+
body,
|
|
145
|
+
host,
|
|
146
|
+
sourceLocation,
|
|
147
|
+
referer,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Send message to forms queue
|
|
152
|
+
const { requestId, messageId } = await state.messageDispatcher.dispatch(message);
|
|
153
|
+
response.status = 201;
|
|
154
|
+
response.headers.set('x-request-id', requestId);
|
|
155
|
+
response.headers.set('x-message-id', messageId);
|
|
156
|
+
return response;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return error(log, `Failed to send message to forms queue: ${err}`, 500, response);
|
|
159
|
+
}
|
|
160
|
+
}
|
package/src/html-pipe.js
CHANGED
|
@@ -62,11 +62,14 @@ export async function htmlPipe(state, req) {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
try { // fetch config first, since we need to compute the content-bus-id from the fstab ...
|
|
65
|
+
state.timer?.update('config-fetch');
|
|
65
66
|
await fetchConfig(state, req, res);
|
|
67
|
+
|
|
66
68
|
// ...and apply the folder mapping
|
|
67
69
|
await folderMapping(state, req, res);
|
|
68
70
|
|
|
69
71
|
// load metadata and content in parallel
|
|
72
|
+
state.timer?.update('content-fetch');
|
|
70
73
|
await Promise.all([
|
|
71
74
|
fetchMetadata(state, req, res),
|
|
72
75
|
fetchContent(state, req, res),
|
|
@@ -80,9 +83,12 @@ export async function htmlPipe(state, req) {
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
if (state.content.sourceBus === 'code') {
|
|
86
|
+
state.timer?.update('serialize');
|
|
83
87
|
await renderCode(state, req, res);
|
|
84
88
|
} else {
|
|
89
|
+
state.timer?.update('parse');
|
|
85
90
|
await parseMarkdown(state);
|
|
91
|
+
state.timer?.update('render');
|
|
86
92
|
await splitSections(state);
|
|
87
93
|
await getMetadata(state); // this one extracts the metadata from the mdast
|
|
88
94
|
await unwrapSoleImages(state);
|
|
@@ -96,6 +102,7 @@ export async function htmlPipe(state, req) {
|
|
|
96
102
|
await addHeadingIds(state);
|
|
97
103
|
await render(state, req, res);
|
|
98
104
|
await removeHlxProps(state, req, res);
|
|
105
|
+
state.timer?.update('serialize');
|
|
99
106
|
await tohtml(state, req, res);
|
|
100
107
|
}
|
|
101
108
|
|
package/src/index.d.ts
CHANGED
|
@@ -83,6 +83,19 @@ declare interface S3Loader {
|
|
|
83
83
|
headObject(bucketId, key): Promise<PipelineResponse>;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
declare interface DispatchMessageResponse {
|
|
87
|
+
messageId:string,
|
|
88
|
+
requestId:string,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
declare interface FormsMessageDispatcher {
|
|
92
|
+
/**
|
|
93
|
+
* Dispatches the message to the forms queue
|
|
94
|
+
* @param {object} message
|
|
95
|
+
*/
|
|
96
|
+
dispatch(message:object): Promise<DispatchMessageResponse>;
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
/**
|
|
87
100
|
* Timer
|
|
88
101
|
*/
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export * from './html-pipe.js';
|
|
13
13
|
export * from './json-pipe.js';
|
|
14
|
+
export * from './options-pipe.js';
|
|
15
|
+
export * from './forms-pipe.js';
|
|
14
16
|
export * from './PipelineContent.js';
|
|
15
17
|
export * from './PipelineRequest.js';
|
|
16
18
|
export * from './PipelineResponse.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { PipelineResponse } from './PipelineResponse.js';
|
|
13
|
+
import fetchMetadata from './steps/fetch-metadata.js';
|
|
14
|
+
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles options requests
|
|
18
|
+
* @param {PipelineState} state pipeline options
|
|
19
|
+
* @param {PipelineRequest} request
|
|
20
|
+
* @returns {Response} a response
|
|
21
|
+
*/
|
|
22
|
+
export async function optionsPipe(state, request) {
|
|
23
|
+
// todo: improve
|
|
24
|
+
const response = new PipelineResponse('', {
|
|
25
|
+
status: 204,
|
|
26
|
+
headers: {
|
|
27
|
+
// Set preflight cache duration
|
|
28
|
+
'access-control-max-age': '86400',
|
|
29
|
+
// Allow content type header
|
|
30
|
+
'access-control-allow-headers': 'content-type',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
await fetchMetadata(state, request, response);
|
|
34
|
+
await setCustomResponseHeaders(state, request, response);
|
|
35
|
+
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
@@ -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 { toString } from 'hast-util-to-string';
|
|
13
|
+
import { visit } from 'unist-util-visit';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Adds missing `id` attributes to the headings
|
|
@@ -16,17 +18,16 @@
|
|
|
16
18
|
* @param {PipelineContent } content The current context of processing pipeline
|
|
17
19
|
*/
|
|
18
20
|
export default async function fixSections({ content }) {
|
|
19
|
-
const { slugger,
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
21
|
+
const { slugger, hast } = content;
|
|
22
|
+
visit(hast, (node) => {
|
|
23
|
+
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName)) {
|
|
24
|
+
const { properties } = node;
|
|
25
|
+
if (!properties.id) {
|
|
26
|
+
const text = toString(node).trim();
|
|
27
|
+
if (text) {
|
|
28
|
+
properties.id = slugger.slug(text);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
32
33
|
}
|
|
@@ -9,57 +9,57 @@
|
|
|
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 { h } from 'hastscript';
|
|
13
|
+
import { selectAll, select } from 'hast-util-select';
|
|
14
|
+
import { toString } from 'hast-util-to-string';
|
|
12
15
|
import { toClassName } from './utils.js';
|
|
16
|
+
import { replace, childNodes } from '../utils/hast-utils.js';
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Creates a "DIV representation" of a table.
|
|
16
20
|
* @type PipelineStep
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {HTMLTableElement} $table the table element
|
|
21
|
+
* @param {HTMLTTableElement} $table the table element
|
|
19
22
|
* @returns {HTMLDivElement} the resulting div
|
|
20
23
|
*/
|
|
21
|
-
function tableToDivs(
|
|
22
|
-
const $cards =
|
|
23
|
-
|
|
24
|
-
// iterate over the table to avoid problem with query selector and nested tables
|
|
24
|
+
function tableToDivs($table) {
|
|
25
|
+
const $cards = h('div');
|
|
25
26
|
const $rows = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
$rows.push(...$tbody.rows);
|
|
27
|
+
for (const child of $table.children) {
|
|
28
|
+
if (child.tagName === 'thead' || child.tagName === 'tbody') {
|
|
29
|
+
$rows.push(...childNodes(child));
|
|
30
|
+
}
|
|
31
31
|
}
|
|
32
|
+
|
|
32
33
|
if ($rows.length === 0) {
|
|
33
34
|
return $cards;
|
|
34
35
|
}
|
|
35
|
-
const $
|
|
36
|
+
const $headerCols = childNodes($rows.shift());
|
|
36
37
|
|
|
37
38
|
// special case, only 1 row and 1 column with a nested table
|
|
38
|
-
if ($rows.length === 0 && $
|
|
39
|
-
const $nestedTable =
|
|
39
|
+
if ($rows.length === 0 && $headerCols.length === 1) {
|
|
40
|
+
const $nestedTable = select(':scope table', $headerCols[0]);
|
|
40
41
|
if ($nestedTable) {
|
|
41
42
|
return $nestedTable;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// get columns names
|
|
46
|
-
const clazz =
|
|
47
|
-
.map((e) => toClassName(e
|
|
47
|
+
const clazz = $headerCols
|
|
48
|
+
.map((e) => toClassName(toString(e)))
|
|
48
49
|
.filter((c) => !!c)
|
|
49
50
|
.join('-');
|
|
50
51
|
if (clazz) {
|
|
51
|
-
$cards.
|
|
52
|
+
$cards.properties.className = [clazz];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// construct page block
|
|
55
56
|
for (const $row of $rows) {
|
|
56
|
-
const $card =
|
|
57
|
-
for (const $cell of $row
|
|
58
|
-
|
|
59
|
-
$
|
|
60
|
-
$card.append($div);
|
|
57
|
+
const $card = h('div');
|
|
58
|
+
for (const $cell of childNodes($row)) {
|
|
59
|
+
// convert to div
|
|
60
|
+
$card.children.push(h('div', $cell.children));
|
|
61
61
|
}
|
|
62
|
-
$cards.
|
|
62
|
+
$cards.children.push($card);
|
|
63
63
|
}
|
|
64
64
|
return $cards;
|
|
65
65
|
}
|
|
@@ -70,9 +70,10 @@ function tableToDivs(document, $table) {
|
|
|
70
70
|
* @param context The current context of processing pipeline
|
|
71
71
|
*/
|
|
72
72
|
export default function createPageBlocks({ content }) {
|
|
73
|
-
const {
|
|
74
|
-
|
|
75
|
-
const $div = tableToDivs(
|
|
76
|
-
|
|
73
|
+
const { hast } = content;
|
|
74
|
+
selectAll('div > table', hast).forEach(($table) => {
|
|
75
|
+
const $div = tableToDivs($table);
|
|
76
|
+
// replace child in parent
|
|
77
|
+
replace(hast, $table, $div);
|
|
77
78
|
});
|
|
78
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 { h } from 'hastscript';
|
|
13
|
+
import { selectAll } from 'hast-util-select';
|
|
14
|
+
import { replace } from '../utils/hast-utils.js';
|
|
12
15
|
import { optimizeImageURL } from './utils.js';
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -17,19 +20,20 @@ import { optimizeImageURL } from './utils.js';
|
|
|
17
20
|
* @param context The current context of processing pipeline
|
|
18
21
|
*/
|
|
19
22
|
export default async function createPictures({ content }) {
|
|
20
|
-
const {
|
|
23
|
+
const { hast } = content;
|
|
21
24
|
|
|
22
25
|
// transform <img> to <picture>
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const source =
|
|
26
|
-
|
|
27
|
-
source.
|
|
28
|
-
|
|
29
|
-
picture
|
|
30
|
-
img.
|
|
31
|
-
img.
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
selectAll('img[src^="./media_"]', hast).forEach((img, i) => {
|
|
27
|
+
const { src } = img.properties;
|
|
28
|
+
const source = h('source');
|
|
29
|
+
source.properties.media = '(max-width: 400px)';
|
|
30
|
+
source.properties.srcset = optimizeImageURL(src, 750);
|
|
31
|
+
|
|
32
|
+
const picture = h('picture', source);
|
|
33
|
+
img.properties.loading = i > 0 ? 'lazy' : 'eager';
|
|
34
|
+
img.properties.src = optimizeImageURL(src, 2000);
|
|
35
|
+
|
|
36
|
+
replace(hast, img, picture);
|
|
37
|
+
picture.children.push(img);
|
|
34
38
|
});
|
|
35
39
|
}
|