@xrow/docusaurus-changelog 4.172.1 → 4.174.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.
Files changed (4) hide show
  1. package/README.md +4 -0
  2. package/index.d.ts +11 -0
  3. package/index.js +151 -15
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -20,4 +20,8 @@ The CI Tools Docusaurus build image registers the component in Docusaurus
20
20
  The feed is loaded in the browser, so the static Docusaurus build remains stable
21
21
  even when the release feed is temporarily unavailable.
22
22
 
23
+ GitLab API descriptions produced by semantic-release are displayed as release
24
+ cards with their full categorized notes and commit links. Atom feeds fall back to
25
+ their summary because they do not expose the structured GitLab description.
26
+
23
27
  The package is published to npmjs.com from ci-tools semver tag pipelines when `NPM_TOKEN` is configured.
package/index.d.ts CHANGED
@@ -5,6 +5,15 @@ export interface ReleaseEntry {
5
5
  href: string;
6
6
  updated: string;
7
7
  summary: string;
8
+ description: string;
9
+ }
10
+
11
+ export interface ReleaseNotes {
12
+ intro: string[];
13
+ sections: Array<{
14
+ title: string;
15
+ items: string[];
16
+ }>;
8
17
  }
9
18
 
10
19
  export interface ChangelogProps {
@@ -17,4 +26,6 @@ export function releasesFromAtomDocument(document: Document): ReleaseEntry[];
17
26
 
18
27
  export function releasesFromGitLabApiResponse(releases: unknown[]): ReleaseEntry[];
19
28
 
29
+ export function releaseNotesFromMarkdown(value: string, releaseTitle?: string): ReleaseNotes;
30
+
20
31
  export default function Changelog(props: ChangelogProps): ReactNode;
package/index.js CHANGED
@@ -21,6 +21,7 @@ export function releasesFromAtomDocument(document) {
21
21
  href: releaseLink(entry),
22
22
  updated: elementText(entry, 'updated') || elementText(entry, 'published'),
23
23
  summary: elementText(entry, 'summary') || elementText(entry, 'content'),
24
+ description: '',
24
25
  }));
25
26
  }
26
27
 
@@ -45,9 +46,111 @@ export function releasesFromGitLabApiResponse(releases) {
45
46
  href: release?._links?.self || release?.assets?.links?.[0]?.url || '',
46
47
  updated: release.released_at || release.created_at || '',
47
48
  summary: plainSummary(release.description),
49
+ description: release.description || '',
48
50
  }));
49
51
  }
50
52
 
53
+ function inlineMarkdownText(value) {
54
+ return (value ?? '')
55
+ .replace(/!\[([^\]]*)]\([^)]+\)/g, '$1')
56
+ .replace(/\[([^\]]+)]\([^)]+\)/g, '$1')
57
+ .replace(/[*_`~]/g, '');
58
+ }
59
+
60
+ function plainInlineMarkdown(value) {
61
+ return inlineMarkdownText(value).trim();
62
+ }
63
+
64
+ export function releaseNotesFromMarkdown(value, releaseTitle = '') {
65
+ const intro = [];
66
+ const sections = [];
67
+ let currentSection;
68
+
69
+ for (const rawLine of (value ?? '').split('\n')) {
70
+ const line = rawLine.trim();
71
+ if (!line || /^-{3,}$/.test(line)) {
72
+ continue;
73
+ }
74
+
75
+ const heading = line.match(/^#{1,6}\s+(.+)$/);
76
+ if (heading) {
77
+ const title = plainInlineMarkdown(heading[1]);
78
+ if (sections.length === 0 && intro.length === 0 && releaseTitle && title.startsWith(releaseTitle)) {
79
+ continue;
80
+ }
81
+ currentSection = {title, items: []};
82
+ sections.push(currentSection);
83
+ continue;
84
+ }
85
+
86
+ const listItem = line.match(/^(?:[-*+] |\d+[.)] )(.+)$/);
87
+ const item = listItem?.[1] ?? line;
88
+ if (currentSection) {
89
+ currentSection.items.push(item);
90
+ } else {
91
+ intro.push(item);
92
+ }
93
+ }
94
+
95
+ return {
96
+ intro,
97
+ sections: sections.filter((section) => section.title || section.items.length > 0),
98
+ };
99
+ }
100
+
101
+ function inlineMarkdown(value) {
102
+ const nodes = [];
103
+ const linkPattern = /\[([^\]]+)]\((https?:\/\/[^)\s]+)\)/g;
104
+ let cursor = 0;
105
+ let match;
106
+
107
+ while ((match = linkPattern.exec(value)) !== null) {
108
+ if (match.index > cursor) {
109
+ nodes.push(inlineMarkdownText(value.slice(cursor, match.index)));
110
+ }
111
+ nodes.push(React.createElement('a', {href: match[2], key: `${match.index}-${match[2]}`}, match[1]));
112
+ cursor = match.index + match[0].length;
113
+ }
114
+
115
+ if (cursor < value.length) {
116
+ nodes.push(inlineMarkdownText(value.slice(cursor)));
117
+ }
118
+
119
+ return nodes;
120
+ }
121
+
122
+ function ReleaseNotes({description, releaseTitle, summary}) {
123
+ const notes = releaseNotesFromMarkdown(description, releaseTitle);
124
+
125
+ if (notes.intro.length === 0 && notes.sections.length === 0) {
126
+ return summary ? React.createElement('p', null, summary) : null;
127
+ }
128
+
129
+ return React.createElement(
130
+ 'div',
131
+ {className: 'ci-tools-changelog__notes'},
132
+ notes.intro.map((line, index) =>
133
+ React.createElement('p', {key: `intro-${index}`}, inlineMarkdown(line)),
134
+ ),
135
+ notes.sections.map((section, sectionIndex) =>
136
+ React.createElement(
137
+ 'section',
138
+ {className: 'ci-tools-changelog__section', key: `${section.title}-${sectionIndex}`},
139
+ React.createElement('h4', null, section.title),
140
+ section.items.length > 0
141
+ ? React.createElement(
142
+ 'ul',
143
+ null,
144
+ section.items.map((item, itemIndex) =>
145
+ React.createElement('li', {key: `${itemIndex}-${item}`}, inlineMarkdown(item)),
146
+ ),
147
+ )
148
+ : null,
149
+ ),
150
+ ),
151
+ );
152
+ }
153
+
51
154
  function parseAtomFeed(text) {
52
155
  if (typeof DOMParser === 'undefined') {
53
156
  throw new Error('DOMParser is not available in this environment.');
@@ -148,26 +251,59 @@ export default function Changelog({url, children, limit = 10}) {
148
251
  state.status === 'ready' && state.releases.length > 0
149
252
  ? React.createElement(
150
253
  'ol',
151
- {className: 'ci-tools-changelog__list'},
254
+ {
255
+ className: 'ci-tools-changelog__list',
256
+ style: {display: 'grid', gap: '1.5rem', listStyle: 'none', padding: 0},
257
+ },
152
258
  state.releases.map((release) =>
153
259
  React.createElement(
154
260
  'li',
155
- {key: release.href || release.title, className: 'ci-tools-changelog__item'},
261
+ {
262
+ key: release.href || release.title,
263
+ className: 'ci-tools-changelog__item',
264
+ style: {
265
+ border: '1px solid var(--ifm-color-emphasis-300, #d0d7de)',
266
+ borderRadius: 'var(--ifm-card-border-radius, 0.5rem)',
267
+ padding: '1.25rem 1.5rem',
268
+ },
269
+ },
156
270
  React.createElement(
157
- 'a',
158
- {href: release.href || url},
159
- release.title,
271
+ 'article',
272
+ null,
273
+ React.createElement(
274
+ 'header',
275
+ {
276
+ style: {
277
+ alignItems: 'baseline',
278
+ display: 'flex',
279
+ flexWrap: 'wrap',
280
+ gap: '0.5rem 1rem',
281
+ justifyContent: 'space-between',
282
+ },
283
+ },
284
+ React.createElement(
285
+ 'h3',
286
+ {style: {marginBottom: '0.5rem'}},
287
+ React.createElement(
288
+ 'a',
289
+ {href: release.href || url},
290
+ release.title,
291
+ ),
292
+ ),
293
+ release.updated
294
+ ? React.createElement(
295
+ 'time',
296
+ {dateTime: release.updated},
297
+ formatDate(release.updated),
298
+ )
299
+ : null,
300
+ ),
301
+ React.createElement(ReleaseNotes, {
302
+ description: release.description,
303
+ releaseTitle: release.title,
304
+ summary: release.summary,
305
+ }),
160
306
  ),
161
- release.updated
162
- ? React.createElement(
163
- 'time',
164
- {dateTime: release.updated},
165
- formatDate(release.updated),
166
- )
167
- : null,
168
- release.summary
169
- ? React.createElement('p', null, release.summary)
170
- : null,
171
307
  ),
172
308
  ),
173
309
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrow/docusaurus-changelog",
3
- "version": "4.172.1",
3
+ "version": "4.174.0",
4
4
  "description": "Docusaurus MDX changelog component for GitLab release Atom feeds.",
5
5
  "type": "module",
6
6
  "main": "index.js",