@mintplayer/ng-spark 22.22.0 → 22.24.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 +181 -0
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +308 -329
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-grid.mjs +2259 -1117
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-history.mjs +611 -0
- package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
- package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs +365 -753
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
- package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs +52 -0
- package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +1122 -765
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +786 -410
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +292 -284
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +2187 -1225
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +516 -303
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +17 -55
- package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
- package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-shell.mjs +1013 -397
- package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark.mjs +13 -50
- package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
- package/package.json +17 -1
- package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
- package/types/mintplayer-ng-spark-client-operations.d.ts +133 -160
- package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
- package/types/mintplayer-ng-spark-grid.d.ts +885 -448
- package/types/mintplayer-ng-spark-history.d.ts +223 -0
- package/types/mintplayer-ng-spark-icon.d.ts +15 -13
- package/types/mintplayer-ng-spark-models.d.ts +624 -573
- package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
- package/types/mintplayer-ng-spark-panels.d.ts +108 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +120 -149
- package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
- package/types/mintplayer-ng-spark-po-detail.d.ts +177 -117
- package/types/mintplayer-ng-spark-po-edit.d.ts +64 -65
- package/types/mintplayer-ng-spark-po-form.d.ts +507 -371
- package/types/mintplayer-ng-spark-query-list.d.ts +118 -82
- package/types/mintplayer-ng-spark-renderers.d.ts +79 -85
- package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
- package/types/mintplayer-ng-spark-routes.d.ts +7 -11
- package/types/mintplayer-ng-spark-services.d.ts +377 -261
- package/types/mintplayer-ng-spark-shell.d.ts +209 -128
- package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
- package/types/mintplayer-ng-spark.d.ts +7 -12
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { NavigationEnd, Routes } from "@angular/router";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import { InjectionToken, OnInit, Provider } from "@angular/core";
|
|
4
|
+
import { EntityAttributeDefinition, PersistentObject, QueryResultItem, SparkCellColumn } from "@mintplayer/ng-spark/models";
|
|
5
|
+
import { SparkDetailContext } from "@mintplayer/ng-spark/panels";
|
|
6
|
+
import { SparkAttributeRendererRegistration } from "@mintplayer/ng-spark/renderers";
|
|
7
|
+
/** A target's score and the caller's vote (`/spark/moderation/vote`, `/votes`). */
|
|
8
|
+
export interface ModerationVoteState {
|
|
9
|
+
id: string;
|
|
10
|
+
score: number;
|
|
11
|
+
up: number;
|
|
12
|
+
down: number;
|
|
13
|
+
/** +1, −1 or 0. */
|
|
14
|
+
myVote: -1 | 0 | 1;
|
|
15
|
+
locked: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the caller holds the right to upvote / downvote this type (an earned privilege or a
|
|
18
|
+
* group grant). Not a promise the vote is accepted: an own post, a lock or a suspension still
|
|
19
|
+
* refuse it. Absent from an older server, which the widget reads as "may".
|
|
20
|
+
*/
|
|
21
|
+
canUpvote?: boolean;
|
|
22
|
+
canDownvote?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** A user's reputation. Someone else's carries only `userId` and `total`. */
|
|
25
|
+
export interface ModerationReputation {
|
|
26
|
+
userId: string;
|
|
27
|
+
total: number;
|
|
28
|
+
pending: number;
|
|
29
|
+
privileges: string[];
|
|
30
|
+
suspended: boolean;
|
|
31
|
+
/** The caller's own only: whether they may open the review queue (earned or granted). */
|
|
32
|
+
canReview?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ModerationReputationLine {
|
|
35
|
+
label: string;
|
|
36
|
+
kind: string;
|
|
37
|
+
points: number;
|
|
38
|
+
targetId?: string | null;
|
|
39
|
+
targetType?: string | null;
|
|
40
|
+
atUtc: string;
|
|
41
|
+
pending: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** `/spark/moderation/status` — the moderator panel's view of one target. */
|
|
44
|
+
export interface ModerationStatus {
|
|
45
|
+
id: string;
|
|
46
|
+
authorId?: string | null;
|
|
47
|
+
locked: boolean;
|
|
48
|
+
lockReason?: string | null;
|
|
49
|
+
canLock: boolean;
|
|
50
|
+
canReview: boolean;
|
|
51
|
+
canSuspend: boolean;
|
|
52
|
+
openCaseId?: string | null;
|
|
53
|
+
openFlags: number;
|
|
54
|
+
}
|
|
55
|
+
export interface ModerationCaseSummary {
|
|
56
|
+
id: string;
|
|
57
|
+
/** `flag`, or the fraud rule (`serial`, `concentration`, `reciprocal`, `fast-voting`, `registration-cluster`). */
|
|
58
|
+
kind: string;
|
|
59
|
+
status: string;
|
|
60
|
+
targetId?: string | null;
|
|
61
|
+
targetType?: string | null;
|
|
62
|
+
accountIds: string[];
|
|
63
|
+
flagCount: number;
|
|
64
|
+
voteCount: number;
|
|
65
|
+
summary?: string | null;
|
|
66
|
+
openedAtUtc: string;
|
|
67
|
+
decision?: string | null;
|
|
68
|
+
}
|
|
69
|
+
export interface ModerationCaseDetail {
|
|
70
|
+
case: ModerationCaseSummary;
|
|
71
|
+
matrix: {
|
|
72
|
+
voterId: string;
|
|
73
|
+
authorId: string;
|
|
74
|
+
votes: number;
|
|
75
|
+
}[];
|
|
76
|
+
timeline: {
|
|
77
|
+
voteId: string;
|
|
78
|
+
voterId: string;
|
|
79
|
+
authorId?: string | null;
|
|
80
|
+
targetId: string;
|
|
81
|
+
direction: number;
|
|
82
|
+
castAtUtc: string;
|
|
83
|
+
targetPostedAtUtc?: string | null;
|
|
84
|
+
secondsAfterPost?: number | null;
|
|
85
|
+
}[];
|
|
86
|
+
accounts: {
|
|
87
|
+
id: string;
|
|
88
|
+
createdAtUtc?: string | null;
|
|
89
|
+
ageDays?: number | null;
|
|
90
|
+
registrationMethod?: string | null;
|
|
91
|
+
/** How many other accounts share a network hash with this one — never an address. */
|
|
92
|
+
sharesNetworkWith: number;
|
|
93
|
+
reputationBefore: number;
|
|
94
|
+
reputationAfter: number;
|
|
95
|
+
}[];
|
|
96
|
+
flags: {
|
|
97
|
+
flaggerId: string;
|
|
98
|
+
reason: string;
|
|
99
|
+
raisedAtUtc: string;
|
|
100
|
+
status: string;
|
|
101
|
+
}[];
|
|
102
|
+
}
|
|
103
|
+
export interface ModerationAuditEntry {
|
|
104
|
+
id: string;
|
|
105
|
+
actorId?: string | null;
|
|
106
|
+
action: string;
|
|
107
|
+
targetId?: string | null;
|
|
108
|
+
targetType?: string | null;
|
|
109
|
+
subjectUserId?: string | null;
|
|
110
|
+
caseId?: string | null;
|
|
111
|
+
reason?: string | null;
|
|
112
|
+
details?: string | null;
|
|
113
|
+
atUtc: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Client for `MintPlayer.Spark.Moderation`'s endpoints (`/spark/moderation/*`, #460). Every call is
|
|
117
|
+
* a POST with the ids in the body — the route table is literal and RavenDB ids contain slashes.
|
|
118
|
+
*
|
|
119
|
+
* A target the caller cannot see (hidden, missing, not moderatable) is the standard refusal: 404
|
|
120
|
+
* (or 401 when signing in would help). A rule (own post, locked, suspended) is a 400 with `errors`,
|
|
121
|
+
* a quota (votes per day) a 429 whose `result.retryAfterSeconds` says when to try again.
|
|
122
|
+
*/
|
|
123
|
+
export declare class SparkModerationService {
|
|
124
|
+
#private;
|
|
125
|
+
private readonly spark;
|
|
126
|
+
private readonly pending;
|
|
127
|
+
private flushScheduled;
|
|
128
|
+
private readonly router;
|
|
129
|
+
/** Counts the caller's accepted votes, so the own reputation badge re-reads (a downvote costs reputation). */
|
|
130
|
+
readonly votesCast: import("@angular/core").Signal<number>;
|
|
131
|
+
private readonly navigated;
|
|
132
|
+
/**
|
|
133
|
+
* Changes whenever the caller's own reputation may have changed: after every navigation (a
|
|
134
|
+
* sign-in, a sign-out, another page) and after the caller's own votes. The own badge and the
|
|
135
|
+
* review-queue link read it to re-fetch {@link ownReputation}; a component in the app shell lives
|
|
136
|
+
* for the whole session and would otherwise show the first answer until a full reload.
|
|
137
|
+
*/
|
|
138
|
+
readonly ownReputationChanged: import("@angular/core").Signal<{
|
|
139
|
+
navigation: NavigationEnd | null;
|
|
140
|
+
votes: number;
|
|
141
|
+
}>;
|
|
142
|
+
/**
|
|
143
|
+
* The caller's own reputation, shared by every caller in flight at once: the badge and the
|
|
144
|
+
* review-queue link both re-read on the same navigation and cost one request.
|
|
145
|
+
*/
|
|
146
|
+
ownReputation(): Promise<ModerationReputation>;
|
|
147
|
+
/** Casts (+1 / −1) or withdraws (0) the caller's vote; resolves to the target's new state. */
|
|
148
|
+
vote(type: string, id: string, direction: -1 | 0 | 1): Promise<ModerationVoteState>;
|
|
149
|
+
/**
|
|
150
|
+
* The score and the caller's vote for one target, batched with every other call made in the same
|
|
151
|
+
* macrotask for the same type (one `POST /spark/moderation/votes`, ≤ 100 ids). Resolves to `null`
|
|
152
|
+
* for an id the server left out (not visible to the caller).
|
|
153
|
+
*/
|
|
154
|
+
voteState(type: string, id: string): Promise<ModerationVoteState | null>;
|
|
155
|
+
/** `POST /spark/moderation/votes` for up to 100 ids of one type. */
|
|
156
|
+
votes(type: string, ids: string[]): Promise<ModerationVoteState[]>;
|
|
157
|
+
flag(type: string, id: string, reason: string): Promise<void>;
|
|
158
|
+
lock(type: string, id: string, reason?: string): Promise<void>;
|
|
159
|
+
unlock(type: string, id: string): Promise<void>;
|
|
160
|
+
/** What the moderator panel shows about a target, including which tools the caller holds. */
|
|
161
|
+
status(type: string, id: string): Promise<ModerationStatus>;
|
|
162
|
+
/** A user's reputation; the caller's own when `userId` is omitted (with privileges and suspension). */
|
|
163
|
+
reputation(userId?: string): Promise<ModerationReputation>;
|
|
164
|
+
/** The caller's own ledger, newest first. Voters are never named; a reversal reads "Voting corrected (−N)". */
|
|
165
|
+
history(take?: number): Promise<ModerationReputationLine[]>;
|
|
166
|
+
cases(status?: string, skip?: number, take?: number): Promise<ModerationCaseSummary[]>;
|
|
167
|
+
case(caseId: string): Promise<ModerationCaseDetail>;
|
|
168
|
+
/** `uphold` / `decline` (flag cases), `dismiss` / `reverse` / `merge` / `suspend` (fraud cases; the last two name `accountId`). */
|
|
169
|
+
decide(caseId: string, decision: string, accountId?: string, reason?: string): Promise<void>;
|
|
170
|
+
suspend(userId: string, days?: number | null, reason?: string): Promise<void>;
|
|
171
|
+
unsuspend(userId: string): Promise<void>;
|
|
172
|
+
audit(skip?: number, take?: number): Promise<ModerationAuditEntry[]>;
|
|
173
|
+
private flush;
|
|
174
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkModerationService, never>;
|
|
175
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
176
|
+
}
|
|
177
|
+
/** Where the review queue is routed; the moderator panel links there. Set by {@link provideSparkModeration}. */
|
|
178
|
+
export declare const SPARK_MODERATION_REVIEW_PATH: InjectionToken<string>;
|
|
179
|
+
/**
|
|
180
|
+
* The vote widget (#460): up, score, down. An attribute renderer — register it with
|
|
181
|
+
* {@link sparkModerationRenderers} and give an attribute of an `IModeratable` type
|
|
182
|
+
* `"renderer": "spark-vote"`. The attribute's own value is ignored: the score comes from
|
|
183
|
+
* `/spark/moderation/votes` (batched per page), never from the document, so a vote never moves the
|
|
184
|
+
* row's etag.
|
|
185
|
+
*
|
|
186
|
+
* An arrow the caller holds no right for (`canUpvote` / `canDownvote` false on the state) is
|
|
187
|
+
* disabled, so a user without the privilege sees the score but is not offered a click that the server
|
|
188
|
+
* refuses — except the active arrow, since withdrawing a vote needs no right.
|
|
189
|
+
*
|
|
190
|
+
* On the detail page the entity type is the object's. In a query grid a row carries no type, so the
|
|
191
|
+
* column needs `"rendererOptions": { "type": "<entity type id or alias>" }`.
|
|
192
|
+
*/
|
|
193
|
+
export declare class SparkVoteComponent {
|
|
194
|
+
private readonly moderation;
|
|
195
|
+
value: import("@angular/core").InputSignal<unknown>;
|
|
196
|
+
attribute: import("@angular/core").InputSignal<EntityAttributeDefinition | undefined>;
|
|
197
|
+
column: import("@angular/core").InputSignal<SparkCellColumn | undefined>;
|
|
198
|
+
options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
|
|
199
|
+
item: import("@angular/core").InputSignal<Record<string, any> | PersistentObject | QueryResultItem | undefined>;
|
|
200
|
+
protected readonly state: import("@angular/core").WritableSignal<ModerationVoteState | null>;
|
|
201
|
+
protected readonly busy: import("@angular/core").WritableSignal<boolean>;
|
|
202
|
+
protected readonly error: import("@angular/core").WritableSignal<string | null>;
|
|
203
|
+
protected readonly target: import("@angular/core").Signal<{
|
|
204
|
+
id: string;
|
|
205
|
+
type: string;
|
|
206
|
+
} | null>;
|
|
207
|
+
constructor();
|
|
208
|
+
cast(direction: 1 | -1): Promise<void>;
|
|
209
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkVoteComponent, never>;
|
|
210
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkVoteComponent, "spark-vote", never, {
|
|
211
|
+
"value": {
|
|
212
|
+
"alias": "value";
|
|
213
|
+
"required": false;
|
|
214
|
+
"isSignal": true;
|
|
215
|
+
};
|
|
216
|
+
"attribute": {
|
|
217
|
+
"alias": "attribute";
|
|
218
|
+
"required": false;
|
|
219
|
+
"isSignal": true;
|
|
220
|
+
};
|
|
221
|
+
"column": {
|
|
222
|
+
"alias": "column";
|
|
223
|
+
"required": false;
|
|
224
|
+
"isSignal": true;
|
|
225
|
+
};
|
|
226
|
+
"options": {
|
|
227
|
+
"alias": "options";
|
|
228
|
+
"required": false;
|
|
229
|
+
"isSignal": true;
|
|
230
|
+
};
|
|
231
|
+
"item": {
|
|
232
|
+
"alias": "item";
|
|
233
|
+
"required": false;
|
|
234
|
+
"isSignal": true;
|
|
235
|
+
};
|
|
236
|
+
}, {}, never, never, true, never>;
|
|
237
|
+
}
|
|
238
|
+
/** A readable message for a refused moderation call (400 errors, 429 quota, 404 refusal). */
|
|
239
|
+
export declare function describeModerationError(e: unknown): string;
|
|
240
|
+
/**
|
|
241
|
+
* **Flag** on the detail page of every object (#460). Registered by {@link provideSparkModeration}
|
|
242
|
+
* as a `SPARK_DETAIL_ACTIONS` entry. The button is offered to every signed-in viewer of a live row;
|
|
243
|
+
* the server decides (`Flag/T`, a visible moderatable row) and answers a refusal inline — the
|
|
244
|
+
* permissions endpoint does not report moderation rights, and a type that is not moderatable answers
|
|
245
|
+
* like a missing row, so the button cannot know in advance.
|
|
246
|
+
*/
|
|
247
|
+
export declare class SparkFlagButtonComponent {
|
|
248
|
+
private readonly moderation;
|
|
249
|
+
private readonly lang;
|
|
250
|
+
context: import("@angular/core").InputSignal<SparkDetailContext>;
|
|
251
|
+
protected readonly busy: import("@angular/core").WritableSignal<boolean>;
|
|
252
|
+
protected readonly done: import("@angular/core").WritableSignal<boolean>;
|
|
253
|
+
protected readonly error: import("@angular/core").WritableSignal<string | null>;
|
|
254
|
+
flag(): Promise<void>;
|
|
255
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkFlagButtonComponent, never>;
|
|
256
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkFlagButtonComponent, "spark-flag-button", never, {
|
|
257
|
+
"context": {
|
|
258
|
+
"alias": "context";
|
|
259
|
+
"required": true;
|
|
260
|
+
"isSignal": true;
|
|
261
|
+
};
|
|
262
|
+
}, {}, never, never, true, never>;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The moderator panel under a moderatable object's detail page (#460): lock state and Lock/Unlock
|
|
266
|
+
* (`Lock/T`), open flags with a link to the review queue (`Review/Moderation`), and Suspend author
|
|
267
|
+
* (`Suspend/Moderation`). Restore/Purge and Revert are the SoftDelete and History entry points'.
|
|
268
|
+
* Renders nothing for a caller with none of these rights, or for a type that is not moderatable.
|
|
269
|
+
*/
|
|
270
|
+
export declare class SparkModeratorPanelComponent {
|
|
271
|
+
private readonly moderation;
|
|
272
|
+
private readonly lang;
|
|
273
|
+
protected readonly reviewPath: string;
|
|
274
|
+
context: import("@angular/core").InputSignal<SparkDetailContext>;
|
|
275
|
+
protected readonly status: import("@angular/core").WritableSignal<ModerationStatus | null>;
|
|
276
|
+
protected readonly busy: import("@angular/core").WritableSignal<boolean>;
|
|
277
|
+
protected readonly error: import("@angular/core").WritableSignal<string | null>;
|
|
278
|
+
constructor();
|
|
279
|
+
lock(): Promise<void>;
|
|
280
|
+
unlock(): Promise<void>;
|
|
281
|
+
suspendAuthor(userId: string): Promise<void>;
|
|
282
|
+
private run;
|
|
283
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkModeratorPanelComponent, never>;
|
|
284
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkModeratorPanelComponent, "spark-moderator-panel", never, {
|
|
285
|
+
"context": {
|
|
286
|
+
"alias": "context";
|
|
287
|
+
"required": true;
|
|
288
|
+
"isSignal": true;
|
|
289
|
+
};
|
|
290
|
+
}, {}, never, never, true, never>;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A user's reputation (#460): `<spark-reputation-badge [userId]="post.authorId" />`, or without
|
|
294
|
+
* `userId` the signed-in user's own, with the not-yet-credited part (delayed crediting) beside it.
|
|
295
|
+
*
|
|
296
|
+
* The own badge usually sits in the app shell, which lives for the whole session: it re-reads the
|
|
297
|
+
* reputation after every navigation (a sign-in, a sign-out, opening another page) and after the
|
|
298
|
+
* caller's own votes, or it would show the first value it read until a full page reload.
|
|
299
|
+
*/
|
|
300
|
+
export declare class SparkReputationBadgeComponent {
|
|
301
|
+
private readonly moderation;
|
|
302
|
+
userId: import("@angular/core").InputSignal<string | null | undefined>;
|
|
303
|
+
protected readonly reputation: import("@angular/core").WritableSignal<ModerationReputation | null>;
|
|
304
|
+
constructor();
|
|
305
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkReputationBadgeComponent, never>;
|
|
306
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkReputationBadgeComponent, "spark-reputation-badge", never, {
|
|
307
|
+
"userId": {
|
|
308
|
+
"alias": "userId";
|
|
309
|
+
"required": false;
|
|
310
|
+
"isSignal": true;
|
|
311
|
+
};
|
|
312
|
+
}, {}, never, never, true, never>;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* The review queue (#460): open cases on the left, the selected case's review surface on the right —
|
|
316
|
+
* the voter→target matrix, the timeline against the posts' creation, the accounts side by side (age,
|
|
317
|
+
* registration method, how many accounts share a network with it, reputation now and after reversing)
|
|
318
|
+
* and the rule that fired — with the decisions the case allows. Needs `Review/Moderation`; merge and
|
|
319
|
+
* suspend also `Suspend/Moderation` (the server refuses otherwise).
|
|
320
|
+
*
|
|
321
|
+
* Routed by {@link sparkModerationRoutes} at `moderation/review`; `?case=` selects a case.
|
|
322
|
+
*/
|
|
323
|
+
export declare class SparkReviewQueueComponent implements OnInit {
|
|
324
|
+
private readonly moderation;
|
|
325
|
+
private readonly route;
|
|
326
|
+
private readonly router;
|
|
327
|
+
private readonly spark;
|
|
328
|
+
protected readonly cases: import("@angular/core").WritableSignal<ModerationCaseSummary[]>;
|
|
329
|
+
/** Per case id: the target post's title and route, once resolved. */
|
|
330
|
+
protected readonly targets: import("@angular/core").WritableSignal<Record<string, {
|
|
331
|
+
title: string | null;
|
|
332
|
+
link: string[] | null;
|
|
333
|
+
}>>;
|
|
334
|
+
protected readonly selected: import("@angular/core").WritableSignal<ModerationCaseDetail | null>;
|
|
335
|
+
protected readonly busy: import("@angular/core").WritableSignal<boolean>;
|
|
336
|
+
protected readonly error: import("@angular/core").WritableSignal<string | null>;
|
|
337
|
+
ngOnInit(): Promise<void>;
|
|
338
|
+
open(caseId: string): Promise<void>;
|
|
339
|
+
decide(decision: string, accountId?: string): Promise<void>;
|
|
340
|
+
private load;
|
|
341
|
+
/**
|
|
342
|
+
* Names each case's post: its title (the object's breadcrumb) and a link to it. A case carries only
|
|
343
|
+
* the target's id and type name; without this the queue listed "flag · Spam" with no way to tell
|
|
344
|
+
* which post it was about. A target the caller cannot open (purged, hidden) keeps just its id.
|
|
345
|
+
*/
|
|
346
|
+
private resolveTargets;
|
|
347
|
+
private run;
|
|
348
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkReviewQueueComponent, never>;
|
|
349
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkReviewQueueComponent, "spark-review-queue", never, {}, {}, never, never, true, never>;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* A link to the review queue (#460) that renders only for a caller who may review — earned, or
|
|
353
|
+
* granted by a group such as moderators (`canReview` on the caller's own reputation). Anyone else
|
|
354
|
+
* would open a page whose every request answers 404. Re-checked after each navigation, so it appears
|
|
355
|
+
* on sign-in and disappears on sign-out.
|
|
356
|
+
*
|
|
357
|
+
* `<spark-review-queue-link linkClass="btn btn-sm btn-link" />` — `linkClass` styles the anchor.
|
|
358
|
+
*/
|
|
359
|
+
export declare class SparkReviewQueueLinkComponent {
|
|
360
|
+
private readonly moderation;
|
|
361
|
+
/** Classes for the anchor itself (the host element is only a wrapper). */
|
|
362
|
+
linkClass: import("@angular/core").InputSignal<string>;
|
|
363
|
+
protected readonly canReview: import("@angular/core").WritableSignal<boolean>;
|
|
364
|
+
constructor();
|
|
365
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SparkReviewQueueLinkComponent, never>;
|
|
366
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SparkReviewQueueLinkComponent, "spark-review-queue-link", never, {
|
|
367
|
+
"linkClass": {
|
|
368
|
+
"alias": "linkClass";
|
|
369
|
+
"required": false;
|
|
370
|
+
"isSignal": true;
|
|
371
|
+
};
|
|
372
|
+
}, {}, never, never, true, never>;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Adds the Moderation UI (#460) to the pages `sparkRoutes()` routes to: a **Flag** action on every
|
|
376
|
+
* detail page and the **moderator panel** under it. Needs the server package
|
|
377
|
+
* `MintPlayer.Spark.Moderation`.
|
|
378
|
+
*
|
|
379
|
+
* ```ts
|
|
380
|
+
* providers: [
|
|
381
|
+
* provideSpark(...),
|
|
382
|
+
* provideSparkModeration(),
|
|
383
|
+
* provideSparkAttributeRenderers([...sparkModerationRenderers, ...yourRenderers]),
|
|
384
|
+
* ]
|
|
385
|
+
* // routes: [...sparkModerationRoutes(), ...sparkRoutes()]
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* The vote widget is an attribute renderer (`"renderer": "spark-vote"` on an attribute of an
|
|
389
|
+
* `IModeratable` type), so it is registered through {@link sparkModerationRenderers} with the app's
|
|
390
|
+
* own renderers — `provideSparkAttributeRenderers` takes one list.
|
|
391
|
+
*/
|
|
392
|
+
export declare function provideSparkModeration(options?: {
|
|
393
|
+
reviewPath?: string;
|
|
394
|
+
panelOrder?: number;
|
|
395
|
+
}): Provider[];
|
|
396
|
+
/** The vote widget as an attribute renderer named `spark-vote` (detail page and grid column). */
|
|
397
|
+
export declare const sparkModerationRenderers: SparkAttributeRendererRegistration[];
|
|
398
|
+
/**
|
|
399
|
+
* The review-queue page at `moderation/review`. Put it **before** `sparkRoutes()` — a literal first
|
|
400
|
+
* segment is fine anywhere, but keeping add-on routes first avoids surprises with parameterised ones.
|
|
401
|
+
*/
|
|
402
|
+
export declare function sparkModerationRoutes(path?: string): Routes;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { InjectionToken, Provider, Type } from "@angular/core";
|
|
2
|
+
import { EntityPermissions, EntityType, PersistentObject, SparkDeletedFilter, SparkQuery } from "@mintplayer/ng-spark/models";
|
|
3
|
+
/**
|
|
4
|
+
* What a detail-page extension is told about the object on screen (#460).
|
|
5
|
+
*
|
|
6
|
+
* Handed to every {@link SPARK_DETAIL_PANELS} and {@link SPARK_DETAIL_ACTIONS} component through its
|
|
7
|
+
* `context` input. A new object is passed whenever the page's state changes (a reload, a patch), so a
|
|
8
|
+
* component reads it as a plain value and never needs to subscribe to anything.
|
|
9
|
+
*/
|
|
10
|
+
export interface SparkDetailContext {
|
|
11
|
+
/** The entity type id or alias from the route (`/po/:type/:id`). */
|
|
12
|
+
type: string;
|
|
13
|
+
/** The object id from the route. */
|
|
14
|
+
id: string;
|
|
15
|
+
item: PersistentObject;
|
|
16
|
+
entityType: EntityType;
|
|
17
|
+
/**
|
|
18
|
+
* Type-level rights (`/spark/permissions/{type}`). `null` while they load — treat as "not allowed".
|
|
19
|
+
* The optional add-on flags (`canRestore`, `canViewHistory`, …) are absent on an older server.
|
|
20
|
+
*/
|
|
21
|
+
permissions: EntityPermissions | null;
|
|
22
|
+
/**
|
|
23
|
+
* The soft-deletion mode the object was loaded with — the route's `?deleted=` query parameter
|
|
24
|
+
* (`include` / `only`), or `null` for a normal load. `only` means the object was opened from the
|
|
25
|
+
* recycle bin and is a deleted row.
|
|
26
|
+
*/
|
|
27
|
+
deleted: SparkDeletedFilter | null;
|
|
28
|
+
/** Re-fetches the object (and its permissions) with the same mode. */
|
|
29
|
+
reload(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What a query-page extension is told about the list on screen (#460).
|
|
33
|
+
*
|
|
34
|
+
* Handed to every {@link SPARK_QUERY_LIST_ACTIONS} component through its `context` input.
|
|
35
|
+
*/
|
|
36
|
+
export interface SparkQueryListContext {
|
|
37
|
+
query: SparkQuery;
|
|
38
|
+
/** Null for a query whose rows are not an entity type (a composed query). */
|
|
39
|
+
entityType: EntityType | null;
|
|
40
|
+
/** Type-level rights of `entityType`; null while they load or when there is no entity type. */
|
|
41
|
+
permissions: EntityPermissions | null;
|
|
42
|
+
/** The soft-deletion mode the list currently runs with (`exclude` unless the route says otherwise). */
|
|
43
|
+
deleted: SparkDeletedFilter;
|
|
44
|
+
/**
|
|
45
|
+
* Switches the list's soft-deletion mode. Written to the route as `?deleted=`, so the recycle bin
|
|
46
|
+
* survives a reload and the back button; `exclude` removes the parameter.
|
|
47
|
+
*/
|
|
48
|
+
setDeleted(mode: SparkDeletedFilter): void;
|
|
49
|
+
/** Re-runs the query, keeping page and sort. */
|
|
50
|
+
reload(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A component shown under the attributes of every routed detail page (`sparkRoutes()`'s
|
|
54
|
+
* `po/:type/:id`). It must declare `context = input.required<SparkDetailContext>()`.
|
|
55
|
+
*
|
|
56
|
+
* `sparkRoutes()` passes no templates to the pages it routes to, so a multi-provider is the only way
|
|
57
|
+
* an add-on (History, Moderation) can put something on them without the app replacing the page.
|
|
58
|
+
*/
|
|
59
|
+
export interface SparkDetailPanel {
|
|
60
|
+
/** Unique name; a second registration with the same id replaces the first. */
|
|
61
|
+
id: string;
|
|
62
|
+
component: Type<unknown>;
|
|
63
|
+
/** Ascending; panels without an order come last, in registration order. */
|
|
64
|
+
order?: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A component rendered in the action bar of every routed detail page, after the built-in and custom
|
|
68
|
+
* actions. It must declare `context = input.required<SparkDetailContext>()` and render nothing when
|
|
69
|
+
* it does not apply (it is responsible for its own permission checks).
|
|
70
|
+
*/
|
|
71
|
+
export interface SparkDetailAction {
|
|
72
|
+
id: string;
|
|
73
|
+
component: Type<unknown>;
|
|
74
|
+
/** Priority in the action bar's overflow ordering; defaults to 60. Lower stays visible longer. */
|
|
75
|
+
priority?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A component rendered in the action bar of every routed query page (`query/:queryId`, `po/:type`).
|
|
79
|
+
* It must declare `context = input.required<SparkQueryListContext>()`.
|
|
80
|
+
*/
|
|
81
|
+
export interface SparkQueryListAction {
|
|
82
|
+
id: string;
|
|
83
|
+
component: Type<unknown>;
|
|
84
|
+
/** Priority in the action bar's overflow ordering; defaults to 60. */
|
|
85
|
+
priority?: number;
|
|
86
|
+
}
|
|
87
|
+
/** Multi-provider: panels under the attributes of the routed detail page. */
|
|
88
|
+
export declare const SPARK_DETAIL_PANELS: InjectionToken<SparkDetailPanel[]>;
|
|
89
|
+
/** Multi-provider: extra buttons in the routed detail page's action bar. */
|
|
90
|
+
export declare const SPARK_DETAIL_ACTIONS: InjectionToken<SparkDetailAction[]>;
|
|
91
|
+
/** Multi-provider: extra buttons in the routed query page's action bar. */
|
|
92
|
+
export declare const SPARK_QUERY_LIST_ACTIONS: InjectionToken<SparkQueryListAction[]>;
|
|
93
|
+
/** Registers detail panels (multi — every call adds). */
|
|
94
|
+
export declare function provideSparkDetailPanels(...panels: SparkDetailPanel[]): Provider[];
|
|
95
|
+
/** Registers detail-page actions (multi — every call adds). */
|
|
96
|
+
export declare function provideSparkDetailActions(...actions: SparkDetailAction[]): Provider[];
|
|
97
|
+
/** Registers query-page actions (multi — every call adds). */
|
|
98
|
+
export declare function provideSparkQueryListActions(...actions: SparkQueryListAction[]): Provider[];
|
|
99
|
+
/**
|
|
100
|
+
* Normalises a multi-provided list: the last registration of an id wins (so an app can replace a
|
|
101
|
+
* shipped panel by registering its own under the same id), then sorted by `key` ascending with
|
|
102
|
+
* unordered entries last, keeping registration order among equals.
|
|
103
|
+
*/
|
|
104
|
+
export declare function orderSparkExtensions<T extends {
|
|
105
|
+
id: string;
|
|
106
|
+
}>(entries: readonly T[] | null | undefined, key: (entry: T) => number | undefined): T[];
|
|
107
|
+
/** Reads `?deleted=` into a filter, ignoring anything that is not one of the three modes. */
|
|
108
|
+
export declare function parseSparkDeletedParam(value: string | null | undefined): SparkDeletedFilter | null;
|