@eeacms/volto-cca-policy 1.0.30 → 1.0.31

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
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [1.0.31](https://github.com/eea/volto-cca-policy/compare/1.0.30...1.0.31) - 29 September 2026
8
+
7
9
  ### [1.0.30](https://github.com/eea/volto-cca-policy/compare/1.0.29...1.0.30) - 25 September 2026
8
10
 
9
11
  #### :nail_care: Enhancements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -24,7 +24,8 @@ export function cleanDocumentTitle(title) {
24
24
  if (!title || typeof title !== 'string') return '';
25
25
  const pipeIndex = title.indexOf('|');
26
26
  const firstPart = pipeIndex === -1 ? title : title.slice(0, pipeIndex);
27
- return firstPart.trim() || title.trim();
27
+ const normalizedTitle = title.replace(/\s+/g, ' ').trim();
28
+ return firstPart.replace(/\s+/g, ' ').trim() || normalizedTitle;
28
29
  }
29
30
 
30
31
  /**
@@ -33,8 +34,8 @@ export function cleanDocumentTitle(title) {
33
34
  */
34
35
  export function matchesDocumentTitle(docTitle, searchTitle) {
35
36
  if (!docTitle || !searchTitle) return false;
36
- const d1 = docTitle.trim().toLowerCase();
37
- const s1 = searchTitle.trim().toLowerCase();
37
+ const d1 = docTitle.replace(/\s+/g, ' ').trim().toLowerCase();
38
+ const s1 = searchTitle.replace(/\s+/g, ' ').trim().toLowerCase();
38
39
  if (d1 === s1) return true;
39
40
 
40
41
  const dClean = cleanDocumentTitle(docTitle).toLowerCase();
@@ -78,6 +78,15 @@ describe('matchesDocumentTitle', () => {
78
78
  ).toBe(true);
79
79
  });
80
80
 
81
+ it('matches a single-line marker title to a newline-bearing catalogue title', () => {
82
+ expect(
83
+ matchesDocumentTitle(
84
+ 'EFFIS \n- European Forest Fire Information System – Fire Hazard and Wildfire risk views. | Tool catalogue',
85
+ 'EFFIS - European Forest Fire Information System – Fire Hazard and Wildfire risk views.',
86
+ ),
87
+ ).toBe(true);
88
+ });
89
+
81
90
  it('returns false for non-matching or empty titles', () => {
82
91
  expect(matchesDocumentTitle('Alpha', 'Beta')).toBe(false);
83
92
  expect(matchesDocumentTitle(null, 'Beta')).toBe(false);
@@ -4,7 +4,17 @@
4
4
  */
5
5
 
6
6
  export const DOC_MARKER_RE =
7
- /!?\[\[?\s*(?:doc|document|tool|tools)\s*[:-]\s*([^\]\r\n]+)\]?\]/gi;
7
+ /!?\[\[?\s*(?:doc|document|tool|tools)\s*[:-]\s*([^\]]+)\]?\]/gi;
8
+
9
+ export function normalizeDocMarkerWhitespace(markdown) {
10
+ if (typeof markdown !== 'string') return markdown;
11
+ DOC_MARKER_RE.lastIndex = 0;
12
+ const normalized = markdown.replace(DOC_MARKER_RE, (marker) =>
13
+ marker.replace(/\s+/g, ' '),
14
+ );
15
+ DOC_MARKER_RE.lastIndex = 0;
16
+ return normalized;
17
+ }
8
18
 
9
19
  export const NEXT_STEPS_HEADING_RE =
10
20
  /^(suggested\s+next\s+steps|next\s+steps|recommended\s+next\s+steps|recommended\s+workflow|suggested\s+workflow|action\s+plan|how\s+to\s+use(\s+(them|these|this|the\s+tools))?|how\s+to\s+apply(\s+(them|these|this|the\s+tools))?|how\s+to\s+get\s+started|step-by-step\s+guidance)\b/i;
@@ -22,6 +22,7 @@ import {
22
22
  cleanStrayLeadingAsterisks,
23
23
  deduplicateAdjacentHeadings,
24
24
  normalizeCitationParagraphsToLists,
25
+ normalizeDocMarkerWhitespace,
25
26
  parseMarkdownLines,
26
27
  shouldUnwrapCodeNode,
27
28
  unwrapCardLists,
@@ -75,11 +76,19 @@ export const remarkCcaDocCards = function () {
75
76
  const parseFn =
76
77
  typeof this?.parse === 'function' ? (str) => this.parse(str) : null;
77
78
 
78
- return (tree) => {
79
+ return (tree, file) => {
79
80
  if (!tree || !Array.isArray(tree.children)) {
80
81
  return tree;
81
82
  }
82
83
 
84
+ const markdown = file?.value;
85
+ if (typeof markdown === 'string' && typeof parseFn === 'function') {
86
+ const normalizedMarkdown = normalizeDocMarkerWhitespace(markdown);
87
+ if (normalizedMarkdown !== markdown) {
88
+ tree = parseFn(normalizedMarkdown);
89
+ }
90
+ }
91
+
83
92
  // 1. Pre-pass: unwrap code blocks that contain markdown sections or doc markers
84
93
  for (let i = 0; i < tree.children.length; i += 1) {
85
94
  const node = tree.children[i];
@@ -45,6 +45,27 @@ describe('remarkCcaDocCards', () => {
45
45
  expect(cards(result)).toEqual(['Climate Action Plan']);
46
46
  });
47
47
 
48
+ it('normalizes line breaks in a catalogue title before parsing markdown', () => {
49
+ const markdown =
50
+ '![[doc: EFFIS \n- European Forest Fire Information System – Fire Hazard and Wildfire risk views.]]';
51
+ const parse = jest.fn((source) => ({
52
+ type: 'root',
53
+ children: [{ type: 'text', value: source }],
54
+ }));
55
+ const transform = remarkCcaDocCards.call({ parse });
56
+ const result = transform(
57
+ { type: 'root', children: [] },
58
+ { value: markdown },
59
+ );
60
+
61
+ expect(parse).toHaveBeenCalledWith(
62
+ '![[doc: EFFIS - European Forest Fire Information System – Fire Hazard and Wildfire risk views.]]',
63
+ );
64
+ expect(cards(result)).toEqual([
65
+ 'EFFIS - European Forest Fire Information System – Fire Hazard and Wildfire risk views.',
66
+ ]);
67
+ });
68
+
48
69
  it('keeps text before and after a mid-sentence marker', () => {
49
70
  const tree = {
50
71
  type: 'root',