@adobe/helix-md2jcr 1.1.21 → 1.1.23

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.23](https://github.com/adobe/helix-md2jcr/compare/v1.1.22...v1.1.23) (2025-06-17)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * gracefully handle non existing models ([#106](https://github.com/adobe/helix-md2jcr/issues/106)) ([7564c93](https://github.com/adobe/helix-md2jcr/commit/7564c938c51dd00af66c292c5b0c71a306462127))
7
+
8
+ ## [1.1.22](https://github.com/adobe/helix-md2jcr/compare/v1.1.21...v1.1.22) (2025-06-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **103:** custom section metadata fields not being added to jcr result ([#104](https://github.com/adobe/helix-md2jcr/issues/104)) ([bd4b5e6](https://github.com/adobe/helix-md2jcr/commit/bd4b5e69287badbd1ccead61066472231390c9eb))
14
+
1
15
  ## [1.1.21](https://github.com/adobe/helix-md2jcr/compare/v1.1.20...v1.1.21) (2025-05-30)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-md2jcr",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Helix Service that converts markdown to JCR content structure",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@adobe/eslint-config-helix": "2.0.9",
58
- "@adobe/helix-mediahandler": "2.7.8",
58
+ "@adobe/helix-mediahandler": "2.7.10",
59
59
  "@commitlint/cli": "^19.6.1",
60
60
  "@commitlint/config-conventional": "^19.6.0",
61
61
  "@semantic-release/changelog": "6.0.3",
@@ -38,24 +38,24 @@ function findModelById(models, modelId) {
38
38
  * @return {Field} field The field or undefined if not found.
39
39
  */
40
40
  function getField(model, fieldName) {
41
- return model.fields.find((field) => field.name === fieldName);
41
+ return model ? model.fields.find((field) => field.name === fieldName) : undefined;
42
42
  }
43
43
 
44
44
  /**
45
- * Return a comma-separated list of fields in the model. If no fields are found,
46
- * return an empty string.
47
- * @param model
48
- * @return {string}
45
+ * Return a list of field names in the model, excluding 'classes'.
46
+ * @param {Model} model The model.
47
+ * @return {Array<string>} An array of field names.
49
48
  */
50
- function getModelFields(model) {
51
- return model.fields
52
- .map((f) => f.name)
53
- .filter((f) => f !== 'classes')
54
- .join(',');
49
+ function getModelFieldNames(model) {
50
+ return model
51
+ ? model.fields
52
+ .map((f) => f.name)
53
+ .filter((f) => f !== 'classes')
54
+ : [];
55
55
  }
56
56
 
57
57
  export {
58
58
  getField,
59
59
  findModelById,
60
- getModelFields,
60
+ getModelFieldNames,
61
61
  };
@@ -11,7 +11,11 @@
11
11
  */
12
12
  import { find } from 'unist-util-find';
13
13
  import { toString } from 'mdast-util-to-string';
14
- import { findModelById, getField, getModelFields } from '../../domain/Models.js';
14
+ import {
15
+ findModelById,
16
+ getField,
17
+ getModelFieldNames,
18
+ } from '../../domain/Models.js';
15
19
  import { findAll } from '../../utils/mdast.js';
16
20
  import { encodeHTMLEntities, stripNewlines } from '../../utils.js';
17
21
  import link from '../partials/supports/link.js';
@@ -70,8 +74,8 @@ function buildPageMetadata(table, models) {
70
74
  // we need to add a property called modelFields for the Universal Editor
71
75
  // that has all the fields for the model
72
76
  if (model) {
73
- const fields = getModelFields(model);
74
- mappedMetadata.modelFields = `[${fields}]`;
77
+ const fields = getModelFieldNames(model);
78
+ mappedMetadata.modelFields = `[${fields.join(',')}]`;
75
79
  }
76
80
 
77
81
  return mappedMetadata;
@@ -13,55 +13,168 @@ import Handlebars from 'handlebars';
13
13
  import { find } from 'unist-util-find';
14
14
  import { toString } from 'mdast-util-to-string';
15
15
  import { findAll } from '../../utils/mdast.js';
16
- import { findModelById, getField, getModelFields } from '../../domain/Models.js';
16
+ import {
17
+ findModelById,
18
+ getField,
19
+ getModelFieldNames,
20
+ } from '../../domain/Models.js';
21
+
22
+ /**
23
+ * Normalizes strings by converting to lowercase and replacing spaces with hyphens
24
+ * @param {string} str
25
+ * @return {string}
26
+ */
27
+ function normalizeString(str) {
28
+ return str.toLowerCase().replaceAll(' ', '-');
29
+ }
30
+
31
+ /**
32
+ * Helper function to find the section model ID from the grid table.
33
+ * @param {Node} tree
34
+ * @return {string|null}
35
+ */
36
+ function getSectionModelId(tree) {
37
+ const blockModelIdCell = find(tree, { type: 'text', value: 'blockModelId' });
38
+ if (!blockModelIdCell) {
39
+ return null;
40
+ }
41
+
42
+ const parentRow = find(tree, (node) => node.type === 'gtRow' && find(node, (child) => child === blockModelIdCell));
43
+
44
+ if (!parentRow) {
45
+ return null;
46
+ }
47
+
48
+ const cells = findAll(parentRow, (node) => node.type === 'gtCell', true);
49
+ return cells.length > 1 ? toString(cells[1]) : null;
50
+ }
51
+
52
+ /**
53
+ * Processes metadata rows and extracts field values
54
+ * @param {Array} rows
55
+ * @param {Model} model
56
+ * @return {object} result with attributes and modelFields
57
+ */
58
+ function processMetadataRows(rows, model) {
59
+ const attributes = {};
60
+
61
+ // always push the model fields so UE can use them, even if no attributes are found
62
+ const modelFields = getModelFieldNames(model);
63
+
64
+ for (const row of rows) {
65
+ const cells = findAll(row, (n) => n.type === 'gtCell', true);
66
+ if (cells.length < 2) {
67
+ // eslint-disable-next-line no-continue
68
+ continue;
69
+ }
70
+
71
+ const key = toString(cells[0])?.trim();
72
+ const value = toString(cells[1])?.trim();
73
+
74
+ // Skip empty keys/values and the special blockModelId field
75
+ if (!key || !value || key === 'blockModelId') {
76
+ // eslint-disable-next-line no-continue
77
+ continue;
78
+ }
79
+
80
+ const field = getField(model, key);
81
+ if (!field) {
82
+ // eslint-disable-next-line no-continue
83
+ continue;
84
+ }
85
+
86
+ if (field.component === 'multiselect') {
87
+ const multiValue = value.split(',')
88
+ .map((s) => s.trim())
89
+ .filter(Boolean);
90
+ attributes[key] = `[${multiValue.join(',')}]`;
91
+ } else {
92
+ attributes[key] = value;
93
+ }
94
+ }
95
+
96
+ return { attributes, modelFields };
97
+ }
98
+
99
+ /**
100
+ * Finds and processes the section metadata table
101
+ * @param {Array} children
102
+ * @param {Array} models
103
+ * @return {object|null}
104
+ */
105
+ function findSectionMetadata(children, models) {
106
+ for (const child of children) {
107
+ if (child.type !== 'gridTable') {
108
+ // eslint-disable-next-line no-continue
109
+ continue;
110
+ }
111
+
112
+ const headerCell = find(child, { type: 'gtCell' });
113
+ if (!headerCell) {
114
+ // eslint-disable-next-line no-continue
115
+ continue;
116
+ }
117
+
118
+ const isMetadata = normalizeString(toString(headerCell)) === 'section-metadata';
119
+ if (!isMetadata) {
120
+ // eslint-disable-next-line no-continue
121
+ continue;
122
+ }
123
+
124
+ // Found section metadata table
125
+ const sectionModelId = getSectionModelId(child) || 'section';
126
+ const model = findModelById(models, sectionModelId);
127
+
128
+ if (!model) {
129
+ return null;
130
+ }
131
+
132
+ const [, ...rows] = findAll(child, (n) => n.type === 'gtRow', false);
133
+
134
+ const result = processMetadataRows(rows, model);
135
+
136
+ return {
137
+ attributes: result.attributes,
138
+ modelFields: result.modelFields,
139
+ };
140
+ }
141
+
142
+ // see if there is a global section model to use
143
+ const sectionModel = findModelById(models, 'section');
144
+ if (!sectionModel) {
145
+ return null;
146
+ }
147
+
148
+ return {
149
+ modelFields: getModelFieldNames(sectionModel),
150
+ };
151
+ }
17
152
 
18
153
  function sectionHelper(index, children, options) {
19
- const {
20
- models,
21
- } = options.data.root;
154
+ const { models } = options.data.root;
155
+ const uniqueName = Handlebars.helpers.nameHelper.call(this, 'section');
22
156
 
23
- const attributes = {
157
+ const metadataResult = findSectionMetadata(children, models);
158
+
159
+ let attributes = {
24
160
  'sling:resourceType': 'core/franklin/components/section/v1/section',
25
161
  'jcr:primaryType': 'nt:unstructured',
26
162
  };
163
+ let modelFields = [];
27
164
 
28
- const model = findModelById(models, 'section');
29
- if (model) {
30
- const fields = getModelFields(model);
31
- attributes.modelFields = `[${fields}]`;
165
+ if (metadataResult) {
166
+ attributes = { ...attributes, ...metadataResult.attributes };
167
+ modelFields = metadataResult.modelFields;
32
168
  }
33
169
 
34
- const uniqueName = Handlebars.helpers.nameHelper.call(this, 'section');
35
-
36
- // find the table that has a gtHeader that has a header value that contains 'section-metadata'
37
- for (const child of children) {
38
- if (child.type === 'gridTable') {
39
- const cell = find(child, { type: 'gtCell' });
40
- if (cell) {
41
- const isMetadata = toString(cell).toLowerCase().replaceAll(' ', '-') === 'section-metadata';
42
- if (isMetadata) {
43
- const [, ...rows] = findAll(child, (n) => n.type === 'gtRow', false);
44
- for (const row of rows) {
45
- const cells = findAll(row, (n) => n.type === 'gtCell', true);
46
- const key = toString(cells[0]);
47
- const value = toString(cells[1]);
48
- if (key.trim() !== '' && value.trim() !== '') {
49
- const field = getField(model, key);
50
- if (field?.component === 'multiselect') {
51
- const multiValue = value.split(',').map((s) => s.trim()).filter(Boolean);
52
- attributes[key.toLowerCase().replaceAll(' ', '-')] = `[${multiValue.join(',')}]`;
53
- } else {
54
- attributes[key.toLowerCase().replaceAll(' ', '-')] = value;
55
- }
56
- }
57
- }
58
- break;
59
- }
60
- }
61
- }
170
+ if (modelFields.length > 0) {
171
+ attributes.modelFields = `[${modelFields.join(',')}]`;
62
172
  }
63
173
 
64
- const attributesStr = Object.entries(attributes).map(([k, v]) => `${k}="${v}"`).join(' ');
174
+ const attributesStr = Object.entries(attributes)
175
+ .map(([k, v]) => `${k}="${v}"`)
176
+ .join(' ');
177
+
65
178
  return `<section${uniqueName} ${attributesStr}>${options.fn(this)}</section${uniqueName}>`;
66
179
  }
67
180
 
@@ -18,7 +18,8 @@ import { toHast } from 'mdast-util-to-hast';
18
18
  import { toHtml } from 'hast-util-to-html';
19
19
  import { getComponentById, getComponentByTitle, getModelId } from '../../domain/Definitions.js';
20
20
  import {
21
- findModelById, getField, getModelFields,
21
+ findModelById,
22
+ getField, getModelFieldNames,
22
23
  } from '../../domain/Models.js';
23
24
  import { findAll } from '../../utils/mdast.js';
24
25
  import link from './supports/link.js';
@@ -360,7 +361,7 @@ function getBlockItems(mdast, modelHelper, definitions, allowedComponents) {
360
361
  const component = getComponentById(definitions, componentId);
361
362
  const properties = {
362
363
  ...component.defaultFields,
363
- modelFields: `[${getModelFields(fieldGroup.model)}]`,
364
+ modelFields: `[${getModelFieldNames(fieldGroup.model).join(',')}]`,
364
365
  };
365
366
 
366
367
  extractProperties(row, fieldGroup.model, 'blockItem', component, fieldGroup.fields, properties);
@@ -428,7 +429,7 @@ function gridTablePartial(context) {
428
429
  };
429
430
 
430
431
  if (model) {
431
- properties.modelFields = `[${getModelFields(model)}]`;
432
+ properties.modelFields = `[${getModelFieldNames(model).join(',')}]`;
432
433
  }
433
434
 
434
435
  let blockProperties = '';