@atcute/bluesky-moderation 2.0.3 → 3.0.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.
Files changed (67) hide show
  1. package/README.md +126 -128
  2. package/dist/_test-util/mock.d.ts +2735 -0
  3. package/dist/_test-util/mock.d.ts.map +1 -0
  4. package/dist/_test-util/mock.js +109 -0
  5. package/dist/_test-util/mock.js.map +1 -0
  6. package/dist/_test-util/moderation-behavior.d.ts +57 -0
  7. package/dist/_test-util/moderation-behavior.d.ts.map +1 -0
  8. package/dist/_test-util/moderation-behavior.js +158 -0
  9. package/dist/_test-util/moderation-behavior.js.map +1 -0
  10. package/dist/behaviors.d.ts +22 -18
  11. package/dist/behaviors.d.ts.map +1 -0
  12. package/dist/behaviors.js +18 -21
  13. package/dist/behaviors.js.map +1 -1
  14. package/dist/decision.d.ts +26 -24
  15. package/dist/decision.d.ts.map +1 -0
  16. package/dist/decision.js +14 -16
  17. package/dist/decision.js.map +1 -1
  18. package/dist/index.d.ts +12 -11
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/internal/keyword-filter.d.ts +4 -3
  21. package/dist/internal/keyword-filter.d.ts.map +1 -0
  22. package/dist/internal/keyword-filter.js.map +1 -1
  23. package/dist/keyword-filter.d.ts +8 -6
  24. package/dist/keyword-filter.d.ts.map +1 -0
  25. package/dist/keyword-filter.js +8 -7
  26. package/dist/keyword-filter.js.map +1 -1
  27. package/dist/label.d.ts +31 -27
  28. package/dist/label.d.ts.map +1 -0
  29. package/dist/label.js +24 -28
  30. package/dist/label.js.map +1 -1
  31. package/dist/subjects/feed-generator.d.ts +4 -3
  32. package/dist/subjects/feed-generator.d.ts.map +1 -0
  33. package/dist/subjects/feed-generator.js.map +1 -1
  34. package/dist/subjects/list.d.ts +3 -2
  35. package/dist/subjects/list.d.ts.map +1 -0
  36. package/dist/subjects/list.js.map +1 -1
  37. package/dist/subjects/notification.d.ts +4 -3
  38. package/dist/subjects/notification.d.ts.map +1 -0
  39. package/dist/subjects/notification.js.map +1 -1
  40. package/dist/subjects/post.d.ts +5 -3
  41. package/dist/subjects/post.d.ts.map +1 -0
  42. package/dist/subjects/post.js.map +1 -1
  43. package/dist/subjects/profile.d.ts +3 -2
  44. package/dist/subjects/profile.d.ts.map +1 -0
  45. package/dist/subjects/profile.js.map +1 -1
  46. package/dist/types.d.ts +3 -2
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/ui.d.ts +3 -2
  49. package/dist/ui.d.ts.map +1 -0
  50. package/dist/ui.js +4 -4
  51. package/dist/ui.js.map +1 -1
  52. package/lib/_test-util/mock.ts +214 -0
  53. package/lib/_test-util/moderation-behavior.ts +259 -0
  54. package/lib/behaviors.ts +21 -18
  55. package/lib/decision.ts +26 -26
  56. package/lib/index.ts +11 -11
  57. package/lib/internal/keyword-filter.ts +1 -1
  58. package/lib/keyword-filter.ts +9 -6
  59. package/lib/label.ts +32 -28
  60. package/lib/subjects/feed-generator.ts +4 -4
  61. package/lib/subjects/list.ts +4 -4
  62. package/lib/subjects/notification.ts +4 -4
  63. package/lib/subjects/post.ts +6 -7
  64. package/lib/subjects/profile.ts +3 -3
  65. package/lib/types.ts +2 -2
  66. package/lib/ui.ts +7 -7
  67. package/package.json +14 -12
package/dist/ui.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DisplayContext } from './behaviors.js';
2
- import { type ModerationCause, type ModerationDecision } from './decision.js';
1
+ import { DisplayContext } from './behaviors.ts';
2
+ import { type ModerationCause, type ModerationDecision } from './decision.ts';
3
3
  export interface DisplayRestrictions {
4
4
  noOverride: boolean;
5
5
  filters: ModerationCause[];
@@ -8,3 +8,4 @@ export interface DisplayRestrictions {
8
8
  informs: ModerationCause[];
9
9
  }
10
10
  export declare const getDisplayRestrictions: (decision: ModerationDecision, context: DisplayContext) => DisplayRestrictions;
11
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,cAAc,EAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnG,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,sBAAsB,gFA8JlC,CAAC"}
package/dist/ui.js CHANGED
@@ -133,10 +133,10 @@ export const getDisplayRestrictions = (decision, context) => {
133
133
  }
134
134
  return {
135
135
  noOverride,
136
- filters: filters.sort(sortByPriority),
137
- blurs: blurs.sort(sortByPriority),
138
- alerts: alerts.sort(sortByPriority),
139
- informs: informs.sort(sortByPriority),
136
+ filters: filters.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
137
+ blurs: blurs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
138
+ alerts: alerts.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
139
+ informs: informs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
140
140
  };
141
141
  };
142
142
  const sortByPriority = (a, b) => {
package/dist/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ import type { ComAtprotoLabelDefs } from '@atcute/atproto';
2
+ import type {
3
+ AppBskyActorDefs,
4
+ AppBskyEmbedRecord,
5
+ AppBskyFeedDefs,
6
+ AppBskyFeedPost,
7
+ AppBskyGraphDefs,
8
+ AppBskyNotificationListNotifications,
9
+ } from '@atcute/bluesky';
10
+ import type { $type, Did, GenericUri, Handle, ResourceUri } from '@atcute/lexicons';
11
+
12
+ const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq';
13
+
14
+ export const post = ({
15
+ text,
16
+ facets,
17
+ reply,
18
+ embed,
19
+ }: {
20
+ text: string;
21
+ facets?: AppBskyFeedPost.Main['facets'];
22
+ reply?: AppBskyFeedPost.ReplyRef;
23
+ embed?: AppBskyFeedPost.Main['embed'];
24
+ }): AppBskyFeedPost.Main => {
25
+ return {
26
+ $type: 'app.bsky.feed.post',
27
+ text,
28
+ facets,
29
+ reply,
30
+ embed,
31
+ langs: ['en'],
32
+ createdAt: new Date().toISOString(),
33
+ };
34
+ };
35
+
36
+ export const postView = ({
37
+ record,
38
+ author,
39
+ embed,
40
+ replyCount,
41
+ repostCount,
42
+ likeCount,
43
+ viewer,
44
+ labels,
45
+ }: {
46
+ record: AppBskyFeedPost.Main;
47
+ author: AppBskyActorDefs.ProfileViewBasic;
48
+ embed?: AppBskyFeedDefs.PostView['embed'];
49
+ replyCount?: number;
50
+ repostCount?: number;
51
+ likeCount?: number;
52
+ viewer?: AppBskyFeedDefs.ViewerState;
53
+ labels?: ComAtprotoLabelDefs.Label[];
54
+ }): $type.enforce<AppBskyFeedDefs.PostView> => {
55
+ return {
56
+ $type: 'app.bsky.feed.defs#postView',
57
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
58
+ cid: FAKE_CID,
59
+ author,
60
+ record,
61
+ embed,
62
+ replyCount,
63
+ repostCount,
64
+ likeCount,
65
+ indexedAt: new Date().toISOString(),
66
+ viewer,
67
+ labels,
68
+ };
69
+ };
70
+
71
+ export const embedRecordView = ({
72
+ record,
73
+ author,
74
+ labels,
75
+ }: {
76
+ record: AppBskyFeedPost.Main;
77
+ author: AppBskyActorDefs.ProfileViewBasic;
78
+ labels?: ComAtprotoLabelDefs.Label[];
79
+ }): $type.enforce<AppBskyEmbedRecord.View> => {
80
+ return {
81
+ $type: 'app.bsky.embed.record#view',
82
+ record: {
83
+ $type: 'app.bsky.embed.record#viewRecord',
84
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
85
+ cid: FAKE_CID,
86
+ author,
87
+ value: record,
88
+ labels,
89
+ indexedAt: new Date().toISOString(),
90
+ },
91
+ };
92
+ };
93
+
94
+ export const profileView = ({
95
+ handle,
96
+ displayName,
97
+ viewer,
98
+ labels,
99
+ }: {
100
+ handle: Handle;
101
+ displayName?: string;
102
+ viewer?: AppBskyActorDefs.ViewerState;
103
+ labels?: ComAtprotoLabelDefs.Label[];
104
+ }): $type.omit<AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic> => {
105
+ return {
106
+ did: `did:web:${handle}`,
107
+ handle,
108
+ displayName,
109
+ viewer,
110
+ labels,
111
+ };
112
+ };
113
+
114
+ export const actorViewerState = ({
115
+ muted,
116
+ mutedByList,
117
+ blockedBy,
118
+ blocking,
119
+ blockingByList,
120
+ following,
121
+ followedBy,
122
+ }: {
123
+ muted?: boolean;
124
+ mutedByList?: AppBskyGraphDefs.ListViewBasic;
125
+ blockedBy?: boolean;
126
+ blocking?: ResourceUri;
127
+ blockingByList?: AppBskyGraphDefs.ListViewBasic;
128
+ following?: ResourceUri;
129
+ followedBy?: ResourceUri;
130
+ }): AppBskyActorDefs.ViewerState => {
131
+ return {
132
+ muted,
133
+ mutedByList,
134
+ blockedBy,
135
+ blocking,
136
+ blockingByList,
137
+ following,
138
+ followedBy,
139
+ };
140
+ };
141
+
142
+ export const listViewBasic = ({ name }: { name: string }): AppBskyGraphDefs.ListViewBasic => {
143
+ return {
144
+ uri: 'at://did:plc:fake/app.bsky.graph.list/fake',
145
+ cid: FAKE_CID,
146
+ name,
147
+ purpose: 'app.bsky.graph.defs#modlist',
148
+ indexedAt: new Date().toISOString(),
149
+ };
150
+ };
151
+
152
+ export const replyNotification = ({
153
+ author,
154
+ record,
155
+ labels,
156
+ }: {
157
+ record: AppBskyFeedPost.Main;
158
+ author: AppBskyActorDefs.ProfileView;
159
+ labels?: ComAtprotoLabelDefs.Label[];
160
+ }): AppBskyNotificationListNotifications.Notification => {
161
+ return {
162
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
163
+ cid: FAKE_CID,
164
+ author,
165
+ reason: 'reply',
166
+ reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`,
167
+ record,
168
+ isRead: false,
169
+ indexedAt: new Date().toISOString(),
170
+ labels,
171
+ };
172
+ };
173
+
174
+ export const followNotification = ({
175
+ author,
176
+ subjectDid,
177
+ labels,
178
+ }: {
179
+ author: AppBskyActorDefs.ProfileView;
180
+ subjectDid: string;
181
+ labels?: ComAtprotoLabelDefs.Label[];
182
+ }): AppBskyNotificationListNotifications.Notification => {
183
+ return {
184
+ uri: `at://${author.did}/app.bsky.graph.follow/fake`,
185
+ cid: FAKE_CID,
186
+ author,
187
+ reason: 'follow',
188
+ record: {
189
+ $type: 'app.bsky.graph.follow',
190
+ createdAt: new Date().toISOString(),
191
+ subject: subjectDid,
192
+ },
193
+ isRead: false,
194
+ indexedAt: new Date().toISOString(),
195
+ labels,
196
+ };
197
+ };
198
+
199
+ export const label = ({
200
+ val,
201
+ uri,
202
+ src,
203
+ }: {
204
+ val: string;
205
+ uri: GenericUri;
206
+ src?: Did;
207
+ }): ComAtprotoLabelDefs.Label => {
208
+ return {
209
+ src: src || 'did:plc:fake-labeler',
210
+ uri,
211
+ val,
212
+ cts: new Date().toISOString(),
213
+ };
214
+ };
@@ -0,0 +1,259 @@
1
+ import type { ComAtprotoLabelDefs } from '@atcute/atproto';
2
+
3
+ import { expect } from 'vitest';
4
+
5
+ import { LabelPreference, type DisplayRestrictions, type ModerationOptions } from '../index.ts';
6
+
7
+ import * as m from './mock.ts';
8
+
9
+ export type ModerationTestSuiteResultFlag = 'filter' | 'blur' | 'alert' | 'inform' | 'noOverride';
10
+
11
+ export interface ModerationTestSuiteScenario {
12
+ cfg: string;
13
+ subject: 'post' | 'profile' | 'userlist' | 'feedgen';
14
+ author: string;
15
+ quoteAuthor?: string;
16
+ labels: {
17
+ post?: string[];
18
+ profile?: string[];
19
+ account?: string[];
20
+ quotedPost?: string[];
21
+ quotedAccount?: string[];
22
+ };
23
+ behaviors: {
24
+ profileList?: ModerationTestSuiteResultFlag[];
25
+ profileView?: ModerationTestSuiteResultFlag[];
26
+ profileMedia?: ModerationTestSuiteResultFlag[];
27
+ contentList?: ModerationTestSuiteResultFlag[];
28
+ contentView?: ModerationTestSuiteResultFlag[];
29
+ contentMedia?: ModerationTestSuiteResultFlag[];
30
+ };
31
+ }
32
+
33
+ export type SuiteUsers = Record<
34
+ string,
35
+ {
36
+ blocking: boolean;
37
+ blockingByList: boolean;
38
+ blockedBy: boolean;
39
+ muted: boolean;
40
+ mutedByList: boolean;
41
+ }
42
+ >;
43
+
44
+ export type SuiteConfigurations = Record<
45
+ string,
46
+ {
47
+ authed?: boolean;
48
+ adultContentEnabled?: boolean;
49
+ settings?: Record<string, LabelPreference>;
50
+ }
51
+ >;
52
+
53
+ export type SuiteScenarios = Record<string, ModerationTestSuiteScenario>;
54
+
55
+ expect.extend({
56
+ toBeModerationResult(
57
+ actual: DisplayRestrictions,
58
+ expected: ModerationTestSuiteResultFlag[] | undefined,
59
+ context = '',
60
+ stringifiedResult: string | undefined = undefined,
61
+ _ignoreCause = false,
62
+ ) {
63
+ const fail = (msg: string) => ({
64
+ pass: false,
65
+ message: () => `${msg}.${stringifiedResult ? ` Full result: ${stringifiedResult}` : ''}`,
66
+ });
67
+ // let cause = actual.causes?.type as string
68
+ // if (actual.cause?.type === 'label') {
69
+ // cause = `label:${actual.cause.labelDef.id}`
70
+ // } else if (actual.cause?.type === 'muted') {
71
+ // if (actual.cause.source.type === 'list') {
72
+ // cause = 'muted-by-list'
73
+ // }
74
+ // } else if (actual.cause?.type === 'blocking') {
75
+ // if (actual.cause.source.type === 'list') {
76
+ // cause = 'blocking-by-list'
77
+ // }
78
+ // }
79
+ if (!expected) {
80
+ // if (!ignoreCause && actual.cause) {
81
+ // return fail(`${context} expected to be a no-op, got ${cause}`)
82
+ // }
83
+ if (actual.informs.length > 0) {
84
+ return fail(`${context} expected to be a no-op, got inform=true`);
85
+ }
86
+ if (actual.alerts.length > 0) {
87
+ return fail(`${context} expected to be a no-op, got alert=true`);
88
+ }
89
+ if (actual.blurs.length > 0) {
90
+ return fail(`${context} expected to be a no-op, got blur=true`);
91
+ }
92
+ if (actual.filters.length > 0) {
93
+ return fail(`${context} expected to be a no-op, got filter=true`);
94
+ }
95
+ if (actual.noOverride) {
96
+ return fail(`${context} expected to be a no-op, got noOverride=true`);
97
+ }
98
+ } else {
99
+ // if (!ignoreCause && cause !== expected.cause) {
100
+ // return fail(`${context} expected to be ${expected.cause}, got ${cause}`)
101
+ // }
102
+ const expectedInform = expected.includes('inform');
103
+ if (!!(actual.informs.length > 0) !== expectedInform) {
104
+ return fail(
105
+ `${context} expected to be inform=${expectedInform}, got ${actual.informs.length > 0 || false}`,
106
+ );
107
+ }
108
+ const expectedAlert = expected.includes('alert');
109
+ if (!!(actual.alerts.length > 0) !== expectedAlert) {
110
+ return fail(
111
+ `${context} expected to be alert=${expectedAlert}, got ${actual.alerts.length > 0 || false}`,
112
+ );
113
+ }
114
+ const expectedBlur = expected.includes('blur');
115
+ if (!!(actual.blurs.length > 0) !== expectedBlur) {
116
+ return fail(
117
+ `${context} expected to be blur=${expectedBlur}, got ${actual.blurs.length > 0 || false}`,
118
+ );
119
+ }
120
+ const expectedFilter = expected.includes('filter');
121
+ if (!!(actual.filters.length > 0) !== expectedFilter) {
122
+ return fail(
123
+ `${context} expected to be filter=${expectedFilter}, got ${actual.filters.length > 0 || false}`,
124
+ );
125
+ }
126
+ const expectedNoOverride = expected.includes('noOverride');
127
+ if (!!actual.noOverride !== expectedNoOverride) {
128
+ return fail(
129
+ `${context} expected to be noOverride=${expectedNoOverride}, got ${actual.noOverride || false}`,
130
+ );
131
+ }
132
+ }
133
+ return { pass: true, message: () => '' };
134
+ },
135
+ });
136
+
137
+ declare module 'vitest' {
138
+ // oxlint-disable-next-line no-unused-vars -- required for module augmentation
139
+ interface Assertion<T = any> {
140
+ toBeModerationResult(
141
+ expected?: ModerationTestSuiteResultFlag[],
142
+ context?: string,
143
+ stringifiedResult?: string,
144
+ ignoreCause?: boolean,
145
+ ): void;
146
+ }
147
+
148
+ interface AsymmetricMatchers {
149
+ toBeModerationResult(
150
+ expected?: ModerationTestSuiteResultFlag[],
151
+ context?: string,
152
+ stringifiedResult?: string,
153
+ ignoreCause?: boolean,
154
+ ): any;
155
+ }
156
+ }
157
+
158
+ export class ModerationBehaviorSuiteRunner {
159
+ users: SuiteUsers;
160
+ configurations: SuiteConfigurations;
161
+ scenarios: SuiteScenarios;
162
+
163
+ constructor(users: SuiteUsers, configurations: SuiteConfigurations, scenarios: SuiteScenarios) {
164
+ this.users = users;
165
+ this.configurations = configurations;
166
+ this.scenarios = scenarios;
167
+ }
168
+
169
+ postScenario(scenario: ModerationTestSuiteScenario) {
170
+ if (scenario.subject !== 'post') {
171
+ throw new Error('Scenario subject must be "post"');
172
+ }
173
+ const author = this.profileView(scenario.author, scenario.labels);
174
+ return m.postView({
175
+ record: m.post({
176
+ text: 'Post text',
177
+ }),
178
+ author,
179
+ labels: (scenario.labels.post || []).map((val) =>
180
+ m.label({ val, uri: `at://${author.did}/app.bsky.feed.post/fake` }),
181
+ ),
182
+ embed: scenario.quoteAuthor
183
+ ? m.embedRecordView({
184
+ record: m.post({
185
+ text: 'Quoted post text',
186
+ }),
187
+ labels: (scenario.labels.quotedPost || []).map((val) =>
188
+ m.label({
189
+ val,
190
+ uri: `at://${author.did}/app.bsky.feed.post/fake`,
191
+ }),
192
+ ),
193
+ author: this.profileView(scenario.quoteAuthor, {
194
+ account: scenario.labels.quotedAccount,
195
+ }),
196
+ })
197
+ : undefined,
198
+ });
199
+ }
200
+
201
+ profileScenario(scenario: ModerationTestSuiteScenario) {
202
+ if (scenario.subject !== 'profile') {
203
+ throw new Error('Scenario subject must be "profile"');
204
+ }
205
+ return this.profileView(scenario.author, scenario.labels);
206
+ }
207
+
208
+ profileView(name: string, scenarioLabels: ModerationTestSuiteScenario['labels']) {
209
+ const def = this.users[name];
210
+
211
+ const labels: ComAtprotoLabelDefs.Label[] = [];
212
+ if (scenarioLabels.account) {
213
+ for (const l of scenarioLabels.account) {
214
+ labels.push(m.label({ val: l, uri: `did:web:${name}` }));
215
+ }
216
+ }
217
+ if (scenarioLabels.profile) {
218
+ for (const l of scenarioLabels.profile) {
219
+ labels.push(
220
+ m.label({
221
+ val: l,
222
+ uri: `at://did:web:${name}/app.bsky.actor.profile/self`,
223
+ }),
224
+ );
225
+ }
226
+ }
227
+
228
+ return m.profileView({
229
+ handle: `${name}.test`,
230
+ labels,
231
+ viewer: m.actorViewerState({
232
+ muted: def.muted || def.mutedByList,
233
+ mutedByList: def.mutedByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
234
+ blockedBy: def.blockedBy,
235
+ blocking:
236
+ def.blocking || def.blockingByList ? 'at://did:web:self.test/app.bsky.graph.block/fake' : undefined,
237
+ blockingByList: def.blockingByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
238
+ }),
239
+ });
240
+ }
241
+
242
+ moderationOptions(scenario: ModerationTestSuiteScenario): ModerationOptions {
243
+ return {
244
+ viewerDid: this.configurations[scenario.cfg].authed === false ? undefined : 'did:web:self.test',
245
+ prefs: {
246
+ adultContentEnabled: Boolean(this.configurations[scenario.cfg]?.adultContentEnabled),
247
+ globalLabelPrefs: this.configurations[scenario.cfg].settings || {},
248
+ prefsByLabelers: {
249
+ 'did:plc:fake-labeler': {
250
+ labelPrefs: {},
251
+ },
252
+ },
253
+ hiddenPosts: [],
254
+ keywordFilters: [],
255
+ temporaryMutes: [],
256
+ },
257
+ };
258
+ }
259
+ }
package/lib/behaviors.ts CHANGED
@@ -1,36 +1,39 @@
1
- export enum LabelTarget {
1
+ export const LabelTarget = {
2
2
  /** label is intended for account's content */
3
- Content = 'content',
3
+ Content: 'content',
4
4
  /** label is intended for account's profile */
5
- Profile = 'profile',
5
+ Profile: 'profile',
6
6
  /** label is intended for account's content and profile */
7
- Account = 'account',
8
- }
7
+ Account: 'account',
8
+ } as const;
9
+ export type LabelTarget = (typeof LabelTarget)[keyof typeof LabelTarget];
9
10
 
10
- export enum DisplayContext {
11
+ export const DisplayContext = {
11
12
  /** content in expanded view */
12
- ContentView = 'contentView',
13
+ ContentView: 'contentView',
13
14
  /** images or video contained in content */
14
- ContentMedia = 'contentMedia',
15
+ ContentMedia: 'contentMedia',
15
16
  /** content in a list/feed */
16
- ContentList = 'contentList',
17
+ ContentList: 'contentList',
17
18
 
18
19
  /** profile in expanded view */
19
- ProfileView = 'profileView',
20
+ ProfileView: 'profileView',
20
21
  /** profile's avatar or banner */
21
- ProfileMedia = 'profileMedia',
22
+ ProfileMedia: 'profileMedia',
22
23
  /** profile in a list */
23
- ProfileList = 'profileList',
24
- }
24
+ ProfileList: 'profileList',
25
+ } as const;
26
+ export type DisplayContext = (typeof DisplayContext)[keyof typeof DisplayContext];
25
27
 
26
- export enum ModerationAction {
28
+ export const ModerationAction = {
27
29
  /** should cause blurring */
28
- Blur = 'blur',
30
+ Blur: 'blur',
29
31
  /** should cause an alert */
30
- Alert = 'alert',
32
+ Alert: 'alert',
31
33
  /** should cause a notice */
32
- Inform = 'inform',
33
- }
34
+ Inform: 'inform',
35
+ } as const;
36
+ export type ModerationAction = (typeof ModerationAction)[keyof typeof ModerationAction];
34
37
 
35
38
  export type BehaviorMapping = { [C in DisplayContext]?: ModerationAction };
36
39
  export type LabelBehaviorMatrix = { [T in LabelTarget]: BehaviorMapping };