@solutiondesk/changelog 0.0.0 → 1.0.0-9fc05ac
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/LICENSE +21 -0
- package/README.md +74 -28
- package/dist/index.cjs +255 -0
- package/dist/index.d.cts +229 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +245 -0
- package/dist/react.cjs +508 -0
- package/dist/react.d.cts +288 -0
- package/dist/react.d.ts +288 -0
- package/dist/react.js +501 -0
- package/package.json +75 -7
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
9
|
+
|
|
10
|
+
// src/seen.ts
|
|
11
|
+
function browserStorage() {
|
|
12
|
+
try {
|
|
13
|
+
const storage = globalThis.localStorage;
|
|
14
|
+
return storage ?? null;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function createSeenTracker(workspace, storage = browserStorage()) {
|
|
20
|
+
const key = `sd-changelog-seen:${workspace.toLowerCase()}`;
|
|
21
|
+
const lastSeen = () => {
|
|
22
|
+
try {
|
|
23
|
+
return storage?.getItem(key) ?? null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
lastSeen,
|
|
30
|
+
markSeen(publishedAt) {
|
|
31
|
+
try {
|
|
32
|
+
storage?.setItem(key, publishedAt);
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
unseen(entries) {
|
|
37
|
+
const seen = lastSeen();
|
|
38
|
+
if (!seen) return 0;
|
|
39
|
+
return entries.filter((entry) => entry.publishedAt > seen).length;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var CATEGORY_LABELS = {
|
|
44
|
+
en: { new: "New", improved: "Improved", fixed: "Fixed", announcement: "Announcement" },
|
|
45
|
+
de: { new: "Neu", improved: "Verbessert", fixed: "Behoben", announcement: "Ank\xFCndigung" }
|
|
46
|
+
};
|
|
47
|
+
function categoryLabel(category, locale = "en") {
|
|
48
|
+
return CATEGORY_LABELS[locale.toLowerCase().startsWith("de") ? "de" : "en"][category];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ../sdk-core/src/http.ts
|
|
52
|
+
var DEFAULT_BASE_URL = "https://api.solutiondesk.io";
|
|
53
|
+
var SolutionDeskError = class extends Error {
|
|
54
|
+
constructor(message, init) {
|
|
55
|
+
super(message);
|
|
56
|
+
/** The HTTP status; `0` when no answer arrived (network error, aborted). */
|
|
57
|
+
__publicField(this, "status");
|
|
58
|
+
/** The parsed JSON body of the error answer (usually `{ error: string }`), else its text. */
|
|
59
|
+
__publicField(this, "body");
|
|
60
|
+
/** The requested URL. */
|
|
61
|
+
__publicField(this, "url");
|
|
62
|
+
/** Messages per input field on a `400` (e.g. `{ email: "Enter a valid email address." }`). */
|
|
63
|
+
__publicField(this, "fields");
|
|
64
|
+
this.name = "SolutionDeskError";
|
|
65
|
+
this.status = init.status;
|
|
66
|
+
this.body = init.body ?? null;
|
|
67
|
+
this.url = init.url;
|
|
68
|
+
this.fields = fieldsOf(init.body);
|
|
69
|
+
if (init.cause !== void 0) {
|
|
70
|
+
Object.defineProperty(this, "cause", { value: init.cause, enumerable: false });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** `404`: unknown workspace, module turned off or unknown item — on purpose indistinguishable. */
|
|
74
|
+
get isNotFound() {
|
|
75
|
+
return this.status === 404;
|
|
76
|
+
}
|
|
77
|
+
/** `429`: over the rate limit; try again later. */
|
|
78
|
+
get isRateLimited() {
|
|
79
|
+
return this.status === 429;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function fieldsOf(body) {
|
|
83
|
+
if (!body || typeof body !== "object") return {};
|
|
84
|
+
const fields = body.fields;
|
|
85
|
+
if (!fields || typeof fields !== "object") return {};
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
88
|
+
if (typeof value === "string") out[key] = value;
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function messageOf(body, status) {
|
|
93
|
+
if (body && typeof body === "object") {
|
|
94
|
+
const { error, message, statusCode } = body;
|
|
95
|
+
if (typeof statusCode === "number" && typeof message === "string" && message) return message;
|
|
96
|
+
if (typeof error === "string" && error) return error;
|
|
97
|
+
if (typeof message === "string" && message) return message;
|
|
98
|
+
if (Array.isArray(message) && typeof message[0] === "string") return message[0];
|
|
99
|
+
}
|
|
100
|
+
if (typeof body === "string" && body.trim() && body.length <= 300) return body.trim();
|
|
101
|
+
return `SolutionDesk answered ${status}.`;
|
|
102
|
+
}
|
|
103
|
+
function createMemoryEtagCache(maxEntries = 500) {
|
|
104
|
+
const entries = /* @__PURE__ */ new Map();
|
|
105
|
+
return {
|
|
106
|
+
get: (key) => entries.get(key),
|
|
107
|
+
set(key, value) {
|
|
108
|
+
entries.delete(key);
|
|
109
|
+
entries.set(key, value);
|
|
110
|
+
if (entries.size > maxEntries) {
|
|
111
|
+
const oldest = entries.keys().next().value;
|
|
112
|
+
if (oldest !== void 0) entries.delete(oldest);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function queryString(query) {
|
|
118
|
+
if (!query) return "";
|
|
119
|
+
const parts = [];
|
|
120
|
+
for (const [key, value] of Object.entries(query)) {
|
|
121
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
122
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
123
|
+
}
|
|
124
|
+
return parts.length ? `?${parts.join("&")}` : "";
|
|
125
|
+
}
|
|
126
|
+
var seg = (value) => encodeURIComponent(String(value));
|
|
127
|
+
function parseBody(text) {
|
|
128
|
+
if (!text) return null;
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(text);
|
|
131
|
+
} catch {
|
|
132
|
+
return text;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function createHttp(options = {}) {
|
|
136
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
137
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
138
|
+
const cache = options.etagCache === true ? createMemoryEtagCache() : options.etagCache ? options.etagCache : void 0;
|
|
139
|
+
const url = (path, query) => `${baseUrl}/v1${path}${queryString(query)}`;
|
|
140
|
+
async function requestWithMeta(req) {
|
|
141
|
+
if (!fetchImpl) {
|
|
142
|
+
throw new Error("No global fetch available: pass `fetch` to the client.");
|
|
143
|
+
}
|
|
144
|
+
const target = url(req.path, req.query);
|
|
145
|
+
const headers = {
|
|
146
|
+
accept: req.accept ?? "application/json",
|
|
147
|
+
...options.headers,
|
|
148
|
+
...req.options?.headers
|
|
149
|
+
};
|
|
150
|
+
if (req.body !== void 0) headers["content-type"] = "application/json";
|
|
151
|
+
if (req.apiKey) headers.authorization = `Bearer ${req.apiKey}`;
|
|
152
|
+
const cacheKey = req.method === "GET" && !req.apiKey ? target : void 0;
|
|
153
|
+
const cached = cacheKey && cache ? cache.get(cacheKey) : void 0;
|
|
154
|
+
if (cached && !Object.keys(headers).some((h) => h.toLowerCase() === "if-none-match")) {
|
|
155
|
+
headers["if-none-match"] = cached.etag;
|
|
156
|
+
}
|
|
157
|
+
const init = {
|
|
158
|
+
...req.options?.fetchOptions,
|
|
159
|
+
method: req.method,
|
|
160
|
+
headers
|
|
161
|
+
};
|
|
162
|
+
if (req.body !== void 0) init.body = JSON.stringify(req.body);
|
|
163
|
+
if (req.options?.signal !== void 0) init.signal = req.options.signal;
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
response = await fetchImpl(target, init);
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
169
|
+
throw new SolutionDeskError(`The request to SolutionDesk failed: ${reason}`, {
|
|
170
|
+
status: 0,
|
|
171
|
+
url: target,
|
|
172
|
+
cause
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const finalUrl = response.url;
|
|
176
|
+
const meta = {
|
|
177
|
+
status: response.status,
|
|
178
|
+
finalUrl: typeof finalUrl === "string" && finalUrl ? finalUrl : null
|
|
179
|
+
};
|
|
180
|
+
if (response.status === 304 && cached) {
|
|
181
|
+
return { data: cached.body, ...meta };
|
|
182
|
+
}
|
|
183
|
+
const text = await response.text();
|
|
184
|
+
const body = response.ok && req.responseType === "text" ? text : parseBody(text);
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new SolutionDeskError(messageOf(body, response.status), {
|
|
187
|
+
status: response.status,
|
|
188
|
+
body,
|
|
189
|
+
url: target
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const etag = response.headers.get("etag");
|
|
193
|
+
if (cacheKey && cache && etag) cache.set(cacheKey, { etag, body });
|
|
194
|
+
return { data: body, ...meta };
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
baseUrl,
|
|
198
|
+
url,
|
|
199
|
+
requestWithMeta,
|
|
200
|
+
request: async (req) => (await requestWithMeta(req)).data
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function required(value, name) {
|
|
204
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
205
|
+
throw new TypeError(`SolutionDesk: \`${name}\` is required.`);
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/client.ts
|
|
211
|
+
function createChangelogClient(options) {
|
|
212
|
+
const workspace = required(options.workspace, "workspace");
|
|
213
|
+
const http = createHttp(options);
|
|
214
|
+
const base = `/changelog/${seg(workspace)}`;
|
|
215
|
+
const list = (params = {}, requestOptions) => http.request({
|
|
216
|
+
method: "GET",
|
|
217
|
+
path: base,
|
|
218
|
+
query: { category: params.category, limit: params.limit, cursor: params.cursor },
|
|
219
|
+
options: requestOptions
|
|
220
|
+
});
|
|
221
|
+
async function* allEntries(params = {}, requestOptions) {
|
|
222
|
+
let cursor = null;
|
|
223
|
+
do {
|
|
224
|
+
const page = await list(
|
|
225
|
+
{ ...params, limit: params.limit ?? 50, cursor },
|
|
226
|
+
requestOptions
|
|
227
|
+
);
|
|
228
|
+
yield* page.entries;
|
|
229
|
+
cursor = page.nextCursor;
|
|
230
|
+
} while (cursor);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
workspace,
|
|
234
|
+
list,
|
|
235
|
+
allEntries,
|
|
236
|
+
getEntry: (slug, requestOptions) => http.request({
|
|
237
|
+
method: "GET",
|
|
238
|
+
path: `${base}/entries/${seg(required(slug, "slug"))}`,
|
|
239
|
+
options: requestOptions
|
|
240
|
+
})
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/react/use-changelog.ts
|
|
245
|
+
function useChangelog(options) {
|
|
246
|
+
const { workspace, client: given, limit = 10, category, trackSeen = true, initialData } = options;
|
|
247
|
+
const { baseUrl, fetch: fetchImpl, headers, etagCache } = options;
|
|
248
|
+
const headersKey = JSON.stringify(headers ?? {});
|
|
249
|
+
const client = react.useMemo(
|
|
250
|
+
() => given ?? createChangelogClient({
|
|
251
|
+
workspace,
|
|
252
|
+
baseUrl,
|
|
253
|
+
fetch: fetchImpl,
|
|
254
|
+
headers: JSON.parse(headersKey),
|
|
255
|
+
etagCache
|
|
256
|
+
}),
|
|
257
|
+
[given, workspace, baseUrl, fetchImpl, headersKey, etagCache]
|
|
258
|
+
);
|
|
259
|
+
const tracker = react.useMemo(
|
|
260
|
+
() => trackSeen ? createSeenTracker(workspace, options.storage) : null,
|
|
261
|
+
[trackSeen, workspace, options.storage]
|
|
262
|
+
);
|
|
263
|
+
const [data, setData] = react.useState(initialData ?? null);
|
|
264
|
+
const [entries, setEntries] = react.useState(initialData?.entries ?? []);
|
|
265
|
+
const [status, setStatus] = react.useState(
|
|
266
|
+
initialData ? "ready" : "loading"
|
|
267
|
+
);
|
|
268
|
+
const [error, setError] = react.useState(null);
|
|
269
|
+
const [unseen, setUnseen] = react.useState(0);
|
|
270
|
+
const [loadingMore, setLoadingMore] = react.useState(false);
|
|
271
|
+
const generation = react.useRef(0);
|
|
272
|
+
const moreInFlight = react.useRef(false);
|
|
273
|
+
const settle = react.useCallback(
|
|
274
|
+
(page) => {
|
|
275
|
+
if (!tracker) return;
|
|
276
|
+
const newest = page.entries[0]?.publishedAt;
|
|
277
|
+
const seen = tracker.lastSeen();
|
|
278
|
+
if (!seen && newest) tracker.markSeen(newest);
|
|
279
|
+
setUnseen(tracker.unseen(page.entries));
|
|
280
|
+
},
|
|
281
|
+
[tracker]
|
|
282
|
+
);
|
|
283
|
+
const reload = react.useCallback(async () => {
|
|
284
|
+
const run = ++generation.current;
|
|
285
|
+
try {
|
|
286
|
+
const page = await client.list({ limit, category });
|
|
287
|
+
if (run !== generation.current) return;
|
|
288
|
+
setData(page);
|
|
289
|
+
setEntries(page.entries);
|
|
290
|
+
setError(null);
|
|
291
|
+
setStatus("ready");
|
|
292
|
+
settle(page);
|
|
293
|
+
} catch (caught) {
|
|
294
|
+
if (run !== generation.current) return;
|
|
295
|
+
setError(caught);
|
|
296
|
+
setStatus("error");
|
|
297
|
+
}
|
|
298
|
+
}, [client, limit, category, settle]);
|
|
299
|
+
react.useEffect(() => {
|
|
300
|
+
void reload();
|
|
301
|
+
return () => {
|
|
302
|
+
generation.current++;
|
|
303
|
+
};
|
|
304
|
+
}, [reload]);
|
|
305
|
+
const loadMore = react.useCallback(async () => {
|
|
306
|
+
if (!data?.nextCursor || moreInFlight.current) return;
|
|
307
|
+
const run = generation.current;
|
|
308
|
+
moreInFlight.current = true;
|
|
309
|
+
setLoadingMore(true);
|
|
310
|
+
try {
|
|
311
|
+
const page = await client.list({ limit, category, cursor: data.nextCursor });
|
|
312
|
+
if (run !== generation.current) return;
|
|
313
|
+
setData(page);
|
|
314
|
+
setEntries((current) => [...current, ...page.entries]);
|
|
315
|
+
setError(null);
|
|
316
|
+
} catch (caught) {
|
|
317
|
+
if (run === generation.current) setError(caught);
|
|
318
|
+
} finally {
|
|
319
|
+
moreInFlight.current = false;
|
|
320
|
+
setLoadingMore(false);
|
|
321
|
+
}
|
|
322
|
+
}, [client, limit, category, data]);
|
|
323
|
+
const markAllSeen = react.useCallback(() => {
|
|
324
|
+
const newest = entries[0]?.publishedAt;
|
|
325
|
+
if (tracker && newest) tracker.markSeen(newest);
|
|
326
|
+
setUnseen(0);
|
|
327
|
+
}, [tracker, entries]);
|
|
328
|
+
return {
|
|
329
|
+
status,
|
|
330
|
+
entries,
|
|
331
|
+
data,
|
|
332
|
+
error,
|
|
333
|
+
unseen,
|
|
334
|
+
markAllSeen,
|
|
335
|
+
hasMore: !!data?.nextCursor,
|
|
336
|
+
loadingMore,
|
|
337
|
+
loadMore,
|
|
338
|
+
reload
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function formatChangelogDate(iso, locale = "en") {
|
|
342
|
+
return new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", year: "numeric" }).format(
|
|
343
|
+
new Date(iso)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
function ChangelogEntryBody({ html, className }) {
|
|
347
|
+
if (!html) return null;
|
|
348
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
"data-sd-changelog-body": "",
|
|
352
|
+
className,
|
|
353
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function ChangelogEntryView({
|
|
358
|
+
entry,
|
|
359
|
+
locale = "en",
|
|
360
|
+
showBody = true,
|
|
361
|
+
classNames = {},
|
|
362
|
+
titleAs: Title = "h3"
|
|
363
|
+
}) {
|
|
364
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("article", { "data-sd-changelog-entry": "", "data-category": entry.category, className: classNames.entry, children: [
|
|
365
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-sd-changelog-meta": "", className: classNames.meta, children: [
|
|
366
|
+
/* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: entry.publishedAt, className: classNames.date, children: formatChangelogDate(entry.publishedAt, locale) }),
|
|
367
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-sd-changelog-category": entry.category, className: classNames.category, children: categoryLabel(entry.category, locale) })
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(Title, { className: classNames.title, children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: entry.url, children: entry.title }) }),
|
|
370
|
+
entry.summary ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: classNames.summary, children: entry.summary }) : null,
|
|
371
|
+
showBody ? /* @__PURE__ */ jsxRuntime.jsx(ChangelogEntryBody, { html: entry.html, className: classNames.body }) : null
|
|
372
|
+
] });
|
|
373
|
+
}
|
|
374
|
+
function ChangelogFeed(props) {
|
|
375
|
+
const {
|
|
376
|
+
locale = "en",
|
|
377
|
+
showBody = true,
|
|
378
|
+
className,
|
|
379
|
+
entryClassNames,
|
|
380
|
+
renderEntry,
|
|
381
|
+
loading = null,
|
|
382
|
+
empty = null,
|
|
383
|
+
fallback = null,
|
|
384
|
+
moreLabel,
|
|
385
|
+
moreClassName,
|
|
386
|
+
...options
|
|
387
|
+
} = props;
|
|
388
|
+
const changelog = useChangelog({ trackSeen: false, ...options });
|
|
389
|
+
const de = locale.toLowerCase().startsWith("de");
|
|
390
|
+
if (changelog.status === "loading") return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loading });
|
|
391
|
+
if (changelog.status === "error") return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
392
|
+
if (changelog.entries.length === 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: empty });
|
|
393
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-sd-changelog": "", className, children: [
|
|
394
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { "data-sd-changelog-list": "", children: changelog.entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsx("li", { id: entry.slug, children: renderEntry ? renderEntry(entry) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
395
|
+
ChangelogEntryView,
|
|
396
|
+
{
|
|
397
|
+
entry,
|
|
398
|
+
locale,
|
|
399
|
+
showBody,
|
|
400
|
+
classNames: entryClassNames
|
|
401
|
+
}
|
|
402
|
+
) }, entry.slug)) }),
|
|
403
|
+
changelog.hasMore ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
404
|
+
"button",
|
|
405
|
+
{
|
|
406
|
+
type: "button",
|
|
407
|
+
"data-sd-changelog-more": "",
|
|
408
|
+
className: moreClassName,
|
|
409
|
+
disabled: changelog.loadingMore,
|
|
410
|
+
"aria-busy": changelog.loadingMore || void 0,
|
|
411
|
+
onClick: () => void changelog.loadMore(),
|
|
412
|
+
children: moreLabel ?? (de ? "\xC4ltere Neuigkeiten" : "Older updates")
|
|
413
|
+
}
|
|
414
|
+
) : null
|
|
415
|
+
] });
|
|
416
|
+
}
|
|
417
|
+
function WhatsNew(props) {
|
|
418
|
+
const {
|
|
419
|
+
locale = "en",
|
|
420
|
+
label,
|
|
421
|
+
title,
|
|
422
|
+
classNames = {},
|
|
423
|
+
entryClassNames,
|
|
424
|
+
renderBadge,
|
|
425
|
+
...options
|
|
426
|
+
} = props;
|
|
427
|
+
const changelog = useChangelog(options);
|
|
428
|
+
const [open, setOpen] = react.useState(false);
|
|
429
|
+
const root = react.useRef(null);
|
|
430
|
+
const panelId = react.useId();
|
|
431
|
+
const de = locale.toLowerCase().startsWith("de");
|
|
432
|
+
const fallback = de ? "Was ist neu?" : "What's new";
|
|
433
|
+
react.useEffect(() => {
|
|
434
|
+
if (!open) return;
|
|
435
|
+
const onKey = (event) => event.key === "Escape" && setOpen(false);
|
|
436
|
+
const onClick = (event) => {
|
|
437
|
+
if (root.current && !root.current.contains(event.target)) setOpen(false);
|
|
438
|
+
};
|
|
439
|
+
document.addEventListener("keydown", onKey);
|
|
440
|
+
document.addEventListener("mousedown", onClick);
|
|
441
|
+
return () => {
|
|
442
|
+
document.removeEventListener("keydown", onKey);
|
|
443
|
+
document.removeEventListener("mousedown", onClick);
|
|
444
|
+
};
|
|
445
|
+
}, [open]);
|
|
446
|
+
if (changelog.status !== "ready" || !changelog.data) return null;
|
|
447
|
+
const { data } = changelog;
|
|
448
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
449
|
+
"div",
|
|
450
|
+
{
|
|
451
|
+
ref: root,
|
|
452
|
+
"data-sd-whats-new": "",
|
|
453
|
+
"data-open": open ? "" : void 0,
|
|
454
|
+
className: classNames.root,
|
|
455
|
+
style: { position: "relative" },
|
|
456
|
+
children: [
|
|
457
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
458
|
+
"button",
|
|
459
|
+
{
|
|
460
|
+
type: "button",
|
|
461
|
+
"aria-expanded": open,
|
|
462
|
+
"aria-controls": panelId,
|
|
463
|
+
className: classNames.button,
|
|
464
|
+
onClick: () => {
|
|
465
|
+
if (!open) changelog.markAllSeen();
|
|
466
|
+
setOpen(!open);
|
|
467
|
+
},
|
|
468
|
+
children: [
|
|
469
|
+
label ?? (data.widget.label || fallback),
|
|
470
|
+
changelog.unseen > 0 ? renderBadge ? renderBadge(changelog.unseen) : /* @__PURE__ */ jsxRuntime.jsx("span", { "data-sd-whats-new-badge": "", className: classNames.badge, children: changelog.unseen }) : null
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
),
|
|
474
|
+
open ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
475
|
+
"div",
|
|
476
|
+
{
|
|
477
|
+
id: panelId,
|
|
478
|
+
role: "dialog",
|
|
479
|
+
"aria-label": typeof title === "string" ? title : data.title || fallback,
|
|
480
|
+
"data-sd-whats-new-panel": "",
|
|
481
|
+
className: classNames.panel,
|
|
482
|
+
style: classNames.panel ? void 0 : { position: "absolute", zIndex: 50 },
|
|
483
|
+
children: [
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-sd-whats-new-header": "", className: classNames.header, children: title ?? (data.title || fallback) }),
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { "data-sd-whats-new-list": "", className: classNames.list, children: changelog.entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
486
|
+
ChangelogEntryView,
|
|
487
|
+
{
|
|
488
|
+
entry,
|
|
489
|
+
locale,
|
|
490
|
+
showBody: false,
|
|
491
|
+
classNames: entryClassNames
|
|
492
|
+
}
|
|
493
|
+
) }, entry.slug)) }),
|
|
494
|
+
/* @__PURE__ */ jsxRuntime.jsx("a", { href: data.pageUrl, "data-sd-whats-new-all": "", className: classNames.footer, children: de ? "Alle Neuigkeiten" : "All updates" })
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
) : null
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
exports.ChangelogEntryBody = ChangelogEntryBody;
|
|
504
|
+
exports.ChangelogEntryView = ChangelogEntryView;
|
|
505
|
+
exports.ChangelogFeed = ChangelogFeed;
|
|
506
|
+
exports.WhatsNew = WhatsNew;
|
|
507
|
+
exports.formatChangelogDate = formatChangelogDate;
|
|
508
|
+
exports.useChangelog = useChangelog;
|