1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
import {type AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api'
import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
import * as bsky from '#/types/bsky'
export function threadgateViewToAllowUISetting(
threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
): ThreadgateAllowUISetting[] {
// Validate the record for clarity, since backwards compat code is a little confusing
const threadgate =
threadgateView &&
bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord)
? threadgateView.record
: undefined
return threadgateRecordToAllowUISetting(threadgate)
}
/**
* Converts a full {@link AppBskyFeedThreadgate.Record} to a list of
* {@link ThreadgateAllowUISetting}, for use by app UI.
*/
export function threadgateRecordToAllowUISetting(
threadgate: AppBskyFeedThreadgate.Record | undefined,
): ThreadgateAllowUISetting[] {
/*
* If `threadgate` doesn't exist (default), or if `threadgate.allow === undefined`, it means
* anyone can reply.
*
* If `threadgate.allow === []` it means no one can reply, and we translate to UI code
* here. This was a historical choice, and we have no lexicon representation
* for 'replies disabled' other than an empty array.
*/
if (!threadgate || threadgate.allow === undefined) {
return [{type: 'everybody'}]
}
if (threadgate.allow.length === 0) {
return [{type: 'nobody'}]
}
const settings: ThreadgateAllowUISetting[] = threadgate.allow
.map(allow => {
let setting: ThreadgateAllowUISetting | undefined
if (AppBskyFeedThreadgate.isMentionRule(allow)) {
setting = {type: 'mention'}
} else if (AppBskyFeedThreadgate.isFollowingRule(allow)) {
setting = {type: 'following'}
} else if (AppBskyFeedThreadgate.isListRule(allow)) {
setting = {type: 'list', list: allow.list}
} else if (AppBskyFeedThreadgate.isFollowerRule(allow)) {
setting = {type: 'followers'}
}
return setting
})
.filter(n => !!n)
return settings
}
/**
* Converts an array of {@link ThreadgateAllowUISetting} to the `allow` prop on
* {@link AppBskyFeedThreadgate.Record}.
*
* If the `allow` property on the record is undefined, we infer that to mean
* that everyone can reply. If it's an empty array, we infer that to mean that
* no one can reply.
*/
export function threadgateAllowUISettingToAllowRecordValue(
threadgate: ThreadgateAllowUISetting[],
): AppBskyFeedThreadgate.Record['allow'] {
if (threadgate.find(v => v.type === 'everybody')) {
return undefined
}
let allow: Exclude<AppBskyFeedThreadgate.Record['allow'], undefined> = []
if (!threadgate.find(v => v.type === 'nobody')) {
for (const rule of threadgate) {
if (rule.type === 'mention') {
allow.push({$type: 'app.bsky.feed.threadgate#mentionRule'})
} else if (rule.type === 'following') {
allow.push({$type: 'app.bsky.feed.threadgate#followingRule'})
} else if (rule.type === 'followers') {
allow.push({$type: 'app.bsky.feed.threadgate#followerRule'})
} else if (rule.type === 'list') {
allow.push({
$type: 'app.bsky.feed.threadgate#listRule',
list: rule.list,
})
}
}
}
return allow
}
/**
* Merges two {@link AppBskyFeedThreadgate.Record} objects, combining their
* `allow` and `hiddenReplies` arrays and de-deduplicating them.
*
* Note: `allow` can be undefined here, be sure you don't accidentally set it
* to an empty array. See other comments in this file.
*/
export function mergeThreadgateRecords(
prev: AppBskyFeedThreadgate.Record,
next: Partial<AppBskyFeedThreadgate.Record>,
): AppBskyFeedThreadgate.Record {
// can be undefined if everyone can reply!
const allow: AppBskyFeedThreadgate.Record['allow'] | undefined =
prev.allow || next.allow
? [...(prev.allow || []), ...(next.allow || [])].filter(
(v, i, a) => a.findIndex(t => t.$type === v.$type) === i,
)
: undefined
const hiddenReplies = Array.from(
new Set([...(prev.hiddenReplies || []), ...(next.hiddenReplies || [])]),
)
return createThreadgateRecord({
post: prev.post,
allow, // can be undefined!
hiddenReplies,
})
}
/**
* Create a new {@link AppBskyFeedThreadgate.Record} object with the given
* properties.
*/
export function createThreadgateRecord(
threadgate: Partial<AppBskyFeedThreadgate.Record>,
): AppBskyFeedThreadgate.Record {
if (!threadgate.post) {
throw new Error('Cannot create a threadgate record without a post URI')
}
return {
$type: 'app.bsky.feed.threadgate',
post: threadgate.post,
createdAt: new Date().toISOString(),
allow: threadgate.allow, // can be undefined!
hiddenReplies: threadgate.hiddenReplies || [],
}
}
|