@adobe/helix-importer 1.0.0 → 1.2.1

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 CHANGED
@@ -1,3 +1,24 @@
1
+ ## [1.2.1](https://github.com/adobe/helix-importer/compare/v1.2.0...v1.2.1) (2022-01-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * export MemoryHandler ([#12](https://github.com/adobe/helix-importer/issues/12)) ([3a83a16](https://github.com/adobe/helix-importer/commit/3a83a1620b0eb4752698ee9b994e8b0fd7ce085f))
7
+
8
+ # [1.2.0](https://github.com/adobe/helix-importer/compare/v1.1.0...v1.2.0) (2022-01-20)
9
+
10
+
11
+ ### Features
12
+
13
+ * export new Blocks helper ([#7](https://github.com/adobe/helix-importer/issues/7)) ([0d7bfa7](https://github.com/adobe/helix-importer/commit/0d7bfa7bd7a98a6fb19f6aec6f8e29be0549e53e))
14
+
15
+ # [1.1.0](https://github.com/adobe/helix-importer/compare/v1.0.0...v1.1.0) (2022-01-20)
16
+
17
+
18
+ ### Features
19
+
20
+ * backport helpers from project into library ([#5](https://github.com/adobe/helix-importer/issues/5)) ([3c93126](https://github.com/adobe/helix-importer/commit/3c931269be98b1accfcc983d19bb7a5abdb6e36a))
21
+
1
22
  # 1.0.0 (2022-01-19)
2
23
 
3
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-importer",
3
- "version": "1.0.0",
3
+ "version": "1.2.1",
4
4
  "description": "Helix Importer tool: create md / docx from html",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -10,37 +10,53 @@
10
10
  },
11
11
  "scripts": {
12
12
  "lint": "eslint .",
13
- "test": "mocha test/**/*.spec.js",
14
- "semantic-release": "semantic-release"
13
+ "test": "c8 mocha",
14
+ "test-ci": "c8 mocha && codecov",
15
+ "semantic-release": "semantic-release",
16
+ "prepare": "npx husky install"
17
+ },
18
+ "mocha": {
19
+ "recursive": "true",
20
+ "spec": "test/**/*.spec.js",
21
+ "reporter": "mocha-multi-reporters",
22
+ "reporter-options": "configFile=.mocha-multi.json"
23
+ },
24
+ "lint-staged": {
25
+ "*.js": "eslint"
15
26
  },
16
27
  "devDependencies": {
17
28
  "@adobe/eslint-config-helix": "1.3.2",
18
- "@adobe/helix-docx2md": "1.0.5",
19
- "@adobe/helix-mediahandler": "1.0.1",
29
+ "@adobe/helix-docx2md": "1.0.6",
30
+ "@adobe/helix-mediahandler": "1.0.2",
20
31
  "@semantic-release/changelog": "6.0.1",
21
32
  "@semantic-release/exec": "6.0.3",
22
33
  "@semantic-release/git": "10.0.1",
23
- "eslint": "8.6.0",
34
+ "eslint": "8.7.0",
24
35
  "eslint-plugin-header": "3.1.1",
25
36
  "eslint-plugin-import": "2.25.4",
26
- "mocha": "9.1.3",
27
- "mock-fs": "4.13.0",
28
- "semantic-release": "18.0.1"
37
+ "mocha": "9.2.0",
38
+ "mock-fs": "5.1.2",
39
+ "c8": "7.11.0",
40
+ "codecov": "3.8.3",
41
+ "husky": "7.0.4",
42
+ "lint-staged": "12.3.1",
43
+ "mocha-multi-reporters": "1.5.1",
44
+ "semantic-release": "19.0.2"
29
45
  },
30
46
  "author": "",
31
47
  "license": "Apache-2.0",
32
48
  "dependencies": {
33
- "@adobe/helix-md2docx": "1.1.0",
49
+ "@adobe/helix-md2docx": "1.2.2",
34
50
  "form-data": "4.0.0",
35
- "fs-extra": "9.0.1",
51
+ "fs-extra": "10.0.0",
36
52
  "hast-util-to-html": "8.0.3",
37
53
  "hast-util-to-mdast": "8.3.0",
38
- "jsdom": "16.4.0",
39
- "node-fetch": "2.6.1",
40
- "rehype-parse": "7.0.1",
41
- "rehype-remark": "8.0.0",
42
- "remark-stringify": "8.1.1",
54
+ "jsdom": "19.0.0",
55
+ "node-fetch": "3.2.0",
56
+ "rehype-parse": "8.0.3",
57
+ "rehype-remark": "9.1.2",
58
+ "remark-stringify": "10.0.2",
43
59
  "sanitize-filename": "1.6.3",
44
- "unified": "9.2.0"
60
+ "unified": "10.1.1"
45
61
  }
46
62
  }
@@ -15,7 +15,7 @@
15
15
  import { JSDOM } from 'jsdom';
16
16
 
17
17
  import path from 'path';
18
- import unified from 'unified';
18
+ import { unified } from 'unified';
19
19
  import parse from 'rehype-parse';
20
20
  import { toHtml } from 'hast-util-to-html';
21
21
  import rehype2remark from 'rehype-remark';
@@ -50,7 +50,7 @@ export default class PageImporter {
50
50
  const { name } = resource;
51
51
  const { directory } = resource;
52
52
  const sanitizedName = FileUtils.sanitizeFilename(name);
53
- this.logger.log(`Computing Markdonw for ${directory}/${sanitizedName}`);
53
+ this.logger.log(`Computing Markdown for ${directory}/${sanitizedName}`);
54
54
 
55
55
  const processor = unified()
56
56
  .use(parse, { emitParseErrors: true })
@@ -73,36 +73,36 @@ export default class PageImporter {
73
73
  .use(() => {
74
74
  // use custom tag and rendering because text is always encoded by default
75
75
  // we need the raw url
76
- processor.Compiler.prototype.visitors.hlxembed = (node) => node.value;
76
+ // processor.Compiler.prototype.visitors.hlxembed = (node) => node.value;
77
77
  })
78
78
  .use(() => {
79
- processor.Compiler.prototype.visitors.table = (node) => node.value;
79
+ // processor.Compiler.prototype.visitors.table = (node) => node.value;
80
80
  })
81
81
  .use(() => {
82
- processor.Compiler.prototype.visitors.u = (node) => {
83
- // u handling: remove the u is the first element is a link
84
- if (node.children && node.children.length > 0) {
85
- const children = node.children.map((child) => processor.stringify(child));
86
- if (node.children[0].type === 'link') {
87
- // first element in the <u> is a link: remove the <u> - unsupported case
88
- return `${children.join()}`;
89
- }
90
- return `<u>${children.join()}</u>`;
91
- }
92
- return '';
93
- };
82
+ // processor.Compiler.prototype.visitors.u = (node) => {
83
+ // // u handling: remove the u is the first element is a link
84
+ // if (node.children && node.children.length > 0) {
85
+ // const children = node.children.map((child) => processor.stringify(child));
86
+ // if (node.children[0].type === 'link') {
87
+ // // first element in the <u> is a link: remove the <u> - unsupported case
88
+ // return `${children.join()}`;
89
+ // }
90
+ // return `<u>${children.join()}</u>`;
91
+ // }
92
+ // return '';
93
+ // };
94
94
  })
95
95
  .use(() => {
96
- const originalEmphasis = processor.Compiler.prototype.visitors.emphasis;
97
- processor.Compiler.prototype.visitors.emphasis = (node) => {
98
- // @ts-ignore
99
- const ori = originalEmphasis.apply(processor.Compiler(), [node]);
100
- return ori;
101
- };
96
+ // const originalEmphasis = processor.Compiler.prototype.visitors.emphasis;
97
+ // processor.Compiler.prototype.visitors.emphasis = (node) => {
98
+ // // @ts-ignore
99
+ // const ori = originalEmphasis.apply(processor.Compiler(), [node]);
100
+ // return ori;
101
+ // };
102
102
  });
103
103
 
104
104
  const file = await processor.process(resource.document.innerHTML);
105
- let contents = file.contents.toString();
105
+ let contents = String(file);
106
106
 
107
107
  // process image links
108
108
  const { document } = resource;
package/src/index.js CHANGED
@@ -17,7 +17,9 @@ import PageImporterParams from './importer/PageImporterParams.js';
17
17
  import PageImporterResource from './importer/PageImporterResource.js';
18
18
 
19
19
  import FSHandler from './storage/FSHandler.js';
20
+ import MemoryHandler from './storage/MemoryHandler.js';
20
21
 
22
+ import Blocks from './utils/Blocks.js';
21
23
  import CSV from './utils/CSV.js';
22
24
  import DOMUtils from './utils/DOMUtils.js';
23
25
  import FileUtils from './utils/FileUtils.js';
@@ -35,6 +37,8 @@ export {
35
37
  PageImporterParams,
36
38
  PageImporterResource,
37
39
  FSHandler,
40
+ MemoryHandler,
41
+ Blocks,
38
42
  CSV,
39
43
  DOMUtils,
40
44
  FileUtils,
@@ -0,0 +1,96 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import DOMUtils from './DOMUtils.js';
13
+
14
+ const DEFAULT_COLSPAN = 2;
15
+
16
+ export default class Blocks {
17
+ static getMetadataBlock(document, metadata) {
18
+ const table = document.createElement('table');
19
+
20
+ let row = document.createElement('tr');
21
+ table.append(row);
22
+
23
+ const hCell = document.createElement('th');
24
+ row.append(hCell);
25
+
26
+ hCell.innerHTML = 'Metadata';
27
+ hCell.setAttribute('colspan', DEFAULT_COLSPAN);
28
+
29
+ // eslint-disable-next-line no-restricted-syntax, guard-for-in
30
+ for (const key in metadata) {
31
+ row = document.createElement('tr');
32
+ table.append(row);
33
+ const keyCell = document.createElement('td');
34
+ row.append(keyCell);
35
+ keyCell.textContent = key;
36
+ const valueCell = document.createElement('td');
37
+ row.append(valueCell);
38
+ const value = metadata[key];
39
+ if (value) {
40
+ if (Array.isArray(value)) {
41
+ let list = '';
42
+ value.forEach((v) => {
43
+ // p tags in table are stripped out
44
+ // list must receive special hlx_replaceTag command to be post-processed
45
+ list += `hlx_replaceTag(p)${v}hlx_replaceTag(/p)`;
46
+ });
47
+ valueCell.textContent = list;
48
+ } else if (typeof value === 'string') {
49
+ valueCell.textContent = value;
50
+ } else {
51
+ valueCell.append(value);
52
+ }
53
+ }
54
+ }
55
+
56
+ return table;
57
+ }
58
+
59
+ static computeBlockName(str) {
60
+ return str
61
+ .replace(/-/g, ' ')
62
+ .replace(/\s(.)/g, (s) => s.toUpperCase())
63
+ .replace(/^(.)/g, (s) => s.toUpperCase());
64
+ }
65
+
66
+ static convertBlocksToTables(element, document) {
67
+ element.querySelectorAll('main > div:nth-child(4) > div[class]').forEach((block) => {
68
+ const name = Blocks.computeBlockName(block.className);
69
+ const data = [[name]];
70
+ const divs = block.querySelectorAll(':scope > div');
71
+ if (divs) {
72
+ divs.forEach((div) => {
73
+ const subDivs = div.querySelectorAll(':scope > div');
74
+ if (subDivs && subDivs.length > 0) {
75
+ const rowData = [];
76
+ subDivs.forEach((cell) => {
77
+ if (cell.nodeName === 'DIV') {
78
+ // remove transparent divs
79
+ const cellContent = [];
80
+ Array.from(cell.childNodes).forEach((c) => cellContent.push(c));
81
+ rowData.push(cellContent);
82
+ } else {
83
+ rowData.push(cell);
84
+ }
85
+ });
86
+ data.push(rowData);
87
+ } else {
88
+ data.push([div.innerHTML]);
89
+ }
90
+ });
91
+ }
92
+ const table = DOMUtils.createTable(data, document);
93
+ block.replaceWith(table);
94
+ });
95
+ }
96
+ }
@@ -162,6 +162,31 @@ export default class DOMUtils {
162
162
  });
163
163
  }
164
164
 
165
+ static createTable(data, document) {
166
+ const table = document.createElement('table');
167
+
168
+ data.forEach((row, index) => {
169
+ const tr = document.createElement('tr');
170
+
171
+ row.forEach((cell) => {
172
+ const t = document.createElement(index === 0 ? 'th' : 'td');
173
+ if (typeof cell === 'string') {
174
+ t.innerHTML = cell;
175
+ } else if (Array.isArray(cell)) {
176
+ cell.forEach((c) => {
177
+ t.append(c);
178
+ });
179
+ } else {
180
+ t.append(cell);
181
+ }
182
+ tr.appendChild(t);
183
+ });
184
+ table.appendChild(tr);
185
+ });
186
+
187
+ return table;
188
+ }
189
+
165
190
  static generateEmbed(url) {
166
191
  return JSDOM.fragment(`<table><tr><th>Embed</th></tr><tr><td><a href="${url}">${url}</a></td></tr></table>`);
167
192
  }
@@ -204,4 +229,16 @@ export default class DOMUtils {
204
229
  }
205
230
  });
206
231
  }
232
+
233
+ static replaceBackgroundByImg(element, document) {
234
+ const url = element?.style?.['background-image'];
235
+ if (url) {
236
+ const src = url.replace(/url\(/gm, '').replace(/'/gm, '').replace(/\)/gm, '');
237
+ const img = document.createElement('img');
238
+ img.src = src;
239
+ element.replaceWith(img);
240
+ return img;
241
+ }
242
+ return element;
243
+ }
207
244
  }
@@ -0,0 +1,138 @@
1
+ /*
2
+ * Copyright 2010 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
+ import { strictEqual } from 'assert';
14
+ import { describe, it } from 'mocha';
15
+
16
+ import { JSDOM } from 'jsdom';
17
+
18
+ import Blocks from '../../src/utils/Blocks.js';
19
+
20
+ describe('Blocks#computeBlockName tests', () => {
21
+ it('computeBlockName - can convert', () => {
22
+ strictEqual(Blocks.computeBlockName('promotion'), 'Promotion');
23
+ strictEqual(Blocks.computeBlockName('hero-animation'), 'Hero Animation');
24
+ strictEqual(Blocks.computeBlockName('how-to-carousel'), 'How To Carousel');
25
+ });
26
+ });
27
+
28
+ const trim = (html) => html
29
+ .replace(/^\s*/gm, '')
30
+ .replace(/\s*$/gm, '')
31
+ .replace(/\n/gm, '')
32
+ .replace(/\/>\s*</gm, '/><');
33
+
34
+ describe('Blocks#convertBlocksToTables tests', () => {
35
+ const test = (input, expected) => {
36
+ const { document } = (new JSDOM(input)).window;
37
+ Blocks.convertBlocksToTables(document, document);
38
+ strictEqual(trim(document.body.innerHTML), trim(expected));
39
+ };
40
+
41
+ const div = '<div></div>'; // ignored div for the tests
42
+
43
+ it('convertBlocksToTables basic block', () => {
44
+ test(
45
+ `<main>${div}${div}${div}
46
+ <div>
47
+ <div class="a-block">
48
+ <div>cell11</div>
49
+ <div>cell21</div>
50
+ </div>
51
+ </div>
52
+ </main>`,
53
+ `<main>${div}${div}${div}
54
+ <div>
55
+ <table>
56
+ <tr><th>A Block</th></tr>
57
+ <tr><td>cell11</td></tr>
58
+ <tr><td>cell21</td></tr>
59
+ </table>
60
+ </div>
61
+ </main>`,
62
+ );
63
+ test(
64
+ `<main>${div}${div}${div}
65
+ <div>
66
+ <div class="another-block">
67
+ <div>
68
+ <div>cell11</div>
69
+ <div>cell12</div>
70
+ </div>
71
+ <div>
72
+ <div>cell21</div>
73
+ <div>cell22</div>
74
+ </div>
75
+ <div>
76
+ <div><img src="https://www.sample.com/image.jpeg"></div>
77
+ <div><a href="https://www.sample.com/">A link</a></div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </main>`,
82
+ `<main>${div}${div}${div}
83
+ <div>
84
+ <table>
85
+ <tr><th>Another Block</th></tr>
86
+ <tr><td>cell11</td><td>cell12</td></tr>
87
+ <tr><td>cell21</td><td>cell22</td></tr>
88
+ <tr><td><img src="https://www.sample.com/image.jpeg"></td><td><a href="https://www.sample.com/">A link</a></td></tr>
89
+ </table>
90
+ </div>
91
+ </main>`,
92
+ );
93
+ test(
94
+ `<main>${div}${div}${div}
95
+ <div>
96
+ <div class="promotion">
97
+ <div>
98
+ <div><a href="https://blog.adobe.com/en/promotions/doc-cloud-education.html">https://blog.adobe.com/en/promotions/doc-cloud-education.html</a></div>
99
+ </div>
100
+ </div>
101
+ </main>`,
102
+ `<main>${div}${div}${div}
103
+ <div>
104
+ <table>
105
+ <tr><th>Promotion</th></tr>
106
+ <tr><td><a href="https://blog.adobe.com/en/promotions/doc-cloud-education.html">https://blog.adobe.com/en/promotions/doc-cloud-education.html</a></td></tr>
107
+ </table>
108
+ </div>
109
+ </main>`,
110
+ );
111
+ });
112
+ });
113
+
114
+ describe('Blocks#getMetadataBlock tests', () => {
115
+ const test = (metadata, expected) => {
116
+ const { document } = (new JSDOM()).window;
117
+ const table = Blocks.getMetadataBlock(document, metadata);
118
+ strictEqual(trim(table.outerHTML), trim(expected));
119
+ };
120
+
121
+ it('getMetadataBlock string meta', () => {
122
+ test({ title: 'Some title' }, '<table><tr><th colspan="2">Metadata</th></tr><tr><td>title</td><td>Some title</td></tr></table>');
123
+ test({ Author: 'Name of the author', 'Creation Date': '2022/01/01' }, '<table><tr><th colspan="2">Metadata</th></tr><tr><td>Author</td><td>Name of the author</td></tr><tr><td>Creation Date</td><td>2022/01/01</td></tr></table>');
124
+ });
125
+
126
+ it('getMetadataBlock element meta', () => {
127
+ const { document } = (new JSDOM()).window;
128
+ const img = document.createElement('img');
129
+ img.src = 'https://www.sample.com/image.jpeg';
130
+ img.title = 'Image title';
131
+ img.alt = 'Image alt';
132
+ test({ title: 'Some title', Image: img }, '<table><tr><th colspan="2">Metadata</th></tr><tr><td>title</td><td>Some title</td></tr><tr><td>Image</td><td><img src="https://www.sample.com/image.jpeg" title="Image title" alt="Image alt"></td></tr></table>');
133
+ });
134
+
135
+ it('getMetadataBlock lists', () => {
136
+ test({ title: 'Some title', Tags: ['Creative', 'Experience Cloud', 'Photography'] }, '<table><tr><th colspan="2">Metadata</th></tr><tr><td>title</td><td>Some title</td></tr><tr><td>Tags</td><td>hlx_replaceTag(p)Creativehlx_replaceTag(/p)hlx_replaceTag(p)Experience Cloudhlx_replaceTag(/p)hlx_replaceTag(p)Photographyhlx_replaceTag(/p)</td></tr></table>');
137
+ });
138
+ });
@@ -170,101 +170,191 @@ describe('DOMUtils#removeCommments tests', () => {
170
170
  test('<p><!-- useless comment \n multiline \n multiline --></p>', '<p></p>');
171
171
  test('<!-- useless comment --><p>The content stays</p><!-- another useless comment with \n line break -->', '<p>The content stays</p>');
172
172
  });
173
+ });
174
+
175
+ describe('DOMUtils#removeSpans tests', () => {
176
+ const test = (input, expected) => {
177
+ const { document } = (new JSDOM(input)).window;
178
+ DOMUtils.removeSpans(document);
179
+ strictEqual(document.body.innerHTML, expected);
180
+ };
181
+
182
+ it('remove spans', () => {
183
+ // do nothing
184
+ test('<p></p>', '<p></p>');
185
+
186
+ // remove spans
187
+ test('<p><span></span></p>', '<p></p>');
188
+ test('<p><span>Content should remain</span> the same</p>', '<p>Content should remain the same</p>');
189
+ test('<p>Spacing<span> should</span> remain the same</p>', '<p>Spacing should remain the same</p>');
190
+ test('<p>Spacing<span> should</span> remain the <span>same even</span> with<span> multiple spans</span></p>', '<p>Spacing should remain the same even with multiple spans</p>');
191
+ });
192
+ });
193
+
194
+ describe('DOMUtils#removeNoscripts tests', () => {
195
+ const test = (input, expected) => {
196
+ strictEqual(DOMUtils.removeNoscripts(input), expected);
197
+ };
198
+
199
+ it('remove no scripts', () => {
200
+ // do nothing
201
+ test('<p>Some content</p>', '<p>Some content</p>');
202
+
203
+ // remove noscript
204
+ test('<body>Do A<noscript>Do Z</noscript></body>', '<body>Do A</body>');
205
+ test('<body>Do A<noscript>Do Z</noscript> but also do B<noscript>and X</noscript></body>', '<body>Do A but also do B</body>');
206
+ test('<body>Do A<noscript>Do Z\n Do X</noscript> but also do B<noscript>and W \n and Y</noscript></body>', '<body>Do A but also do B</body>');
207
+ });
208
+ });
209
+
210
+ describe('DOMUtils#replaceByCaptions tests', () => {
211
+ const test = (input, selectors, expected) => {
212
+ const { document } = (new JSDOM(input)).window;
213
+ DOMUtils.replaceByCaptions(document, selectors);
214
+ strictEqual(document.body.innerHTML, expected);
215
+ };
216
+
217
+ it('replace by captions', () => {
218
+ // do nothing
219
+ test('<p>Some content</p>', ['i'], '<p>Some content</p>');
173
220
 
174
- describe('DOMUtils#removeSpans tests', () => {
175
- const test = (input, expected) => {
176
- const { document } = (new JSDOM(input)).window;
177
- DOMUtils.removeSpans(document);
178
- strictEqual(document.body.innerHTML, expected);
179
- };
180
-
181
- it('remove spans', () => {
182
- // do nothing
183
- test('<p></p>', '<p></p>');
184
-
185
- // remove spans
186
- test('<p><span></span></p>', '<p></p>');
187
- test('<p><span>Content should remain</span> the same</p>', '<p>Content should remain the same</p>');
188
- test('<p>Spacing<span> should</span> remain the same</p>', '<p>Spacing should remain the same</p>');
189
- test('<p>Spacing<span> should</span> remain the <span>same even</span> with<span> multiple spans</span></p>', '<p>Spacing should remain the same even with multiple spans</p>');
190
- });
221
+ test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><p>Some more content</p>', ['figcaption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p>Some more content</p>');
222
+ test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><div class="custom-caption">Another copyright to author.</div><p>Some more content</p>', ['figcaption', '.custom-caption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p><em>Another copyright to author.</em></p><p>Some more content</p>');
191
223
  });
224
+ });
192
225
 
193
- describe('DOMUtils#removeNoscripts tests', () => {
194
- const test = (input, expected) => {
195
- strictEqual(DOMUtils.removeNoscripts(input), expected);
196
- };
226
+ describe('DOMUtils#replaceEmbeds', () => {
227
+ const test = (input, expected) => {
228
+ const { document } = (new JSDOM(input)).window;
229
+ DOMUtils.replaceEmbeds(document);
230
+ strictEqual(document.body.innerHTML, expected);
231
+ };
197
232
 
198
- it('remove no scripts', () => {
199
- // do nothing
200
- test('<p>Some content</p>', '<p>Some content</p>');
233
+ it('replace embeds', () => {
234
+ // do nothing
235
+ test('<p>Some content</p>', '<p>Some content</p>');
236
+ });
201
237
 
202
- // remove noscript
203
- test('<body>Do A<noscript>Do Z</noscript></body>', '<body>Do A</body>');
204
- test('<body>Do A<noscript>Do Z</noscript> but also do B<noscript>and X</noscript></body>', '<body>Do A but also do B</body>');
205
- test('<body>Do A<noscript>Do Z\n Do X</noscript> but also do B<noscript>and W \n and Y</noscript></body>', '<body>Do A but also do B</body>');
206
- });
238
+ it('replace embeds deals with iframes', () => {
239
+ test('<p>Some content</p><iframe src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
240
+ test('<p>Some content</p><iframe data-src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
241
+ test('<p>Some content</p><iframe data-src="https://www.youtube.com/data-src" src="https://www.youtube.com/src"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/data-src">https://www.youtube.com/data-src</a></td></tr></tbody></table>');
207
242
  });
208
243
 
209
- describe('DOMUtils#replaceByCaptions tests', () => {
210
- const test = (input, selectors, expected) => {
211
- const { document } = (new JSDOM(input)).window;
212
- DOMUtils.replaceByCaptions(document, selectors);
213
- strictEqual(document.body.innerHTML, expected);
214
- };
244
+ it('replace embeds deals video tag / content blocks', () => {
245
+ // Video block
246
+ test('<p>Some content</p><video src="https://www.server.com/video.mp4"></video>', '<p>Some content</p><table><tbody><tr><th>Video</th></tr><tr><td><video src="https://www.server.com/video.mp4"></video></td></tr></tbody></table>');
215
247
 
216
- it('replace by captions', () => {
217
- // do nothing
218
- test('<p>Some content</p>', ['i'], '<p>Some content</p>');
248
+ // Animation block
249
+ test('<p>Some content</p><video src="https://www.server.com/video.mp4" autoplay="true"></video>', '<p>Some content</p><table><tbody><tr><th>Animation</th></tr><tr><td><video src="https://www.server.com/video.mp4" autoplay="true"></video></td></tr></tbody></table>');
250
+ });
251
+ });
252
+
253
+ describe('DOMUtils#encodeImagesForTable', () => {
254
+ const test = (input, expected) => {
255
+ const { document } = (new JSDOM(input)).window;
256
+ DOMUtils.encodeImagesForTable(document);
257
+ strictEqual(document.body.innerHTML, expected);
258
+ };
219
259
 
220
- test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><p>Some more content</p>', ['figcaption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p>Some more content</p>');
221
- test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><div class="custom-caption">Another copyright to author.</div><p>Some more content</p>', ['figcaption', '.custom-caption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p><em>Another copyright to author.</em></p><p>Some more content</p>');
222
- });
260
+ it('encode images for table', () => {
261
+ // do nothing
262
+ test('<p>Some content</p>', '<p>Some content</p>');
263
+
264
+ // encode pipe if image is in table
265
+ test('<p>Some content</p><table><tr><td><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe"></td></tr></table>', '<p>Some content</p><table><tbody><tr><td><img src="https://www.server.com/image.jpg" title="Some title \\| which contains a pipe"></td></tr></tbody></table>');
266
+
267
+ // don't encode pipe if image is not in a table
268
+ test('<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">', '<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">');
223
269
  });
270
+ });
224
271
 
225
- describe('DOMUtils#replaceEmbeds', () => {
226
- const test = (input, expected) => {
227
- const { document } = (new JSDOM(input)).window;
228
- DOMUtils.replaceEmbeds(document);
229
- strictEqual(document.body.innerHTML, expected);
230
- };
231
-
232
- it('replace embeds', () => {
233
- // do nothing
234
- test('<p>Some content</p>', '<p>Some content</p>');
235
- });
236
-
237
- it('replace embeds deals with iframes', () => {
238
- test('<p>Some content</p><iframe src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
239
- test('<p>Some content</p><iframe data-src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
240
- test('<p>Some content</p><iframe data-src="https://www.youtube.com/data-src" src="https://www.youtube.com/src"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/data-src">https://www.youtube.com/data-src</a></td></tr></tbody></table>');
241
- });
242
-
243
- it('replace embeds deals video tag / content blocks', () => {
244
- // Video block
245
- test('<p>Some content</p><video src="https://www.server.com/video.mp4"></video>', '<p>Some content</p><table><tbody><tr><th>Video</th></tr><tr><td><video src="https://www.server.com/video.mp4"></video></td></tr></tbody></table>');
246
-
247
- // Animation block
248
- test('<p>Some content</p><video src="https://www.server.com/video.mp4" autoplay="true"></video>', '<p>Some content</p><table><tbody><tr><th>Animation</th></tr><tr><td><video src="https://www.server.com/video.mp4" autoplay="true"></video></td></tr></tbody></table>');
249
- });
272
+ describe('DOM#createTable tests', () => {
273
+ const test = (data, expected) => {
274
+ const { document } = (new JSDOM()).window;
275
+ const table = DOMUtils.createTable(data, document);
276
+ strictEqual(table.outerHTML, expected);
277
+ };
278
+
279
+ it('createTable - basic tables', () => {
280
+ test(
281
+ [[]],
282
+ '<table><tr></tr></table>',
283
+ );
284
+ test(
285
+ [['header']],
286
+ '<table><tr><th>header</th></tr></table>',
287
+ );
288
+ test(
289
+ [['header'], ['cell']],
290
+ '<table><tr><th>header</th></tr><tr><td>cell</td></tr></table>',
291
+ );
292
+ test(
293
+ [['header1', 'header2'], ['cell11', 'cell12'], ['cell21', 'cell22']],
294
+ '<table><tr><th>header1</th><th>header2</th></tr><tr><td>cell11</td><td>cell12</td></tr><tr><td>cell21</td><td>cell22</td></tr></table>',
295
+ );
296
+ // TODO deal with colspan ?
297
+ test(
298
+ [['header1'], ['cell11', 'cell12'], ['cell21', 'cell22']],
299
+ '<table><tr><th>header1</th></tr><tr><td>cell11</td><td>cell12</td></tr><tr><td>cell21</td><td>cell22</td></tr></table>',
300
+ );
250
301
  });
251
302
 
252
- describe('DOMUtils#encodeImagesForTable', () => {
253
- const test = (input, expected) => {
254
- const { document } = (new JSDOM(input)).window;
255
- DOMUtils.encodeImagesForTable(document);
256
- strictEqual(document.body.innerHTML, expected);
257
- };
303
+ it('createTable - deals with Elements', () => {
304
+ const { document } = (new JSDOM()).window;
305
+
306
+ const img = document.createElement('img');
307
+ img.src = 'https://www.sample.com/image.jpeg';
308
+
309
+ const a = document.createElement('a');
310
+ a.href = 'https://www.sample.com/';
311
+
312
+ test(
313
+ [['header'], [img]],
314
+ '<table><tr><th>header</th></tr><tr><td><img src="https://www.sample.com/image.jpeg"></td></tr></table>',
315
+ );
316
+ test(
317
+ [['header'], [img, a, 'some text']],
318
+ '<table><tr><th>header</th></tr><tr><td><img src="https://www.sample.com/image.jpeg"></td><td><a href="https://www.sample.com/"></a></td><td>some text</td></tr></table>',
319
+ );
320
+ test(
321
+ [['header'], [[img, a, 'some text']]],
322
+ '<table><tr><th>header</th></tr><tr><td><img src="https://www.sample.com/image.jpeg"><a href="https://www.sample.com/"></a>some text</td></tr></table>',
323
+ );
324
+ });
325
+ });
326
+
327
+ describe('DOMUtils#replaceBackgroundByImg', () => {
328
+ const createElement = (tag, attrs, styles, innerHTML) => {
329
+ const { document } = (new JSDOM()).window;
330
+ const element = document.createElement(tag);
331
+ // eslint-disable-next-line no-restricted-syntax, guard-for-in
332
+ for (const a in attrs) {
333
+ element.setAttribute(a, attrs[a]);
334
+ }
335
+ // eslint-disable-next-line no-restricted-syntax, guard-for-in
336
+ for (const p in styles) {
337
+ element.style[p] = styles[p];
338
+ }
339
+ element.innerHTML = innerHTML;
340
+ return element;
341
+ };
258
342
 
259
- it('encode images for table', () => {
260
- // do nothing
261
- test('<p>Some content</p>', '<p>Some content</p>');
343
+ const test = (element, expected) => {
344
+ const { document } = (new JSDOM()).window;
345
+ const ret = DOMUtils.replaceBackgroundByImg(element, document);
346
+ strictEqual(ret.outerHTML, expected);
347
+ };
348
+
349
+ it('no background-image style', () => {
350
+ test(createElement('p', {}, {}, 'Some content'), '<p>Some content</p>');
262
351
 
263
- // encode pipe if image is in table
264
- test('<p>Some content</p><table><tr><td><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe"></td></tr></table>', '<p>Some content</p><table><tbody><tr><td><img src="https://www.server.com/image.jpg" title="Some title \\| which contains a pipe"></td></tr></tbody></table>');
352
+ test(createElement('img', { src: 'https://www.server.com/image.jpg', title: 'Some title' }, {}, ''), '<img src="https://www.server.com/image.jpg" title="Some title">');
353
+ });
265
354
 
266
- // don't encode pipe if image is not in a table
267
- test('<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">', '<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">');
268
- });
355
+ it('with background-image style', () => {
356
+ test(createElement('p', {}, { 'background-image': 'url(https://www.server.com/image.jpg)' }, 'Some content'), '<img src="https://www.server.com/image.jpg">');
357
+ test(createElement('p', { class: 'class-is-lost' }, { 'background-image': 'url("https://www.server.com/image.jpg")' }, 'Some content'), '<img src="https://www.server.com/image.jpg">');
358
+ test(createElement('div', { class: 'class-is-lost' }, { 'background-image': 'url("https://www.server.com/image.jpg")' }, '<div><div>Some divs</div><div>More divs</div></div>'), '<img src="https://www.server.com/image.jpg">');
269
359
  });
270
360
  });