g-comments 0.0.0-stage → 0.1.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/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +354 -2
- package/dist/chunk-6E3M3PQN.js +3056 -0
- package/dist/chunk-6E3M3PQN.js.map +1 -0
- package/dist/chunk-X6RAR374.cjs +3077 -0
- package/dist/chunk-X6RAR374.cjs.map +1 -0
- package/dist/element.d.ts +770 -0
- package/dist/element.js +1226 -0
- package/dist/element.js.map +1 -0
- package/dist/index.cjs +68 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/messages-BlBkT6Kr.d.cts +649 -0
- package/dist/messages-BlBkT6Kr.d.ts +649 -0
- package/dist/react.cjs +2557 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +320 -0
- package/dist/react.d.ts +320 -0
- package/dist/react.js +2549 -0
- package/dist/react.js.map +1 -0
- package/package.json +89 -3
- package/styles/styles.css +1145 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/** Public sort names (`DiscourseComments::Sort`). */
|
|
2
|
+
type Sort = "best" | "hot" | "newest" | "oldest";
|
|
3
|
+
/** Report reasons accepted by `POST …/comments/:id/flag` (`Comment::Flag::FLAG_TYPES`). */
|
|
4
|
+
type FlagType = "off_topic" | "inappropriate" | "spam" | "illegal" | "notify_moderators";
|
|
5
|
+
/** `discourse_comments_thread_creation` setting values. */
|
|
6
|
+
type ThreadCreation = "first_view" | "first_comment";
|
|
7
|
+
/** `CommenterSerializer`: the signed-in reader, mention results, exchange/me responses. */
|
|
8
|
+
interface Commenter {
|
|
9
|
+
id: number;
|
|
10
|
+
username: string;
|
|
11
|
+
name: string | null;
|
|
12
|
+
avatar_url: string;
|
|
13
|
+
title: string | null;
|
|
14
|
+
staff: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** `ClientConfig#auth_providers` entry. */
|
|
17
|
+
interface AuthProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
title: string;
|
|
20
|
+
}
|
|
21
|
+
interface ForumInfo {
|
|
22
|
+
name: string;
|
|
23
|
+
base_url: string;
|
|
24
|
+
locale: string;
|
|
25
|
+
}
|
|
26
|
+
interface Features {
|
|
27
|
+
nested: boolean;
|
|
28
|
+
hot_sort: boolean;
|
|
29
|
+
mentions: boolean;
|
|
30
|
+
uploads: boolean;
|
|
31
|
+
/** `null` when the reactions plugin is not installed. */
|
|
32
|
+
reactions: boolean | null;
|
|
33
|
+
}
|
|
34
|
+
interface Limits {
|
|
35
|
+
min_post_length: number;
|
|
36
|
+
max_post_length: number;
|
|
37
|
+
roots_per_page: number;
|
|
38
|
+
children_per_page: number;
|
|
39
|
+
max_image_size_kb: number;
|
|
40
|
+
/** Pipe-separated list, as stored in the `authorized_extensions` site setting. */
|
|
41
|
+
authorized_extensions: string;
|
|
42
|
+
}
|
|
43
|
+
/** `ConfigSerializer` (`GET /comments/config`). */
|
|
44
|
+
interface Config {
|
|
45
|
+
forum: ForumInfo;
|
|
46
|
+
features: Features;
|
|
47
|
+
limits: Limits;
|
|
48
|
+
auth_providers: AuthProvider[];
|
|
49
|
+
sorts: Sort[];
|
|
50
|
+
default_sort: Sort;
|
|
51
|
+
thread_creation: ThreadCreation;
|
|
52
|
+
guidelines_url: string | null;
|
|
53
|
+
scope_version: number;
|
|
54
|
+
/** Present only when the request carried a valid user API key. */
|
|
55
|
+
current_user?: Commenter;
|
|
56
|
+
/** Set when the forum served the request anonymously under load; the key is still valid. */
|
|
57
|
+
degraded?: true;
|
|
58
|
+
}
|
|
59
|
+
/** `ThreadSerializer`: a page's thread once its topic exists. */
|
|
60
|
+
interface Thread {
|
|
61
|
+
/** Opaque handle; the topic id as a string. */
|
|
62
|
+
id: string;
|
|
63
|
+
topic_id: number;
|
|
64
|
+
key: string;
|
|
65
|
+
url: string;
|
|
66
|
+
title: string | null;
|
|
67
|
+
count: number;
|
|
68
|
+
closed: boolean;
|
|
69
|
+
listed: boolean;
|
|
70
|
+
can_comment: boolean;
|
|
71
|
+
nested: boolean;
|
|
72
|
+
sorts: Sort[];
|
|
73
|
+
sort: Sort;
|
|
74
|
+
pinned_ids: number[];
|
|
75
|
+
realtime: "live" | "poll";
|
|
76
|
+
message_bus_last_id: number;
|
|
77
|
+
forum_url: string;
|
|
78
|
+
}
|
|
79
|
+
/** `VirtualThreadSerializer`: a page whose topic does not exist yet. */
|
|
80
|
+
interface VirtualThread {
|
|
81
|
+
id: `v:${string}`;
|
|
82
|
+
topic_id: null;
|
|
83
|
+
key: string;
|
|
84
|
+
url: string;
|
|
85
|
+
title: string;
|
|
86
|
+
count: 0;
|
|
87
|
+
closed: false;
|
|
88
|
+
listed: false;
|
|
89
|
+
can_comment: boolean;
|
|
90
|
+
nested: boolean;
|
|
91
|
+
sorts: Sort[];
|
|
92
|
+
sort: Sort;
|
|
93
|
+
pinned_ids: [];
|
|
94
|
+
realtime: "pending";
|
|
95
|
+
/** MessageBus channel where the forum announces `{ topic_id }` on creation. */
|
|
96
|
+
pending_channel: string;
|
|
97
|
+
message_bus_last_id: null;
|
|
98
|
+
forum_url: null;
|
|
99
|
+
}
|
|
100
|
+
type AnyThread = Thread | VirtualThread;
|
|
101
|
+
/** `CommentSerializer#author`; `null` on deleted placeholders. */
|
|
102
|
+
interface CommentAuthor {
|
|
103
|
+
id: number | null;
|
|
104
|
+
username: string | null;
|
|
105
|
+
name: string | null;
|
|
106
|
+
avatar_url: string | null;
|
|
107
|
+
title: string | null;
|
|
108
|
+
staff: boolean;
|
|
109
|
+
}
|
|
110
|
+
interface CommentReplyTo {
|
|
111
|
+
number: number;
|
|
112
|
+
username: string | null;
|
|
113
|
+
}
|
|
114
|
+
interface CommentLikes {
|
|
115
|
+
count: number;
|
|
116
|
+
liked: boolean;
|
|
117
|
+
can_like: boolean;
|
|
118
|
+
}
|
|
119
|
+
interface CommentPermissions {
|
|
120
|
+
edit: boolean;
|
|
121
|
+
delete: boolean;
|
|
122
|
+
flag: boolean;
|
|
123
|
+
pin: boolean;
|
|
124
|
+
}
|
|
125
|
+
/** `CommentSerializer`. Treat as immutable: the SDK replaces nodes instead of mutating them. */
|
|
126
|
+
interface Comment {
|
|
127
|
+
id: number;
|
|
128
|
+
number: number;
|
|
129
|
+
author: CommentAuthor | null;
|
|
130
|
+
/** Server-sanitized cooked HTML with absolute links; `""` for deleted placeholders. */
|
|
131
|
+
html: string;
|
|
132
|
+
created_at: string;
|
|
133
|
+
updated_at: string;
|
|
134
|
+
edited: boolean;
|
|
135
|
+
/** `null` for top-level comments. Carries the parent's post number, not its id. */
|
|
136
|
+
reply_to: CommentReplyTo | null;
|
|
137
|
+
children: Comment[];
|
|
138
|
+
direct_reply_count: number;
|
|
139
|
+
total_descendant_count: number;
|
|
140
|
+
pinned: boolean;
|
|
141
|
+
likes: CommentLikes;
|
|
142
|
+
can: CommentPermissions;
|
|
143
|
+
pending: boolean;
|
|
144
|
+
deleted: boolean;
|
|
145
|
+
hidden: boolean;
|
|
146
|
+
url: string | null;
|
|
147
|
+
}
|
|
148
|
+
/** `{ pending }` from `POST …/comments` when the comment went to the review queue. */
|
|
149
|
+
interface Pending {
|
|
150
|
+
reviewable_id: number | null;
|
|
151
|
+
message: string | null;
|
|
152
|
+
pending_count: number | null;
|
|
153
|
+
}
|
|
154
|
+
/** `UploadSerializer` as returned by `POST …/attachments`. */
|
|
155
|
+
interface Upload {
|
|
156
|
+
id: number;
|
|
157
|
+
url: string;
|
|
158
|
+
original_filename: string;
|
|
159
|
+
filesize: number;
|
|
160
|
+
width: number | null;
|
|
161
|
+
height: number | null;
|
|
162
|
+
thumbnail_width: number | null;
|
|
163
|
+
thumbnail_height: number | null;
|
|
164
|
+
extension: string;
|
|
165
|
+
short_url: string;
|
|
166
|
+
short_path: string;
|
|
167
|
+
retain_hours: number | null;
|
|
168
|
+
human_filesize: string;
|
|
169
|
+
dominant_color: string | null;
|
|
170
|
+
}
|
|
171
|
+
/** Comment counts keyed by encoded ref (`url:…`, `id:<host>/<identifier>`, `t:<topic_id>`). */
|
|
172
|
+
type Counts = Record<string, number>;
|
|
173
|
+
type CountRef = {
|
|
174
|
+
url: string;
|
|
175
|
+
} | {
|
|
176
|
+
identifier: string;
|
|
177
|
+
host?: string;
|
|
178
|
+
} | {
|
|
179
|
+
topicId: number;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
type CommentsErrorType = "invalid_key" | "not_logged_in" | "invalid_access" | "invalid_parameters" | "not_found" | "rate_limit" | "thread_conflict" | "page_not_allowed" | "invalid_url" | "no_category" | "thread_not_created" | "invalid_grant" | "user_not_allowed" | "submission_rejected" | "not_a_root_comment" | "pin_limit_reached" | "cancelled" | "storage_unavailable" | "unreachable" | "unknown";
|
|
183
|
+
interface CommentsErrorOptions {
|
|
184
|
+
status?: number;
|
|
185
|
+
errors?: string[];
|
|
186
|
+
extras?: Record<string, unknown>;
|
|
187
|
+
waitSeconds?: number;
|
|
188
|
+
hint?: string;
|
|
189
|
+
serverType?: string;
|
|
190
|
+
cause?: unknown;
|
|
191
|
+
}
|
|
192
|
+
declare class CommentsError extends Error {
|
|
193
|
+
readonly name = "CommentsError";
|
|
194
|
+
readonly type: CommentsErrorType;
|
|
195
|
+
/** HTTP status, or 0 when no response was received. */
|
|
196
|
+
readonly status: number;
|
|
197
|
+
/** Human-readable messages from the server, safe to show inline. */
|
|
198
|
+
readonly errors: string[];
|
|
199
|
+
readonly extras: Record<string, unknown> | undefined;
|
|
200
|
+
/** Seconds until a rate-limited action may be retried. */
|
|
201
|
+
readonly waitSeconds: number | undefined;
|
|
202
|
+
/** Developer-facing explanation of the probable cause (setup problems). */
|
|
203
|
+
readonly hint: string | undefined;
|
|
204
|
+
/** The raw `error_type` from the server, if it sent one. */
|
|
205
|
+
readonly serverType: string | undefined;
|
|
206
|
+
constructor(type: CommentsErrorType, message: string, options?: CommentsErrorOptions);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** What the widget was doing when an `error` event fired. */
|
|
210
|
+
type CommentsAction = "open" | "load" | "loadMore" | "loadReplies" | "context" | "post" | "edit" | "delete" | "like" | "flag" | "pin" | "upload" | "preview" | "signIn";
|
|
211
|
+
/** Delivered to `<DiscourseComments onEvent>`. */
|
|
212
|
+
type CommentsEvent = {
|
|
213
|
+
type: "comment.posted";
|
|
214
|
+
threadId: string;
|
|
215
|
+
comment: Comment;
|
|
216
|
+
}
|
|
217
|
+
/** The comment went to the review queue instead of being published. */
|
|
218
|
+
| {
|
|
219
|
+
type: "comment.pending";
|
|
220
|
+
threadId: string;
|
|
221
|
+
pending: Pending;
|
|
222
|
+
} | {
|
|
223
|
+
type: "comment.liked";
|
|
224
|
+
threadId: string;
|
|
225
|
+
comment: Comment;
|
|
226
|
+
liked: boolean;
|
|
227
|
+
} | {
|
|
228
|
+
type: "auth.signedIn";
|
|
229
|
+
user: Commenter;
|
|
230
|
+
}
|
|
231
|
+
/** `expired` is true when the forum ended the session (the key was revoked or expired). */
|
|
232
|
+
| {
|
|
233
|
+
type: "auth.signedOut";
|
|
234
|
+
expired: boolean;
|
|
235
|
+
} | {
|
|
236
|
+
type: "error";
|
|
237
|
+
action: CommentsAction;
|
|
238
|
+
error: CommentsError;
|
|
239
|
+
threadId?: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
type AuthStatus = "anonymous" | "signing-in" | "signed-in";
|
|
243
|
+
interface AuthState {
|
|
244
|
+
readonly status: AuthStatus;
|
|
245
|
+
readonly user: Commenter | null;
|
|
246
|
+
/** The forum served a request anonymously under load. The key is kept; treat the UI as read-only. */
|
|
247
|
+
readonly degraded: boolean;
|
|
248
|
+
/** Why the last sign-in failed or the session ended (`invalid_key` means it expired). */
|
|
249
|
+
readonly error: CommentsError | null;
|
|
250
|
+
}
|
|
251
|
+
interface SignInOptions {
|
|
252
|
+
/** An `auth_providers[].name` to go straight to that provider. */
|
|
253
|
+
provider?: string;
|
|
254
|
+
/** Defaults to `popup`; a blocked popup falls back to `redirect`. */
|
|
255
|
+
mode?: "popup" | "redirect";
|
|
256
|
+
}
|
|
257
|
+
interface CommentsAuth {
|
|
258
|
+
readonly status: AuthStatus;
|
|
259
|
+
readonly user: Commenter | null;
|
|
260
|
+
/** From `config().auth_providers`; empty until the config has loaded. */
|
|
261
|
+
readonly providers: readonly AuthProvider[];
|
|
262
|
+
/** Per-browser id the forum's user API key is bound to. */
|
|
263
|
+
readonly clientId: string;
|
|
264
|
+
/** Immutable, referentially stable while nothing changes. */
|
|
265
|
+
state(): AuthState;
|
|
266
|
+
onChange(listener: (state: AuthState) => void): () => void;
|
|
267
|
+
/** Resolves with the user (popup) or `null` once a redirect has started. */
|
|
268
|
+
signIn(options?: SignInOptions): Promise<Commenter | null>;
|
|
269
|
+
/** Abandons a pending popup sign-in. */
|
|
270
|
+
cancelSignIn(): void;
|
|
271
|
+
signOut(): Promise<void>;
|
|
272
|
+
/** Completes a redirect sign-in from `#gc_code=…&gc_nonce=…`; runs once per page load. */
|
|
273
|
+
handleRedirect(): Promise<Commenter | null>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface ComposerTiming {
|
|
277
|
+
typingDurationMsecs: number;
|
|
278
|
+
composerOpenDurationMsecs: number;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Measures what the forum's fast-typer check needs (`typing_duration_msecs`,
|
|
282
|
+
* `composer_open_duration_msecs`). Without it, a new account's first comments
|
|
283
|
+
* land in the review queue.
|
|
284
|
+
*/
|
|
285
|
+
interface ComposerTimer {
|
|
286
|
+
readonly started: boolean;
|
|
287
|
+
/** Call when the composer opens or gains focus; repeated calls are no-ops. */
|
|
288
|
+
start(): void;
|
|
289
|
+
/** Call on every input event. */
|
|
290
|
+
typing(): void;
|
|
291
|
+
measure(): ComposerTiming;
|
|
292
|
+
/** Returns the measurement and resets the timer. */
|
|
293
|
+
stop(): ComposerTiming;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
type ThreadStatus = "idle" | "loading" | "ready" | "error";
|
|
297
|
+
/** How updates arrive: MessageBus, the interval fallback, or not at all. */
|
|
298
|
+
type ConnectionMode = "live" | "interval" | "off";
|
|
299
|
+
interface BranchState {
|
|
300
|
+
/** Last children page loaded for this comment (-1 = only the preloaded ones). */
|
|
301
|
+
readonly page: number;
|
|
302
|
+
readonly loading: boolean;
|
|
303
|
+
readonly hasMore: boolean;
|
|
304
|
+
}
|
|
305
|
+
interface ThreadSnapshot {
|
|
306
|
+
readonly thread: AnyThread;
|
|
307
|
+
/** True until the page's topic exists (`thread.id` starts with `v:`). */
|
|
308
|
+
readonly virtual: boolean;
|
|
309
|
+
/** Top-level comments with their loaded replies nested in `children`. */
|
|
310
|
+
readonly comments: readonly Comment[];
|
|
311
|
+
readonly sort: Sort;
|
|
312
|
+
readonly status: ThreadStatus;
|
|
313
|
+
readonly error: CommentsError | null;
|
|
314
|
+
readonly page: number;
|
|
315
|
+
readonly hasMore: boolean;
|
|
316
|
+
readonly loadingMore: boolean;
|
|
317
|
+
/** Top-level comments that arrived live and wait for `showPending()`. */
|
|
318
|
+
readonly pendingNew: number;
|
|
319
|
+
readonly branches: Readonly<Record<number, BranchState>>;
|
|
320
|
+
/** The reader's own comments that went to the review queue. */
|
|
321
|
+
readonly awaitingApproval: readonly Pending[];
|
|
322
|
+
readonly connection: ConnectionMode;
|
|
323
|
+
}
|
|
324
|
+
type ThreadEvent = {
|
|
325
|
+
type: "change";
|
|
326
|
+
snapshot: ThreadSnapshot;
|
|
327
|
+
} | {
|
|
328
|
+
type: "comment.posted";
|
|
329
|
+
comment: Comment;
|
|
330
|
+
} | {
|
|
331
|
+
type: "comment.pending";
|
|
332
|
+
pending: Pending;
|
|
333
|
+
} | {
|
|
334
|
+
type: "comment.edited";
|
|
335
|
+
comment: Comment;
|
|
336
|
+
} | {
|
|
337
|
+
type: "comment.removed";
|
|
338
|
+
id: number;
|
|
339
|
+
} | {
|
|
340
|
+
type: "comment.received";
|
|
341
|
+
comment: Comment;
|
|
342
|
+
buffered: boolean;
|
|
343
|
+
} | {
|
|
344
|
+
type: "thread.created";
|
|
345
|
+
thread: Thread;
|
|
346
|
+
};
|
|
347
|
+
interface PostOptions {
|
|
348
|
+
body: string;
|
|
349
|
+
/** Id of the comment being replied to (never a post number). */
|
|
350
|
+
replyTo?: number;
|
|
351
|
+
typingDurationMsecs?: number;
|
|
352
|
+
composerOpenDurationMsecs?: number;
|
|
353
|
+
/** Timer to read when the durations are not passed; defaults to `thread.composer`. */
|
|
354
|
+
composer?: ComposerTimer;
|
|
355
|
+
}
|
|
356
|
+
type PostResult = {
|
|
357
|
+
type: "created";
|
|
358
|
+
comment: Comment;
|
|
359
|
+
} | {
|
|
360
|
+
type: "pending";
|
|
361
|
+
pending: Pending;
|
|
362
|
+
};
|
|
363
|
+
interface FlagOptions {
|
|
364
|
+
type: FlagType;
|
|
365
|
+
message?: string;
|
|
366
|
+
}
|
|
367
|
+
interface CommentContext {
|
|
368
|
+
ancestors: Comment[];
|
|
369
|
+
ancestorsTruncated: boolean;
|
|
370
|
+
comment: Comment;
|
|
371
|
+
}
|
|
372
|
+
interface ThreadHandle {
|
|
373
|
+
/** Current handle; changes from `v:…` to the topic id once the thread is created. */
|
|
374
|
+
readonly id: string;
|
|
375
|
+
/** Default timer for the thread's main composer. */
|
|
376
|
+
readonly composer: ComposerTimer;
|
|
377
|
+
/** Immutable; the same object is returned until something changes. */
|
|
378
|
+
snapshot(): ThreadSnapshot;
|
|
379
|
+
subscribe(listener: (event: ThreadEvent) => void): () => void;
|
|
380
|
+
load(): Promise<void>;
|
|
381
|
+
loadMore(): Promise<void>;
|
|
382
|
+
loadReplies(commentId: number): Promise<void>;
|
|
383
|
+
setSort(sort: Sort): Promise<void>;
|
|
384
|
+
post(options: PostOptions): Promise<PostResult>;
|
|
385
|
+
edit(commentId: number, body: string): Promise<Comment>;
|
|
386
|
+
remove(commentId: number): Promise<void>;
|
|
387
|
+
like(commentId: number): Promise<Comment>;
|
|
388
|
+
unlike(commentId: number): Promise<Comment>;
|
|
389
|
+
flag(commentId: number, options: FlagOptions): Promise<void>;
|
|
390
|
+
pin(commentId: number): Promise<void>;
|
|
391
|
+
unpin(commentId: number): Promise<void>;
|
|
392
|
+
/** Markdown source, for editing. */
|
|
393
|
+
raw(commentId: number): Promise<string>;
|
|
394
|
+
/** Moves live-arrived top-level comments into `comments`. */
|
|
395
|
+
showPending(): void;
|
|
396
|
+
/** Loads a comment with its ancestors into the tree (deep links). */
|
|
397
|
+
context(commentId: number): Promise<CommentContext>;
|
|
398
|
+
dispose(): void;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
interface OpenThreadOptions {
|
|
402
|
+
/** Defaults to `<link rel="canonical">`, else `location.href`. */
|
|
403
|
+
url?: string;
|
|
404
|
+
/** Defaults to `document.title`. */
|
|
405
|
+
title?: string;
|
|
406
|
+
/** Optional stable key, namespaced by the page's canonical host on the forum. */
|
|
407
|
+
identifier?: string;
|
|
408
|
+
/** Initial sort; defaults to the forum's default. */
|
|
409
|
+
sort?: Sort;
|
|
410
|
+
}
|
|
411
|
+
interface UploadOptions {
|
|
412
|
+
filename?: string;
|
|
413
|
+
/** Marks the file as pasted from the clipboard. */
|
|
414
|
+
pasted?: boolean;
|
|
415
|
+
}
|
|
416
|
+
interface CommentsClient {
|
|
417
|
+
/** Normalized forum base URL. */
|
|
418
|
+
readonly forum: string;
|
|
419
|
+
readonly forumOrigin: string;
|
|
420
|
+
readonly locale: string | undefined;
|
|
421
|
+
readonly auth: CommentsAuth;
|
|
422
|
+
readonly threads: {
|
|
423
|
+
/** Resolves (or creates) the page's thread and loads its first page of comments. */
|
|
424
|
+
open(options?: OpenThreadOptions): Promise<ThreadHandle>;
|
|
425
|
+
};
|
|
426
|
+
readonly attachments: {
|
|
427
|
+
upload(threadId: string, file: Blob, options?: UploadOptions): Promise<Upload>;
|
|
428
|
+
};
|
|
429
|
+
/** Cached; refetched after sign-in and sign-out. */
|
|
430
|
+
config(): Promise<Config>;
|
|
431
|
+
/** Batched per tick, at most 50 refs per request. Keys are encoded refs (see `encodeRef`). */
|
|
432
|
+
counts(refs: readonly CountRef[]): Promise<Counts>;
|
|
433
|
+
count(ref: CountRef): Promise<number>;
|
|
434
|
+
/** Cooks markdown into sanitized HTML (signed-in only). */
|
|
435
|
+
preview(body: string): Promise<string>;
|
|
436
|
+
/** User search for @mentions (signed-in only). */
|
|
437
|
+
mentions(query: string, threadId?: string): Promise<Commenter[]>;
|
|
438
|
+
/** Stops realtime, timers and listeners for every thread of this client. */
|
|
439
|
+
dispose(): void;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** `auto` follows `prefers-color-scheme`; `inherit` leaves the colours to the page. */
|
|
443
|
+
type Theme = "auto" | "light" | "dark" | "inherit";
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Where signing in happens: `inline` shows a "Join the conversation" panel,
|
|
447
|
+
* `on-submit` lets anonymous readers write and signs them in when they post,
|
|
448
|
+
* `manual` leaves it to the site (`useCommenter().signIn()`).
|
|
449
|
+
*/
|
|
450
|
+
type SignInMode = "inline" | "on-submit" | "manual";
|
|
451
|
+
|
|
452
|
+
/** How an attribute's string becomes a prop: a list means one of those values. */
|
|
453
|
+
type AttributeType = "string" | "number" | "boolean" | readonly string[];
|
|
454
|
+
interface AttributeSpec {
|
|
455
|
+
/** The camelCase prop (and element property) the attribute feeds. */
|
|
456
|
+
readonly prop: string;
|
|
457
|
+
readonly type: AttributeType;
|
|
458
|
+
}
|
|
459
|
+
/** Attribute name to spec; the keys are the element's `observedAttributes`. */
|
|
460
|
+
type AttributeSchema = Readonly<Record<string, AttributeSpec>>;
|
|
461
|
+
|
|
462
|
+
declare const HTMLElementBase: typeof HTMLElement;
|
|
463
|
+
/**
|
|
464
|
+
* A custom element that renders a React tree into its own shadow root:
|
|
465
|
+
* attributes become props (see `schema`), `gc-*` classes become `::part()`
|
|
466
|
+
* names, and the tree re-renders once per task however many attributes change.
|
|
467
|
+
*/
|
|
468
|
+
declare abstract class ReactHostElement<P extends object> extends HTMLElementBase {
|
|
469
|
+
#private;
|
|
470
|
+
/** Attribute to prop mapping; subclasses override it and call `reflect()`. */
|
|
471
|
+
static readonly schema: AttributeSchema;
|
|
472
|
+
static get observedAttributes(): string[];
|
|
473
|
+
/** Mirrors the schema's props onto the prototype as attribute-backed accessors. */
|
|
474
|
+
static reflect(this: typeof ReactHostElement<object>): void;
|
|
475
|
+
protected constructor(mountTag: string, styles: readonly string[]);
|
|
476
|
+
connectedCallback(): void;
|
|
477
|
+
disconnectedCallback(): void;
|
|
478
|
+
attributeChangedCallback(): void;
|
|
479
|
+
/** Re-renders once the current task ends; a no-op before the element is connected. */
|
|
480
|
+
protected requestRender(): void;
|
|
481
|
+
/** The current attribute values as props. */
|
|
482
|
+
protected props(): P;
|
|
483
|
+
/** The React tree for `props`; `null` renders nothing. */
|
|
484
|
+
protected abstract render(props: P): unknown;
|
|
485
|
+
/** Dispatches `gc:<name>` from the host; it bubbles and crosses the shadow boundary. */
|
|
486
|
+
protected emit(name: string, detail?: unknown): void;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** `<discourse-comments>` attributes and the `<Comments>` props they map to. */
|
|
490
|
+
declare const COMMENTS_ATTRIBUTES: {
|
|
491
|
+
readonly forum: {
|
|
492
|
+
readonly prop: "forum";
|
|
493
|
+
readonly type: "string";
|
|
494
|
+
};
|
|
495
|
+
readonly identifier: {
|
|
496
|
+
readonly prop: "identifier";
|
|
497
|
+
readonly type: "string";
|
|
498
|
+
};
|
|
499
|
+
readonly url: {
|
|
500
|
+
readonly prop: "url";
|
|
501
|
+
readonly type: "string";
|
|
502
|
+
};
|
|
503
|
+
readonly title: {
|
|
504
|
+
readonly prop: "title";
|
|
505
|
+
readonly type: "string";
|
|
506
|
+
};
|
|
507
|
+
readonly sort: {
|
|
508
|
+
readonly prop: "sort";
|
|
509
|
+
readonly type: readonly ["best", "hot", "newest", "oldest"];
|
|
510
|
+
};
|
|
511
|
+
readonly "sign-in": {
|
|
512
|
+
readonly prop: "signIn";
|
|
513
|
+
readonly type: readonly ["inline", "on-submit", "manual"];
|
|
514
|
+
};
|
|
515
|
+
readonly theme: {
|
|
516
|
+
readonly prop: "theme";
|
|
517
|
+
readonly type: readonly ["auto", "light", "dark", "inherit"];
|
|
518
|
+
};
|
|
519
|
+
readonly locale: {
|
|
520
|
+
readonly prop: "locale";
|
|
521
|
+
readonly type: "string";
|
|
522
|
+
};
|
|
523
|
+
readonly "max-depth": {
|
|
524
|
+
readonly prop: "maxDepth";
|
|
525
|
+
readonly type: "number";
|
|
526
|
+
};
|
|
527
|
+
readonly "read-only": {
|
|
528
|
+
readonly prop: "readOnly";
|
|
529
|
+
readonly type: "boolean";
|
|
530
|
+
};
|
|
531
|
+
readonly highlight: {
|
|
532
|
+
readonly prop: "highlight";
|
|
533
|
+
readonly type: "number";
|
|
534
|
+
};
|
|
535
|
+
readonly "guidelines-url": {
|
|
536
|
+
readonly prop: "guidelinesUrl";
|
|
537
|
+
readonly type: "string";
|
|
538
|
+
};
|
|
539
|
+
readonly unstyled: {
|
|
540
|
+
readonly prop: "unstyled";
|
|
541
|
+
readonly type: "boolean";
|
|
542
|
+
};
|
|
543
|
+
readonly debug: {
|
|
544
|
+
readonly prop: "debug";
|
|
545
|
+
readonly type: "boolean";
|
|
546
|
+
};
|
|
547
|
+
};
|
|
548
|
+
/** The props read from a `<discourse-comments>` element's attributes. */
|
|
549
|
+
interface DiscourseCommentsElementProps {
|
|
550
|
+
forum?: string;
|
|
551
|
+
identifier?: string;
|
|
552
|
+
url?: string;
|
|
553
|
+
title?: string;
|
|
554
|
+
sort?: Sort;
|
|
555
|
+
signIn?: SignInMode;
|
|
556
|
+
theme?: Theme;
|
|
557
|
+
locale?: string;
|
|
558
|
+
maxDepth?: number;
|
|
559
|
+
readOnly?: boolean;
|
|
560
|
+
highlight?: number;
|
|
561
|
+
guidelinesUrl?: string;
|
|
562
|
+
unstyled?: boolean;
|
|
563
|
+
debug?: boolean;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* `<discourse-comments forum identifier url title sort sign-in theme locale
|
|
567
|
+
* max-depth read-only highlight guidelines-url unstyled debug>`: the drop-in
|
|
568
|
+
* comments section in a shadow root with the default stylesheet. Every
|
|
569
|
+
* attribute is also a property, and the widget's events are dispatched from the
|
|
570
|
+
* element as `gc:<type>` custom events (`gc:comment.posted`, `gc:error`, …).
|
|
571
|
+
*/
|
|
572
|
+
declare class DiscourseCommentsElement extends ReactHostElement<DiscourseCommentsElementProps> {
|
|
573
|
+
#private;
|
|
574
|
+
static readonly schema: {
|
|
575
|
+
readonly forum: {
|
|
576
|
+
readonly prop: "forum";
|
|
577
|
+
readonly type: "string";
|
|
578
|
+
};
|
|
579
|
+
readonly identifier: {
|
|
580
|
+
readonly prop: "identifier";
|
|
581
|
+
readonly type: "string";
|
|
582
|
+
};
|
|
583
|
+
readonly url: {
|
|
584
|
+
readonly prop: "url";
|
|
585
|
+
readonly type: "string";
|
|
586
|
+
};
|
|
587
|
+
readonly title: {
|
|
588
|
+
readonly prop: "title";
|
|
589
|
+
readonly type: "string";
|
|
590
|
+
};
|
|
591
|
+
readonly sort: {
|
|
592
|
+
readonly prop: "sort";
|
|
593
|
+
readonly type: readonly ["best", "hot", "newest", "oldest"];
|
|
594
|
+
};
|
|
595
|
+
readonly "sign-in": {
|
|
596
|
+
readonly prop: "signIn";
|
|
597
|
+
readonly type: readonly ["inline", "on-submit", "manual"];
|
|
598
|
+
};
|
|
599
|
+
readonly theme: {
|
|
600
|
+
readonly prop: "theme";
|
|
601
|
+
readonly type: readonly ["auto", "light", "dark", "inherit"];
|
|
602
|
+
};
|
|
603
|
+
readonly locale: {
|
|
604
|
+
readonly prop: "locale";
|
|
605
|
+
readonly type: "string";
|
|
606
|
+
};
|
|
607
|
+
readonly "max-depth": {
|
|
608
|
+
readonly prop: "maxDepth";
|
|
609
|
+
readonly type: "number";
|
|
610
|
+
};
|
|
611
|
+
readonly "read-only": {
|
|
612
|
+
readonly prop: "readOnly";
|
|
613
|
+
readonly type: "boolean";
|
|
614
|
+
};
|
|
615
|
+
readonly highlight: {
|
|
616
|
+
readonly prop: "highlight";
|
|
617
|
+
readonly type: "number";
|
|
618
|
+
};
|
|
619
|
+
readonly "guidelines-url": {
|
|
620
|
+
readonly prop: "guidelinesUrl";
|
|
621
|
+
readonly type: "string";
|
|
622
|
+
};
|
|
623
|
+
readonly unstyled: {
|
|
624
|
+
readonly prop: "unstyled";
|
|
625
|
+
readonly type: "boolean";
|
|
626
|
+
};
|
|
627
|
+
readonly debug: {
|
|
628
|
+
readonly prop: "debug";
|
|
629
|
+
readonly type: "boolean";
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
forum: string | undefined;
|
|
633
|
+
identifier: string | undefined;
|
|
634
|
+
url: string | undefined;
|
|
635
|
+
sort: Sort | undefined;
|
|
636
|
+
signIn: SignInMode | undefined;
|
|
637
|
+
theme: Theme | undefined;
|
|
638
|
+
locale: string | undefined;
|
|
639
|
+
maxDepth: number | undefined;
|
|
640
|
+
readOnly: boolean;
|
|
641
|
+
highlight: number | undefined;
|
|
642
|
+
guidelinesUrl: string | undefined;
|
|
643
|
+
unstyled: boolean;
|
|
644
|
+
debug: boolean;
|
|
645
|
+
constructor();
|
|
646
|
+
/**
|
|
647
|
+
* The client the widget uses: one set here (for options such as `headers`),
|
|
648
|
+
* else the page-wide shared client for `forum`. `null` without either.
|
|
649
|
+
*/
|
|
650
|
+
get client(): CommentsClient | null;
|
|
651
|
+
set client(client: CommentsClient | null);
|
|
652
|
+
protected render(props: DiscourseCommentsElementProps): unknown;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
declare const FORMATS: readonly ["text", "number"];
|
|
656
|
+
/** `<discourse-comment-count>` attributes and the `<CommentCount>` props they map to. */
|
|
657
|
+
declare const COUNT_ATTRIBUTES: {
|
|
658
|
+
readonly forum: {
|
|
659
|
+
readonly prop: "forum";
|
|
660
|
+
readonly type: "string";
|
|
661
|
+
};
|
|
662
|
+
readonly identifier: {
|
|
663
|
+
readonly prop: "identifier";
|
|
664
|
+
readonly type: "string";
|
|
665
|
+
};
|
|
666
|
+
readonly host: {
|
|
667
|
+
readonly prop: "host";
|
|
668
|
+
readonly type: "string";
|
|
669
|
+
};
|
|
670
|
+
readonly url: {
|
|
671
|
+
readonly prop: "url";
|
|
672
|
+
readonly type: "string";
|
|
673
|
+
};
|
|
674
|
+
readonly "topic-id": {
|
|
675
|
+
readonly prop: "topicId";
|
|
676
|
+
readonly type: "number";
|
|
677
|
+
};
|
|
678
|
+
readonly format: {
|
|
679
|
+
readonly prop: "format";
|
|
680
|
+
readonly type: readonly ["text", "number"];
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
/** The props read from a `<discourse-comment-count>` element's attributes. */
|
|
684
|
+
interface DiscourseCommentCountElementProps {
|
|
685
|
+
forum?: string;
|
|
686
|
+
identifier?: string;
|
|
687
|
+
host?: string;
|
|
688
|
+
url?: string;
|
|
689
|
+
topicId?: number;
|
|
690
|
+
/** `text` is "12 comments"; `number` is just the number. */
|
|
691
|
+
format?: (typeof FORMATS)[number];
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* `<discourse-comment-count forum identifier host url topic-id format>`:
|
|
695
|
+
* "12 comments" for a page, fetched once and batched with the other counts on
|
|
696
|
+
* the page. Unstyled: it inherits the page's font and colour.
|
|
697
|
+
*/
|
|
698
|
+
declare class DiscourseCommentCountElement extends ReactHostElement<DiscourseCommentCountElementProps> {
|
|
699
|
+
#private;
|
|
700
|
+
static readonly schema: {
|
|
701
|
+
readonly forum: {
|
|
702
|
+
readonly prop: "forum";
|
|
703
|
+
readonly type: "string";
|
|
704
|
+
};
|
|
705
|
+
readonly identifier: {
|
|
706
|
+
readonly prop: "identifier";
|
|
707
|
+
readonly type: "string";
|
|
708
|
+
};
|
|
709
|
+
readonly host: {
|
|
710
|
+
readonly prop: "host";
|
|
711
|
+
readonly type: "string";
|
|
712
|
+
};
|
|
713
|
+
readonly url: {
|
|
714
|
+
readonly prop: "url";
|
|
715
|
+
readonly type: "string";
|
|
716
|
+
};
|
|
717
|
+
readonly "topic-id": {
|
|
718
|
+
readonly prop: "topicId";
|
|
719
|
+
readonly type: "number";
|
|
720
|
+
};
|
|
721
|
+
readonly format: {
|
|
722
|
+
readonly prop: "format";
|
|
723
|
+
readonly type: readonly ["text", "number"];
|
|
724
|
+
};
|
|
725
|
+
};
|
|
726
|
+
forum: string | undefined;
|
|
727
|
+
identifier: string | undefined;
|
|
728
|
+
host: string | undefined;
|
|
729
|
+
url: string | undefined;
|
|
730
|
+
topicId: number | undefined;
|
|
731
|
+
format: DiscourseCommentCountElementProps["format"];
|
|
732
|
+
constructor();
|
|
733
|
+
/** The client the count uses: one set here, else the shared client for `forum`. */
|
|
734
|
+
get client(): CommentsClient | null;
|
|
735
|
+
set client(client: CommentsClient | null);
|
|
736
|
+
protected render(props: DiscourseCommentCountElementProps): unknown;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
interface DefineElementsOptions {
|
|
740
|
+
/** Tag name for the comments section; default `discourse-comments`. */
|
|
741
|
+
comments?: string;
|
|
742
|
+
/** Tag name for the count; default `discourse-comment-count`. */
|
|
743
|
+
count?: string;
|
|
744
|
+
}
|
|
745
|
+
declare const COMMENTS_TAG = "discourse-comments";
|
|
746
|
+
declare const COUNT_TAG = "discourse-comment-count";
|
|
747
|
+
/**
|
|
748
|
+
* Registers the elements, skipping names that are already defined (importing
|
|
749
|
+
* the module does this with the default names). Pass other names to register
|
|
750
|
+
* the same elements under them as well.
|
|
751
|
+
*/
|
|
752
|
+
declare function defineElements({ comments, count, }?: DefineElementsOptions): void;
|
|
753
|
+
|
|
754
|
+
/** `gc:<type>` events dispatched by `<discourse-comments>`, keyed by event name. */
|
|
755
|
+
type DiscourseCommentsEventMap = {
|
|
756
|
+
[E in CommentsEvent as `gc:${E["type"]}`]: CustomEvent<E>;
|
|
757
|
+
} & {
|
|
758
|
+
/** With `sign-in="manual"`, an anonymous reader asked to like or reply. */
|
|
759
|
+
"gc:sign-in-required": CustomEvent<undefined>;
|
|
760
|
+
};
|
|
761
|
+
declare global {
|
|
762
|
+
interface HTMLElementTagNameMap {
|
|
763
|
+
"discourse-comments": DiscourseCommentsElement;
|
|
764
|
+
"discourse-comment-count": DiscourseCommentCountElement;
|
|
765
|
+
}
|
|
766
|
+
interface HTMLElementEventMap extends DiscourseCommentsEventMap {
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export { type AttributeSchema, type AttributeSpec, type AttributeType, COMMENTS_ATTRIBUTES, COMMENTS_TAG, COUNT_ATTRIBUTES, COUNT_TAG, type CommentsEvent, type DefineElementsOptions, DiscourseCommentCountElement, type DiscourseCommentCountElementProps, DiscourseCommentsElement, type DiscourseCommentsElementProps, type DiscourseCommentsEventMap, defineElements };
|