+2
-2
app.config.js
+2
-2
app.config.js
+1
-3
bskyembed/tailwind.config.cjs
+1
-3
bskyembed/tailwind.config.cjs
-1
bskyembed/tsconfig.snippet.json
-1
bskyembed/tsconfig.snippet.json
+11
-8
bskylink/src/db/index.ts
+11
-8
bskylink/src/db/index.ts
···
1
import assert from 'assert'
2
import {
3
Kysely,
4
-
KyselyPlugin,
5
Migrator,
6
-
PluginTransformQueryArgs,
7
-
PluginTransformResultArgs,
8
PostgresDialect,
9
-
QueryResult,
10
-
RootOperationNode,
11
-
UnknownRow,
12
} from 'kysely'
13
import {default as Pg} from 'pg'
14
15
import {dbLogger as log} from '../logger.js'
16
import {default as migrations} from './migrations/index.js'
17
import {DbMigrationProvider} from './migrations/provider.js'
18
-
import {DbSchema} from './schema.js'
19
20
export class Database {
21
migrator: Migrator
22
destroyed = false
23
24
-
constructor(public db: Kysely<DbSchema>, public cfg: PgConfig) {
25
this.migrator = new Migrator({
26
db,
27
migrationTableSchema: cfg.schema,
···
1
import assert from 'assert'
2
import {
3
Kysely,
4
+
type KyselyPlugin,
5
Migrator,
6
+
type PluginTransformQueryArgs,
7
+
type PluginTransformResultArgs,
8
PostgresDialect,
9
+
type QueryResult,
10
+
type RootOperationNode,
11
+
type UnknownRow,
12
} from 'kysely'
13
import {default as Pg} from 'pg'
14
15
import {dbLogger as log} from '../logger.js'
16
import {default as migrations} from './migrations/index.js'
17
import {DbMigrationProvider} from './migrations/provider.js'
18
+
import {type DbSchema} from './schema.js'
19
20
export class Database {
21
migrator: Migrator
22
destroyed = false
23
24
+
constructor(
25
+
public db: Kysely<DbSchema>,
26
+
public cfg: PgConfig,
27
+
) {
28
this.migrator = new Migrator({
29
db,
30
migrationTableSchema: cfg.schema,
+7
-4
bskylink/src/index.ts
+7
-4
bskylink/src/index.ts
···
1
import events from 'node:events'
2
-
import http from 'node:http'
3
4
import cors from 'cors'
5
import express from 'express'
6
-
import {createHttpTerminator, HttpTerminator} from 'http-terminator'
7
8
-
import {Config} from './config.js'
9
import {AppContext} from './context.js'
10
import {default as routes, errorHandler} from './routes/index.js'
11
···
17
public server?: http.Server
18
private terminator?: HttpTerminator
19
20
-
constructor(public app: express.Application, public ctx: AppContext) {}
21
22
static async create(cfg: Config): Promise<LinkService> {
23
let app = express()
···
1
import events from 'node:events'
2
+
import type http from 'node:http'
3
4
import cors from 'cors'
5
import express from 'express'
6
+
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
7
8
+
import {type Config} from './config.js'
9
import {AppContext} from './context.js'
10
import {default as routes, errorHandler} from './routes/index.js'
11
···
17
public server?: http.Server
18
private terminator?: HttpTerminator
19
20
+
constructor(
21
+
public app: express.Application,
22
+
public ctx: AppContext,
23
+
) {}
24
25
static async create(cfg: Config): Promise<LinkService> {
26
let app = express()
+7
-4
bskyogcard/src/index.ts
+7
-4
bskyogcard/src/index.ts
···
1
import events from 'node:events'
2
-
import http from 'node:http'
3
4
import express from 'express'
5
-
import {createHttpTerminator, HttpTerminator} from 'http-terminator'
6
7
-
import {Config} from './config.js'
8
import {AppContext} from './context.js'
9
import {default as routes, errorHandler} from './routes/index.js'
10
···
15
public server?: http.Server
16
private terminator?: HttpTerminator
17
18
-
constructor(public app: express.Application, public ctx: AppContext) {}
19
20
static async create(cfg: Config): Promise<CardService> {
21
let app = express()
···
1
import events from 'node:events'
2
+
import type http from 'node:http'
3
4
import express from 'express'
5
+
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
6
7
+
import {type Config} from './config.js'
8
import {AppContext} from './context.js'
9
import {default as routes, errorHandler} from './routes/index.js'
10
···
15
public server?: http.Server
16
private terminator?: HttpTerminator
17
18
+
constructor(
19
+
public app: express.Application,
20
+
public ctx: AppContext,
21
+
) {}
22
23
static async create(cfg: Config): Promise<CardService> {
24
let app = express()
+1
-1
modules/expo-emoji-picker/src/EmojiPicker.android.tsx
+1
-1
modules/expo-emoji-picker/src/EmojiPicker.android.tsx
+1
-1
package.json
+1
-1
package.json
+3
-4
scripts/post-web-build.js
+3
-4
scripts/post-web-build.js
+3
-3
src/components/ContextMenu/index.tsx
+3
-3
src/components/ContextMenu/index.tsx
···
190
if (item) playHaptic('Light')
191
setHoveredMenuItem(item)
192
},
193
-
} satisfies ContextType),
194
[
195
measurement,
196
setMeasurement,
···
710
const xOffset = position
711
? position.x
712
: align === 'left'
713
-
? measurement.x
714
-
: measurement.x + measurement.width - layout.width
715
716
registerHoverable(
717
id,
···
190
if (item) playHaptic('Light')
191
setHoveredMenuItem(item)
192
},
193
+
}) satisfies ContextType,
194
[
195
measurement,
196
setMeasurement,
···
710
const xOffset = position
711
? position.x
712
: align === 'left'
713
+
? measurement.x
714
+
: measurement.x + measurement.width - layout.width
715
716
registerHoverable(
717
id,
+4
-4
src/components/Link.tsx
+4
-4
src/components/Link.tsx
+3
-3
src/components/Post/Embed/ImageEmbed.tsx
+3
-3
src/components/Post/Embed/ImageEmbed.tsx
+2
-2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
+2
-2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
+2
-2
src/components/PostControls/PostMenu/PostMenuItems.tsx
+2
-2
src/components/PostControls/PostMenu/PostMenuItems.tsx
+2
-2
src/components/Select/index.web.tsx
+2
-2
src/components/Select/index.web.tsx
+12
-6
src/components/WhoCanReply.tsx
+12
-6
src/components/WhoCanReply.tsx
···
1
import React from 'react'
2
-
import {Keyboard, Platform, StyleProp, View, ViewStyle} from 'react-native'
3
import {
4
-
AppBskyFeedDefs,
5
AppBskyFeedPost,
6
-
AppBskyGraphDefs,
7
AtUri,
8
} from '@atproto/api'
9
import {msg, Trans} from '@lingui/macro'
···
13
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
14
import {isNative} from '#/platform/detection'
15
import {
16
-
ThreadgateAllowUISetting,
17
threadgateViewToAllowUISetting,
18
} from '#/state/queries/threadgate'
19
import {atoms as a, useTheme} from '#/alf'
···
70
const description = anyoneCanReply
71
? _(msg`Everybody can reply`)
72
: noOneCanReply
73
-
? _(msg`Replies disabled`)
74
-
: _(msg`Some people can reply`)
75
76
const onPressOpen = () => {
77
if (isNative && Keyboard.isVisible()) {
···
1
import React from 'react'
2
import {
3
+
Keyboard,
4
+
Platform,
5
+
type StyleProp,
6
+
View,
7
+
type ViewStyle,
8
+
} from 'react-native'
9
+
import {
10
+
type AppBskyFeedDefs,
11
AppBskyFeedPost,
12
+
type AppBskyGraphDefs,
13
AtUri,
14
} from '@atproto/api'
15
import {msg, Trans} from '@lingui/macro'
···
19
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
20
import {isNative} from '#/platform/detection'
21
import {
22
+
type ThreadgateAllowUISetting,
23
threadgateViewToAllowUISetting,
24
} from '#/state/queries/threadgate'
25
import {atoms as a, useTheme} from '#/alf'
···
76
const description = anyoneCanReply
77
? _(msg`Everybody can reply`)
78
: noOneCanReply
79
+
? _(msg`Replies disabled`)
80
+
: _(msg`Some people can reply`)
81
82
const onPressOpen = () => {
83
if (isNative && Keyboard.isVisible()) {
+2
-2
src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx
+2
-2
src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx
+2
-2
src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx
+2
-2
src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx
+2
-2
src/components/dialogs/SearchablePeopleList.tsx
+2
-2
src/components/dialogs/SearchablePeopleList.tsx
+2
-2
src/components/dms/EmojiReactionPicker.tsx
+2
-2
src/components/dms/EmojiReactionPicker.tsx
+1
-2
src/components/dms/MessageContextMenu.tsx
+1
-2
src/components/dms/MessageContextMenu.tsx
+23
-24
src/components/icons/VerifiedCheck.tsx
+23
-24
src/components/icons/VerifiedCheck.tsx
···
3
4
import {type Props, useCommonSVGProps} from '#/components/icons/common'
5
6
-
export const VerifiedCheck = React.forwardRef<Svg, Props>(function LogoImpl(
7
-
props,
8
-
ref,
9
-
) {
10
-
const {fill, size, style, ...rest} = useCommonSVGProps(props)
11
12
-
return (
13
-
<Svg
14
-
fill="none"
15
-
{...rest}
16
-
ref={ref}
17
-
viewBox="0 0 24 24"
18
-
width={size}
19
-
height={size}
20
-
style={[style]}>
21
-
<Circle cx="12" cy="12" r="11.5" fill={fill} />
22
-
<Path
23
-
fill="#fff"
24
-
fillRule="evenodd"
25
-
clipRule="evenodd"
26
-
d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z"
27
-
/>
28
-
</Svg>
29
-
)
30
-
})
···
3
4
import {type Props, useCommonSVGProps} from '#/components/icons/common'
5
6
+
export const VerifiedCheck = React.forwardRef<Svg, Props>(
7
+
function LogoImpl(props, ref) {
8
+
const {fill, size, style, ...rest} = useCommonSVGProps(props)
9
10
+
return (
11
+
<Svg
12
+
fill="none"
13
+
{...rest}
14
+
ref={ref}
15
+
viewBox="0 0 24 24"
16
+
width={size}
17
+
height={size}
18
+
style={[style]}>
19
+
<Circle cx="12" cy="12" r="11.5" fill={fill} />
20
+
<Path
21
+
fill="#fff"
22
+
fillRule="evenodd"
23
+
clipRule="evenodd"
24
+
d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z"
25
+
/>
26
+
</Svg>
27
+
)
28
+
},
29
+
)
+28
-29
src/components/icons/VerifierCheck.tsx
+28
-29
src/components/icons/VerifierCheck.tsx
···
3
4
import {type Props, useCommonSVGProps} from '#/components/icons/common'
5
6
-
export const VerifierCheck = React.forwardRef<Svg, Props>(function LogoImpl(
7
-
props,
8
-
ref,
9
-
) {
10
-
const {fill, size, style, ...rest} = useCommonSVGProps(props)
11
12
-
return (
13
-
<Svg
14
-
fill="none"
15
-
{...rest}
16
-
ref={ref}
17
-
viewBox="0 0 24 24"
18
-
width={size}
19
-
height={size}
20
-
style={[style]}>
21
-
<Path
22
-
fill={fill}
23
-
fillRule="evenodd"
24
-
clipRule="evenodd"
25
-
d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z"
26
-
/>
27
-
<Path
28
-
fill="#fff"
29
-
fillRule="evenodd"
30
-
clipRule="evenodd"
31
-
d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z"
32
-
/>
33
-
</Svg>
34
-
)
35
-
})
···
3
4
import {type Props, useCommonSVGProps} from '#/components/icons/common'
5
6
+
export const VerifierCheck = React.forwardRef<Svg, Props>(
7
+
function LogoImpl(props, ref) {
8
+
const {fill, size, style, ...rest} = useCommonSVGProps(props)
9
10
+
return (
11
+
<Svg
12
+
fill="none"
13
+
{...rest}
14
+
ref={ref}
15
+
viewBox="0 0 24 24"
16
+
width={size}
17
+
height={size}
18
+
style={[style]}>
19
+
<Path
20
+
fill={fill}
21
+
fillRule="evenodd"
22
+
clipRule="evenodd"
23
+
d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z"
24
+
/>
25
+
<Path
26
+
fill="#fff"
27
+
fillRule="evenodd"
28
+
clipRule="evenodd"
29
+
d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z"
30
+
/>
31
+
</Svg>
32
+
)
33
+
},
34
+
)
+4
-4
src/components/moderation/ContentHider.tsx
+4
-4
src/components/moderation/ContentHider.tsx
···
1
import React from 'react'
2
-
import {StyleProp, View, ViewStyle} from 'react-native'
3
-
import {ModerationUI} from '@atproto/api'
4
import {msg, Trans} from '@lingui/macro'
5
import {useLingui} from '@lingui/react'
6
···
148
modui.noOverride
149
? _(msg`Learn more about the moderation applied to this content`)
150
: override
151
-
? _(msg`Hides the content`)
152
-
: _(msg`Shows the content`)
153
}>
154
{state => (
155
<View
···
1
import React from 'react'
2
+
import {type StyleProp, View, type ViewStyle} from 'react-native'
3
+
import {type ModerationUI} from '@atproto/api'
4
import {msg, Trans} from '@lingui/macro'
5
import {useLingui} from '@lingui/react'
6
···
148
modui.noOverride
149
? _(msg`Learn more about the moderation applied to this content`)
150
: override
151
+
? _(msg`Hides the content`)
152
+
: _(msg`Shows the content`)
153
}>
154
{state => (
155
<View
+6
-6
src/components/moderation/ReportDialog/index.tsx
+6
-6
src/components/moderation/ReportDialog/index.tsx
···
512
backgroundColor: active
513
? t.palette.primary_500
514
: completed
515
-
? t.palette.primary_100
516
-
: t.atoms.bg_contrast_25.backgroundColor,
517
borderColor: active
518
? t.palette.primary_500
519
: completed
520
-
? t.palette.primary_400
521
-
: t.atoms.border_contrast_low.borderColor,
522
},
523
]}>
524
{completed ? (
···
533
color: active
534
? 'white'
535
: completed
536
-
? t.palette.primary_700
537
-
: t.atoms.text_contrast_medium.color,
538
fontVariant: ['tabular-nums'],
539
width: 24,
540
height: 24,
···
512
backgroundColor: active
513
? t.palette.primary_500
514
: completed
515
+
? t.palette.primary_100
516
+
: t.atoms.bg_contrast_25.backgroundColor,
517
borderColor: active
518
? t.palette.primary_500
519
: completed
520
+
? t.palette.primary_400
521
+
: t.atoms.border_contrast_low.borderColor,
522
},
523
]}>
524
{completed ? (
···
533
color: active
534
? 'white'
535
: completed
536
+
? t.palette.primary_700
537
+
: t.atoms.text_contrast_medium.color,
538
fontVariant: ['tabular-nums'],
539
width: 24,
540
height: 24,
+2
-2
src/components/verification/VerificationCheckButton.tsx
+2
-2
src/components/verification/VerificationCheckButton.tsx
+7
-7
src/components/verification/VerificationsDialog.tsx
+7
-7
src/components/verification/VerificationsDialog.tsx
···
64
? _(msg`You are verified`)
65
: _(msg`Your verifications`)
66
: state.profile.isVerified
67
-
? _(msg`${userName} is verified`)
68
-
: _(
69
-
msg({
70
-
message: `${userName}'s verifications`,
71
-
comment: `Possessive, meaning "the verifications of {userName}"`,
72
-
}),
73
-
)
74
75
return (
76
<Dialog.ScrollableInner
···
64
? _(msg`You are verified`)
65
: _(msg`Your verifications`)
66
: state.profile.isVerified
67
+
? _(msg`${userName} is verified`)
68
+
: _(
69
+
msg({
70
+
message: `${userName}'s verifications`,
71
+
comment: `Possessive, meaning "the verifications of {userName}"`,
72
+
}),
73
+
)
74
75
return (
76
<Dialog.ScrollableInner
+9
-9
src/lib/moderation.ts
+9
-9
src/lib/moderation.ts
···
1
import React from 'react'
2
import {
3
-
AppBskyLabelerDefs,
4
BskyAgent,
5
-
ComAtprotoLabelDefs,
6
-
InterpretedLabelValueDefinition,
7
LABELS,
8
-
ModerationCause,
9
-
ModerationOpts,
10
-
ModerationUI,
11
} from '@atproto/api'
12
13
import {sanitizeDisplayName} from '#/lib/strings/display-names'
14
import {sanitizeHandle} from '#/lib/strings/handles'
15
-
import {AppModerationCause} from '#/components/Pills'
16
17
export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
18
export const OTHER_SELF_LABELS = ['graphic-media']
···
29
cause.source.type === 'labeler'
30
? cause.source.did
31
: cause.source.type === 'list'
32
-
? cause.source.list.uri
33
-
: 'user'
34
if (cause.type === 'label') {
35
return `label:${cause.label.val}:${source}`
36
}
···
1
import React from 'react'
2
import {
3
+
type AppBskyLabelerDefs,
4
BskyAgent,
5
+
type ComAtprotoLabelDefs,
6
+
type InterpretedLabelValueDefinition,
7
LABELS,
8
+
type ModerationCause,
9
+
type ModerationOpts,
10
+
type ModerationUI,
11
} from '@atproto/api'
12
13
import {sanitizeDisplayName} from '#/lib/strings/display-names'
14
import {sanitizeHandle} from '#/lib/strings/handles'
15
+
import {type AppModerationCause} from '#/components/Pills'
16
17
export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
18
export const OTHER_SELF_LABELS = ['graphic-media']
···
29
cause.source.type === 'labeler'
30
? cause.source.did
31
: cause.source.type === 'list'
32
+
? cause.source.list.uri
33
+
: 'user'
34
if (cause.type === 'label') {
35
return `label:${cause.label.val}:${source}`
36
}
+6
-6
src/lib/moderation/useModerationCauseDescription.ts
+6
-6
src/lib/moderation/useModerationCauseDescription.ts
···
1
import React from 'react'
2
import {
3
BSKY_LABELER_DID,
4
-
ModerationCause,
5
-
ModerationCauseSource,
6
} from '@atproto/api'
7
import {msg} from '@lingui/macro'
8
import {useLingui} from '@lingui/react'
···
12
import {useSession} from '#/state/session'
13
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
14
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
15
-
import {Props as SVGIconProps} from '#/components/icons/common'
16
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
17
import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
18
-
import {AppModerationCause} from '#/components/Pills'
19
import {useGlobalLabelStrings} from './useGlobalLabelStrings'
20
import {getDefinition, getLabelStrings} from './useLabelInfo'
21
···
153
def.identifier === '!no-unauthenticated'
154
? EyeSlash
155
: def.severity === 'alert'
156
-
? Warning
157
-
: CircleInfo,
158
name: strings.name,
159
description: strings.description,
160
source,
···
1
import React from 'react'
2
import {
3
BSKY_LABELER_DID,
4
+
type ModerationCause,
5
+
type ModerationCauseSource,
6
} from '@atproto/api'
7
import {msg} from '@lingui/macro'
8
import {useLingui} from '@lingui/react'
···
12
import {useSession} from '#/state/session'
13
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
14
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
15
+
import {type Props as SVGIconProps} from '#/components/icons/common'
16
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
17
import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
18
+
import {type AppModerationCause} from '#/components/Pills'
19
import {useGlobalLabelStrings} from './useGlobalLabelStrings'
20
import {getDefinition, getLabelStrings} from './useLabelInfo'
21
···
153
def.identifier === '!no-unauthenticated'
154
? EyeSlash
155
: def.severity === 'alert'
156
+
? Warning
157
+
: CircleInfo,
158
name: strings.name,
159
description: strings.description,
160
source,
+5
-5
src/lib/statsig/statsig.tsx
+5
-5
src/lib/statsig/statsig.tsx
···
1
import React from 'react'
2
import {Platform} from 'react-native'
3
-
import {AppState, AppStateStatus} from 'react-native'
4
import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
5
6
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
7
import {logger} from '#/logger'
8
-
import {MetricEvents} from '#/logger/metrics'
9
import {isWeb} from '#/platform/detection'
10
import * as persisted from '#/state/persisted'
11
import {useSession} from '../../state/session'
12
import {timeout} from '../async/timeout'
13
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
14
-
import {Gate} from './gates'
15
16
const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
17
···
51
process.env.NODE_ENV === 'development'
52
? 'development'
53
: IS_TESTFLIGHT
54
-
? 'staging'
55
-
: 'production',
56
},
57
// Don't block on waiting for network. The fetched config will kick in on next load.
58
// This ensures the UI is always consistent and doesn't update mid-session.
···
1
import React from 'react'
2
import {Platform} from 'react-native'
3
+
import {AppState, type AppStateStatus} from 'react-native'
4
import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
5
6
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
7
import {logger} from '#/logger'
8
+
import {type MetricEvents} from '#/logger/metrics'
9
import {isWeb} from '#/platform/detection'
10
import * as persisted from '#/state/persisted'
11
import {useSession} from '../../state/session'
12
import {timeout} from '../async/timeout'
13
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
14
+
import {type Gate} from './gates'
15
16
const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
17
···
51
process.env.NODE_ENV === 'development'
52
? 'development'
53
: IS_TESTFLIGHT
54
+
? 'staging'
55
+
: 'production',
56
},
57
// Don't block on waiting for network. The fetched config will kick in on next load.
58
// This ensures the UI is always consistent and doesn't update mid-session.
+2
-2
src/lib/strings/embed-player.ts
+2
-2
src/lib/strings/embed-player.ts
+6
-6
src/platform/polyfills.ts
+6
-6
src/platform/polyfills.ts
+2
-2
src/screens/Login/index.tsx
+2
-2
src/screens/Login/index.tsx
+1
-1
src/screens/Messages/ChatList.tsx
+1
-1
src/screens/Messages/ChatList.tsx
+2
-2
src/screens/Onboarding/StepFinished.tsx
+2
-2
src/screens/Onboarding/StepFinished.tsx
+2
-2
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
+2
-2
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
+2
-2
src/screens/Search/Explore.tsx
+2
-2
src/screens/Search/Explore.tsx
···
342
])
343
344
const topBorder = useMemo(
345
-
() => ({type: 'topBorder', key: 'top-border'} as const),
346
[],
347
)
348
const trendingTopicsModule = useMemo(
349
-
() => ({type: 'trendingTopics', key: 'trending-topics'} as const),
350
[],
351
)
352
const suggestedFollowsModule = useMemo(() => {
···
342
])
343
344
const topBorder = useMemo(
345
+
() => ({type: 'topBorder', key: 'top-border'}) as const,
346
[],
347
)
348
const trendingTopicsModule = useMemo(
349
+
() => ({type: 'trendingTopics', key: 'trending-topics'}) as const,
350
[],
351
)
352
const suggestedFollowsModule = useMemo(() => {
+61
-21
src/screens/Settings/AppIconSettings/useAppIconSets.ts
+61
-21
src/screens/Settings/AppIconSettings/useAppIconSets.ts
···
2
import {msg} from '@lingui/macro'
3
import {useLingui} from '@lingui/react'
4
5
-
import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
6
7
export function useAppIconSets() {
8
const {_} = useLingui()
···
13
id: 'default_light',
14
name: _(msg({context: 'Name of app icon variant', message: 'Light'})),
15
iosImage: () => {
16
-
return require(`../../../../assets/app-icons/ios_icon_default_light.png`)
17
},
18
androidImage: () => {
19
-
return require(`../../../../assets/app-icons/android_icon_default_light.png`)
20
},
21
},
22
{
23
id: 'default_dark',
24
name: _(msg({context: 'Name of app icon variant', message: 'Dark'})),
25
iosImage: () => {
26
-
return require(`../../../../assets/app-icons/ios_icon_default_dark.png`)
27
},
28
androidImage: () => {
29
-
return require(`../../../../assets/app-icons/android_icon_default_dark.png`)
30
},
31
},
32
] satisfies AppIconSet[]
···
39
id: 'core_aurora',
40
name: _(msg({context: 'Name of app icon variant', message: 'Aurora'})),
41
iosImage: () => {
42
-
return require(`../../../../assets/app-icons/ios_icon_core_aurora.png`)
43
},
44
androidImage: () => {
45
-
return require(`../../../../assets/app-icons/android_icon_core_aurora.png`)
46
},
47
},
48
// {
···
59
id: 'core_sunrise',
60
name: _(msg({context: 'Name of app icon variant', message: 'Sunrise'})),
61
iosImage: () => {
62
-
return require(`../../../../assets/app-icons/ios_icon_core_sunrise.png`)
63
},
64
androidImage: () => {
65
-
return require(`../../../../assets/app-icons/android_icon_core_sunrise.png`)
66
},
67
},
68
{
69
id: 'core_sunset',
70
name: _(msg({context: 'Name of app icon variant', message: 'Sunset'})),
71
iosImage: () => {
72
-
return require(`../../../../assets/app-icons/ios_icon_core_sunset.png`)
73
},
74
androidImage: () => {
75
-
return require(`../../../../assets/app-icons/android_icon_core_sunset.png`)
76
},
77
},
78
{
···
81
msg({context: 'Name of app icon variant', message: 'Midnight'}),
82
),
83
iosImage: () => {
84
-
return require(`../../../../assets/app-icons/ios_icon_core_midnight.png`)
85
},
86
androidImage: () => {
87
-
return require(`../../../../assets/app-icons/android_icon_core_midnight.png`)
88
},
89
},
90
{
···
93
msg({context: 'Name of app icon variant', message: 'Flat Blue'}),
94
),
95
iosImage: () => {
96
-
return require(`../../../../assets/app-icons/ios_icon_core_flat_blue.png`)
97
},
98
androidImage: () => {
99
-
return require(`../../../../assets/app-icons/android_icon_core_flat_blue.png`)
100
},
101
},
102
{
···
105
msg({context: 'Name of app icon variant', message: 'Flat White'}),
106
),
107
iosImage: () => {
108
-
return require(`../../../../assets/app-icons/ios_icon_core_flat_white.png`)
109
},
110
androidImage: () => {
111
-
return require(`../../../../assets/app-icons/android_icon_core_flat_white.png`)
112
},
113
},
114
{
···
117
msg({context: 'Name of app icon variant', message: 'Flat Black'}),
118
),
119
iosImage: () => {
120
-
return require(`../../../../assets/app-icons/ios_icon_core_flat_black.png`)
121
},
122
androidImage: () => {
123
-
return require(`../../../../assets/app-icons/android_icon_core_flat_black.png`)
124
},
125
},
126
{
···
132
}),
133
),
134
iosImage: () => {
135
-
return require(`../../../../assets/app-icons/ios_icon_core_classic.png`)
136
},
137
androidImage: () => {
138
-
return require(`../../../../assets/app-icons/android_icon_core_classic.png`)
139
},
140
},
141
] satisfies AppIconSet[]
···
2
import {msg} from '@lingui/macro'
3
import {useLingui} from '@lingui/react'
4
5
+
import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types'
6
7
export function useAppIconSets() {
8
const {_} = useLingui()
···
13
id: 'default_light',
14
name: _(msg({context: 'Name of app icon variant', message: 'Light'})),
15
iosImage: () => {
16
+
return require(
17
+
`../../../../assets/app-icons/ios_icon_default_light.png`,
18
+
)
19
},
20
androidImage: () => {
21
+
return require(
22
+
`../../../../assets/app-icons/android_icon_default_light.png`,
23
+
)
24
},
25
},
26
{
27
id: 'default_dark',
28
name: _(msg({context: 'Name of app icon variant', message: 'Dark'})),
29
iosImage: () => {
30
+
return require(
31
+
`../../../../assets/app-icons/ios_icon_default_dark.png`,
32
+
)
33
},
34
androidImage: () => {
35
+
return require(
36
+
`../../../../assets/app-icons/android_icon_default_dark.png`,
37
+
)
38
},
39
},
40
] satisfies AppIconSet[]
···
47
id: 'core_aurora',
48
name: _(msg({context: 'Name of app icon variant', message: 'Aurora'})),
49
iosImage: () => {
50
+
return require(
51
+
`../../../../assets/app-icons/ios_icon_core_aurora.png`,
52
+
)
53
},
54
androidImage: () => {
55
+
return require(
56
+
`../../../../assets/app-icons/android_icon_core_aurora.png`,
57
+
)
58
},
59
},
60
// {
···
71
id: 'core_sunrise',
72
name: _(msg({context: 'Name of app icon variant', message: 'Sunrise'})),
73
iosImage: () => {
74
+
return require(
75
+
`../../../../assets/app-icons/ios_icon_core_sunrise.png`,
76
+
)
77
},
78
androidImage: () => {
79
+
return require(
80
+
`../../../../assets/app-icons/android_icon_core_sunrise.png`,
81
+
)
82
},
83
},
84
{
85
id: 'core_sunset',
86
name: _(msg({context: 'Name of app icon variant', message: 'Sunset'})),
87
iosImage: () => {
88
+
return require(
89
+
`../../../../assets/app-icons/ios_icon_core_sunset.png`,
90
+
)
91
},
92
androidImage: () => {
93
+
return require(
94
+
`../../../../assets/app-icons/android_icon_core_sunset.png`,
95
+
)
96
},
97
},
98
{
···
101
msg({context: 'Name of app icon variant', message: 'Midnight'}),
102
),
103
iosImage: () => {
104
+
return require(
105
+
`../../../../assets/app-icons/ios_icon_core_midnight.png`,
106
+
)
107
},
108
androidImage: () => {
109
+
return require(
110
+
`../../../../assets/app-icons/android_icon_core_midnight.png`,
111
+
)
112
},
113
},
114
{
···
117
msg({context: 'Name of app icon variant', message: 'Flat Blue'}),
118
),
119
iosImage: () => {
120
+
return require(
121
+
`../../../../assets/app-icons/ios_icon_core_flat_blue.png`,
122
+
)
123
},
124
androidImage: () => {
125
+
return require(
126
+
`../../../../assets/app-icons/android_icon_core_flat_blue.png`,
127
+
)
128
},
129
},
130
{
···
133
msg({context: 'Name of app icon variant', message: 'Flat White'}),
134
),
135
iosImage: () => {
136
+
return require(
137
+
`../../../../assets/app-icons/ios_icon_core_flat_white.png`,
138
+
)
139
},
140
androidImage: () => {
141
+
return require(
142
+
`../../../../assets/app-icons/android_icon_core_flat_white.png`,
143
+
)
144
},
145
},
146
{
···
149
msg({context: 'Name of app icon variant', message: 'Flat Black'}),
150
),
151
iosImage: () => {
152
+
return require(
153
+
`../../../../assets/app-icons/ios_icon_core_flat_black.png`,
154
+
)
155
},
156
androidImage: () => {
157
+
return require(
158
+
`../../../../assets/app-icons/android_icon_core_flat_black.png`,
159
+
)
160
},
161
},
162
{
···
168
}),
169
),
170
iosImage: () => {
171
+
return require(
172
+
`../../../../assets/app-icons/ios_icon_core_classic.png`,
173
+
)
174
},
175
androidImage: () => {
176
+
return require(
177
+
`../../../../assets/app-icons/android_icon_core_classic.png`,
178
+
)
179
},
180
},
181
] satisfies AppIconSet[]
+2
-2
src/screens/Settings/components/ChangeHandleDialog.tsx
+2
-2
src/screens/Settings/components/ChangeHandleDialog.tsx
+2
-2
src/state/cache/profile-shadow.ts
+2
-2
src/state/cache/profile-shadow.ts
+11
-22
src/state/queries/explore-feed-previews.tsx
+11
-22
src/state/queries/explore-feed-previews.tsx
···
36
const LIMIT = 8 // sliced to 6, overfetch to account for moderation
37
const PINNED_POST_URIS: Record<string, boolean> = {
38
// 📰 News
39
-
'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b':
40
-
true,
41
// Gardening
42
-
'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27':
43
-
true,
44
// Web Development Trending
45
-
'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22':
46
-
true,
47
// Anime & Manga EN
48
-
'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y':
49
-
true,
50
// 📽️ Film
51
-
'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g':
52
-
true,
53
// PopSky
54
-
'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g':
55
-
true,
56
// Science
57
-
'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s':
58
-
true,
59
// Birds! 🦉
60
-
'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d':
61
-
true,
62
// Astronomy
63
-
'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w':
64
-
true,
65
// What's Cooking 🍽️
66
-
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q':
67
-
true,
68
// BookSky 💙📚 #booksky
69
-
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e':
70
-
true,
71
}
72
73
export type FeedPreviewItem =
···
36
const LIMIT = 8 // sliced to 6, overfetch to account for moderation
37
const PINNED_POST_URIS: Record<string, boolean> = {
38
// 📰 News
39
+
'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b': true,
40
// Gardening
41
+
'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27': true,
42
// Web Development Trending
43
+
'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22': true,
44
// Anime & Manga EN
45
+
'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y': true,
46
// 📽️ Film
47
+
'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g': true,
48
// PopSky
49
+
'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g': true,
50
// Science
51
+
'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s': true,
52
// Birds! 🦉
53
+
'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d': true,
54
// Astronomy
55
+
'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w': true,
56
// What's Cooking 🍽️
57
+
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q': true,
58
// BookSky 💙📚 #booksky
59
+
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e': true,
60
}
61
62
export type FeedPreviewItem =
+2
-3
src/state/queries/notifications/settings.ts
+2
-3
src/state/queries/notifications/settings.ts
+5
-5
src/state/queries/notifications/unread.tsx
+5
-5
src/state/queries/notifications/unread.tsx
···
14
import {useModerationOpts} from '../../preferences/moderation-opts'
15
import {truncateAndInvalidate} from '../util'
16
import {RQKEY as RQKEY_NOTIFS} from './feed'
17
-
import {CachedFeedPage, FeedPage} from './types'
18
import {fetchPage} from './util'
19
20
const UPDATE_INTERVAL = 30 * 1e3 // 30sec
···
94
data.event === '30+'
95
? 30
96
: data.event === ''
97
-
? 0
98
-
: parseInt(data.event, 10) || 1,
99
}
100
setNumUnread(data.event)
101
}
···
164
unreadCount >= 30
165
? '30+'
166
: unreadCount === 0
167
-
? ''
168
-
: String(unreadCount)
169
170
// track last sync
171
const now = new Date()
···
14
import {useModerationOpts} from '../../preferences/moderation-opts'
15
import {truncateAndInvalidate} from '../util'
16
import {RQKEY as RQKEY_NOTIFS} from './feed'
17
+
import {type CachedFeedPage, type FeedPage} from './types'
18
import {fetchPage} from './util'
19
20
const UPDATE_INTERVAL = 30 * 1e3 // 30sec
···
94
data.event === '30+'
95
? 30
96
: data.event === ''
97
+
? 0
98
+
: parseInt(data.event, 10) || 1,
99
}
100
setNumUnread(data.event)
101
}
···
164
unreadCount >= 30
165
? '30+'
166
: unreadCount === 0
167
+
? ''
168
+
: String(unreadCount)
169
170
// track last sync
171
const now = new Date()
+2
-2
src/state/queries/usePostThread/index.ts
+2
-2
src/state/queries/usePostThread/index.ts
+1
-1
src/state/queries/usePostThread/traversal.ts
+1
-1
src/state/queries/usePostThread/traversal.ts
+5
-13
src/storage/index.ts
+5
-13
src/storage/index.ts
···
1
import {useCallback, useEffect, useState} from 'react'
2
import {MMKV} from 'react-native-mmkv'
3
4
-
import {Account, Device} from '#/storage/schema'
5
6
export * from '#/storage/schema'
7
···
83
}
84
}
85
86
-
type StorageSchema<T extends Storage<any, any>> = T extends Storage<
87
-
any,
88
-
infer U
89
-
>
90
-
? U
91
-
: never
92
-
type StorageScopes<T extends Storage<any, any>> = T extends Storage<
93
-
infer S,
94
-
any
95
-
>
96
-
? S
97
-
: never
98
99
/**
100
* Hook to use a storage instance. Acts like a useState hook, but persists the
···
1
import {useCallback, useEffect, useState} from 'react'
2
import {MMKV} from 'react-native-mmkv'
3
4
+
import {type Account, type Device} from '#/storage/schema'
5
6
export * from '#/storage/schema'
7
···
83
}
84
}
85
86
+
type StorageSchema<T extends Storage<any, any>> =
87
+
T extends Storage<any, infer U> ? U : never
88
+
type StorageScopes<T extends Storage<any, any>> =
89
+
T extends Storage<infer S, any> ? S : never
90
91
/**
92
* Hook to use a storage instance. Acts like a useState hook, but persists the
+2
-1
src/style.css
+2
-1
src/style.css
···
328
329
/* #/components/Select/index.web.tsx */
330
.radix-select-content {
331
+
box-shadow:
332
+
0px 6px 24px -10px rgba(22, 23, 24, 0.25),
333
0px 6px 12px -12px rgba(22, 23, 24, 0.15);
334
min-width: var(--radix-select-trigger-width);
335
max-height: var(--radix-select-content-available-height);
+16
-16
src/view/com/composer/Composer.tsx
+16
-16
src/view/com/composer/Composer.tsx
···
518
thread.posts.length > 1
519
? _(msg`Your posts have been published`)
520
: replyTo
521
-
? _(msg`Your reply has been published`)
522
-
: _(msg`Your post has been published`),
523
)
524
}, [
525
_,
···
1000
}),
1001
)
1002
: isThread
1003
-
? _(
1004
-
msg({
1005
-
message: 'Publish posts',
1006
-
comment:
1007
-
'Accessibility label for button to publish multiple posts in a thread',
1008
-
}),
1009
-
)
1010
-
: _(
1011
-
msg({
1012
-
message: 'Publish post',
1013
-
comment:
1014
-
'Accessibility label for button to publish a single post',
1015
-
}),
1016
-
)
1017
}
1018
variant="solid"
1019
color="primary"
···
518
thread.posts.length > 1
519
? _(msg`Your posts have been published`)
520
: replyTo
521
+
? _(msg`Your reply has been published`)
522
+
: _(msg`Your post has been published`),
523
)
524
}, [
525
_,
···
1000
}),
1001
)
1002
: isThread
1003
+
? _(
1004
+
msg({
1005
+
message: 'Publish posts',
1006
+
comment:
1007
+
'Accessibility label for button to publish multiple posts in a thread',
1008
+
}),
1009
+
)
1010
+
: _(
1011
+
msg({
1012
+
message: 'Publish post',
1013
+
comment:
1014
+
'Accessibility label for button to publish a single post',
1015
+
}),
1016
+
)
1017
}
1018
variant="solid"
1019
color="primary"
+10
-10
src/view/com/composer/photos/Gallery.tsx
+10
-10
src/view/com/composer/photos/Gallery.tsx
···
1
import React from 'react'
2
import {
3
-
ImageStyle,
4
Keyboard,
5
-
LayoutChangeEvent,
6
StyleSheet,
7
TouchableOpacity,
8
View,
9
-
ViewStyle,
10
} from 'react-native'
11
import {Image} from 'expo-image'
12
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
···
14
import {useLingui} from '@lingui/react'
15
16
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
17
-
import {Dimensions} from '#/lib/media/types'
18
import {colors, s} from '#/lib/styles'
19
import {isNative} from '#/platform/detection'
20
-
import {ComposerImage, cropImage} from '#/state/gallery'
21
import {Text} from '#/view/com/util/text/Text'
22
import {useTheme} from '#/alf'
23
import * as Dialog from '#/components/Dialog'
24
-
import {PostAction} from '../state/composer'
25
import {EditImageDialog} from './EditImageDialog'
26
import {ImageAltTextDialog} from './ImageAltTextDialog'
27
···
74
altTextControlStyle: isOverflow
75
? {left: 4, bottom: 4}
76
: !isMobile && images.length < 3
77
-
? {left: 8, top: 8}
78
-
: {left: 4, top: 4},
79
imageControlsStyle: {
80
display: 'flex' as const,
81
flexDirection: 'row' as const,
···
83
...(isOverflow
84
? {top: 4, right: 4, gap: 4}
85
: !isMobile && images.length < 3
86
-
? {top: 8, right: 8, gap: 8}
87
-
: {top: 4, right: 4, gap: 4}),
88
zIndex: 1,
89
},
90
imageStyle: {
···
1
import React from 'react'
2
import {
3
+
type ImageStyle,
4
Keyboard,
5
+
type LayoutChangeEvent,
6
StyleSheet,
7
TouchableOpacity,
8
View,
9
+
type ViewStyle,
10
} from 'react-native'
11
import {Image} from 'expo-image'
12
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
···
14
import {useLingui} from '@lingui/react'
15
16
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
17
+
import {type Dimensions} from '#/lib/media/types'
18
import {colors, s} from '#/lib/styles'
19
import {isNative} from '#/platform/detection'
20
+
import {type ComposerImage, cropImage} from '#/state/gallery'
21
import {Text} from '#/view/com/util/text/Text'
22
import {useTheme} from '#/alf'
23
import * as Dialog from '#/components/Dialog'
24
+
import {type PostAction} from '../state/composer'
25
import {EditImageDialog} from './EditImageDialog'
26
import {ImageAltTextDialog} from './ImageAltTextDialog'
27
···
74
altTextControlStyle: isOverflow
75
? {left: 4, bottom: 4}
76
: !isMobile && images.length < 3
77
+
? {left: 8, top: 8}
78
+
: {left: 4, top: 4},
79
imageControlsStyle: {
80
display: 'flex' as const,
81
flexDirection: 'row' as const,
···
83
...(isOverflow
84
? {top: 4, right: 4, gap: 4}
85
: !isMobile && images.length < 3
86
+
? {top: 8, right: 8, gap: 8}
87
+
: {top: 4, right: 4, gap: 4}),
88
zIndex: 1,
89
},
90
imageStyle: {
+21
-16
src/view/com/composer/state/composer.ts
+21
-16
src/view/com/composer/state/composer.ts
···
1
-
import {ImagePickerAsset} from 'expo-image-picker'
2
import {
3
-
AppBskyFeedPostgate,
4
AppBskyRichtextFacet,
5
-
BskyPreferences,
6
RichText,
7
} from '@atproto/api'
8
import {nanoid} from 'nanoid/non-secure'
9
10
-
import {SelfLabel} from '#/lib/moderation'
11
import {insertMentionAt} from '#/lib/strings/mention-manip'
12
import {shortenLinks} from '#/lib/strings/rich-text-manip'
13
import {
···
15
postUriToRelativePath,
16
toBskyAppUrl,
17
} from '#/lib/strings/url-helpers'
18
-
import {ComposerImage, createInitialImages} from '#/state/gallery'
19
import {createPostgateRecord} from '#/state/queries/postgate/util'
20
-
import {Gif} from '#/state/queries/tenor'
21
import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate'
22
-
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
23
-
import {ComposerOpts} from '#/state/shell/composer'
24
import {
25
-
LinkFacetMatch,
26
suggestLinkCardUri,
27
} from '#/view/com/composer/text-input/text-input-util'
28
-
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
29
30
type ImagesMedia = {
31
type: 'images'
···
514
text: initText
515
? initText
516
: initMention
517
-
? insertMentionAt(
518
-
`@${initMention}`,
519
-
initMention.length + 1,
520
-
`${initMention}`,
521
-
)
522
-
: '',
523
})
524
525
let link: Link | undefined
···
1
+
import {type ImagePickerAsset} from 'expo-image-picker'
2
import {
3
+
type AppBskyFeedPostgate,
4
AppBskyRichtextFacet,
5
+
type BskyPreferences,
6
RichText,
7
} from '@atproto/api'
8
import {nanoid} from 'nanoid/non-secure'
9
10
+
import {type SelfLabel} from '#/lib/moderation'
11
import {insertMentionAt} from '#/lib/strings/mention-manip'
12
import {shortenLinks} from '#/lib/strings/rich-text-manip'
13
import {
···
15
postUriToRelativePath,
16
toBskyAppUrl,
17
} from '#/lib/strings/url-helpers'
18
+
import {type ComposerImage, createInitialImages} from '#/state/gallery'
19
import {createPostgateRecord} from '#/state/queries/postgate/util'
20
+
import {type Gif} from '#/state/queries/tenor'
21
import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate'
22
+
import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate'
23
+
import {type ComposerOpts} from '#/state/shell/composer'
24
import {
25
+
type LinkFacetMatch,
26
suggestLinkCardUri,
27
} from '#/view/com/composer/text-input/text-input-util'
28
+
import {
29
+
createVideoState,
30
+
type VideoAction,
31
+
videoReducer,
32
+
type VideoState,
33
+
} from './video'
34
35
type ImagesMedia = {
36
type: 'images'
···
519
text: initText
520
? initText
521
: initMention
522
+
? insertMentionAt(
523
+
`@${initMention}`,
524
+
initMention.length + 1,
525
+
`${initMention}`,
526
+
)
527
+
: '',
528
})
529
530
let link: Link | undefined
+2
-2
src/view/com/composer/text-input/web/Autocomplete.tsx
+2
-2
src/view/com/composer/text-input/web/Autocomplete.tsx
+6
-6
src/view/com/lightbox/Lightbox.web.tsx
+6
-6
src/view/com/lightbox/Lightbox.web.tsx
···
1
import React, {useCallback, useEffect, useState} from 'react'
2
import {
3
Image,
4
-
ImageStyle,
5
Pressable,
6
StyleSheet,
7
TouchableOpacity,
8
TouchableWithoutFeedback,
9
View,
10
-
ViewStyle,
11
} from 'react-native'
12
import {
13
FontAwesomeIcon,
14
-
FontAwesomeIconStyle,
15
} from '@fortawesome/react-native-fontawesome'
16
import {msg} from '@lingui/macro'
17
import {useLingui} from '@lingui/react'
···
21
import {colors, s} from '#/lib/styles'
22
import {useLightbox, useLightboxControls} from '#/state/lightbox'
23
import {Text} from '../util/text/Text'
24
-
import {ImageSource} from './ImageViewing/@types'
25
import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader'
26
27
export function Lightbox() {
···
121
img.type === 'circle-avi'
122
? '50%'
123
: img.type === 'rect-avi'
124
-
? '10%'
125
-
: 0,
126
} as ImageStyle
127
}
128
alt={img.alt}
···
1
import React, {useCallback, useEffect, useState} from 'react'
2
import {
3
Image,
4
+
type ImageStyle,
5
Pressable,
6
StyleSheet,
7
TouchableOpacity,
8
TouchableWithoutFeedback,
9
View,
10
+
type ViewStyle,
11
} from 'react-native'
12
import {
13
FontAwesomeIcon,
14
+
type FontAwesomeIconStyle,
15
} from '@fortawesome/react-native-fontawesome'
16
import {msg} from '@lingui/macro'
17
import {useLingui} from '@lingui/react'
···
21
import {colors, s} from '#/lib/styles'
22
import {useLightbox, useLightboxControls} from '#/state/lightbox'
23
import {Text} from '../util/text/Text'
24
+
import {type ImageSource} from './ImageViewing/@types'
25
import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader'
26
27
export function Lightbox() {
···
121
img.type === 'circle-avi'
122
? '50%'
123
: img.type === 'rect-avi'
124
+
? '10%'
125
+
: 0,
126
} as ImageStyle
127
}
128
alt={img.alt}
+2
-2
src/view/com/post-thread/PostThreadItem.tsx
+2
-2
src/view/com/post-thread/PostThreadItem.tsx
+5
-6
src/view/com/posts/PostFeed.tsx
+5
-6
src/view/com/posts/PostFeed.tsx
···
132
key: string
133
}
134
135
-
export function getItemsForFeedback(feedRow: FeedRow):
136
-
| {
137
-
item: FeedPostSliceItem
138
-
feedContext: string | undefined
139
-
reqId: string | undefined
140
-
}[] {
141
if (feedRow.type === 'sliceItem') {
142
return feedRow.slice.items.map(item => ({
143
item,
···
132
key: string
133
}
134
135
+
export function getItemsForFeedback(feedRow: FeedRow): {
136
+
item: FeedPostSliceItem
137
+
feedContext: string | undefined
138
+
reqId: string | undefined
139
+
}[] {
140
if (feedRow.type === 'sliceItem') {
141
return feedRow.slice.items.map(item => ({
142
item,
+8
-4
src/view/com/posts/PostFeedErrorMessage.tsx
+8
-4
src/view/com/posts/PostFeedErrorMessage.tsx
···
1
import React from 'react'
2
import {View} from 'react-native'
3
-
import {AppBskyActorDefs, AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api'
4
import {msg as msgLingui, Trans} from '@lingui/macro'
5
import {useLingui} from '@lingui/react'
6
import {useNavigation} from '@react-navigation/native'
7
8
import {usePalette} from '#/lib/hooks/usePalette'
9
-
import {NavigationProp} from '#/lib/routes/types'
10
import {cleanError} from '#/lib/strings/errors'
11
import {logger} from '#/logger'
12
-
import {FeedDescriptor} from '#/state/queries/post-feed'
13
import {useRemoveFeedMutation} from '#/state/queries/preferences'
14
import * as Prompt from '#/components/Prompt'
15
import {EmptyState} from '../util/EmptyState'
···
119
[KnownError.FeedTooManyRequests]: _l(
120
msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`,
121
),
122
-
}[knownError]),
123
[_l, knownError],
124
)
125
const [_, uri] = feedDesc.split('|')
···
1
import React from 'react'
2
import {View} from 'react-native'
3
+
import {
4
+
type AppBskyActorDefs,
5
+
AppBskyFeedGetAuthorFeed,
6
+
AtUri,
7
+
} from '@atproto/api'
8
import {msg as msgLingui, Trans} from '@lingui/macro'
9
import {useLingui} from '@lingui/react'
10
import {useNavigation} from '@react-navigation/native'
11
12
import {usePalette} from '#/lib/hooks/usePalette'
13
+
import {type NavigationProp} from '#/lib/routes/types'
14
import {cleanError} from '#/lib/strings/errors'
15
import {logger} from '#/logger'
16
+
import {type FeedDescriptor} from '#/state/queries/post-feed'
17
import {useRemoveFeedMutation} from '#/state/queries/preferences'
18
import * as Prompt from '#/components/Prompt'
19
import {EmptyState} from '../util/EmptyState'
···
123
[KnownError.FeedTooManyRequests]: _l(
124
msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`,
125
),
126
+
})[knownError],
127
[_l, knownError],
128
)
129
const [_, uri] = feedDesc.split('|')
+6
-6
src/view/com/profile/ProfileMenu.tsx
+6
-6
src/view/com/profile/ProfileMenu.tsx
···
461
msg`The account will be able to interact with you after unblocking.`,
462
)
463
: profile.associated?.labeler
464
-
? _(
465
-
msg`Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you.`,
466
-
)
467
-
: _(
468
-
msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
469
-
)
470
}
471
onConfirm={blockAccount}
472
confirmButtonCta={
···
461
msg`The account will be able to interact with you after unblocking.`,
462
)
463
: profile.associated?.labeler
464
+
? _(
465
+
msg`Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you.`,
466
+
)
467
+
: _(
468
+
msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
469
+
)
470
}
471
onConfirm={blockAccount}
472
confirmButtonCta={
+8
-3
src/view/com/util/List.web.tsx
+8
-3
src/view/com/util/List.web.tsx
···
1
import React, {isValidElement, memo, startTransition, useRef} from 'react'
2
-
import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native'
3
-
import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
4
5
import {batchedUpdates} from '#/lib/batchedUpdates'
6
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
···
205
behavior: animated ? 'smooth' : 'instant',
206
})
207
},
208
-
} as any), // TODO: Better types.
209
[getScrollableNode],
210
)
211
···
1
import React, {isValidElement, memo, startTransition, useRef} from 'react'
2
+
import {
3
+
type FlatListProps,
4
+
StyleSheet,
5
+
View,
6
+
type ViewProps,
7
+
} from 'react-native'
8
+
import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
9
10
import {batchedUpdates} from '#/lib/batchedUpdates'
11
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
···
210
behavior: animated ? 'smooth' : 'instant',
211
})
212
},
213
+
}) as any, // TODO: Better types.
214
[getScrollableNode],
215
)
216
+8
-8
src/view/screens/DebugMod.tsx
+8
-8
src/view/screens/DebugMod.tsx
···
112
}),
113
]
114
: scenario[0] === 'label' && target[0] === 'profile'
115
-
? [
116
-
mock.label({
117
-
src: isSelfLabel ? did : undefined,
118
-
val: label[0],
119
-
uri: `at://${did}/app.bsky.actor.profile/self`,
120
-
}),
121
-
]
122
-
: undefined,
123
viewer: mock.actorViewerState({
124
following: isFollowing
125
? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234`
···
112
}),
113
]
114
: scenario[0] === 'label' && target[0] === 'profile'
115
+
? [
116
+
mock.label({
117
+
src: isSelfLabel ? did : undefined,
118
+
val: label[0],
119
+
uri: `at://${did}/app.bsky.actor.profile/self`,
120
+
}),
121
+
]
122
+
: undefined,
123
viewer: mock.actorViewerState({
124
following: isFollowing
125
? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234`
+61
-59
svgo.config.mjs
+61
-59
svgo.config.mjs
···
1
const preset = [
2
-
"removeDoctype",
3
-
"removeXMLProcInst",
4
-
"removeComments",
5
-
"removeMetadata",
6
-
"removeEditorsNSData",
7
-
"cleanupAttrs",
8
-
"mergeStyles",
9
-
"inlineStyles",
10
-
"minifyStyles",
11
-
"cleanupIds",
12
-
"removeUselessDefs",
13
-
"cleanupNumericValues",
14
-
"convertColors",
15
-
"removeUnknownsAndDefaults",
16
-
"removeNonInheritableGroupAttrs",
17
-
"removeUselessStrokeAndFill",
18
-
"removeDimensions",
19
-
"cleanupEnableBackground",
20
-
"removeHiddenElems",
21
-
"removeEmptyText",
22
-
"convertShapeToPath",
23
-
"convertEllipseToCircle",
24
-
"moveElemsAttrsToGroup",
25
-
"moveGroupAttrsToElems",
26
-
"collapseGroups",
27
-
"convertPathData",
28
-
"convertTransform",
29
-
"removeEmptyAttrs",
30
-
"removeEmptyContainers",
31
-
"removeUnusedNS",
32
-
"mergePaths",
33
-
"sortAttrs",
34
-
"sortDefsChildren",
35
-
"removeTitle",
36
-
"removeDesc",
37
]
38
39
export default {
40
-
plugins: [...preset.map(name => ({
41
-
name,
42
-
params: {
43
-
floatPrecision: 3,
44
-
transformPrecision: 5,
45
-
// minimise diff in ouput from svgomg
46
-
// maybe remove in future? will produce smaller output
47
-
convertToZ: false,
48
-
removeUseless: false,
49
-
}
50
-
})),
51
-
{
52
-
name: 'addTrailingWhitespace',
53
-
fn() {
54
-
return {
55
-
root: {
56
-
exit (root) {
57
-
root.children.push({ type: 'text', value: '\n' })
58
-
return root
59
-
}
60
}
61
-
}
62
-
}
63
-
}]
64
-
};
···
1
const preset = [
2
+
'removeDoctype',
3
+
'removeXMLProcInst',
4
+
'removeComments',
5
+
'removeMetadata',
6
+
'removeEditorsNSData',
7
+
'cleanupAttrs',
8
+
'mergeStyles',
9
+
'inlineStyles',
10
+
'minifyStyles',
11
+
'cleanupIds',
12
+
'removeUselessDefs',
13
+
'cleanupNumericValues',
14
+
'convertColors',
15
+
'removeUnknownsAndDefaults',
16
+
'removeNonInheritableGroupAttrs',
17
+
'removeUselessStrokeAndFill',
18
+
'removeDimensions',
19
+
'cleanupEnableBackground',
20
+
'removeHiddenElems',
21
+
'removeEmptyText',
22
+
'convertShapeToPath',
23
+
'convertEllipseToCircle',
24
+
'moveElemsAttrsToGroup',
25
+
'moveGroupAttrsToElems',
26
+
'collapseGroups',
27
+
'convertPathData',
28
+
'convertTransform',
29
+
'removeEmptyAttrs',
30
+
'removeEmptyContainers',
31
+
'removeUnusedNS',
32
+
'mergePaths',
33
+
'sortAttrs',
34
+
'sortDefsChildren',
35
+
'removeTitle',
36
+
'removeDesc',
37
]
38
39
export default {
40
+
plugins: [
41
+
...preset.map(name => ({
42
+
name,
43
+
params: {
44
+
floatPrecision: 3,
45
+
transformPrecision: 5,
46
+
// minimise diff in ouput from svgomg
47
+
// maybe remove in future? will produce smaller output
48
+
convertToZ: false,
49
+
removeUseless: false,
50
+
},
51
+
})),
52
+
{
53
+
name: 'addTrailingWhitespace',
54
+
fn() {
55
+
return {
56
+
root: {
57
+
exit(root) {
58
+
root.children.push({type: 'text', value: '\n'})
59
+
return root
60
+
},
61
+
},
62
}
63
+
},
64
+
},
65
+
],
66
+
}
+4
-4
yarn.lock
+4
-4
yarn.lock
···
16220
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
16221
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
16222
16223
-
prettier@^2.8.3:
16224
-
version "2.8.8"
16225
-
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
16226
-
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
16227
16228
pretty-bytes@^5.6.0:
16229
version "5.6.0"
···
16220
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
16221
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
16222
16223
+
prettier@^3.6.0:
16224
+
version "3.6.0"
16225
+
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.0.tgz#18ec98d62cb0757a5d4eab40253ff3e6d0fc8dea"
16226
+
integrity sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==
16227
16228
pretty-bytes@^5.6.0:
16229
version "5.6.0"