tangled
alpha
login
or
join now
quilling.dev
/
social-app
7
fork
atom
An ATproto social media client -- with an independent Appview.
7
fork
atom
overview
issues
pulls
pipelines
refactor: comment out file
for typecheck purposes
serenity
5 months ago
98de81a4
e703962e
+415
-415
1 changed file
expand all
collapse all
unified
split
src
view
com
util
post-embeds
QuoteEmbed.tsx
+415
-415
src/view/com/util/post-embeds/QuoteEmbed.tsx
···
1
1
-
import React from 'react'
2
2
-
import {
3
3
-
type StyleProp,
4
4
-
StyleSheet,
5
5
-
TouchableOpacity,
6
6
-
View,
7
7
-
type ViewStyle,
8
8
-
} from 'react-native'
9
9
-
import {
10
10
-
AppBskyEmbedExternal,
11
11
-
AppBskyEmbedImages,
12
12
-
AppBskyEmbedRecord,
13
13
-
AppBskyEmbedRecordWithMedia,
14
14
-
AppBskyEmbedVideo,
15
15
-
type AppBskyFeedDefs,
16
16
-
AppBskyFeedPost,
17
17
-
moderatePost,
18
18
-
type ModerationDecision,
19
19
-
RichText as RichTextAPI,
20
20
-
} from '@atproto/api'
21
21
-
import {AtUri} from '@atproto/api'
22
22
-
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
23
23
-
import {msg, Trans} from '@lingui/macro'
24
24
-
import {useLingui} from '@lingui/react'
25
25
-
import {useQueryClient} from '@tanstack/react-query'
26
26
-
27
27
-
import {HITSLOP_20} from '#/lib/constants'
28
28
-
import {usePalette} from '#/lib/hooks/usePalette'
29
29
-
import {InfoCircleIcon} from '#/lib/icons'
30
30
-
import {makeProfileLink} from '#/lib/routes/links'
31
31
-
import {s} from '#/lib/styles'
32
32
-
import {useDirectFetchRecords} from '#/state/preferences/direct-fetch-records'
33
33
-
import {useModerationOpts} from '#/state/preferences/moderation-opts'
34
34
-
import {useDirectFetchRecord} from '#/state/queries/direct-fetch-record'
35
35
-
import {precacheProfile} from '#/state/queries/profile'
36
36
-
import {useResolveLinkQuery} from '#/state/queries/resolve-link'
37
37
-
import {useSession} from '#/state/session'
38
38
-
import {atoms as a, useTheme} from '#/alf'
39
39
-
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash'
40
40
-
import {RichText} from '#/components/RichText'
41
41
-
import {SubtleWebHover} from '#/components/SubtleWebHover'
42
42
-
import * as bsky from '#/types/bsky'
43
43
-
import {ContentHider} from '../../../../components/moderation/ContentHider'
44
44
-
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
45
45
-
import {Link} from '../Link'
46
46
-
import {PostMeta} from '../PostMeta'
47
47
-
import {Text} from '../text/Text'
48
48
-
import {PostEmbeds} from '.'
49
49
-
import {type QuoteEmbedViewContext} from './types'
50
50
-
51
51
-
export function MaybeQuoteEmbed({
52
52
-
embed,
53
53
-
onOpen,
54
54
-
style,
55
55
-
allowNestedQuotes,
56
56
-
viewContext,
57
57
-
}: {
58
58
-
embed: AppBskyEmbedRecord.View
59
59
-
onOpen?: () => void
60
60
-
style?: StyleProp<ViewStyle>
61
61
-
allowNestedQuotes?: boolean
62
62
-
viewContext?: QuoteEmbedViewContext
63
63
-
}) {
64
64
-
const {_} = useLingui()
65
65
-
const t = useTheme()
66
66
-
const pal = usePalette('default')
67
67
-
const {currentAccount} = useSession()
68
68
-
69
69
-
const directFetchEnabled = useDirectFetchRecords()
70
70
-
const shouldDirectFetch =
71
71
-
(AppBskyEmbedRecord.isViewBlocked(embed.record) ||
72
72
-
AppBskyEmbedRecord.isViewDetached(embed.record)) &&
73
73
-
directFetchEnabled
74
74
-
75
75
-
const directRecord = useDirectFetchRecord({
76
76
-
uri:
77
77
-
AppBskyEmbedRecord.isViewBlocked(embed.record) ||
78
78
-
AppBskyEmbedRecord.isViewDetached(embed.record)
79
79
-
? embed.record.uri
80
80
-
: '',
81
81
-
enabled: shouldDirectFetch,
82
82
-
})
83
83
-
if (
84
84
-
AppBskyEmbedRecord.isViewRecord(embed.record) &&
85
85
-
AppBskyFeedPost.isRecord(embed.record.value) &&
86
86
-
AppBskyFeedPost.validateRecord(embed.record.value).success
87
87
-
) {
88
88
-
return (
89
89
-
<QuoteEmbedModerated
90
90
-
viewRecord={embed.record}
91
91
-
onOpen={onOpen}
92
92
-
style={style}
93
93
-
allowNestedQuotes={allowNestedQuotes}
94
94
-
viewContext={viewContext}
95
95
-
/>
96
96
-
)
97
97
-
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
98
98
-
const record = directRecord.data
99
99
-
if (record !== undefined) {
100
100
-
return (
101
101
-
<View>
102
102
-
<QuoteEmbedModerated
103
103
-
viewRecord={record}
104
104
-
onOpen={onOpen}
105
105
-
style={style}
106
106
-
allowNestedQuotes={allowNestedQuotes}
107
107
-
viewContext={viewContext}
108
108
-
visibilityLabel={_(msg`Blocked`)}
109
109
-
/>
110
110
-
</View>
111
111
-
)
112
112
-
}
113
113
-
114
114
-
return (
115
115
-
<View
116
116
-
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
117
117
-
<InfoCircleIcon size={18} style={pal.text} />
118
118
-
<Text type="lg" style={pal.text}>
119
119
-
{directFetchEnabled ? (
120
120
-
<Trans>Blocked...</Trans>
121
121
-
) : (
122
122
-
<Trans>Blocked</Trans>
123
123
-
)}
124
124
-
</Text>
125
125
-
</View>
126
126
-
)
127
127
-
} else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
128
128
-
return (
129
129
-
<View
130
130
-
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
131
131
-
<InfoCircleIcon size={18} style={pal.text} />
132
132
-
<Text type="lg" style={pal.text}>
133
133
-
<Trans>Deleted</Trans>
134
134
-
</Text>
135
135
-
</View>
136
136
-
)
137
137
-
} else if (AppBskyEmbedRecord.isViewDetached(embed.record)) {
138
138
-
const isViewerOwner = currentAccount?.did
139
139
-
? embed.record.uri.includes(currentAccount.did)
140
140
-
: false
141
141
-
142
142
-
const record = directRecord.data
143
143
-
if (record !== undefined) {
144
144
-
return (
145
145
-
<View>
146
146
-
<QuoteEmbedModerated
147
147
-
viewRecord={record}
148
148
-
onOpen={onOpen}
149
149
-
style={style}
150
150
-
allowNestedQuotes={allowNestedQuotes}
151
151
-
viewContext={viewContext}
152
152
-
visibilityLabel={
153
153
-
isViewerOwner ? _(`Removed by you`) : _(msg`Removed by author`)
154
154
-
}
155
155
-
/>
156
156
-
</View>
157
157
-
)
158
158
-
}
159
159
-
160
160
-
return (
161
161
-
<View
162
162
-
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
163
163
-
<InfoCircleIcon size={18} style={pal.text} />
164
164
-
<Text type="lg" style={pal.text}>
165
165
-
{isViewerOwner ? (
166
166
-
<Trans>Removed by you</Trans>
167
167
-
) : (
168
168
-
<Trans>Removed by author</Trans>
169
169
-
)}
170
170
-
{directFetchEnabled ? <Trans>...</Trans> : undefined}
171
171
-
</Text>
172
172
-
</View>
173
173
-
)
174
174
-
}
175
175
-
return null
176
176
-
}
177
177
-
178
178
-
function QuoteEmbedModerated({
179
179
-
viewRecord,
180
180
-
onOpen,
181
181
-
style,
182
182
-
allowNestedQuotes,
183
183
-
viewContext,
184
184
-
visibilityLabel,
185
185
-
}: {
186
186
-
viewRecord: AppBskyEmbedRecord.ViewRecord
187
187
-
onOpen?: () => void
188
188
-
style?: StyleProp<ViewStyle>
189
189
-
allowNestedQuotes?: boolean
190
190
-
viewContext?: QuoteEmbedViewContext
191
191
-
visibilityLabel?: string
192
192
-
}) {
193
193
-
const moderationOpts = useModerationOpts()
194
194
-
const postView = React.useMemo(
195
195
-
() => viewRecordToPostView(viewRecord),
196
196
-
[viewRecord],
197
197
-
)
198
198
-
const moderation = React.useMemo(() => {
199
199
-
return moderationOpts ? moderatePost(postView, moderationOpts) : undefined
200
200
-
}, [postView, moderationOpts])
201
201
-
202
202
-
return (
203
203
-
<QuoteEmbed
204
204
-
quote={postView}
205
205
-
moderation={moderation}
206
206
-
onOpen={onOpen}
207
207
-
style={style}
208
208
-
allowNestedQuotes={allowNestedQuotes}
209
209
-
viewContext={viewContext}
210
210
-
visibilityLabel={visibilityLabel}
211
211
-
/>
212
212
-
)
213
213
-
}
214
214
-
215
215
-
export function QuoteEmbed({
216
216
-
quote,
217
217
-
moderation,
218
218
-
onOpen,
219
219
-
style,
220
220
-
allowNestedQuotes,
221
221
-
visibilityLabel,
222
222
-
}: {
223
223
-
quote: AppBskyFeedDefs.PostView
224
224
-
moderation?: ModerationDecision
225
225
-
onOpen?: () => void
226
226
-
style?: StyleProp<ViewStyle>
227
227
-
allowNestedQuotes?: boolean
228
228
-
viewContext?: QuoteEmbedViewContext
229
229
-
visibilityLabel?: string
230
230
-
}) {
231
231
-
const t = useTheme()
232
232
-
const queryClient = useQueryClient()
233
233
-
const pal = usePalette('default')
234
234
-
const itemUrip = new AtUri(quote.uri)
235
235
-
const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
236
236
-
const itemTitle = `Post by ${quote.author.handle}`
237
237
-
238
238
-
const richText = React.useMemo(() => {
239
239
-
if (
240
240
-
!bsky.dangerousIsType<AppBskyFeedPost.Record>(
241
241
-
quote.record,
242
242
-
AppBskyFeedPost.isRecord,
243
243
-
)
244
244
-
)
245
245
-
return undefined
246
246
-
const {text, facets} = quote.record
247
247
-
return text.trim()
248
248
-
? new RichTextAPI({text: text, facets: facets})
249
249
-
: undefined
250
250
-
}, [quote.record])
251
251
-
252
252
-
const embed = React.useMemo(() => {
253
253
-
const e = quote.embed
254
254
-
255
255
-
if (allowNestedQuotes) {
256
256
-
return e
257
257
-
} else {
258
258
-
if (
259
259
-
AppBskyEmbedImages.isView(e) ||
260
260
-
AppBskyEmbedExternal.isView(e) ||
261
261
-
AppBskyEmbedVideo.isView(e)
262
262
-
) {
263
263
-
return e
264
264
-
} else if (
265
265
-
AppBskyEmbedRecordWithMedia.isView(e) &&
266
266
-
(AppBskyEmbedImages.isView(e.media) ||
267
267
-
AppBskyEmbedExternal.isView(e.media) ||
268
268
-
AppBskyEmbedVideo.isView(e.media))
269
269
-
) {
270
270
-
return e.media
271
271
-
}
272
272
-
}
273
273
-
}, [quote.embed, allowNestedQuotes])
274
274
-
275
275
-
const onBeforePress = React.useCallback(() => {
276
276
-
precacheProfile(queryClient, quote.author)
277
277
-
onOpen?.()
278
278
-
}, [queryClient, quote.author, onOpen])
279
279
-
280
280
-
const [hover, setHover] = React.useState(false)
281
281
-
return (
282
282
-
<View
283
283
-
onPointerEnter={() => {
284
284
-
setHover(true)
285
285
-
}}
286
286
-
onPointerLeave={() => {
287
287
-
setHover(false)
288
288
-
}}>
289
289
-
<ContentHider
290
290
-
modui={moderation?.ui('contentList')}
291
291
-
style={[
292
292
-
a.rounded_md,
293
293
-
a.p_md,
294
294
-
a.mt_sm,
295
295
-
a.border,
296
296
-
t.atoms.border_contrast_low,
297
297
-
style,
298
298
-
]}
299
299
-
childContainerStyle={[a.pt_sm]}>
300
300
-
<SubtleWebHover hover={hover} />
301
301
-
<Link
302
302
-
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
303
303
-
href={itemHref}
304
304
-
title={itemTitle}
305
305
-
onBeforePress={onBeforePress}>
306
306
-
<View pointerEvents="none">
307
307
-
{visibilityLabel !== undefined ? (
308
308
-
<View style={[styles.blockHeader, t.atoms.border_contrast_low]}>
309
309
-
<EyeSlashIcon size="sm" style={pal.text} />
310
310
-
<Text type="lg" style={pal.text}>
311
311
-
{visibilityLabel}
312
312
-
</Text>
313
313
-
</View>
314
314
-
) : undefined}
315
315
-
<PostMeta
316
316
-
author={quote.author}
317
317
-
moderation={moderation}
318
318
-
showAvatar
319
319
-
postHref={itemHref}
320
320
-
timestamp={quote.indexedAt}
321
321
-
/>
322
322
-
</View>
323
323
-
{moderation ? (
324
324
-
<PostAlerts
325
325
-
modui={moderation.ui('contentView')}
326
326
-
style={[a.py_xs]}
327
327
-
/>
328
328
-
) : null}
329
329
-
{richText ? (
330
330
-
<RichText
331
331
-
value={richText}
332
332
-
style={a.text_md}
333
333
-
numberOfLines={20}
334
334
-
disableLinks
335
335
-
/>
336
336
-
) : null}
337
337
-
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
338
338
-
</Link>
339
339
-
</ContentHider>
340
340
-
</View>
341
341
-
)
342
342
-
}
343
343
-
344
344
-
export function QuoteX({onRemove}: {onRemove: () => void}) {
345
345
-
const {_} = useLingui()
346
346
-
return (
347
347
-
<TouchableOpacity
348
348
-
style={[
349
349
-
a.absolute,
350
350
-
a.p_xs,
351
351
-
a.rounded_full,
352
352
-
a.align_center,
353
353
-
a.justify_center,
354
354
-
{
355
355
-
top: 16,
356
356
-
right: 10,
357
357
-
backgroundColor: 'rgba(0, 0, 0, 0.75)',
358
358
-
},
359
359
-
]}
360
360
-
onPress={onRemove}
361
361
-
accessibilityRole="button"
362
362
-
accessibilityLabel={_(msg`Remove quote`)}
363
363
-
accessibilityHint={_(msg`Removes quoted post`)}
364
364
-
onAccessibilityEscape={onRemove}
365
365
-
hitSlop={HITSLOP_20}>
366
366
-
<FontAwesomeIcon size={12} icon="xmark" style={s.white} />
367
367
-
</TouchableOpacity>
368
368
-
)
369
369
-
}
370
370
-
371
371
-
export function LazyQuoteEmbed({uri}: {uri: string}) {
372
372
-
const {data} = useResolveLinkQuery(uri)
373
373
-
const moderationOpts = useModerationOpts()
374
374
-
if (!data || data.type !== 'record' || data.kind !== 'post') {
375
375
-
return null
376
376
-
}
377
377
-
const moderation = moderationOpts
378
378
-
? moderatePost(data.view, moderationOpts)
379
379
-
: undefined
380
380
-
return <QuoteEmbed quote={data.view} moderation={moderation} />
381
381
-
}
382
382
-
383
383
-
function viewRecordToPostView(
384
384
-
viewRecord: AppBskyEmbedRecord.ViewRecord,
385
385
-
): AppBskyFeedDefs.PostView {
386
386
-
const {value, embeds, ...rest} = viewRecord
387
387
-
return {
388
388
-
...rest,
389
389
-
$type: 'app.bsky.feed.defs#postView',
390
390
-
record: value,
391
391
-
embed: embeds?.[0],
392
392
-
}
393
393
-
}
394
394
-
395
395
-
const styles = StyleSheet.create({
396
396
-
errorContainer: {
397
397
-
flexDirection: 'row',
398
398
-
alignItems: 'center',
399
399
-
gap: 4,
400
400
-
borderRadius: 8,
401
401
-
marginTop: 8,
402
402
-
paddingVertical: 14,
403
403
-
paddingHorizontal: 14,
404
404
-
borderWidth: StyleSheet.hairlineWidth,
405
405
-
},
406
406
-
alert: {
407
407
-
marginBottom: 6,
408
408
-
},
409
409
-
blockHeader: {
410
410
-
flexDirection: 'row',
411
411
-
alignItems: 'center',
412
412
-
gap: 4,
413
413
-
marginBottom: 8,
414
414
-
},
415
415
-
})
1
1
+
// import React from 'react'
2
2
+
// import {
3
3
+
// type StyleProp,
4
4
+
// StyleSheet,
5
5
+
// TouchableOpacity,
6
6
+
// View,
7
7
+
// type ViewStyle,
8
8
+
// } from 'react-native'
9
9
+
// import {
10
10
+
// AppBskyEmbedExternal,
11
11
+
// AppBskyEmbedImages,
12
12
+
// AppBskyEmbedRecord,
13
13
+
// AppBskyEmbedRecordWithMedia,
14
14
+
// AppBskyEmbedVideo,
15
15
+
// type AppBskyFeedDefs,
16
16
+
// AppBskyFeedPost,
17
17
+
// moderatePost,
18
18
+
// type ModerationDecision,
19
19
+
// RichText as RichTextAPI,
20
20
+
// } from '@atproto/api'
21
21
+
// import {AtUri} from '@atproto/api'
22
22
+
// import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
23
23
+
// import {msg, Trans} from '@lingui/macro'
24
24
+
// import {useLingui} from '@lingui/react'
25
25
+
// import {useQueryClient} from '@tanstack/react-query'
26
26
+
//
27
27
+
// import {HITSLOP_20} from '#/lib/constants'
28
28
+
// import {usePalette} from '#/lib/hooks/usePalette'
29
29
+
// import {InfoCircleIcon} from '#/lib/icons'
30
30
+
// import {makeProfileLink} from '#/lib/routes/links'
31
31
+
// import {s} from '#/lib/styles'
32
32
+
// import {useDirectFetchRecords} from '#/state/preferences/direct-fetch-records'
33
33
+
// import {useModerationOpts} from '#/state/preferences/moderation-opts'
34
34
+
// import {useDirectFetchRecord} from '#/state/queries/direct-fetch-record'
35
35
+
// import {precacheProfile} from '#/state/queries/profile'
36
36
+
// import {useResolveLinkQuery} from '#/state/queries/resolve-link'
37
37
+
// import {useSession} from '#/state/session'
38
38
+
// import {atoms as a, useTheme} from '#/alf'
39
39
+
// import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash'
40
40
+
// import {RichText} from '#/components/RichText'
41
41
+
// import {SubtleWebHover} from '#/components/SubtleWebHover'
42
42
+
// import * as bsky from '#/types/bsky'
43
43
+
// import {ContentHider} from '../../../../components/moderation/ContentHider'
44
44
+
// import {PostAlerts} from '../../../../components/moderation/PostAlerts'
45
45
+
// import {Link} from '../Link'
46
46
+
// import {PostMeta} from '../PostMeta'
47
47
+
// import {Text} from '../text/Text'
48
48
+
// import {PostEmbeds} from '.'
49
49
+
// import {type QuoteEmbedViewContext} from './types'
50
50
+
//
51
51
+
// export function MaybeQuoteEmbed({
52
52
+
// embed,
53
53
+
// onOpen,
54
54
+
// style,
55
55
+
// allowNestedQuotes,
56
56
+
// viewContext,
57
57
+
// }: {
58
58
+
// embed: AppBskyEmbedRecord.View
59
59
+
// onOpen?: () => void
60
60
+
// style?: StyleProp<ViewStyle>
61
61
+
// allowNestedQuotes?: boolean
62
62
+
// viewContext?: QuoteEmbedViewContext
63
63
+
// }) {
64
64
+
// const {_} = useLingui()
65
65
+
// const t = useTheme()
66
66
+
// const pal = usePalette('default')
67
67
+
// const {currentAccount} = useSession()
68
68
+
//
69
69
+
// const directFetchEnabled = useDirectFetchRecords()
70
70
+
// const shouldDirectFetch =
71
71
+
// (AppBskyEmbedRecord.isViewBlocked(embed.record) ||
72
72
+
// AppBskyEmbedRecord.isViewDetached(embed.record)) &&
73
73
+
// directFetchEnabled
74
74
+
//
75
75
+
// const directRecord = useDirectFetchRecord({
76
76
+
// uri:
77
77
+
// AppBskyEmbedRecord.isViewBlocked(embed.record) ||
78
78
+
// AppBskyEmbedRecord.isViewDetached(embed.record)
79
79
+
// ? embed.record.uri
80
80
+
// : '',
81
81
+
// enabled: shouldDirectFetch,
82
82
+
// })
83
83
+
// if (
84
84
+
// AppBskyEmbedRecord.isViewRecord(embed.record) &&
85
85
+
// AppBskyFeedPost.isRecord(embed.record.value) &&
86
86
+
// AppBskyFeedPost.validateRecord(embed.record.value).success
87
87
+
// ) {
88
88
+
// return (
89
89
+
// <QuoteEmbedModerated
90
90
+
// viewRecord={embed.record}
91
91
+
// onOpen={onOpen}
92
92
+
// style={style}
93
93
+
// allowNestedQuotes={allowNestedQuotes}
94
94
+
// viewContext={viewContext}
95
95
+
// />
96
96
+
// )
97
97
+
// } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
98
98
+
// const record = directRecord.data
99
99
+
// if (record !== undefined) {
100
100
+
// return (
101
101
+
// <View>
102
102
+
// <QuoteEmbedModerated
103
103
+
// viewRecord={record}
104
104
+
// onOpen={onOpen}
105
105
+
// style={style}
106
106
+
// allowNestedQuotes={allowNestedQuotes}
107
107
+
// viewContext={viewContext}
108
108
+
// visibilityLabel={_(msg`Blocked`)}
109
109
+
// />
110
110
+
// </View>
111
111
+
// )
112
112
+
// }
113
113
+
//
114
114
+
// return (
115
115
+
// <View
116
116
+
// style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
117
117
+
// <InfoCircleIcon size={18} style={pal.text} />
118
118
+
// <Text type="lg" style={pal.text}>
119
119
+
// {directFetchEnabled ? (
120
120
+
// <Trans>Blocked...</Trans>
121
121
+
// ) : (
122
122
+
// <Trans>Blocked</Trans>
123
123
+
// )}
124
124
+
// </Text>
125
125
+
// </View>
126
126
+
// )
127
127
+
// } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
128
128
+
// return (
129
129
+
// <View
130
130
+
// style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
131
131
+
// <InfoCircleIcon size={18} style={pal.text} />
132
132
+
// <Text type="lg" style={pal.text}>
133
133
+
// <Trans>Deleted</Trans>
134
134
+
// </Text>
135
135
+
// </View>
136
136
+
// )
137
137
+
// } else if (AppBskyEmbedRecord.isViewDetached(embed.record)) {
138
138
+
// const isViewerOwner = currentAccount?.did
139
139
+
// ? embed.record.uri.includes(currentAccount.did)
140
140
+
// : false
141
141
+
//
142
142
+
// const record = directRecord.data
143
143
+
// if (record !== undefined) {
144
144
+
// return (
145
145
+
// <View>
146
146
+
// <QuoteEmbedModerated
147
147
+
// viewRecord={record}
148
148
+
// onOpen={onOpen}
149
149
+
// style={style}
150
150
+
// allowNestedQuotes={allowNestedQuotes}
151
151
+
// viewContext={viewContext}
152
152
+
// visibilityLabel={
153
153
+
// isViewerOwner ? _(`Removed by you`) : _(msg`Removed by author`)
154
154
+
// }
155
155
+
// />
156
156
+
// </View>
157
157
+
// )
158
158
+
// }
159
159
+
//
160
160
+
// return (
161
161
+
// <View
162
162
+
// style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
163
163
+
// <InfoCircleIcon size={18} style={pal.text} />
164
164
+
// <Text type="lg" style={pal.text}>
165
165
+
// {isViewerOwner ? (
166
166
+
// <Trans>Removed by you</Trans>
167
167
+
// ) : (
168
168
+
// <Trans>Removed by author</Trans>
169
169
+
// )}
170
170
+
// {directFetchEnabled ? <Trans>...</Trans> : undefined}
171
171
+
// </Text>
172
172
+
// </View>
173
173
+
// )
174
174
+
// }
175
175
+
// return null
176
176
+
// }
177
177
+
//
178
178
+
// function QuoteEmbedModerated({
179
179
+
// viewRecord,
180
180
+
// onOpen,
181
181
+
// style,
182
182
+
// allowNestedQuotes,
183
183
+
// viewContext,
184
184
+
// visibilityLabel,
185
185
+
// }: {
186
186
+
// viewRecord: AppBskyEmbedRecord.ViewRecord
187
187
+
// onOpen?: () => void
188
188
+
// style?: StyleProp<ViewStyle>
189
189
+
// allowNestedQuotes?: boolean
190
190
+
// viewContext?: QuoteEmbedViewContext
191
191
+
// visibilityLabel?: string
192
192
+
// }) {
193
193
+
// const moderationOpts = useModerationOpts()
194
194
+
// const postView = React.useMemo(
195
195
+
// () => viewRecordToPostView(viewRecord),
196
196
+
// [viewRecord],
197
197
+
// )
198
198
+
// const moderation = React.useMemo(() => {
199
199
+
// return moderationOpts ? moderatePost(postView, moderationOpts) : undefined
200
200
+
// }, [postView, moderationOpts])
201
201
+
//
202
202
+
// return (
203
203
+
// <QuoteEmbed
204
204
+
// quote={postView}
205
205
+
// moderation={moderation}
206
206
+
// onOpen={onOpen}
207
207
+
// style={style}
208
208
+
// allowNestedQuotes={allowNestedQuotes}
209
209
+
// viewContext={viewContext}
210
210
+
// visibilityLabel={visibilityLabel}
211
211
+
// />
212
212
+
// )
213
213
+
// }
214
214
+
//
215
215
+
// export function QuoteEmbed({
216
216
+
// quote,
217
217
+
// moderation,
218
218
+
// onOpen,
219
219
+
// style,
220
220
+
// allowNestedQuotes,
221
221
+
// visibilityLabel,
222
222
+
// }: {
223
223
+
// quote: AppBskyFeedDefs.PostView
224
224
+
// moderation?: ModerationDecision
225
225
+
// onOpen?: () => void
226
226
+
// style?: StyleProp<ViewStyle>
227
227
+
// allowNestedQuotes?: boolean
228
228
+
// viewContext?: QuoteEmbedViewContext
229
229
+
// visibilityLabel?: string
230
230
+
// }) {
231
231
+
// const t = useTheme()
232
232
+
// const queryClient = useQueryClient()
233
233
+
// const pal = usePalette('default')
234
234
+
// const itemUrip = new AtUri(quote.uri)
235
235
+
// const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
236
236
+
// const itemTitle = `Post by ${quote.author.handle}`
237
237
+
//
238
238
+
// const richText = React.useMemo(() => {
239
239
+
// if (
240
240
+
// !bsky.dangerousIsType<AppBskyFeedPost.Record>(
241
241
+
// quote.record,
242
242
+
// AppBskyFeedPost.isRecord,
243
243
+
// )
244
244
+
// )
245
245
+
// return undefined
246
246
+
// const {text, facets} = quote.record
247
247
+
// return text.trim()
248
248
+
// ? new RichTextAPI({text: text, facets: facets})
249
249
+
// : undefined
250
250
+
// }, [quote.record])
251
251
+
//
252
252
+
// const embed = React.useMemo(() => {
253
253
+
// const e = quote.embed
254
254
+
//
255
255
+
// if (allowNestedQuotes) {
256
256
+
// return e
257
257
+
// } else {
258
258
+
// if (
259
259
+
// AppBskyEmbedImages.isView(e) ||
260
260
+
// AppBskyEmbedExternal.isView(e) ||
261
261
+
// AppBskyEmbedVideo.isView(e)
262
262
+
// ) {
263
263
+
// return e
264
264
+
// } else if (
265
265
+
// AppBskyEmbedRecordWithMedia.isView(e) &&
266
266
+
// (AppBskyEmbedImages.isView(e.media) ||
267
267
+
// AppBskyEmbedExternal.isView(e.media) ||
268
268
+
// AppBskyEmbedVideo.isView(e.media))
269
269
+
// ) {
270
270
+
// return e.media
271
271
+
// }
272
272
+
// }
273
273
+
// }, [quote.embed, allowNestedQuotes])
274
274
+
//
275
275
+
// const onBeforePress = React.useCallback(() => {
276
276
+
// precacheProfile(queryClient, quote.author)
277
277
+
// onOpen?.()
278
278
+
// }, [queryClient, quote.author, onOpen])
279
279
+
//
280
280
+
// const [hover, setHover] = React.useState(false)
281
281
+
// return (
282
282
+
// <View
283
283
+
// onPointerEnter={() => {
284
284
+
// setHover(true)
285
285
+
// }}
286
286
+
// onPointerLeave={() => {
287
287
+
// setHover(false)
288
288
+
// }}>
289
289
+
// <ContentHider
290
290
+
// modui={moderation?.ui('contentList')}
291
291
+
// style={[
292
292
+
// a.rounded_md,
293
293
+
// a.p_md,
294
294
+
// a.mt_sm,
295
295
+
// a.border,
296
296
+
// t.atoms.border_contrast_low,
297
297
+
// style,
298
298
+
// ]}
299
299
+
// childContainerStyle={[a.pt_sm]}>
300
300
+
// <SubtleWebHover hover={hover} />
301
301
+
// <Link
302
302
+
// hoverStyle={{borderColor: pal.colors.borderLinkHover}}
303
303
+
// href={itemHref}
304
304
+
// title={itemTitle}
305
305
+
// onBeforePress={onBeforePress}>
306
306
+
// <View pointerEvents="none">
307
307
+
// {visibilityLabel !== undefined ? (
308
308
+
// <View style={[styles.blockHeader, t.atoms.border_contrast_low]}>
309
309
+
// <EyeSlashIcon size="sm" style={pal.text} />
310
310
+
// <Text type="lg" style={pal.text}>
311
311
+
// {visibilityLabel}
312
312
+
// </Text>
313
313
+
// </View>
314
314
+
// ) : undefined}
315
315
+
// <PostMeta
316
316
+
// author={quote.author}
317
317
+
// moderation={moderation}
318
318
+
// showAvatar
319
319
+
// postHref={itemHref}
320
320
+
// timestamp={quote.indexedAt}
321
321
+
// />
322
322
+
// </View>
323
323
+
// {moderation ? (
324
324
+
// <PostAlerts
325
325
+
// modui={moderation.ui('contentView')}
326
326
+
// style={[a.py_xs]}
327
327
+
// />
328
328
+
// ) : null}
329
329
+
// {richText ? (
330
330
+
// <RichText
331
331
+
// value={richText}
332
332
+
// style={a.text_md}
333
333
+
// numberOfLines={20}
334
334
+
// disableLinks
335
335
+
// />
336
336
+
// ) : null}
337
337
+
// {embed && <PostEmbeds embed={embed} moderation={moderation} />}
338
338
+
// </Link>
339
339
+
// </ContentHider>
340
340
+
// </View>
341
341
+
// )
342
342
+
// }
343
343
+
//
344
344
+
// export function QuoteX({onRemove}: {onRemove: () => void}) {
345
345
+
// const {_} = useLingui()
346
346
+
// return (
347
347
+
// <TouchableOpacity
348
348
+
// style={[
349
349
+
// a.absolute,
350
350
+
// a.p_xs,
351
351
+
// a.rounded_full,
352
352
+
// a.align_center,
353
353
+
// a.justify_center,
354
354
+
// {
355
355
+
// top: 16,
356
356
+
// right: 10,
357
357
+
// backgroundColor: 'rgba(0, 0, 0, 0.75)',
358
358
+
// },
359
359
+
// ]}
360
360
+
// onPress={onRemove}
361
361
+
// accessibilityRole="button"
362
362
+
// accessibilityLabel={_(msg`Remove quote`)}
363
363
+
// accessibilityHint={_(msg`Removes quoted post`)}
364
364
+
// onAccessibilityEscape={onRemove}
365
365
+
// hitSlop={HITSLOP_20}>
366
366
+
// <FontAwesomeIcon size={12} icon="xmark" style={s.white} />
367
367
+
// </TouchableOpacity>
368
368
+
// )
369
369
+
// }
370
370
+
//
371
371
+
// export function LazyQuoteEmbed({uri}: {uri: string}) {
372
372
+
// const {data} = useResolveLinkQuery(uri)
373
373
+
// const moderationOpts = useModerationOpts()
374
374
+
// if (!data || data.type !== 'record' || data.kind !== 'post') {
375
375
+
// return null
376
376
+
// }
377
377
+
// const moderation = moderationOpts
378
378
+
// ? moderatePost(data.view, moderationOpts)
379
379
+
// : undefined
380
380
+
// return <QuoteEmbed quote={data.view} moderation={moderation} />
381
381
+
// }
382
382
+
//
383
383
+
// function viewRecordToPostView(
384
384
+
// viewRecord: AppBskyEmbedRecord.ViewRecord,
385
385
+
// ): AppBskyFeedDefs.PostView {
386
386
+
// const {value, embeds, ...rest} = viewRecord
387
387
+
// return {
388
388
+
// ...rest,
389
389
+
// $type: 'app.bsky.feed.defs#postView',
390
390
+
// record: value,
391
391
+
// embed: embeds?.[0],
392
392
+
// }
393
393
+
// }
394
394
+
//
395
395
+
// const styles = StyleSheet.create({
396
396
+
// errorContainer: {
397
397
+
// flexDirection: 'row',
398
398
+
// alignItems: 'center',
399
399
+
// gap: 4,
400
400
+
// borderRadius: 8,
401
401
+
// marginTop: 8,
402
402
+
// paddingVertical: 14,
403
403
+
// paddingHorizontal: 14,
404
404
+
// borderWidth: StyleSheet.hairlineWidth,
405
405
+
// },
406
406
+
// alert: {
407
407
+
// marginBottom: 6,
408
408
+
// },
409
409
+
// blockHeader: {
410
410
+
// flexDirection: 'row',
411
411
+
// alignItems: 'center',
412
412
+
// gap: 4,
413
413
+
// marginBottom: 8,
414
414
+
// },
415
415
+
// })