@adobe/helix-md2jcr 1.1.23 → 1.1.25
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,17 @@
|
|
|
1
|
+
## [1.1.25](https://github.com/adobe/helix-md2jcr/compare/v1.1.24...v1.1.25) (2025-06-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **108:** support columns block ([#111](https://github.com/adobe/helix-md2jcr/issues/111)) ([7792a12](https://github.com/adobe/helix-md2jcr/commit/7792a12f5178c16f13e15e92b0970f6c826e23ad))
|
|
7
|
+
|
|
8
|
+
## [1.1.24](https://github.com/adobe/helix-md2jcr/compare/v1.1.23...v1.1.24) (2025-06-18)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **deps:** update dependency @adobe/remark-gridtables to v3.0.14 ([#107](https://github.com/adobe/helix-md2jcr/issues/107)) ([a39ea96](https://github.com/adobe/helix-md2jcr/commit/a39ea9604760e18ff13f4d1f2fe9798eefddd28c))
|
|
14
|
+
|
|
1
15
|
## [1.1.23](https://github.com/adobe/helix-md2jcr/compare/v1.1.22...v1.1.23) (2025-06-17)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-md2jcr",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.25",
|
|
4
4
|
"description": "Helix Service that converts markdown to JCR content structure",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@adobe/fetch": "4.2.2",
|
|
32
32
|
"@adobe/helix-markdown-support": "7.1.12",
|
|
33
33
|
"@adobe/helix-shared-process-queue": "3.1.3",
|
|
34
|
-
"@adobe/remark-gridtables": "3.0.
|
|
34
|
+
"@adobe/remark-gridtables": "3.0.14",
|
|
35
35
|
"entities": "^5.0.0",
|
|
36
36
|
"github-slugger": "2.0.0",
|
|
37
37
|
"handlebars": "^4.7.8",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@adobe/eslint-config-helix": "2.0.9",
|
|
58
|
-
"@adobe/helix-mediahandler": "2.7.
|
|
58
|
+
"@adobe/helix-mediahandler": "2.7.13",
|
|
59
59
|
"@commitlint/cli": "^19.6.1",
|
|
60
60
|
"@commitlint/config-conventional": "^19.6.0",
|
|
61
61
|
"@semantic-release/changelog": "6.0.3",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"eslint": "8.57.1",
|
|
68
68
|
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
|
69
69
|
"eslint-plugin-header": "3.1.1",
|
|
70
|
-
"eslint-plugin-import": "2.
|
|
70
|
+
"eslint-plugin-import": "2.32.0",
|
|
71
71
|
"husky": "^9.1.7",
|
|
72
72
|
"junit-report-builder": "5.1.1",
|
|
73
73
|
"lint-staged": "15.5.2",
|
|
@@ -32,20 +32,22 @@ const nameCounter = [];
|
|
|
32
32
|
* or an empty string if the node is unique.
|
|
33
33
|
*/
|
|
34
34
|
function nameHelper(name) {
|
|
35
|
+
// Support for per-context nameCounter
|
|
36
|
+
const context = this || {};
|
|
37
|
+
const counter = context.nameCounter || nameCounter;
|
|
38
|
+
|
|
35
39
|
// reset the name counter if the node is a section or if the name counter is empty
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (name === 'section' || !nameCounter.length) {
|
|
39
|
-
nameCounter.push({});
|
|
40
|
+
if (name === 'section' || !counter.length) {
|
|
41
|
+
counter.push({});
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
// treat sections as a top level node and return the length of the name counter
|
|
43
45
|
if (name === 'section') {
|
|
44
|
-
return
|
|
46
|
+
return counter.length > 1 ? `_${counter.length - 1}` : '';
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// get the current counter
|
|
48
|
-
const currentCounter =
|
|
50
|
+
const currentCounter = counter[counter.length - 1];
|
|
49
51
|
|
|
50
52
|
// increment the counter for the node name
|
|
51
53
|
currentCounter[name] = (currentCounter[name] || 0) + 1;
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
import Handlebars from 'handlebars';
|
|
14
|
+
import { toString } from 'mdast-util-to-string';
|
|
15
|
+
import { find } from 'unist-util-find';
|
|
16
|
+
import { findAllDeep } from '../../utils/mdast.js';
|
|
17
|
+
import whichPartialHelper from '../helpers/which-partial-helper.js';
|
|
18
|
+
import { getModelId } from '../../domain/Definitions.js';
|
|
19
|
+
import {
|
|
20
|
+
findModelById,
|
|
21
|
+
getField,
|
|
22
|
+
} from '../../domain/Models.js';
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line no-unused-vars
|
|
25
|
+
function columnsPartial(context) {
|
|
26
|
+
const {
|
|
27
|
+
children: [tHeader, tBody],
|
|
28
|
+
models,
|
|
29
|
+
definition,
|
|
30
|
+
} = context;
|
|
31
|
+
|
|
32
|
+
const uniqueName = Handlebars.helpers.nameHelper.call(context, 'columns');
|
|
33
|
+
|
|
34
|
+
const rows = findAllDeep(tBody, (node) => node.type === 'gtRow');
|
|
35
|
+
const columns = findAllDeep(rows[0], (node) => node.type === 'gtCell');
|
|
36
|
+
|
|
37
|
+
const textNode = find(tHeader, { type: 'text' });
|
|
38
|
+
const regex = /^(?<blockName>[^(]+)\s*(\((?<classes>[^)]+)\))?$/;
|
|
39
|
+
const match = toString(textNode).match(regex);
|
|
40
|
+
|
|
41
|
+
let headerProps = {};
|
|
42
|
+
|
|
43
|
+
if (match) {
|
|
44
|
+
const blockName = match.groups.blockName.trim();
|
|
45
|
+
const modelId = getModelId(definition, blockName);
|
|
46
|
+
const blockClasses = match.groups.classes ? match.groups.classes.split(',').map((c) => c.trim()) : [];
|
|
47
|
+
|
|
48
|
+
const model = findModelById(models, modelId);
|
|
49
|
+
const classesField = getField(model, 'classes');
|
|
50
|
+
let classes = [];
|
|
51
|
+
|
|
52
|
+
if (blockClasses.length > 0 && classesField) {
|
|
53
|
+
classes = (classesField.multi === true) ? `[${blockClasses.join(',')}]` : blockClasses.join(',');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
headerProps = {
|
|
57
|
+
// name: match.groups.blockName.trim(),
|
|
58
|
+
// modelFields: `[${getModelFieldNames(model).join(',')}]`,
|
|
59
|
+
classes,
|
|
60
|
+
rows: rows.length,
|
|
61
|
+
columns: columns.length,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// remove properties that are empty
|
|
65
|
+
headerProps = Object.fromEntries(Object.entries(headerProps).filter(([_, v]) => v !== '' && (Array.isArray(v) ? v.length > 0 : v !== null)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const attributes = {
|
|
69
|
+
'sling:resourceType': 'core/franklin/components/columns/v1/columns',
|
|
70
|
+
'jcr:primaryType': 'nt:unstructured',
|
|
71
|
+
...headerProps,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const rowEls = rows.map((row, rowIndex) => {
|
|
75
|
+
const cells = findAllDeep(row, (node) => node.type === 'gtCell');
|
|
76
|
+
const columnEls = cells.map((cell, cellIndex) => {
|
|
77
|
+
// custom naming counter for columns
|
|
78
|
+
const nameCounter = [{}];
|
|
79
|
+
|
|
80
|
+
// const columnFilter = filters.find((f) => f.id === 'column');
|
|
81
|
+
// const components = columnFilter?.components || [];
|
|
82
|
+
|
|
83
|
+
// render the children of the cell by calling the associated default content partial function
|
|
84
|
+
// eg. text, image, title, link
|
|
85
|
+
const childrenEls = cell.children.map((child) => {
|
|
86
|
+
const partial = whichPartialHelper(child.type);
|
|
87
|
+
// we shouldn't get a unsupported partial as the mdast should have been cleaned up earlier
|
|
88
|
+
// see mdast-columns-block.js how it cleans up the mdast tree
|
|
89
|
+
if (partial === Handlebars.partials.unsupported) {
|
|
90
|
+
throw new Error(`Unsupported node type in columns: ${child.type}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// call the partial to render the xml for this node, pass the name counter
|
|
94
|
+
// to keep track of the unique names
|
|
95
|
+
return partial({ ...child, nameCounter });
|
|
96
|
+
});
|
|
97
|
+
return `<col${cellIndex + 1} jcr:primaryType="nt:unstructured">${childrenEls.join('\n')}</col${cellIndex + 1}>`;
|
|
98
|
+
});
|
|
99
|
+
return `<row${rowIndex + 1} jcr:primaryType="nt:unstructured">${columnEls.join('\n')}</row${rowIndex + 1}>`;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const attributesStr = Object.entries(attributes).map(([k, v]) => `${k}="${v}"`).join(' ');
|
|
103
|
+
return `<columns${uniqueName} ${attributesStr}>${rowEls.join('\n')}</columns${uniqueName}>`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default columnsPartial;
|
package/src/mdast2jcr/index.js
CHANGED
|
@@ -13,11 +13,16 @@
|
|
|
13
13
|
/* eslint-disable no-param-reassign */
|
|
14
14
|
import Handlebars from 'handlebars';
|
|
15
15
|
import xmlFormatter from 'xml-formatter';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
splitSection,
|
|
18
|
+
unwrapImages as unwrapElements,
|
|
19
|
+
wrapParagraphs,
|
|
20
|
+
} from './utils.js';
|
|
17
21
|
import sanitizeHtml from './mdast-sanitize-html.js';
|
|
18
22
|
import headingPartial from './hb/partials/heading.js';
|
|
19
23
|
import stringPartial from './hb/partials/strong.js';
|
|
20
24
|
import emphasisPartial from './hb/partials/emphasis.js';
|
|
25
|
+
import columnsPartial from './hb/partials/columns.js';
|
|
21
26
|
import linkPartial from './hb/partials/link.js';
|
|
22
27
|
import paragraphWrapperPartial from './hb/partials/paragraph.js';
|
|
23
28
|
import nameHelper, { nameReset } from './hb/helpers/name-helper.js';
|
|
@@ -30,6 +35,7 @@ import pageHelper from './hb/helpers/page-helper.js';
|
|
|
30
35
|
import pageTemplate from './templates/page-template.js';
|
|
31
36
|
import unsupportedPartial from './hb/partials/unsupported.js';
|
|
32
37
|
import sanitizeEds from './mdast-sanitize-eds.js';
|
|
38
|
+
import { splitColumns } from './mdast-columns-block.js';
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* Converts a markdown AST to JCR XML. This function is the main entry point
|
|
@@ -46,6 +52,7 @@ export default async function mdast2jcr(mdast, options = {}) {
|
|
|
46
52
|
mdast = splitSection(mdast);
|
|
47
53
|
mdast = unwrapElements(mdast);
|
|
48
54
|
mdast = wrapParagraphs(mdast);
|
|
55
|
+
mdast = splitColumns(mdast);
|
|
49
56
|
|
|
50
57
|
Handlebars.registerPartial('heading', headingPartial);
|
|
51
58
|
Handlebars.registerPartial('image', imagePartial);
|
|
@@ -53,6 +60,7 @@ export default async function mdast2jcr(mdast, options = {}) {
|
|
|
53
60
|
Handlebars.registerPartial('strong', stringPartial);
|
|
54
61
|
Handlebars.registerPartial('emphasis', emphasisPartial);
|
|
55
62
|
Handlebars.registerPartial('paragraphWrapper', paragraphWrapperPartial);
|
|
63
|
+
Handlebars.registerPartial('columns', columnsPartial);
|
|
56
64
|
Handlebars.registerPartial('gridTable', gridTablePartial);
|
|
57
65
|
Handlebars.registerPartial('unsupported', unsupportedPartial);
|
|
58
66
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 { toString } from 'mdast-util-to-string';
|
|
13
|
+
import { findAllDeep } from './utils/mdast.js';
|
|
14
|
+
import link from './hb/partials/supports/link.js';
|
|
15
|
+
import image from './hb/partials/supports/image.js';
|
|
16
|
+
|
|
17
|
+
export function splitColumns(mdast) {
|
|
18
|
+
const tables = findAllDeep(mdast, (node) => node.type === 'gridTable');
|
|
19
|
+
|
|
20
|
+
tables.map((table) => {
|
|
21
|
+
const header = findAllDeep(table, (node) => node.type === 'gtHeader');
|
|
22
|
+
const value = toString(header);
|
|
23
|
+
if (value.toLowerCase().startsWith('columns')) {
|
|
24
|
+
// eslint-disable-next-line no-param-reassign
|
|
25
|
+
table.type = 'columns';
|
|
26
|
+
}
|
|
27
|
+
return table;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// now that we have columns, we need to merge their children
|
|
31
|
+
findAllDeep(mdast, (node) => node.type === 'columns').forEach((column) => {
|
|
32
|
+
// find all rows of the table body
|
|
33
|
+
const rows = findAllDeep(column.children[1], (node) => node.type === 'gtRow');
|
|
34
|
+
|
|
35
|
+
rows.forEach((row) => {
|
|
36
|
+
const cells = findAllDeep(row, (node) => node.type === 'gtCell');
|
|
37
|
+
|
|
38
|
+
cells.forEach((cell) => {
|
|
39
|
+
const newChildren = [];
|
|
40
|
+
let paragraphGroup = [];
|
|
41
|
+
|
|
42
|
+
const { children } = cell;
|
|
43
|
+
|
|
44
|
+
// group elements that are not links or images into a paragraphWrapper
|
|
45
|
+
// until the next link or image
|
|
46
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
47
|
+
const node = children[i];
|
|
48
|
+
// if we are a liink image, or heading we need to push the node to the new children
|
|
49
|
+
// otherwise we need to group the node and its children into a paragraphWrapper
|
|
50
|
+
if (node?.children?.length === 1
|
|
51
|
+
&& (node.children[0].type === 'link'
|
|
52
|
+
|| node.children[0].type === 'image'
|
|
53
|
+
|| node.type === 'heading')
|
|
54
|
+
) {
|
|
55
|
+
// if we had previously grouped nodes we need to close them off and reset
|
|
56
|
+
if (paragraphGroup.length > 0) {
|
|
57
|
+
newChildren.push({
|
|
58
|
+
type: 'paragraphWrapper',
|
|
59
|
+
children: paragraphGroup,
|
|
60
|
+
});
|
|
61
|
+
// reset the paragraphGroup
|
|
62
|
+
paragraphGroup = [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// push the node as a new child
|
|
66
|
+
if (image.supports(node) || link.supports(node)) {
|
|
67
|
+
newChildren.push(node.children[0]);
|
|
68
|
+
} else {
|
|
69
|
+
newChildren.push(node);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// we have something that needs to be grouped into a paragraphWrapper
|
|
73
|
+
paragraphGroup.push(node);
|
|
74
|
+
|
|
75
|
+
// if it's the last node, we need to add the paragraphWrapper to the newChildren
|
|
76
|
+
if (i === children.length - 1) {
|
|
77
|
+
newChildren.push({
|
|
78
|
+
type: 'paragraphWrapper',
|
|
79
|
+
children: paragraphGroup,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// eslint-disable-next-line no-param-reassign
|
|
85
|
+
cell.children = newChildren;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return mdast;
|
|
91
|
+
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { visit } from 'unist-util-visit';
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* findAll goes through the children of a node searching for nodes that match a
|
|
15
17
|
* test function.
|
|
@@ -42,6 +44,21 @@ function findAll(node, test, flatten = false) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Find all nodes that match the test condition
|
|
49
|
+
* @param {Node} tree - Node(s) to search
|
|
50
|
+
* @param {Function|string} test - Test function or node type
|
|
51
|
+
* @returns {Array} Array of matching nodes
|
|
52
|
+
*/
|
|
53
|
+
function findAllDeep(tree, test) {
|
|
54
|
+
const results = [];
|
|
55
|
+
visit(tree, test, (node) => {
|
|
56
|
+
results.push(node);
|
|
57
|
+
});
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
export {
|
|
46
62
|
findAll,
|
|
63
|
+
findAllDeep,
|
|
47
64
|
};
|