@adobe/helix-md2jcr 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.
- package/CHANGELOG.md +15 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE.txt +264 -0
- package/README.md +27 -0
- package/baseline-tests.sh +45 -0
- package/package.json +81 -0
- package/src/cli/convert2jcr.js +128 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +15 -0
- package/src/md2jcr/index.d.ts +20 -0
- package/src/md2jcr/index.js +36 -0
- package/src/mdast2jcr/domain/Component.js +66 -0
- package/src/mdast2jcr/domain/Definitions.js +78 -0
- package/src/mdast2jcr/domain/FieldGroup.js +85 -0
- package/src/mdast2jcr/domain/FieldGroupFieldResolver.js +96 -0
- package/src/mdast2jcr/domain/ModelHelper.js +123 -0
- package/src/mdast2jcr/domain/Models.js +46 -0
- package/src/mdast2jcr/hast-table-cell-handler.js +48 -0
- package/src/mdast2jcr/hast-table-handler.js +126 -0
- package/src/mdast2jcr/hb/helpers/encode-helper.js +18 -0
- package/src/mdast2jcr/hb/helpers/name-helper.js +61 -0
- package/src/mdast2jcr/hb/helpers/page-helper.js +109 -0
- package/src/mdast2jcr/hb/helpers/section-helper.js +53 -0
- package/src/mdast2jcr/hb/helpers/which-partial-helper.js +22 -0
- package/src/mdast2jcr/hb/partials/blockquote.js +16 -0
- package/src/mdast2jcr/hb/partials/emphasis.js +34 -0
- package/src/mdast2jcr/hb/partials/grid-table.js +495 -0
- package/src/mdast2jcr/hb/partials/heading.js +53 -0
- package/src/mdast2jcr/hb/partials/image.js +24 -0
- package/src/mdast2jcr/hb/partials/link.js +33 -0
- package/src/mdast2jcr/hb/partials/paragraph.js +30 -0
- package/src/mdast2jcr/hb/partials/strong.js +33 -0
- package/src/mdast2jcr/hb/partials/supports/image.js +27 -0
- package/src/mdast2jcr/hb/partials/supports/link.js +46 -0
- package/src/mdast2jcr/hb/partials/table.js +16 -0
- package/src/mdast2jcr/index.d.ts +122 -0
- package/src/mdast2jcr/index.js +94 -0
- package/src/mdast2jcr/mdast-download-images.js +165 -0
- package/src/mdast2jcr/mdast-sanitize-html.js +233 -0
- package/src/mdast2jcr/templates/page-template.js +27 -0
- package/src/mdast2jcr/utils/mdast.js +47 -0
- package/src/mdast2jcr/utils.js +233 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 { find } from 'unist-util-find';
|
|
13
|
+
|
|
14
|
+
const image = {
|
|
15
|
+
supports: (node) => find(node, { type: 'image' }) !== undefined,
|
|
16
|
+
|
|
17
|
+
getProperties: (node) => {
|
|
18
|
+
const imageNode = find(node, { type: 'image' });
|
|
19
|
+
return {
|
|
20
|
+
url: imageNode?.url || '',
|
|
21
|
+
alt: imageNode?.alt || '',
|
|
22
|
+
label: imageNode?.label || '',
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default image;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 { find } from 'unist-util-find';
|
|
13
|
+
|
|
14
|
+
/** @typedef {import('mdast').Node} Node */
|
|
15
|
+
|
|
16
|
+
const link = {
|
|
17
|
+
supports: (node) => find(node, { type: 'link' }) !== undefined,
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the type of the link based on the node's parent. Links can be wrapped
|
|
21
|
+
* in either strong or emphasis tags to denote primary and secondary links respectively.
|
|
22
|
+
* @param parentNode {Node} The parent node of the link
|
|
23
|
+
* @return {string} The type of the link (primary, secondary, or empty string)
|
|
24
|
+
*/
|
|
25
|
+
getType: (parentNode) => {
|
|
26
|
+
if (find(parentNode, { type: 'emphasis' })) {
|
|
27
|
+
return 'secondary';
|
|
28
|
+
}
|
|
29
|
+
if (find(parentNode, { type: 'strong' })) {
|
|
30
|
+
return 'primary';
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
getProperties: (child) => {
|
|
36
|
+
const node = find(child, { type: 'link' });
|
|
37
|
+
const text = find(node, { type: 'text' });
|
|
38
|
+
return {
|
|
39
|
+
href: node.url,
|
|
40
|
+
text: text?.value || '',
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default link;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
function tablePartial() {
|
|
13
|
+
return '<section></section>\n';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default tablePartial;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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
|
+
* The Field object that represents a field in the model.
|
|
15
|
+
*
|
|
16
|
+
* https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/field-types#component-types
|
|
17
|
+
*/
|
|
18
|
+
export interface FieldDef {
|
|
19
|
+
component: 'group' | 'aem-tag' | 'aem-content' | 'boolean' | 'checkbox-group' | 'container' |
|
|
20
|
+
'aem-content-fragment' | 'date-time' | 'aem-experience-fragment' | 'multiselect' |
|
|
21
|
+
'number' | 'radio-group' | 'reference' | 'richtext' | 'select' | 'tab' | 'text';
|
|
22
|
+
valueType: string;
|
|
23
|
+
name: string;
|
|
24
|
+
label: string;
|
|
25
|
+
value?: string;
|
|
26
|
+
multi?: boolean;
|
|
27
|
+
fields?: FieldDef[]; // for nested fields that have a component value type "group"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The Model object that represents a block's model mapping that should be used to generate
|
|
32
|
+
* the jcr xml.
|
|
33
|
+
*/
|
|
34
|
+
export interface ModelDef {
|
|
35
|
+
/**
|
|
36
|
+
* The id of the model.
|
|
37
|
+
*/
|
|
38
|
+
id: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A list of fields that are to be used to map the mdast to the jcr xml.
|
|
42
|
+
*/
|
|
43
|
+
fields: FieldDef[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ResourceType = 'core/franklin/components/text/v1/text'
|
|
47
|
+
| 'core/franklin/components/text/v1/block'
|
|
48
|
+
| 'core/franklin/components/block/v1/block/item'
|
|
49
|
+
| 'core/franklin/components/title/v1/title'
|
|
50
|
+
| 'core/franklin/components/image/v1/image'
|
|
51
|
+
| 'core/franklin/components/button/v1/button'
|
|
52
|
+
| 'core/franklin/components/section/v1/section'
|
|
53
|
+
| 'core/franklin/components/columns/v1/columns'
|
|
54
|
+
| 'core/franklin/components/page/v1/page';
|
|
55
|
+
|
|
56
|
+
export interface ComponentDef {
|
|
57
|
+
title: string;
|
|
58
|
+
id: string;
|
|
59
|
+
plugins: {
|
|
60
|
+
xwalk: {
|
|
61
|
+
page: {
|
|
62
|
+
resourceType: ResourceType;
|
|
63
|
+
template: {
|
|
64
|
+
[key: string]: string;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface GroupDef {
|
|
72
|
+
title: string;
|
|
73
|
+
id: string;
|
|
74
|
+
components: Array<ComponentDef>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DefinitionDef {
|
|
78
|
+
groups: Array<GroupDef>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface Filter {
|
|
82
|
+
id: string,
|
|
83
|
+
components: Array<string>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Options object for the mdast to jcr conversion.
|
|
88
|
+
*/
|
|
89
|
+
export interface Mdast2JCROptions {
|
|
90
|
+
/**
|
|
91
|
+
* An array of models that should be used to generate the jcr xml.
|
|
92
|
+
*/
|
|
93
|
+
models: Array<ModelDef>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A definition object containing groups and components.
|
|
97
|
+
*/
|
|
98
|
+
definition: DefinitionDef;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Defines the filters.
|
|
102
|
+
*/
|
|
103
|
+
filters: Array<Filter>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The keys are the base names of the properties, and the values are objects with the full
|
|
108
|
+
* property names as keys and the field objects as values.
|
|
109
|
+
*/
|
|
110
|
+
export interface CollapsedFields {
|
|
111
|
+
[key: string]: {
|
|
112
|
+
[key: string]: FieldDef;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Converts the mdast to a jcr xml markup.
|
|
118
|
+
* @param {Node} mdast The mdast
|
|
119
|
+
* @param {Mdast2JCROptions} [opts] options
|
|
120
|
+
* @returns {Promise<string>} the xml markup
|
|
121
|
+
*/
|
|
122
|
+
export default function mdast2jcr(mdast: object, opts?: Mdast2JCROptions): Promise<string>;
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
import Handlebars from 'handlebars';
|
|
15
|
+
import xmlFormatter from 'xml-formatter';
|
|
16
|
+
import { splitSection, unwrapImages as unwrapElements, wrapParagraphs } from './utils.js';
|
|
17
|
+
import sanitizeHtml from './mdast-sanitize-html.js';
|
|
18
|
+
import headingPartial from './hb/partials/heading.js';
|
|
19
|
+
import stringPartial from './hb/partials/strong.js';
|
|
20
|
+
import emphasisPartial from './hb/partials/emphasis.js';
|
|
21
|
+
import linkPartial from './hb/partials/link.js';
|
|
22
|
+
import paragraphWrapperPartial from './hb/partials/paragraph.js';
|
|
23
|
+
import nameHelper, { nameReset } from './hb/helpers/name-helper.js';
|
|
24
|
+
import sectionHelper from './hb/helpers/section-helper.js';
|
|
25
|
+
import imagePartial from './hb/partials/image.js';
|
|
26
|
+
import encodeHelper from './hb/helpers/encode-helper.js';
|
|
27
|
+
import whichPartialHelper from './hb/helpers/which-partial-helper.js';
|
|
28
|
+
import gridTablePartial from './hb/partials/grid-table.js';
|
|
29
|
+
import blockQuotePartial from './hb/partials/blockquote.js';
|
|
30
|
+
import tablePartial from './hb/partials/table.js';
|
|
31
|
+
import pageHelper from './hb/helpers/page-helper.js';
|
|
32
|
+
import pageTemplate from './templates/page-template.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts a markdown AST to JCR XML. This function is the main entry point
|
|
36
|
+
* for the mdast2jcr module. The function takes a markdown AST and an options
|
|
37
|
+
* object as input and returns a promise that resolves to a string containing
|
|
38
|
+
* the JCR XML representation of the markdown AST.
|
|
39
|
+
* @param mdast The markdown AST to convert to JCR XML.
|
|
40
|
+
* @param {Mdast2JCROptions} options An options object that can be used to customize the conversion.
|
|
41
|
+
* @returns {Promise<string>}
|
|
42
|
+
*/
|
|
43
|
+
export default async function mdast2jcr(mdast, options = {}) {
|
|
44
|
+
mdast = sanitizeHtml(mdast);
|
|
45
|
+
mdast = splitSection(mdast);
|
|
46
|
+
mdast = unwrapElements(mdast);
|
|
47
|
+
mdast = wrapParagraphs(mdast);
|
|
48
|
+
|
|
49
|
+
Handlebars.registerPartial('heading', headingPartial);
|
|
50
|
+
Handlebars.registerPartial('image', imagePartial);
|
|
51
|
+
Handlebars.registerPartial('link', linkPartial);
|
|
52
|
+
Handlebars.registerPartial('strong', stringPartial);
|
|
53
|
+
Handlebars.registerPartial('emphasis', emphasisPartial);
|
|
54
|
+
Handlebars.registerPartial('paragraphWrapper', paragraphWrapperPartial);
|
|
55
|
+
Handlebars.registerPartial('gridTable', gridTablePartial);
|
|
56
|
+
Handlebars.registerPartial('blockquote', blockQuotePartial); // TODO
|
|
57
|
+
Handlebars.registerPartial('table', tablePartial); // TODO
|
|
58
|
+
|
|
59
|
+
Handlebars.registerHelper('whichPartial', whichPartialHelper);
|
|
60
|
+
Handlebars.registerHelper('encode', encodeHelper);
|
|
61
|
+
Handlebars.registerHelper('nameHelper', nameHelper);
|
|
62
|
+
Handlebars.registerHelper('section', sectionHelper);
|
|
63
|
+
Handlebars.registerHelper('page', pageHelper);
|
|
64
|
+
|
|
65
|
+
// reset the name helper counter
|
|
66
|
+
nameReset();
|
|
67
|
+
|
|
68
|
+
// register page template
|
|
69
|
+
const pageTemplateXML = pageTemplate();
|
|
70
|
+
|
|
71
|
+
const template = Handlebars.compile(pageTemplateXML);
|
|
72
|
+
|
|
73
|
+
const ctx = {
|
|
74
|
+
models: options.models,
|
|
75
|
+
definition: options.definition,
|
|
76
|
+
filters: options.filters,
|
|
77
|
+
children: mdast.children,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let xml = template(ctx);
|
|
81
|
+
|
|
82
|
+
xml = xmlFormatter(xml, {
|
|
83
|
+
indentation: ' ', // 2 spaces
|
|
84
|
+
filter: (node) => node.type !== 'Comment', // Remove comments
|
|
85
|
+
collapseContent: true,
|
|
86
|
+
lineSeparator: '\n',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (process.env.NODE_ENV === 'debug') {
|
|
90
|
+
console.log(xml);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return xml;
|
|
94
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
/* eslint-disable no-param-reassign */
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
import { context as fetchAPI, h1 } from '@adobe/fetch';
|
|
15
|
+
import processQueue from '@adobe/helix-shared-process-queue';
|
|
16
|
+
import { visit } from 'unist-util-visit';
|
|
17
|
+
import getDimensions from 'image-size';
|
|
18
|
+
import mime from 'mime';
|
|
19
|
+
|
|
20
|
+
function createFetchContext() {
|
|
21
|
+
return process.env.HELIX_FETCH_FORCE_HTTP1
|
|
22
|
+
? h1()
|
|
23
|
+
: fetchAPI();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hsize(bytes, decimals = 2) {
|
|
27
|
+
if (bytes === 0) {
|
|
28
|
+
return '0 ';
|
|
29
|
+
}
|
|
30
|
+
const k = 1024;
|
|
31
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
32
|
+
const sizes = [' ', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
33
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
34
|
+
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param ctx
|
|
39
|
+
* @param tree
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
export default async function downloadImages(ctx, tree) {
|
|
43
|
+
const { log, resourceLoader, image2png } = ctx;
|
|
44
|
+
const context = createFetchContext();
|
|
45
|
+
const { fetch } = context;
|
|
46
|
+
|
|
47
|
+
// gather all image nodes
|
|
48
|
+
const images = [];
|
|
49
|
+
visit(tree, (node) => {
|
|
50
|
+
if (node.type === 'image' && node.url) {
|
|
51
|
+
images.push(node);
|
|
52
|
+
}
|
|
53
|
+
return visit.CONTINUE;
|
|
54
|
+
});
|
|
55
|
+
let count = 0;
|
|
56
|
+
|
|
57
|
+
// download images
|
|
58
|
+
await processQueue(images, async (node) => {
|
|
59
|
+
try {
|
|
60
|
+
const ref = crypto.createHash('sha1')
|
|
61
|
+
.update(node.url)
|
|
62
|
+
.digest('hex');
|
|
63
|
+
node.data = ctx.images[ref];
|
|
64
|
+
if (node.data) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const idx = String(count).padStart(2, ' ');
|
|
68
|
+
count += 1;
|
|
69
|
+
let buffer;
|
|
70
|
+
let type = 'application/octet-stream';
|
|
71
|
+
let maybeConvert;
|
|
72
|
+
let dimensions = {
|
|
73
|
+
width: 100,
|
|
74
|
+
height: 100,
|
|
75
|
+
};
|
|
76
|
+
if (node.url.startsWith('data:')) {
|
|
77
|
+
const [prefix, data] = node.url.substring(5).split(',');
|
|
78
|
+
const [typ, enc] = prefix.split(';');
|
|
79
|
+
if (enc !== 'base64') {
|
|
80
|
+
log.warn(`[${idx}] Error decoding data url. unknown encoding: ${enc}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
buffer = Buffer.from(data, 'base64');
|
|
84
|
+
type = typ;
|
|
85
|
+
} else {
|
|
86
|
+
log.info(`[${idx}] GET ${node.url}`);
|
|
87
|
+
|
|
88
|
+
let doFetch = fetch;
|
|
89
|
+
if (node.url.startsWith('res:')) {
|
|
90
|
+
if (!resourceLoader) {
|
|
91
|
+
log.warn(`[${idx}] Error loading image ${node.url}. resource loader missing.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
doFetch = resourceLoader.fetch.bind(resourceLoader);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ret = await doFetch(node.url);
|
|
98
|
+
if (!ret.ok) {
|
|
99
|
+
const text = await ret.text();
|
|
100
|
+
log.error(`[${idx}] ${ret.status} ${text}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
buffer = await ret.buffer();
|
|
104
|
+
type = ret.headers.get('content-type');
|
|
105
|
+
log.info(`[${idx}] ${ret.status} ${hsize(buffer.length).padStart(10)} ${type}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
dimensions = getDimensions(buffer);
|
|
110
|
+
type = mime.getType(dimensions.type);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
maybeConvert = true;
|
|
113
|
+
log.warn(`[${idx}] Error detecting dimensions: ${e} ${type}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!maybeConvert) {
|
|
117
|
+
// only convert unknown and images
|
|
118
|
+
maybeConvert = type === 'application/octet-stream'
|
|
119
|
+
|| (type.startsWith('image/') && type !== 'image/png' && type !== 'image/jpg' && type !== 'image/jpeg' && type !== 'image/gif');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let originalBuffer;
|
|
123
|
+
let originalType;
|
|
124
|
+
if (maybeConvert && image2png) {
|
|
125
|
+
try {
|
|
126
|
+
const result = await image2png({
|
|
127
|
+
src: node.url,
|
|
128
|
+
data: buffer,
|
|
129
|
+
type,
|
|
130
|
+
});
|
|
131
|
+
if (result) {
|
|
132
|
+
originalBuffer = buffer;
|
|
133
|
+
originalType = type;
|
|
134
|
+
buffer = result.data;
|
|
135
|
+
type = result.type;
|
|
136
|
+
dimensions = {
|
|
137
|
+
width: result.width,
|
|
138
|
+
height: result.height,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
log.warn(`[${idx}] Error to convert to png`, e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ext = mime.getExtension(type);
|
|
147
|
+
|
|
148
|
+
node.data = {
|
|
149
|
+
ext,
|
|
150
|
+
key: `${ref}.${ext}`,
|
|
151
|
+
buffer,
|
|
152
|
+
originalBuffer,
|
|
153
|
+
type,
|
|
154
|
+
originalType,
|
|
155
|
+
dimensions,
|
|
156
|
+
};
|
|
157
|
+
ctx.images[ref] = node.data;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
log.error(`Cannot download image ${node.url}: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
}, 8);
|
|
162
|
+
|
|
163
|
+
// reset fetch context
|
|
164
|
+
context.reset();
|
|
165
|
+
}
|