@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.d.cts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The answers of the public changelog API (`GET /v1/changelog/{workspace}/…`).
|
|
6
|
+
* Written by hand so the package has no dependencies; a drift test in the
|
|
7
|
+
* SolutionDesk repository keeps them identical to the API's own contracts.
|
|
8
|
+
*/
|
|
9
|
+
type ChangelogCategory = "new" | "improved" | "fixed" | "announcement";
|
|
10
|
+
type ChangelogWidgetPosition = "bottom-right" | "bottom-left";
|
|
11
|
+
/** A published entry. */
|
|
12
|
+
interface PublicChangelogEntry {
|
|
13
|
+
slug: string;
|
|
14
|
+
title: string;
|
|
15
|
+
/** One or two sentences — what a "What's new" panel shows. May be empty. */
|
|
16
|
+
summary: string;
|
|
17
|
+
category: ChangelogCategory;
|
|
18
|
+
/** ISO 8601. The list is sorted by it, newest first. */
|
|
19
|
+
publishedAt: string;
|
|
20
|
+
/** The details as the team wrote them (Markdown). */
|
|
21
|
+
markdown: string;
|
|
22
|
+
/**
|
|
23
|
+
* The details as safe HTML: raw HTML in the text is escaped, links are only
|
|
24
|
+
* http(s)/mailto/relative, images only from https addresses. Ready for
|
|
25
|
+
* `innerHTML` / `dangerouslySetInnerHTML`. Empty when there are no details.
|
|
26
|
+
*/
|
|
27
|
+
html: string;
|
|
28
|
+
/** The entry on the workspace's hosted changelog page. */
|
|
29
|
+
url: string;
|
|
30
|
+
}
|
|
31
|
+
/** `GET /v1/changelog/{workspace}` — settings and one page of entries. */
|
|
32
|
+
interface PublicChangelogPayload {
|
|
33
|
+
/** The workspace as the hosted page shows it; `logoUrl` is relative to the API. */
|
|
34
|
+
workspace: {
|
|
35
|
+
name: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
logoUrl: string | null;
|
|
38
|
+
};
|
|
39
|
+
/** The heading the team chose; empty ⇒ use your own ("What's new"). */
|
|
40
|
+
title: string;
|
|
41
|
+
/** The team's settings for the "What's new" button (empty label ⇒ your own). */
|
|
42
|
+
widget: {
|
|
43
|
+
label: string;
|
|
44
|
+
position: ChangelogWidgetPosition;
|
|
45
|
+
};
|
|
46
|
+
/** The hosted changelog page. */
|
|
47
|
+
pageUrl: string;
|
|
48
|
+
entries: PublicChangelogEntry[];
|
|
49
|
+
/** Pass as `cursor` for the next page; null on the last one. */
|
|
50
|
+
nextCursor: string | null;
|
|
51
|
+
}
|
|
52
|
+
/** `GET /v1/changelog/{workspace}/entries/{slug}`. */
|
|
53
|
+
interface PublicChangelogEntryPayload {
|
|
54
|
+
entry: PublicChangelogEntry;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The response parts the SDK reads — what every `fetch` implementation returns. */
|
|
58
|
+
interface FetchResponseLike {
|
|
59
|
+
readonly status: number;
|
|
60
|
+
readonly ok: boolean;
|
|
61
|
+
readonly headers: {
|
|
62
|
+
get(name: string): string | null;
|
|
63
|
+
};
|
|
64
|
+
text(): Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
/** What the SDK passes to `fetch` (plus whatever you set in `fetchOptions`). */
|
|
67
|
+
interface FetchInitLike {
|
|
68
|
+
method: string;
|
|
69
|
+
headers: Record<string, string>;
|
|
70
|
+
body?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A `fetch` implementation. The global `fetch` fits, so do `undici`, `node-fetch`
|
|
74
|
+
* and wrappers (e.g. one that adds tracing). Declared bivariantly so a fetch with
|
|
75
|
+
* a richer `init` type is accepted.
|
|
76
|
+
*/
|
|
77
|
+
type FetchLike = {
|
|
78
|
+
bivarianceHack(input: string, init?: FetchInitLike): Promise<FetchResponseLike>;
|
|
79
|
+
}["bivarianceHack"];
|
|
80
|
+
/** A response kept for `If-None-Match` revalidation. */
|
|
81
|
+
interface CachedResponse {
|
|
82
|
+
etag: string;
|
|
83
|
+
body: unknown;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Where the client keeps answers for ETag revalidation. Any `Map` fits; bring
|
|
87
|
+
* your own to share it between clients or to bound it differently.
|
|
88
|
+
*/
|
|
89
|
+
interface EtagCache {
|
|
90
|
+
get(key: string): CachedResponse | undefined;
|
|
91
|
+
set(key: string, value: CachedResponse): unknown;
|
|
92
|
+
}
|
|
93
|
+
/** Options every client factory takes. */
|
|
94
|
+
interface BaseClientOptions {
|
|
95
|
+
/** The API origin, without `/v1`. Defaults to `https://api.solutiondesk.io`. */
|
|
96
|
+
baseUrl?: string;
|
|
97
|
+
/** A `fetch` implementation; defaults to the global one. */
|
|
98
|
+
fetch?: FetchLike;
|
|
99
|
+
/** Headers sent with every request (e.g. `accept-language` forwarded from your visitor). */
|
|
100
|
+
headers?: Record<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Revalidate with `If-None-Match`: the client remembers each answer's `ETag`
|
|
103
|
+
* and, when the API answers `304 Not Modified`, returns the remembered body.
|
|
104
|
+
* `true` keeps up to 500 answers in memory; or pass your own {@link EtagCache}.
|
|
105
|
+
* Off by default (your framework's fetch cache may already do this).
|
|
106
|
+
*/
|
|
107
|
+
etagCache?: boolean | EtagCache;
|
|
108
|
+
}
|
|
109
|
+
/** Per-call options. */
|
|
110
|
+
interface RequestOptions {
|
|
111
|
+
/** Cancels the request (an `AbortSignal`). */
|
|
112
|
+
signal?: unknown;
|
|
113
|
+
/** Extra headers for this request. */
|
|
114
|
+
headers?: Record<string, string>;
|
|
115
|
+
/**
|
|
116
|
+
* Extra `fetch` options for this request, passed through unchanged — e.g.
|
|
117
|
+
* Next.js `{ next: { revalidate: 60, tags: ["legal"] } }` or `{ cache: "no-store" }`.
|
|
118
|
+
*/
|
|
119
|
+
fetchOptions?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ChangelogClientOptions extends BaseClientOptions {
|
|
123
|
+
/** Your workspace's slug — the `acme` in `https://api.solutiondesk.io/v1/changelog/acme`. */
|
|
124
|
+
workspace: string;
|
|
125
|
+
}
|
|
126
|
+
interface ListEntriesParams {
|
|
127
|
+
/** Only entries of this category. */
|
|
128
|
+
category?: ChangelogCategory;
|
|
129
|
+
/** Page size, 1–50 (default 20). */
|
|
130
|
+
limit?: number;
|
|
131
|
+
/** `nextCursor` of the previous page. */
|
|
132
|
+
cursor?: string | null;
|
|
133
|
+
}
|
|
134
|
+
interface ChangelogClient {
|
|
135
|
+
/** The workspace this client reads. */
|
|
136
|
+
readonly workspace: string;
|
|
137
|
+
/**
|
|
138
|
+
* The team's settings and one page of published entries, newest first
|
|
139
|
+
* (`GET /v1/changelog/{workspace}`). Throws a `SolutionDeskError` with status
|
|
140
|
+
* 404 when the workspace doesn't exist or its changelog is turned off.
|
|
141
|
+
*/
|
|
142
|
+
list(params?: ListEntriesParams, options?: RequestOptions): Promise<PublicChangelogPayload>;
|
|
143
|
+
/** Every published entry, page by page — e.g. for a sitemap, an RSS feed or a static build. */
|
|
144
|
+
allEntries(params?: Omit<ListEntriesParams, "cursor">, options?: RequestOptions): AsyncGenerator<PublicChangelogEntry, void, undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* One published entry (`GET /v1/changelog/{workspace}/entries/{slug}`).
|
|
147
|
+
* Throws a `SolutionDeskError` with status 404 for an unknown or unpublished entry.
|
|
148
|
+
*/
|
|
149
|
+
getEntry(slug: string, options?: RequestOptions): Promise<PublicChangelogEntryPayload>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Where "last seen" is kept — `window.localStorage`, `sessionStorage` or your own. */
|
|
153
|
+
interface SeenStorage {
|
|
154
|
+
getItem(key: string): string | null;
|
|
155
|
+
setItem(key: string, value: string): void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface UseChangelogOptions extends Partial<Omit<ChangelogClientOptions, "workspace">> {
|
|
159
|
+
/** Your workspace's slug. */
|
|
160
|
+
workspace: string;
|
|
161
|
+
/** Use this client instead of making one (e.g. one shared by your whole app). */
|
|
162
|
+
client?: ChangelogClient;
|
|
163
|
+
/** Entries per page (1–50, default 10). */
|
|
164
|
+
limit?: number;
|
|
165
|
+
/** Only entries of this category. */
|
|
166
|
+
category?: ChangelogCategory;
|
|
167
|
+
/**
|
|
168
|
+
* Count entries the visitor hasn't seen yet (`unseen`), kept in `storage`.
|
|
169
|
+
* Default true. On the first visit nothing counts: the newest entry becomes
|
|
170
|
+
* the starting point.
|
|
171
|
+
*/
|
|
172
|
+
trackSeen?: boolean;
|
|
173
|
+
/** Where "last seen" lives (default the browser's `localStorage`). */
|
|
174
|
+
storage?: SeenStorage | null;
|
|
175
|
+
/** Server-rendered or cached first page: shown at once, refreshed in the background. */
|
|
176
|
+
initialData?: PublicChangelogPayload;
|
|
177
|
+
}
|
|
178
|
+
interface UseChangelogResult {
|
|
179
|
+
status: "loading" | "ready" | "error";
|
|
180
|
+
/** Every entry loaded so far, newest first. */
|
|
181
|
+
entries: PublicChangelogEntry[];
|
|
182
|
+
/** The last page's answer (title, button settings, page address), or null while loading. */
|
|
183
|
+
data: PublicChangelogPayload | null;
|
|
184
|
+
/**
|
|
185
|
+
* Why loading failed (a `SolutionDeskError`; 404 = unknown workspace or changelog off).
|
|
186
|
+
* A failed {@link UseChangelogResult.loadMore} sets it too, but keeps `status` "ready".
|
|
187
|
+
*/
|
|
188
|
+
error: unknown;
|
|
189
|
+
/** Entries newer than what the visitor last saw. */
|
|
190
|
+
unseen: number;
|
|
191
|
+
/** Remember everything loaded as seen (call it when the visitor opens your panel). */
|
|
192
|
+
markAllSeen(): void;
|
|
193
|
+
/** Whether there are older entries to load. */
|
|
194
|
+
hasMore: boolean;
|
|
195
|
+
/** Whether an older page is loading right now. */
|
|
196
|
+
loadingMore: boolean;
|
|
197
|
+
/** Load the next (older) page and append it. Never rejects; failures land in `error`. */
|
|
198
|
+
loadMore(): Promise<void>;
|
|
199
|
+
/** Load the first page again. */
|
|
200
|
+
reload(): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The published changelog entries of a workspace, with an unseen count for a
|
|
204
|
+
* "new" badge. Fetches in the browser (no key needed); pass `initialData` from
|
|
205
|
+
* your server for a first paint without loading.
|
|
206
|
+
*/
|
|
207
|
+
declare function useChangelog(options: UseChangelogOptions): UseChangelogResult;
|
|
208
|
+
|
|
209
|
+
/** "October 28, 2026" / "28. Oktober 2026" in the visitor's language. */
|
|
210
|
+
declare function formatChangelogDate(iso: string, locale?: string): string;
|
|
211
|
+
/**
|
|
212
|
+
* An entry's details. `html` comes sanitised from SolutionDesk (raw HTML in the
|
|
213
|
+
* team's text is escaped, links and images are allow-listed), so it can be set
|
|
214
|
+
* as HTML directly.
|
|
215
|
+
*/
|
|
216
|
+
declare function ChangelogEntryBody({ html, className }: {
|
|
217
|
+
html: string;
|
|
218
|
+
className?: string;
|
|
219
|
+
}): react.JSX.Element | null;
|
|
220
|
+
interface ChangelogEntryClassNames {
|
|
221
|
+
entry?: string;
|
|
222
|
+
meta?: string;
|
|
223
|
+
date?: string;
|
|
224
|
+
category?: string;
|
|
225
|
+
title?: string;
|
|
226
|
+
summary?: string;
|
|
227
|
+
body?: string;
|
|
228
|
+
}
|
|
229
|
+
/** One entry: date, category, title (linking to the hosted page), summary and details. */
|
|
230
|
+
declare function ChangelogEntryView({ entry, locale, showBody, classNames, titleAs: Title, }: {
|
|
231
|
+
entry: PublicChangelogEntry;
|
|
232
|
+
/** Language of the date and the category name (`de*` → German, else English). */
|
|
233
|
+
locale?: string;
|
|
234
|
+
/** Show the details too (default) or only title and summary. */
|
|
235
|
+
showBody?: boolean;
|
|
236
|
+
classNames?: ChangelogEntryClassNames;
|
|
237
|
+
titleAs?: "h2" | "h3" | "h4";
|
|
238
|
+
}): react.JSX.Element;
|
|
239
|
+
interface ChangelogFeedProps extends UseChangelogOptions {
|
|
240
|
+
/** Language of dates, categories and the few fixed words. */
|
|
241
|
+
locale?: string;
|
|
242
|
+
/** Show each entry's details (default) or only title and summary. */
|
|
243
|
+
showBody?: boolean;
|
|
244
|
+
className?: string;
|
|
245
|
+
entryClassNames?: ChangelogEntryClassNames;
|
|
246
|
+
/** Render an entry yourself instead of {@link ChangelogEntryView}. */
|
|
247
|
+
renderEntry?: (entry: PublicChangelogEntry) => ReactNode;
|
|
248
|
+
/** Shown while the first page loads. */
|
|
249
|
+
loading?: ReactNode;
|
|
250
|
+
/** Shown when there are no entries yet. */
|
|
251
|
+
empty?: ReactNode;
|
|
252
|
+
/** Shown when the changelog can't be loaded (default: nothing). */
|
|
253
|
+
fallback?: ReactNode;
|
|
254
|
+
/** The "load older" button's text (default "Older updates" / "Ältere Neuigkeiten"). */
|
|
255
|
+
moreLabel?: string;
|
|
256
|
+
moreClassName?: string;
|
|
257
|
+
}
|
|
258
|
+
/** A full changelog feed: the published entries, newest first, with "load older". */
|
|
259
|
+
declare function ChangelogFeed(props: ChangelogFeedProps): react.JSX.Element;
|
|
260
|
+
interface WhatsNewClassNames {
|
|
261
|
+
root?: string;
|
|
262
|
+
button?: string;
|
|
263
|
+
badge?: string;
|
|
264
|
+
panel?: string;
|
|
265
|
+
header?: string;
|
|
266
|
+
list?: string;
|
|
267
|
+
footer?: string;
|
|
268
|
+
}
|
|
269
|
+
interface WhatsNewProps extends UseChangelogOptions {
|
|
270
|
+
locale?: string;
|
|
271
|
+
/** The button's content (default: the team's label, else "What's new"). */
|
|
272
|
+
label?: ReactNode;
|
|
273
|
+
/** The panel's heading (default: the team's title, else "What's new"). */
|
|
274
|
+
title?: ReactNode;
|
|
275
|
+
classNames?: WhatsNewClassNames;
|
|
276
|
+
entryClassNames?: ChangelogEntryClassNames;
|
|
277
|
+
/** Render the badge yourself (default: the number in a `<span>`). */
|
|
278
|
+
renderBadge?: (unseen: number) => ReactNode;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A "What's new" button for your app's header or menu: shows how many entries
|
|
282
|
+
* the visitor hasn't seen and opens a panel with the latest ones (title and
|
|
283
|
+
* summary, linking to the hosted page). Closes on Escape and outside clicks.
|
|
284
|
+
* Renders nothing when the changelog can't be loaded.
|
|
285
|
+
*/
|
|
286
|
+
declare function WhatsNew(props: WhatsNewProps): react.JSX.Element | null;
|
|
287
|
+
|
|
288
|
+
export { ChangelogEntryBody, type ChangelogEntryClassNames, ChangelogEntryView, ChangelogFeed, type ChangelogFeedProps, type UseChangelogOptions, type UseChangelogResult, WhatsNew, type WhatsNewClassNames, type WhatsNewProps, formatChangelogDate, useChangelog };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The answers of the public changelog API (`GET /v1/changelog/{workspace}/…`).
|
|
6
|
+
* Written by hand so the package has no dependencies; a drift test in the
|
|
7
|
+
* SolutionDesk repository keeps them identical to the API's own contracts.
|
|
8
|
+
*/
|
|
9
|
+
type ChangelogCategory = "new" | "improved" | "fixed" | "announcement";
|
|
10
|
+
type ChangelogWidgetPosition = "bottom-right" | "bottom-left";
|
|
11
|
+
/** A published entry. */
|
|
12
|
+
interface PublicChangelogEntry {
|
|
13
|
+
slug: string;
|
|
14
|
+
title: string;
|
|
15
|
+
/** One or two sentences — what a "What's new" panel shows. May be empty. */
|
|
16
|
+
summary: string;
|
|
17
|
+
category: ChangelogCategory;
|
|
18
|
+
/** ISO 8601. The list is sorted by it, newest first. */
|
|
19
|
+
publishedAt: string;
|
|
20
|
+
/** The details as the team wrote them (Markdown). */
|
|
21
|
+
markdown: string;
|
|
22
|
+
/**
|
|
23
|
+
* The details as safe HTML: raw HTML in the text is escaped, links are only
|
|
24
|
+
* http(s)/mailto/relative, images only from https addresses. Ready for
|
|
25
|
+
* `innerHTML` / `dangerouslySetInnerHTML`. Empty when there are no details.
|
|
26
|
+
*/
|
|
27
|
+
html: string;
|
|
28
|
+
/** The entry on the workspace's hosted changelog page. */
|
|
29
|
+
url: string;
|
|
30
|
+
}
|
|
31
|
+
/** `GET /v1/changelog/{workspace}` — settings and one page of entries. */
|
|
32
|
+
interface PublicChangelogPayload {
|
|
33
|
+
/** The workspace as the hosted page shows it; `logoUrl` is relative to the API. */
|
|
34
|
+
workspace: {
|
|
35
|
+
name: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
logoUrl: string | null;
|
|
38
|
+
};
|
|
39
|
+
/** The heading the team chose; empty ⇒ use your own ("What's new"). */
|
|
40
|
+
title: string;
|
|
41
|
+
/** The team's settings for the "What's new" button (empty label ⇒ your own). */
|
|
42
|
+
widget: {
|
|
43
|
+
label: string;
|
|
44
|
+
position: ChangelogWidgetPosition;
|
|
45
|
+
};
|
|
46
|
+
/** The hosted changelog page. */
|
|
47
|
+
pageUrl: string;
|
|
48
|
+
entries: PublicChangelogEntry[];
|
|
49
|
+
/** Pass as `cursor` for the next page; null on the last one. */
|
|
50
|
+
nextCursor: string | null;
|
|
51
|
+
}
|
|
52
|
+
/** `GET /v1/changelog/{workspace}/entries/{slug}`. */
|
|
53
|
+
interface PublicChangelogEntryPayload {
|
|
54
|
+
entry: PublicChangelogEntry;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The response parts the SDK reads — what every `fetch` implementation returns. */
|
|
58
|
+
interface FetchResponseLike {
|
|
59
|
+
readonly status: number;
|
|
60
|
+
readonly ok: boolean;
|
|
61
|
+
readonly headers: {
|
|
62
|
+
get(name: string): string | null;
|
|
63
|
+
};
|
|
64
|
+
text(): Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
/** What the SDK passes to `fetch` (plus whatever you set in `fetchOptions`). */
|
|
67
|
+
interface FetchInitLike {
|
|
68
|
+
method: string;
|
|
69
|
+
headers: Record<string, string>;
|
|
70
|
+
body?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A `fetch` implementation. The global `fetch` fits, so do `undici`, `node-fetch`
|
|
74
|
+
* and wrappers (e.g. one that adds tracing). Declared bivariantly so a fetch with
|
|
75
|
+
* a richer `init` type is accepted.
|
|
76
|
+
*/
|
|
77
|
+
type FetchLike = {
|
|
78
|
+
bivarianceHack(input: string, init?: FetchInitLike): Promise<FetchResponseLike>;
|
|
79
|
+
}["bivarianceHack"];
|
|
80
|
+
/** A response kept for `If-None-Match` revalidation. */
|
|
81
|
+
interface CachedResponse {
|
|
82
|
+
etag: string;
|
|
83
|
+
body: unknown;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Where the client keeps answers for ETag revalidation. Any `Map` fits; bring
|
|
87
|
+
* your own to share it between clients or to bound it differently.
|
|
88
|
+
*/
|
|
89
|
+
interface EtagCache {
|
|
90
|
+
get(key: string): CachedResponse | undefined;
|
|
91
|
+
set(key: string, value: CachedResponse): unknown;
|
|
92
|
+
}
|
|
93
|
+
/** Options every client factory takes. */
|
|
94
|
+
interface BaseClientOptions {
|
|
95
|
+
/** The API origin, without `/v1`. Defaults to `https://api.solutiondesk.io`. */
|
|
96
|
+
baseUrl?: string;
|
|
97
|
+
/** A `fetch` implementation; defaults to the global one. */
|
|
98
|
+
fetch?: FetchLike;
|
|
99
|
+
/** Headers sent with every request (e.g. `accept-language` forwarded from your visitor). */
|
|
100
|
+
headers?: Record<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Revalidate with `If-None-Match`: the client remembers each answer's `ETag`
|
|
103
|
+
* and, when the API answers `304 Not Modified`, returns the remembered body.
|
|
104
|
+
* `true` keeps up to 500 answers in memory; or pass your own {@link EtagCache}.
|
|
105
|
+
* Off by default (your framework's fetch cache may already do this).
|
|
106
|
+
*/
|
|
107
|
+
etagCache?: boolean | EtagCache;
|
|
108
|
+
}
|
|
109
|
+
/** Per-call options. */
|
|
110
|
+
interface RequestOptions {
|
|
111
|
+
/** Cancels the request (an `AbortSignal`). */
|
|
112
|
+
signal?: unknown;
|
|
113
|
+
/** Extra headers for this request. */
|
|
114
|
+
headers?: Record<string, string>;
|
|
115
|
+
/**
|
|
116
|
+
* Extra `fetch` options for this request, passed through unchanged — e.g.
|
|
117
|
+
* Next.js `{ next: { revalidate: 60, tags: ["legal"] } }` or `{ cache: "no-store" }`.
|
|
118
|
+
*/
|
|
119
|
+
fetchOptions?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ChangelogClientOptions extends BaseClientOptions {
|
|
123
|
+
/** Your workspace's slug — the `acme` in `https://api.solutiondesk.io/v1/changelog/acme`. */
|
|
124
|
+
workspace: string;
|
|
125
|
+
}
|
|
126
|
+
interface ListEntriesParams {
|
|
127
|
+
/** Only entries of this category. */
|
|
128
|
+
category?: ChangelogCategory;
|
|
129
|
+
/** Page size, 1–50 (default 20). */
|
|
130
|
+
limit?: number;
|
|
131
|
+
/** `nextCursor` of the previous page. */
|
|
132
|
+
cursor?: string | null;
|
|
133
|
+
}
|
|
134
|
+
interface ChangelogClient {
|
|
135
|
+
/** The workspace this client reads. */
|
|
136
|
+
readonly workspace: string;
|
|
137
|
+
/**
|
|
138
|
+
* The team's settings and one page of published entries, newest first
|
|
139
|
+
* (`GET /v1/changelog/{workspace}`). Throws a `SolutionDeskError` with status
|
|
140
|
+
* 404 when the workspace doesn't exist or its changelog is turned off.
|
|
141
|
+
*/
|
|
142
|
+
list(params?: ListEntriesParams, options?: RequestOptions): Promise<PublicChangelogPayload>;
|
|
143
|
+
/** Every published entry, page by page — e.g. for a sitemap, an RSS feed or a static build. */
|
|
144
|
+
allEntries(params?: Omit<ListEntriesParams, "cursor">, options?: RequestOptions): AsyncGenerator<PublicChangelogEntry, void, undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* One published entry (`GET /v1/changelog/{workspace}/entries/{slug}`).
|
|
147
|
+
* Throws a `SolutionDeskError` with status 404 for an unknown or unpublished entry.
|
|
148
|
+
*/
|
|
149
|
+
getEntry(slug: string, options?: RequestOptions): Promise<PublicChangelogEntryPayload>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Where "last seen" is kept — `window.localStorage`, `sessionStorage` or your own. */
|
|
153
|
+
interface SeenStorage {
|
|
154
|
+
getItem(key: string): string | null;
|
|
155
|
+
setItem(key: string, value: string): void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface UseChangelogOptions extends Partial<Omit<ChangelogClientOptions, "workspace">> {
|
|
159
|
+
/** Your workspace's slug. */
|
|
160
|
+
workspace: string;
|
|
161
|
+
/** Use this client instead of making one (e.g. one shared by your whole app). */
|
|
162
|
+
client?: ChangelogClient;
|
|
163
|
+
/** Entries per page (1–50, default 10). */
|
|
164
|
+
limit?: number;
|
|
165
|
+
/** Only entries of this category. */
|
|
166
|
+
category?: ChangelogCategory;
|
|
167
|
+
/**
|
|
168
|
+
* Count entries the visitor hasn't seen yet (`unseen`), kept in `storage`.
|
|
169
|
+
* Default true. On the first visit nothing counts: the newest entry becomes
|
|
170
|
+
* the starting point.
|
|
171
|
+
*/
|
|
172
|
+
trackSeen?: boolean;
|
|
173
|
+
/** Where "last seen" lives (default the browser's `localStorage`). */
|
|
174
|
+
storage?: SeenStorage | null;
|
|
175
|
+
/** Server-rendered or cached first page: shown at once, refreshed in the background. */
|
|
176
|
+
initialData?: PublicChangelogPayload;
|
|
177
|
+
}
|
|
178
|
+
interface UseChangelogResult {
|
|
179
|
+
status: "loading" | "ready" | "error";
|
|
180
|
+
/** Every entry loaded so far, newest first. */
|
|
181
|
+
entries: PublicChangelogEntry[];
|
|
182
|
+
/** The last page's answer (title, button settings, page address), or null while loading. */
|
|
183
|
+
data: PublicChangelogPayload | null;
|
|
184
|
+
/**
|
|
185
|
+
* Why loading failed (a `SolutionDeskError`; 404 = unknown workspace or changelog off).
|
|
186
|
+
* A failed {@link UseChangelogResult.loadMore} sets it too, but keeps `status` "ready".
|
|
187
|
+
*/
|
|
188
|
+
error: unknown;
|
|
189
|
+
/** Entries newer than what the visitor last saw. */
|
|
190
|
+
unseen: number;
|
|
191
|
+
/** Remember everything loaded as seen (call it when the visitor opens your panel). */
|
|
192
|
+
markAllSeen(): void;
|
|
193
|
+
/** Whether there are older entries to load. */
|
|
194
|
+
hasMore: boolean;
|
|
195
|
+
/** Whether an older page is loading right now. */
|
|
196
|
+
loadingMore: boolean;
|
|
197
|
+
/** Load the next (older) page and append it. Never rejects; failures land in `error`. */
|
|
198
|
+
loadMore(): Promise<void>;
|
|
199
|
+
/** Load the first page again. */
|
|
200
|
+
reload(): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The published changelog entries of a workspace, with an unseen count for a
|
|
204
|
+
* "new" badge. Fetches in the browser (no key needed); pass `initialData` from
|
|
205
|
+
* your server for a first paint without loading.
|
|
206
|
+
*/
|
|
207
|
+
declare function useChangelog(options: UseChangelogOptions): UseChangelogResult;
|
|
208
|
+
|
|
209
|
+
/** "October 28, 2026" / "28. Oktober 2026" in the visitor's language. */
|
|
210
|
+
declare function formatChangelogDate(iso: string, locale?: string): string;
|
|
211
|
+
/**
|
|
212
|
+
* An entry's details. `html` comes sanitised from SolutionDesk (raw HTML in the
|
|
213
|
+
* team's text is escaped, links and images are allow-listed), so it can be set
|
|
214
|
+
* as HTML directly.
|
|
215
|
+
*/
|
|
216
|
+
declare function ChangelogEntryBody({ html, className }: {
|
|
217
|
+
html: string;
|
|
218
|
+
className?: string;
|
|
219
|
+
}): react.JSX.Element | null;
|
|
220
|
+
interface ChangelogEntryClassNames {
|
|
221
|
+
entry?: string;
|
|
222
|
+
meta?: string;
|
|
223
|
+
date?: string;
|
|
224
|
+
category?: string;
|
|
225
|
+
title?: string;
|
|
226
|
+
summary?: string;
|
|
227
|
+
body?: string;
|
|
228
|
+
}
|
|
229
|
+
/** One entry: date, category, title (linking to the hosted page), summary and details. */
|
|
230
|
+
declare function ChangelogEntryView({ entry, locale, showBody, classNames, titleAs: Title, }: {
|
|
231
|
+
entry: PublicChangelogEntry;
|
|
232
|
+
/** Language of the date and the category name (`de*` → German, else English). */
|
|
233
|
+
locale?: string;
|
|
234
|
+
/** Show the details too (default) or only title and summary. */
|
|
235
|
+
showBody?: boolean;
|
|
236
|
+
classNames?: ChangelogEntryClassNames;
|
|
237
|
+
titleAs?: "h2" | "h3" | "h4";
|
|
238
|
+
}): react.JSX.Element;
|
|
239
|
+
interface ChangelogFeedProps extends UseChangelogOptions {
|
|
240
|
+
/** Language of dates, categories and the few fixed words. */
|
|
241
|
+
locale?: string;
|
|
242
|
+
/** Show each entry's details (default) or only title and summary. */
|
|
243
|
+
showBody?: boolean;
|
|
244
|
+
className?: string;
|
|
245
|
+
entryClassNames?: ChangelogEntryClassNames;
|
|
246
|
+
/** Render an entry yourself instead of {@link ChangelogEntryView}. */
|
|
247
|
+
renderEntry?: (entry: PublicChangelogEntry) => ReactNode;
|
|
248
|
+
/** Shown while the first page loads. */
|
|
249
|
+
loading?: ReactNode;
|
|
250
|
+
/** Shown when there are no entries yet. */
|
|
251
|
+
empty?: ReactNode;
|
|
252
|
+
/** Shown when the changelog can't be loaded (default: nothing). */
|
|
253
|
+
fallback?: ReactNode;
|
|
254
|
+
/** The "load older" button's text (default "Older updates" / "Ältere Neuigkeiten"). */
|
|
255
|
+
moreLabel?: string;
|
|
256
|
+
moreClassName?: string;
|
|
257
|
+
}
|
|
258
|
+
/** A full changelog feed: the published entries, newest first, with "load older". */
|
|
259
|
+
declare function ChangelogFeed(props: ChangelogFeedProps): react.JSX.Element;
|
|
260
|
+
interface WhatsNewClassNames {
|
|
261
|
+
root?: string;
|
|
262
|
+
button?: string;
|
|
263
|
+
badge?: string;
|
|
264
|
+
panel?: string;
|
|
265
|
+
header?: string;
|
|
266
|
+
list?: string;
|
|
267
|
+
footer?: string;
|
|
268
|
+
}
|
|
269
|
+
interface WhatsNewProps extends UseChangelogOptions {
|
|
270
|
+
locale?: string;
|
|
271
|
+
/** The button's content (default: the team's label, else "What's new"). */
|
|
272
|
+
label?: ReactNode;
|
|
273
|
+
/** The panel's heading (default: the team's title, else "What's new"). */
|
|
274
|
+
title?: ReactNode;
|
|
275
|
+
classNames?: WhatsNewClassNames;
|
|
276
|
+
entryClassNames?: ChangelogEntryClassNames;
|
|
277
|
+
/** Render the badge yourself (default: the number in a `<span>`). */
|
|
278
|
+
renderBadge?: (unseen: number) => ReactNode;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A "What's new" button for your app's header or menu: shows how many entries
|
|
282
|
+
* the visitor hasn't seen and opens a panel with the latest ones (title and
|
|
283
|
+
* summary, linking to the hosted page). Closes on Escape and outside clicks.
|
|
284
|
+
* Renders nothing when the changelog can't be loaded.
|
|
285
|
+
*/
|
|
286
|
+
declare function WhatsNew(props: WhatsNewProps): react.JSX.Element | null;
|
|
287
|
+
|
|
288
|
+
export { ChangelogEntryBody, type ChangelogEntryClassNames, ChangelogEntryView, ChangelogFeed, type ChangelogFeedProps, type UseChangelogOptions, type UseChangelogResult, WhatsNew, type WhatsNewClassNames, type WhatsNewProps, formatChangelogDate, useChangelog };
|