Merge branch 'main' into patch-3
commit
a1127bfcfc
|
@ -202,6 +202,7 @@ func serve(cctx *cli.Context) error {
|
|||
e.GET("/support/tos", server.WebGeneric)
|
||||
e.GET("/support/community-guidelines", server.WebGeneric)
|
||||
e.GET("/support/copyright", server.WebGeneric)
|
||||
e.GET("/intent/compose", server.WebGeneric)
|
||||
|
||||
// profile endpoints; only first populates info
|
||||
e.GET("/profile/:handleOrDID", server.WebProfile)
|
||||
|
|
|
@ -119,7 +119,7 @@ class ShareViewController: UIViewController {
|
|||
// extension does.
|
||||
if let dir = FileManager()
|
||||
.containerURL(
|
||||
forSecurityApplicationGroupIdentifier: "group.\(Bundle.main.bundleIdentifier?.replacingOccurrences(of: ".Share-with-Bluesky", with: "") ?? "")")
|
||||
forSecurityApplicationGroupIdentifier: "group.app.bsky")
|
||||
{
|
||||
let filePath = "\(dir.absoluteString)\(ProcessInfo.processInfo.globallyUniqueString).jpeg"
|
||||
|
||||
|
|
|
@ -108,14 +108,26 @@ class RNUITextView: UIView {
|
|||
fractionOfDistanceBetweenInsertionPoints: nil
|
||||
)
|
||||
|
||||
var lastUpperBound: String.Index? = nil
|
||||
for child in self.reactSubviews() {
|
||||
if let child = child as? RNUITextViewChild, let childText = child.text {
|
||||
let fullText = self.textView.attributedText.string
|
||||
let range = fullText.range(of: childText)
|
||||
|
||||
// We want to skip over the children we have already checked, otherwise we could run into
|
||||
// collisions of similar strings (i.e. links that get shortened to the same hostname but
|
||||
// different paths)
|
||||
let range = fullText.range(of: childText, options: [], range: (lastUpperBound ?? String.Index(utf16Offset: 0, in: fullText) )..<fullText.endIndex)
|
||||
|
||||
if let lowerBound = range?.lowerBound, let upperBound = range?.upperBound {
|
||||
if charIndex >= lowerBound.utf16Offset(in: fullText) && charIndex <= upperBound.utf16Offset(in: fullText) {
|
||||
let lowerOffset = lowerBound.utf16Offset(in: fullText)
|
||||
let upperOffset = upperBound.utf16Offset(in: fullText)
|
||||
|
||||
if charIndex >= lowerOffset,
|
||||
charIndex <= upperOffset
|
||||
{
|
||||
return child
|
||||
} else {
|
||||
lastUpperBound = upperBound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import {Platform} from 'react-native'
|
||||
import {isAndroid, isIOS, isNative, isWeb} from 'platform/detection'
|
||||
|
||||
export function web(value: any) {
|
||||
return Platform.select({
|
||||
web: value,
|
||||
})
|
||||
if (isWeb) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export function ios(value: any) {
|
||||
return Platform.select({
|
||||
ios: value,
|
||||
})
|
||||
if (isIOS) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export function android(value: any) {
|
||||
return Platform.select({
|
||||
android: value,
|
||||
})
|
||||
if (isAndroid) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export function native(value: any) {
|
||||
return Platform.select({
|
||||
native: value,
|
||||
})
|
||||
if (isNative) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||
import {useTheme, atoms as a, flatten} from '#/alf'
|
||||
import {Portal} from '#/components/Portal'
|
||||
import {createInput} from '#/components/forms/TextField'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
import {
|
||||
DialogOuterProps,
|
||||
|
@ -56,7 +57,7 @@ export function Outer({
|
|||
)
|
||||
|
||||
const close = React.useCallback<DialogControlProps['close']>(cb => {
|
||||
if (cb) {
|
||||
if (cb && typeof cb === 'function') {
|
||||
closeCallback.current = cb
|
||||
}
|
||||
sheet.current?.close()
|
||||
|
@ -74,8 +75,16 @@ export function Outer({
|
|||
const onChange = React.useCallback(
|
||||
(index: number) => {
|
||||
if (index === -1) {
|
||||
closeCallback.current?.()
|
||||
closeCallback.current = undefined
|
||||
try {
|
||||
closeCallback.current?.()
|
||||
} catch (e: any) {
|
||||
logger.error(`Dialog closeCallback failed`, {
|
||||
message: e.message,
|
||||
})
|
||||
} finally {
|
||||
closeCallback.current = undefined
|
||||
}
|
||||
|
||||
onClose?.()
|
||||
setOpenIndex(-1)
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ export function Close() {
|
|||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
onPress={close}
|
||||
onPress={() => close()}
|
||||
label={_(msg`Close active dialog`)}>
|
||||
<ButtonIcon icon={X} size="md" />
|
||||
</Button>
|
||||
|
|
|
@ -6,8 +6,13 @@ import {ViewStyleProp} from '#/alf'
|
|||
|
||||
type A11yProps = Required<AccessibilityProps>
|
||||
|
||||
export type DialogControlProps = {
|
||||
open: (options?: DialogControlOpenOptions) => void
|
||||
close: (callback?: () => void) => void
|
||||
}
|
||||
|
||||
export type DialogContextProps = {
|
||||
close: () => void
|
||||
close: DialogControlProps['close']
|
||||
}
|
||||
|
||||
export type DialogControlOpenOptions = {
|
||||
|
@ -20,11 +25,6 @@ export type DialogControlOpenOptions = {
|
|||
index?: number
|
||||
}
|
||||
|
||||
export type DialogControlProps = {
|
||||
open: (options?: DialogControlOpenOptions) => void
|
||||
close: (callback?: () => void) => void
|
||||
}
|
||||
|
||||
export type DialogOuterProps = {
|
||||
control: {
|
||||
ref: React.RefObject<DialogControlProps>
|
||||
|
|
|
@ -89,7 +89,7 @@ export function Cancel({
|
|||
color="secondary"
|
||||
size="small"
|
||||
label={_(msg`Cancel`)}
|
||||
onPress={close}>
|
||||
onPress={() => close()}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
|
|
|
@ -11,12 +11,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'outlineTag', targets: ['tag']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
['outlineTag'],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'outlineTag', targets: ['tag']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: ['outlineTag'],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -27,12 +27,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'inlineTag', targets: ['tag']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
['outlineTag'],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'inlineTag', targets: ['tag']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: ['outlineTag'],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -43,12 +43,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'inlineTag', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
['outlineTag'],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'inlineTag', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: ['outlineTag'],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -59,12 +59,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'inlineTag', targets: ['tag']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'inlineTag', targets: ['tag']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -80,12 +80,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: '希', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: '希', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -96,12 +96,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'politics', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'politics', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -112,12 +112,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'javascript', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'javascript', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -130,12 +130,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'javascript', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'javascript', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -146,12 +146,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'ai', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'ai', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -162,12 +162,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: 'brain', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'brain', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -178,12 +178,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: `:)`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `:)`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -197,23 +197,23 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: yay!`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'yay!', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'yay!', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: yay`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'yay', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'yay', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -226,24 +226,24 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: y!ppee`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'y!ppee', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'y!ppee', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
// single exclamation point, source has double
|
||||
it(`no match: y!ppee!`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'y!ppee!', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'y!ppee!', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -256,23 +256,23 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: S@assy`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'S@assy', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'S@assy', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: s@assy`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 's@assy', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 's@assy', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -286,12 +286,12 @@ describe(`hasMutedWord`, () => {
|
|||
|
||||
// case insensitive
|
||||
it(`match: new york times`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'new york times', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'new york times', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -304,23 +304,23 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: !command`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `!command`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `!command`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: command`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `command`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `command`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -331,12 +331,12 @@ describe(`hasMutedWord`, () => {
|
|||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
const match = hasMutedWord(
|
||||
[{value: `!command`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `!command`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -349,23 +349,23 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: e/acc`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `e/acc`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `e/acc`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: acc`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `acc`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `acc`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -378,45 +378,45 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: super-bad`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `super-bad`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `super-bad`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: super`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `super`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `super`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: super bad`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `super bad`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `super bad`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: superbad`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `superbad`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `superbad`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -429,47 +429,49 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: idk what this would be`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `idk what this would be`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `idk what this would be`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`no match: idk what this would be for`, () => {
|
||||
// extra word
|
||||
const match = hasMutedWord(
|
||||
[{value: `idk what this would be for`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [
|
||||
{value: `idk what this would be for`, targets: ['content']},
|
||||
],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
||||
it(`match: idk`, () => {
|
||||
// extra word
|
||||
const match = hasMutedWord(
|
||||
[{value: `idk`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `idk`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: idkwhatthiswouldbe`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `idkwhatthiswouldbe`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `idkwhatthiswouldbe`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(false)
|
||||
})
|
||||
|
@ -482,45 +484,45 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: context(iykyk)`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `context(iykyk)`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `context(iykyk)`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: context`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `context`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `context`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: iykyk`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `iykyk`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `iykyk`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: (iykyk)`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `(iykyk)`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `(iykyk)`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -533,12 +535,12 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: 🦋`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: `🦋`, targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: `🦋`, targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
@ -553,23 +555,46 @@ describe(`hasMutedWord`, () => {
|
|||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
it(`match: stop worrying`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'stop worrying', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'stop worrying', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
||||
it(`match: turtles, or how`, () => {
|
||||
const match = hasMutedWord(
|
||||
[{value: 'turtles, or how', targets: ['content']}],
|
||||
rt.text,
|
||||
rt.facets,
|
||||
[],
|
||||
)
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'turtles, or how', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe(`languages without spaces`, () => {
|
||||
// I love turtles, or how I learned to stop worrying and love the internet
|
||||
describe(`私はカメが好きです、またはどのようにして心配するのをやめてインターネットを愛するようになったのか`, () => {
|
||||
const rt = new RichText({
|
||||
text: `私はカメが好きです、またはどのようにして心配するのをやめてインターネットを愛するようになったのか`,
|
||||
})
|
||||
rt.detectFacetsWithoutResolution()
|
||||
|
||||
// internet
|
||||
it(`match: インターネット`, () => {
|
||||
const match = hasMutedWord({
|
||||
mutedWords: [{value: 'インターネット', targets: ['content']}],
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
outlineTags: [],
|
||||
languages: ['ja'],
|
||||
})
|
||||
|
||||
expect(match).toBe(true)
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as Linking from 'expo-linking'
|
|||
import {isNative} from 'platform/detection'
|
||||
import {useComposerControls} from 'state/shell'
|
||||
import {useSession} from 'state/session'
|
||||
import {useCloseAllActiveElements} from 'state/util'
|
||||
|
||||
type IntentType = 'compose'
|
||||
|
||||
|
@ -42,6 +43,7 @@ export function useIntentHandler() {
|
|||
}
|
||||
|
||||
function useComposeIntent() {
|
||||
const closeAllActiveElements = useCloseAllActiveElements()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
|
@ -55,6 +57,8 @@ function useComposeIntent() {
|
|||
}) => {
|
||||
if (!hasSession) return
|
||||
|
||||
closeAllActiveElements()
|
||||
|
||||
const imageUris = imageUrisStr
|
||||
?.split(',')
|
||||
.filter(part => {
|
||||
|
@ -82,6 +86,6 @@ function useComposeIntent() {
|
|||
})
|
||||
}, 500)
|
||||
},
|
||||
[openComposer, hasSession],
|
||||
[hasSession, closeAllActiveElements, openComposer],
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,12 +21,34 @@ const REGEX = {
|
|||
WORD_BOUNDARY: /[\s\n\t\r\f\v]+?/g,
|
||||
}
|
||||
|
||||
export function hasMutedWord(
|
||||
mutedWords: AppBskyActorDefs.MutedWord[],
|
||||
text: string,
|
||||
facets?: AppBskyRichtextFacet.Main[],
|
||||
outlineTags?: string[],
|
||||
) {
|
||||
/**
|
||||
* List of 2-letter lang codes for languages that either don't use spaces, or
|
||||
* don't use spaces in a way conducive to word-based filtering.
|
||||
*
|
||||
* For these, we use a simple `String.includes` to check for a match.
|
||||
*/
|
||||
const LANGUAGE_EXCEPTIONS = [
|
||||
'ja', // Japanese
|
||||
'zh', // Chinese
|
||||
'ko', // Korean
|
||||
'th', // Thai
|
||||
'vi', // Vietnamese
|
||||
]
|
||||
|
||||
export function hasMutedWord({
|
||||
mutedWords,
|
||||
text,
|
||||
facets,
|
||||
outlineTags,
|
||||
languages,
|
||||
}: {
|
||||
mutedWords: AppBskyActorDefs.MutedWord[]
|
||||
text: string
|
||||
facets?: AppBskyRichtextFacet.Main[]
|
||||
outlineTags?: string[]
|
||||
languages?: string[]
|
||||
}) {
|
||||
const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '')
|
||||
const tags = ([] as string[])
|
||||
.concat(outlineTags || [])
|
||||
.concat(
|
||||
|
@ -48,8 +70,9 @@ export function hasMutedWord(
|
|||
if (tags.includes(mutedWord)) return true
|
||||
// rest of the checks are for `content` only
|
||||
if (!mute.targets.includes('content')) continue
|
||||
// single character, has to use includes
|
||||
if (mutedWord.length === 1 && postText.includes(mutedWord)) return true
|
||||
// single character or other exception, has to use includes
|
||||
if ((mutedWord.length === 1 || exception) && postText.includes(mutedWord))
|
||||
return true
|
||||
// too long
|
||||
if (mutedWord.length > postText.length) continue
|
||||
// exact match
|
||||
|
@ -134,19 +157,28 @@ export function moderatePost_wrapped(
|
|||
}
|
||||
|
||||
if (AppBskyFeedPost.isRecord(subject.record)) {
|
||||
let muted = hasMutedWord(
|
||||
let muted = hasMutedWord({
|
||||
mutedWords,
|
||||
subject.record.text,
|
||||
subject.record.facets || [],
|
||||
subject.record.tags || [],
|
||||
)
|
||||
text: subject.record.text,
|
||||
facets: subject.record.facets || [],
|
||||
outlineTags: subject.record.tags || [],
|
||||
languages: subject.record.langs,
|
||||
})
|
||||
|
||||
if (
|
||||
subject.record.embed &&
|
||||
AppBskyEmbedImages.isMain(subject.record.embed)
|
||||
) {
|
||||
for (const image of subject.record.embed.images) {
|
||||
muted = muted || hasMutedWord(mutedWords, image.alt, [], [])
|
||||
muted =
|
||||
muted ||
|
||||
hasMutedWord({
|
||||
mutedWords,
|
||||
text: image.alt,
|
||||
facets: [],
|
||||
outlineTags: [],
|
||||
languages: subject.record.langs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,17 +204,25 @@ export function moderatePost_wrapped(
|
|||
if (AppBskyFeedPost.isRecord(subject.embed.record.value)) {
|
||||
embedHidden =
|
||||
embedHidden ||
|
||||
hasMutedWord(
|
||||
hasMutedWord({
|
||||
mutedWords,
|
||||
subject.embed.record.value.text,
|
||||
subject.embed.record.value.facets,
|
||||
subject.embed.record.value.tags,
|
||||
)
|
||||
text: subject.embed.record.value.text,
|
||||
facets: subject.embed.record.value.facets,
|
||||
outlineTags: subject.embed.record.value.tags,
|
||||
languages: subject.embed.record.value.langs,
|
||||
})
|
||||
|
||||
if (AppBskyEmbedImages.isMain(subject.embed.record.value.embed)) {
|
||||
for (const image of subject.embed.record.value.embed.images) {
|
||||
embedHidden =
|
||||
embedHidden || hasMutedWord(mutedWords, image.alt, [], [])
|
||||
embedHidden ||
|
||||
hasMutedWord({
|
||||
mutedWords,
|
||||
text: image.alt,
|
||||
facets: [],
|
||||
outlineTags: [],
|
||||
languages: subject.embed.record.value.langs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ export function AdultContentEnabledPref({
|
|||
</Trans>
|
||||
</Prompt.Description>
|
||||
<Prompt.Actions>
|
||||
<Prompt.Action onPress={prompt.close}>
|
||||
<Prompt.Action onPress={() => prompt.close()}>
|
||||
<Trans>OK</Trans>
|
||||
</Prompt.Action>
|
||||
</Prompt.Actions>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
useQuery,
|
||||
useInfiniteQuery,
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import {
|
||||
AtUri,
|
||||
|
@ -15,7 +13,6 @@ import {
|
|||
AppBskyUnspeccedGetPopularFeedGenerators,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {router} from '#/routes'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
|
@ -219,83 +216,59 @@ const FOLLOWING_FEED_STUB: FeedSourceInfo = {
|
|||
likeUri: '',
|
||||
}
|
||||
|
||||
export function usePinnedFeedsInfos(): {
|
||||
feeds: FeedSourceInfo[]
|
||||
hasPinnedCustom: boolean
|
||||
isLoading: boolean
|
||||
} {
|
||||
const queryClient = useQueryClient()
|
||||
const [tabs, setTabs] = React.useState<FeedSourceInfo[]>([
|
||||
FOLLOWING_FEED_STUB,
|
||||
])
|
||||
const [isLoading, setLoading] = React.useState(true)
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
export function usePinnedFeedsInfos() {
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const pinnedUris = preferences?.feeds?.pinned ?? []
|
||||
|
||||
const hasPinnedCustom = React.useMemo<boolean>(() => {
|
||||
return tabs.some(tab => tab !== FOLLOWING_FEED_STUB)
|
||||
}, [tabs])
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
enabled: !isLoadingPrefs,
|
||||
queryKey: ['pinnedFeedsInfos', pinnedUris.join(',')],
|
||||
queryFn: async () => {
|
||||
let resolved = new Map()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!preferences?.feeds?.pinned) return
|
||||
const uris = preferences.feeds.pinned
|
||||
|
||||
async function fetchFeedInfo() {
|
||||
const reqs = []
|
||||
|
||||
for (const uri of uris) {
|
||||
const cached = queryClient.getQueryData<FeedSourceInfo>(
|
||||
feedSourceInfoQueryKey({uri}),
|
||||
)
|
||||
|
||||
if (cached) {
|
||||
reqs.push(cached)
|
||||
} else {
|
||||
reqs.push(
|
||||
(async () => {
|
||||
// these requests can fail, need to filter those out
|
||||
try {
|
||||
return await queryClient.fetchQuery({
|
||||
staleTime: STALE.SECONDS.FIFTEEN,
|
||||
queryKey: feedSourceInfoQueryKey({uri}),
|
||||
queryFn: async () => {
|
||||
const type = getFeedTypeFromUri(uri)
|
||||
|
||||
if (type === 'feed') {
|
||||
const res =
|
||||
await getAgent().app.bsky.feed.getFeedGenerator({
|
||||
feed: uri,
|
||||
})
|
||||
return hydrateFeedGenerator(res.data.view)
|
||||
} else {
|
||||
const res = await getAgent().app.bsky.graph.getList({
|
||||
list: uri,
|
||||
limit: 1,
|
||||
})
|
||||
return hydrateList(res.data.list)
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
// expected failure
|
||||
logger.info(`usePinnedFeedsInfos: failed to fetch ${uri}`, {
|
||||
error: e,
|
||||
})
|
||||
}
|
||||
})(),
|
||||
)
|
||||
}
|
||||
// Get all feeds. We can do this in a batch.
|
||||
const feedUris = pinnedUris.filter(
|
||||
uri => getFeedTypeFromUri(uri) === 'feed',
|
||||
)
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (feedUris.length > 0) {
|
||||
feedsPromise = getAgent()
|
||||
.app.bsky.feed.getFeedGenerators({
|
||||
feeds: feedUris,
|
||||
})
|
||||
.then(res => {
|
||||
for (let feedView of res.data.feeds) {
|
||||
resolved.set(feedView.uri, hydrateFeedGenerator(feedView))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const views = (await Promise.all(reqs)).filter(
|
||||
Boolean,
|
||||
) as FeedSourceInfo[]
|
||||
// Get all lists. This currently has to be done individually.
|
||||
const listUris = pinnedUris.filter(
|
||||
uri => getFeedTypeFromUri(uri) === 'list',
|
||||
)
|
||||
const listsPromises = listUris.map(listUri =>
|
||||
getAgent()
|
||||
.app.bsky.graph.getList({
|
||||
list: listUri,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
resolved.set(listView.uri, hydrateList(listView))
|
||||
}),
|
||||
)
|
||||
|
||||
setTabs([FOLLOWING_FEED_STUB].concat(views))
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
fetchFeedInfo()
|
||||
}, [queryClient, setTabs, preferences?.feeds?.pinned])
|
||||
|
||||
return {feeds: tabs, hasPinnedCustom, isLoading}
|
||||
// The returned result will have the original order.
|
||||
const result = [FOLLOWING_FEED_STUB]
|
||||
await Promise.allSettled([feedsPromise, ...listsPromises])
|
||||
for (let pinnedUri of pinnedUris) {
|
||||
if (resolved.has(pinnedUri)) {
|
||||
result.push(resolved.get(pinnedUri))
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {useLightboxControls} from './lightbox'
|
|||
import {useModalControls} from './modals'
|
||||
import {useComposerControls} from './shell/composer'
|
||||
import {useSetDrawerOpen} from './shell/drawer-open'
|
||||
import {useDialogStateControlContext} from 'state/dialogs'
|
||||
|
||||
/**
|
||||
* returns true if something was closed
|
||||
|
@ -35,11 +36,19 @@ export function useCloseAllActiveElements() {
|
|||
const {closeLightbox} = useLightboxControls()
|
||||
const {closeAllModals} = useModalControls()
|
||||
const {closeComposer} = useComposerControls()
|
||||
const {closeAllDialogs: closeAlfDialogs} = useDialogStateControlContext()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
return useCallback(() => {
|
||||
closeLightbox()
|
||||
closeAllModals()
|
||||
closeComposer()
|
||||
closeAlfDialogs()
|
||||
setDrawerOpen(false)
|
||||
}, [closeLightbox, closeAllModals, closeComposer, setDrawerOpen])
|
||||
}, [
|
||||
closeLightbox,
|
||||
closeAllModals,
|
||||
closeComposer,
|
||||
closeAlfDialogs,
|
||||
setDrawerOpen,
|
||||
])
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {HomeHeaderLayout} from './HomeHeaderLayout'
|
||||
import {usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||
import {FeedSourceInfo} from '#/state/queries/feed'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {isWeb} from 'platform/detection'
|
||||
|
@ -9,15 +9,22 @@ import {TabBar} from '../pager/TabBar'
|
|||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
|
||||
export function HomeHeader(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
props: RenderTabBarFnProps & {
|
||||
testID?: string
|
||||
onPressSelected: () => void
|
||||
feeds: FeedSourceInfo[]
|
||||
},
|
||||
) {
|
||||
const {feeds} = props
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
|
||||
const pal = usePalette('default')
|
||||
|
||||
const hasPinnedCustom = React.useMemo<boolean>(() => {
|
||||
return feeds.some(tab => tab.uri !== '')
|
||||
}, [feeds])
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
const pinnedNames = feeds.map(f => f.displayName)
|
||||
|
||||
if (!hasPinnedCustom) {
|
||||
return pinnedNames.concat('Feeds ✨')
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@ import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
|||
import {emitSoftReset} from '#/state/events'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
|
||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||
export function HomeScreen(props: Props) {
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
|
||||
const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
|
||||
usePinnedFeedsInfos()
|
||||
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
|
||||
return (
|
||||
|
@ -66,6 +67,8 @@ function HomeScreenReady({
|
|||
const selectedIndex = Math.max(0, maybeFoundIndex)
|
||||
const selectedFeed = allFeeds[selectedIndex]
|
||||
|
||||
useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
|
||||
|
||||
const pagerRef = React.useRef<PagerRef>(null)
|
||||
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
|
||||
React.useLayoutEffect(() => {
|
||||
|
@ -124,10 +127,11 @@ function HomeScreenReady({
|
|||
onSelect={props.onSelect}
|
||||
testID="homeScreenFeedTabs"
|
||||
onPressSelected={onPressSelected}
|
||||
feeds={pinnedFeedInfos}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[onPressSelected],
|
||||
[onPressSelected, pinnedFeedInfos],
|
||||
)
|
||||
|
||||
const renderFollowingEmptyState = React.useCallback(() => {
|
||||
|
|
|
@ -15,7 +15,7 @@ import {emitSoftReset} from '#/state/events'
|
|||
export function DesktopFeeds() {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {feeds: pinnedFeedInfos} = usePinnedFeedsInfos()
|
||||
const {data: pinnedFeedInfos} = usePinnedFeedsInfos()
|
||||
const selectedFeed = useSelectedFeed()
|
||||
const setSelectedFeed = useSetSelectedFeed()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
@ -25,7 +25,9 @@ export function DesktopFeeds() {
|
|||
}
|
||||
return getCurrentRoute(state)
|
||||
})
|
||||
|
||||
if (!pinnedFeedInfos) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<View style={[styles.container, pal.view]}>
|
||||
{pinnedFeedInfos.map(feedInfo => {
|
||||
|
|
Loading…
Reference in New Issue