@xrow/docusaurus-changelog 4.158.1 → 4.159.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.
- package/README.md +7 -0
- package/index.d.ts +18 -0
- package/index.js +148 -0
- package/package.json +20 -5
package/README.md
CHANGED
|
@@ -6,4 +6,11 @@ Render a GitLab releases Atom feed in Docusaurus MDX.
|
|
|
6
6
|
<Changelog url="https://gitlab.com/group/project/-/releases.atom">Changelog</Changelog>
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
The CI Tools Docusaurus build image registers the component in Docusaurus
|
|
10
|
+
`MDXComponents` automatically when the site does not provide a custom
|
|
11
|
+
`src/theme/MDXComponents/index.js`.
|
|
12
|
+
|
|
13
|
+
The feed is loaded in the browser, so the static Docusaurus build remains stable
|
|
14
|
+
even when the release feed is temporarily unavailable.
|
|
15
|
+
|
|
9
16
|
The package is published to npmjs.com from ci-tools semver tag pipelines when `NPM_TOKEN` is configured.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {ReactNode} from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ReleaseEntry {
|
|
4
|
+
title: string;
|
|
5
|
+
href: string;
|
|
6
|
+
updated: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChangelogProps {
|
|
11
|
+
url: string;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function releasesFromAtomDocument(document: Document): ReleaseEntry[];
|
|
17
|
+
|
|
18
|
+
export default function Changelog(props: ChangelogProps): ReactNode;
|
package/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
|
|
3
|
+
function elementText(parent, tagName) {
|
|
4
|
+
const element = parent.getElementsByTagName(tagName)[0];
|
|
5
|
+
return element?.textContent?.trim() ?? '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function releaseLink(entry) {
|
|
9
|
+
const links = Array.from(entry.getElementsByTagName('link'));
|
|
10
|
+
const alternate = links.find((link) => {
|
|
11
|
+
const rel = link.getAttribute('rel');
|
|
12
|
+
return !rel || rel === 'alternate';
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return alternate?.getAttribute('href') ?? links[0]?.getAttribute('href') ?? '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function releasesFromAtomDocument(document) {
|
|
19
|
+
return Array.from(document.getElementsByTagName('entry')).map((entry) => ({
|
|
20
|
+
title: elementText(entry, 'title') || 'Untitled release',
|
|
21
|
+
href: releaseLink(entry),
|
|
22
|
+
updated: elementText(entry, 'updated') || elementText(entry, 'published'),
|
|
23
|
+
summary: elementText(entry, 'summary') || elementText(entry, 'content'),
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseAtomFeed(text) {
|
|
28
|
+
if (typeof DOMParser === 'undefined') {
|
|
29
|
+
throw new Error('DOMParser is not available in this environment.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const document = new DOMParser().parseFromString(text, 'application/xml');
|
|
33
|
+
const parserError = document.getElementsByTagName('parsererror')[0];
|
|
34
|
+
if (parserError) {
|
|
35
|
+
throw new Error(parserError.textContent || 'Could not parse Atom feed.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return releasesFromAtomDocument(document);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatDate(value) {
|
|
42
|
+
if (!value) {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const date = new Date(value);
|
|
47
|
+
if (Number.isNaN(date.getTime())) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
52
|
+
dateStyle: 'medium',
|
|
53
|
+
timeZone: 'UTC',
|
|
54
|
+
}).format(date);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function Changelog({url, children, limit = 10}) {
|
|
58
|
+
const [state, setState] = useState({
|
|
59
|
+
status: 'idle',
|
|
60
|
+
releases: [],
|
|
61
|
+
error: '',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!url) {
|
|
66
|
+
setState({status: 'error', releases: [], error: 'A release feed URL is required.'});
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
setState({status: 'loading', releases: [], error: ''});
|
|
72
|
+
|
|
73
|
+
fetch(url, {
|
|
74
|
+
headers: {
|
|
75
|
+
Accept: 'application/atom+xml, application/xml, text/xml',
|
|
76
|
+
},
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
})
|
|
79
|
+
.then((response) => {
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`Release feed returned HTTP ${response.status}.`);
|
|
82
|
+
}
|
|
83
|
+
return response.text();
|
|
84
|
+
})
|
|
85
|
+
.then((text) => {
|
|
86
|
+
setState({
|
|
87
|
+
status: 'ready',
|
|
88
|
+
releases: parseAtomFeed(text).slice(0, limit),
|
|
89
|
+
error: '',
|
|
90
|
+
});
|
|
91
|
+
})
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
if (error.name === 'AbortError') {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setState({status: 'error', releases: [], error: error.message});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return () => controller.abort();
|
|
100
|
+
}, [url, limit]);
|
|
101
|
+
|
|
102
|
+
return React.createElement(
|
|
103
|
+
'section',
|
|
104
|
+
{className: 'ci-tools-changelog', 'data-feed-url': url},
|
|
105
|
+
children ? React.createElement('h2', null, children) : null,
|
|
106
|
+
state.status === 'loading' || state.status === 'idle'
|
|
107
|
+
? React.createElement('p', null, 'Loading changelog...')
|
|
108
|
+
: null,
|
|
109
|
+
state.status === 'error'
|
|
110
|
+
? React.createElement(
|
|
111
|
+
'p',
|
|
112
|
+
{role: 'status'},
|
|
113
|
+
'Changelog is unavailable.',
|
|
114
|
+
state.error ? ` ${state.error}` : '',
|
|
115
|
+
)
|
|
116
|
+
: null,
|
|
117
|
+
state.status === 'ready' && state.releases.length === 0
|
|
118
|
+
? React.createElement('p', null, 'No releases found.')
|
|
119
|
+
: null,
|
|
120
|
+
state.status === 'ready' && state.releases.length > 0
|
|
121
|
+
? React.createElement(
|
|
122
|
+
'ol',
|
|
123
|
+
{className: 'ci-tools-changelog__list'},
|
|
124
|
+
state.releases.map((release) =>
|
|
125
|
+
React.createElement(
|
|
126
|
+
'li',
|
|
127
|
+
{key: release.href || release.title, className: 'ci-tools-changelog__item'},
|
|
128
|
+
React.createElement(
|
|
129
|
+
'a',
|
|
130
|
+
{href: release.href || url},
|
|
131
|
+
release.title,
|
|
132
|
+
),
|
|
133
|
+
release.updated
|
|
134
|
+
? React.createElement(
|
|
135
|
+
'time',
|
|
136
|
+
{dateTime: release.updated},
|
|
137
|
+
formatDate(release.updated),
|
|
138
|
+
)
|
|
139
|
+
: null,
|
|
140
|
+
release.summary
|
|
141
|
+
? React.createElement('p', null, release.summary)
|
|
142
|
+
: null,
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
: null,
|
|
147
|
+
);
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xrow/docusaurus-changelog",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.159.0",
|
|
4
4
|
"description": "Docusaurus MDX changelog component for GitLab release Atom feeds.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
7
|
-
"module": "
|
|
8
|
-
"types": "
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"import": "./index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test"
|
|
17
|
+
},
|
|
9
18
|
"files": [
|
|
10
|
-
"
|
|
19
|
+
"index.js",
|
|
20
|
+
"index.d.ts"
|
|
11
21
|
],
|
|
12
22
|
"peerDependencies": {
|
|
13
23
|
"react": ">=18"
|
|
14
24
|
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@xmldom/xmldom": "0.9.10",
|
|
27
|
+
"react": "19.2.3",
|
|
28
|
+
"react-dom": "19.2.3"
|
|
29
|
+
},
|
|
15
30
|
"license": "MIT"
|
|
16
31
|
}
|