Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b410ba867f |
@@ -1,5 +1,5 @@
|
||||
name: "Bug Report"
|
||||
description: "Create a report for an issue you have experienced in the app."
|
||||
description: "Create a report for an issue you have experience in the app."
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -19,14 +19,13 @@ body:
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: upload
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Attachments
|
||||
description: |
|
||||
If possible, please provide any images or videos that may help us understand the issue you are experiencing.
|
||||
validations:
|
||||
required: false
|
||||
accept: ".png,.jpg,.jpeg,.gif,.webp,.mp4,.mov,.webm"
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform(s) does this occur on?
|
||||
|
||||
@@ -26,14 +26,13 @@ body:
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: upload
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Attachments
|
||||
description: |
|
||||
If possible, please provide any images or videos that may help us understand the issue you are experiencing.
|
||||
validations:
|
||||
required: false
|
||||
accept: ".png,.jpg,.jpeg,.gif,.webp,.mp4,.mov,.webm"
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform(s) does this occur on?
|
||||
|
||||
@@ -15,7 +15,7 @@ body:
|
||||
implement it in a timely manner.
|
||||
validations:
|
||||
required: true
|
||||
- type: upload
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Attachments
|
||||
description: |
|
||||
@@ -24,7 +24,6 @@ body:
|
||||
in or is missing from.
|
||||
validations:
|
||||
required: false
|
||||
accept: ".png,.jpg,.jpeg,.gif,.webp,.mp4,.mov,.webm"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe Alternatives
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.0"
|
||||
|
||||
- name: ☕️ Setup Cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.0"
|
||||
|
||||
- name: ☕️ Setup Cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
|
||||
Vendored
-135
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* Codemod to replace namespaced React calls with named imports
|
||||
*
|
||||
* Before:
|
||||
* import React from 'react'
|
||||
* React.useEffect(() => {}, [])
|
||||
*
|
||||
* After:
|
||||
* import { useEffect } from 'react'
|
||||
* useEffect(() => {}, [])
|
||||
*
|
||||
* Usage: jscodeshift -t .jscodeshift/react-import.js <file-path>
|
||||
* Example: jscodeshift -t .jscodeshift/react-import.js src/App.native.tsx
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
export const parser = 'tsx'
|
||||
|
||||
export default function transformer(file, api) {
|
||||
const j = api.jscodeshift
|
||||
const root = j(file.source)
|
||||
|
||||
// Find the React import
|
||||
let reactImportPath = null
|
||||
const reactMembers = new Set()
|
||||
|
||||
root.find(j.ImportDeclaration).forEach(path => {
|
||||
const node = path.value
|
||||
if (node.source.value === 'react') {
|
||||
node.specifiers.forEach(spec => {
|
||||
// Check if this is a default import of React
|
||||
if (
|
||||
spec.type === 'ImportDefaultSpecifier' &&
|
||||
spec.local.name === 'React'
|
||||
) {
|
||||
reactImportPath = path
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!reactImportPath) {
|
||||
// No React import found, nothing to do
|
||||
return file.source
|
||||
}
|
||||
|
||||
// Find all React.* member expressions
|
||||
root
|
||||
.find(j.MemberExpression)
|
||||
.filter(path => {
|
||||
const node = path.value
|
||||
return (
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.type === 'Identifier'
|
||||
)
|
||||
})
|
||||
.forEach(path => {
|
||||
const propertyName = path.value.property.name
|
||||
reactMembers.add(propertyName)
|
||||
})
|
||||
|
||||
// Find all React.* JSX member expressions (e.g., <React.Fragment>)
|
||||
root
|
||||
.find(j.JSXMemberExpression)
|
||||
.filter(path => {
|
||||
const node = path.value
|
||||
return node.object.name === 'React' && node.property.name
|
||||
})
|
||||
.forEach(path => {
|
||||
const propertyName = path.value.property.name
|
||||
reactMembers.add(propertyName)
|
||||
})
|
||||
|
||||
// If no React members are used, remove the import
|
||||
if (reactMembers.size === 0) {
|
||||
reactImportPath.prune()
|
||||
return root.toSource()
|
||||
}
|
||||
|
||||
// Sort the members for consistent output
|
||||
const sortedMembers = Array.from(reactMembers).sort()
|
||||
|
||||
// Create new import specifiers
|
||||
const newSpecifiers = sortedMembers.map(name =>
|
||||
j.importSpecifier(j.identifier(name), j.identifier(name)),
|
||||
)
|
||||
|
||||
// Get the existing import specifiers
|
||||
const sortedImports = Array.from(reactImportPath.value.specifiers).sort()
|
||||
const existingSpecifiers = sortedImports.filter(
|
||||
specifier => specifier.type !== 'ImportDefaultSpecifier',
|
||||
)
|
||||
|
||||
const allSpecifiers = [
|
||||
...new Map(
|
||||
[...existingSpecifiers, ...newSpecifiers].map(item => [
|
||||
item.imported.name,
|
||||
item,
|
||||
]),
|
||||
).values(),
|
||||
]
|
||||
|
||||
// Update the import declaration
|
||||
reactImportPath.value.specifiers = allSpecifiers
|
||||
|
||||
// Replace all React.* member expressions with just the identifier
|
||||
root
|
||||
.find(j.MemberExpression)
|
||||
.filter(path => {
|
||||
const node = path.value
|
||||
return (
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.type === 'Identifier'
|
||||
)
|
||||
})
|
||||
.replaceWith(path => {
|
||||
return j.identifier(path.value.property.name)
|
||||
})
|
||||
|
||||
// Replace all React.* JSX member expressions with just the identifier
|
||||
root
|
||||
.find(j.JSXMemberExpression)
|
||||
.filter(path => {
|
||||
const node = path.value
|
||||
return node.object.name === 'React' && node.property.name
|
||||
})
|
||||
.replaceWith(path => {
|
||||
return j.jsxIdentifier(path.value.property.name)
|
||||
})
|
||||
|
||||
return root.toSource()
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* Codemod to replace namespaced React calls with named imports
|
||||
*
|
||||
* Before:
|
||||
* import * as Toast from '#/view/com/util/Toast'
|
||||
* Toast.show(message, 'xmark')
|
||||
*
|
||||
* After:
|
||||
* import * as Toast from '#/components/Toast'
|
||||
* Toast.show(message, {type: 'error'})
|
||||
*
|
||||
* Usage: jscodeshift -t .jscodeshift/toast-v2.js <file-path>
|
||||
* Example: jscodeshift -t .jscodeshift/toast-v2.js src/App.native.tsx
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
export const parser = 'tsx'
|
||||
|
||||
const OLD_IMPORT = '#/view/com/util/Toast'
|
||||
const NEW_IMPORT = '#/components/Toast'
|
||||
|
||||
const convertLegacyToastType = type => {
|
||||
switch (type) {
|
||||
// these ones are fine
|
||||
case 'default':
|
||||
case 'success':
|
||||
case 'error':
|
||||
case 'warning':
|
||||
case 'info':
|
||||
return type
|
||||
// legacy ones need conversion
|
||||
case 'xmark':
|
||||
return 'error'
|
||||
case 'exclamation-circle':
|
||||
return 'warning'
|
||||
case 'check':
|
||||
return 'success'
|
||||
case 'clipboard-check':
|
||||
return 'success'
|
||||
case 'circle-exclamation':
|
||||
case 'exclamation-circle':
|
||||
return 'warning'
|
||||
default:
|
||||
return 'default'
|
||||
}
|
||||
}
|
||||
|
||||
export default function transformer(file, api) {
|
||||
const j = api.jscodeshift
|
||||
const root = j(file.source)
|
||||
|
||||
// Find Toast import declarations using the old path
|
||||
const toastImports = root
|
||||
.find(j.ImportDeclaration)
|
||||
.filter(path => path.value.source.value === OLD_IMPORT)
|
||||
|
||||
if (toastImports.length === 0) {
|
||||
return file.source
|
||||
}
|
||||
|
||||
// Update import path
|
||||
toastImports.forEach(path => {
|
||||
path.value.source.value = NEW_IMPORT
|
||||
})
|
||||
|
||||
// Collect all local names the Toast namespace is bound to
|
||||
const toastLocalNames = new Set()
|
||||
toastImports.forEach(path => {
|
||||
path.value.specifiers.forEach(spec => {
|
||||
if (spec.type === 'ImportNamespaceSpecifier') {
|
||||
toastLocalNames.add(spec.local.name)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Transform Toast.show(message, type) calls
|
||||
root.find(j.CallExpression).forEach(path => {
|
||||
const {callee, arguments: args} = path.value
|
||||
|
||||
// Match <ToastName>.show(...)
|
||||
if (
|
||||
callee.type !== 'MemberExpression' ||
|
||||
callee.object.type !== 'Identifier' ||
|
||||
!toastLocalNames.has(callee.object.name) ||
|
||||
callee.property.name !== 'show'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Only transform 2-arg calls where the second arg is a string literal
|
||||
if (args.length !== 2) return
|
||||
const typeArg = args[1]
|
||||
if (typeArg.type !== 'StringLiteral' && typeArg.type !== 'Literal') return
|
||||
|
||||
const legacyType = typeArg.value
|
||||
const newType = convertLegacyToastType(legacyType)
|
||||
|
||||
// Replace the second argument with an options object: {type: 'newType'}
|
||||
args[1] = j.objectExpression([
|
||||
j.property('init', j.identifier('type'), j.stringLiteral(newType)),
|
||||
])
|
||||
})
|
||||
|
||||
return root.toSource()
|
||||
}
|
||||
@@ -431,30 +431,16 @@ yarn intl:compile # Compile translations for runtime
|
||||
// src/state/queries/profile.ts
|
||||
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
// Query key pattern
|
||||
const RQKEY_ROOT = 'profile'
|
||||
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||
|
||||
/*
|
||||
* Query key name should match the query hook name for consistency
|
||||
*/
|
||||
const profileQueryKeyRoot = 'profile'
|
||||
|
||||
/*
|
||||
* Use object params and createQueryKey helper for better readability and to
|
||||
* avoid bugs with parameter order or types.
|
||||
*/
|
||||
export const createProfileQueryKey = (args: {did: string}) =>
|
||||
createQueryKey(profileQueryKeyRoot, args)
|
||||
|
||||
/*
|
||||
* Query hook should be named use[Name]Query, where [Name] describes the data
|
||||
* being fetched. This is not a strict requirement, but it's a helpful
|
||||
* convention for discoverability
|
||||
*/
|
||||
// Query hook
|
||||
export function useProfileQuery({did}: {did: string}) {
|
||||
const agent = useAgent()
|
||||
|
||||
return useQuery({
|
||||
queryKey: createProfileQueryKey({did}),
|
||||
queryKey: RQKEY(did),
|
||||
queryFn: async () => {
|
||||
const res = await agent.getProfile({actor: did})
|
||||
return res.data
|
||||
@@ -464,12 +450,8 @@ export function useProfileQuery({did}: {did: string}) {
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Mutation hook should match the name of the query hook, but with "Mutation"
|
||||
* suffix. This is not a strict requirement, but it's a helpful convention for
|
||||
* discoverability and consistency.
|
||||
*/
|
||||
export function useProfileMutation() {
|
||||
// Mutation hook
|
||||
export function useUpdateProfile() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@@ -477,9 +459,7 @@ export function useProfileMutation() {
|
||||
// Update logic
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: createProfileQueryKey({did: variables.did}),
|
||||
})
|
||||
queryClient.invalidateQueries({queryKey: RQKEY(variables.did)})
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isNetworkError(error)) {
|
||||
@@ -493,24 +473,6 @@ export function useProfileMutation() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* If cache mutation is needed, include specific interfaces for the specific
|
||||
* mutations you require adjacent to the source queries. Naming should be
|
||||
* descriptive of the mutation's purpose, e.g. use[Name]CacheMutation. This is
|
||||
* not a strict requirement, but it's a helpful convention for discoverability
|
||||
* and consistency.
|
||||
*/
|
||||
export function useProfileCacheMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return (data: Partial<Profile>) => {
|
||||
queryClient.setQueryData(createProfileQueryKey({did: data.did}), oldData => {
|
||||
if (!oldData) return oldData
|
||||
return {...oldData, ...data}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Stale Time Constants** (from `src/state/queries/index.ts`):
|
||||
@@ -529,7 +491,7 @@ export function useDraftsQuery() {
|
||||
const agent = useAgent()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: createQueryKey('drafts'),
|
||||
queryKey: ['drafts'],
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.app.bsky.draft.getDrafts({cursor: pageParam})
|
||||
return res.data
|
||||
@@ -542,19 +504,6 @@ export function useDraftsQuery() {
|
||||
|
||||
To get all items from pages: `data?.pages.flatMap(page => page.items) ?? []`
|
||||
|
||||
**Persisted Queries**
|
||||
|
||||
To persist query data across app restarts, `createQueryKey` supports a third
|
||||
parameter called `options`, which has a `persistedVersion` property. When this
|
||||
property is set to a number, the query will be persisted.
|
||||
|
||||
When this property is updated (e.g. incremented), the persisted data will be cleared and replaced with the new data from the query function. This is useful for cases where the shape of the data has changed and old persisted data would no longer be valid.
|
||||
|
||||
```tsx
|
||||
export const createProfileQueryKey = (args: {did: string}) =>
|
||||
createQueryKey(profileQueryKeyRoot, args, {persistedVersion: 1})
|
||||
```
|
||||
|
||||
### Preferences (React Context)
|
||||
|
||||
```tsx
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26-bookworm AS build-env
|
||||
FROM golang:1.25-bookworm AS build-env
|
||||
|
||||
WORKDIR /usr/src/social-app
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26-bookworm AS build-env
|
||||
FROM golang:1.25-bookworm AS build-env
|
||||
|
||||
WORKDIR /usr/src/social-app
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ appId: xyz.blueskyweb.app
|
||||
- tapOn: "Add user to list"
|
||||
- swipe:
|
||||
direction: DOWN
|
||||
- assertVisible:
|
||||
id: "profileCard-bob.test-link"
|
||||
- assertVisible: "View Bob's profile"
|
||||
|
||||
- tapOn: "Posts"
|
||||
- assertVisible:
|
||||
@@ -124,8 +123,7 @@ appId: xyz.blueskyweb.app
|
||||
- tapOn: "Good Ppl"
|
||||
|
||||
- tapOn: "People"
|
||||
- assertVisible:
|
||||
id: "profileCard-bob.test-link"
|
||||
- assertVisible: "View Bob's profile"
|
||||
- tapOn:
|
||||
point: "90%,43%"
|
||||
- tapOn:
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ module.exports = function (_config) {
|
||||
},
|
||||
icon: './assets/app-icons/ios_icon_default_next.png',
|
||||
userInterfaceStyle: 'automatic',
|
||||
primaryColor: '#006AFF',
|
||||
primaryColor: '#1083fe',
|
||||
newArchEnabled: false,
|
||||
ios: {
|
||||
supportsTablet: false,
|
||||
@@ -64,7 +64,6 @@ module.exports = function (_config) {
|
||||
},
|
||||
icon: IOS_ICON_FILE,
|
||||
infoPlist: {
|
||||
CADisableMinimumFrameDurationOnPhone: true,
|
||||
UIBackgroundModes: ['remote-notification'],
|
||||
NSCameraUsageDescription:
|
||||
'Used for profile pictures, posts, and other kinds of content.',
|
||||
@@ -297,6 +296,7 @@ module.exports = function (_config) {
|
||||
'./plugins/withAndroidManifestFCMIconPlugin.js',
|
||||
'./plugins/withAndroidManifestIntentQueriesPlugin.js',
|
||||
'./plugins/withAndroidStylesAccentColorPlugin.js',
|
||||
'./plugins/withAndroidDayNightThemePlugin.js',
|
||||
'./plugins/withAndroidNoJitpackPlugin.js',
|
||||
'./plugins/shareExtension/withShareExtensions.js',
|
||||
'./plugins/notificationsExtension/withNotificationsExtension.js',
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a10 10 0 0 1-4.136-.893l-4.68.876A1 1 0 0 1 2.02 20.8l.93-4.537A10 10 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 2a8 8 0 0 0-7.111 11.668 1 1 0 0 1 .09.66l-.7 3.415 3.537-.662c.214-.04.435-.009.63.088A8 8 0 1 0 12 4Zm0 4a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2H9a1 1 0 1 1 0-2h2V9a1 1 0 0 1 1-1Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 448 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M10.655 3.718c.55-1.116 2.14-1.116 2.69 0l7.548 15.317c.578 1.172-.515 2.471-1.768 2.103L13 19.336V15a1 1 0 0 0-2 0v4.336l-6.124 1.802c-1.254.369-2.346-.93-1.769-2.103l7.548-15.317Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 284 B |
@@ -1,10 +1,10 @@
|
||||
<svg width="120" height="28" viewBox="0 0 120 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.23031 1.75921C9.52558 4.31406 13.0698 9.49434 14.3713 12.2741C15.6727 9.49434 19.2169 4.31406 22.5122 1.75921C24.8899 -0.0842317 28.7425 -1.5106 28.7425 3.02818C28.7425 3.93462 28.2393 10.6429 27.9441 11.7321C26.9181 15.5183 23.1796 16.4841 19.8539 15.8995C25.667 16.9212 27.1457 20.3055 23.9521 23.6897C16.7665 31.3043 14.3713 19.6163 14.3713 19.6163C14.3713 19.6163 11.976 31.3043 4.79042 23.6897C1.59681 20.3055 3.07554 16.9212 8.88858 15.8995C5.56292 16.4841 1.82441 15.5183 0.798403 11.7321C0.503259 10.6429 0 3.93462 0 3.02818C0 -1.5106 3.85263 -0.0842317 6.23031 1.75921Z" fill="#006AFF"/>
|
||||
<path d="M46.662 12.8778C48.641 13.6012 49.6915 15.2726 49.6915 17.1435C49.6915 20.3116 47.6149 22.2324 43.6326 22.2324H35.497V4.47113H43.3638C47.1507 4.47113 48.983 6.44184 48.983 9.06113C48.983 10.8073 48.2012 12.0796 46.662 12.8778ZM43.1195 7.2401H38.7952V11.88H43.1195C44.8053 11.88 45.7092 10.9819 45.7092 9.48521C45.7092 8.1132 44.7808 7.2401 43.1195 7.2401ZM38.7952 19.4385H43.4616C45.3183 19.4385 46.32 18.5654 46.32 16.9938C46.32 15.3474 45.3672 14.5242 43.4616 14.5242H38.7952V19.4385Z" fill="#006AFF"/>
|
||||
<path d="M54.2645 22.2324H51.1862V4.47113H54.2645V22.2324Z" fill="#006AFF"/>
|
||||
<path d="M64.4712 16.5698V9.36048H67.5495V22.2324H64.5689V20.3615C63.6161 21.8084 62.2968 22.5318 60.6111 22.5318C57.9481 22.5318 56.2135 20.8854 56.2135 17.8919V9.36048H59.2918V17.368C59.2918 18.9895 60.0736 19.8127 61.6616 19.8127C63.1519 19.8127 64.4712 18.6902 64.4712 16.5698Z" fill="#006AFF"/>
|
||||
<path d="M81.5614 16.021V16.7693H72.131C72.3508 18.9895 73.548 20.0871 75.4047 20.0871C76.8217 20.0871 77.7746 19.4635 78.2876 18.2411H81.2438C80.5841 20.8604 78.3853 22.5318 75.3803 22.5318C73.4991 22.5318 71.9844 21.9081 70.8361 20.6858C69.6878 19.4635 69.1015 17.842 69.1015 15.7965C69.1015 13.7759 69.6634 12.1544 70.8117 10.9071C71.9599 9.68477 73.4502 9.06113 75.3314 9.06113C77.2371 9.06113 78.7518 9.70972 79.8756 10.9819C80.9995 12.2542 81.5614 13.9505 81.5614 16.021ZM75.307 11.5058C73.6213 11.5058 72.4486 12.5036 72.1554 14.5741H78.4831C78.2143 12.7032 77.0905 11.5058 75.307 11.5058Z" fill="#006AFF"/>
|
||||
<path d="M88.3842 22.5817C84.7195 22.5817 82.7894 21.1099 82.6184 18.1413H85.6234C85.7944 19.7379 86.5762 20.3366 88.433 20.3366C90.0943 20.3366 90.925 19.8127 90.925 18.7899C90.925 17.8669 90.3386 17.4179 88.4574 17.0936L87.016 16.8442C84.2553 16.3702 82.8871 15.073 82.8871 12.9527C82.8871 10.5329 84.7683 9.06113 88.1154 9.06113C91.7068 9.06113 93.5636 10.508 93.6857 13.4266H90.7784C90.7051 11.855 89.8012 11.3062 88.1154 11.3062C86.6495 11.3062 85.9166 11.8052 85.9166 12.803C85.9166 13.701 86.5518 14.1002 88.0177 14.3746L89.6057 14.624C92.6596 15.1978 93.9789 16.3453 93.9789 18.5405C93.9789 21.1348 91.9267 22.5817 88.3842 22.5817Z" fill="#006AFF"/>
|
||||
<path d="M107.49 22.2324H103.972L100.307 16.2455L98.4015 18.1912V22.2324H95.372V4.47113H98.4015V14.6988L103.532 9.36048H107.197L102.433 14.2249L107.49 22.2324Z" fill="#006AFF"/>
|
||||
<path d="M115.529 12.6034L116.555 9.36048H119.78L114.918 23.2802C114.405 24.7021 113.77 25.7248 112.964 26.2986C112.158 26.8723 111.009 27.1467 109.495 27.1467C108.982 27.1467 108.542 27.1218 108.151 27.0719V24.6023H109.324C110.716 24.6023 111.4 23.7292 111.4 22.5318C111.4 21.9331 111.205 21.06 110.814 19.9374L107.149 9.36048H110.472L111.498 12.5785C112.255 14.9982 112.915 17.393 113.501 19.7628C114.039 17.7173 114.723 15.3225 115.529 12.6034Z" fill="#006AFF"/>
|
||||
<svg width="105" height="32" viewBox="0 0 105 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.7901 9.77492C21.7947 11.2889 23.9508 14.3587 24.7425 16.0059C25.5342 14.3587 27.6903 11.2889 29.6949 9.77492C31.1413 8.68251 33.485 7.83725 33.485 10.5269C33.485 11.064 33.1789 15.0393 32.9993 15.6848C32.3752 17.9285 30.1009 18.5008 28.0778 18.1544C31.6141 18.7598 32.5136 20.7653 30.5709 22.7708C26.1996 27.2831 24.7425 20.3569 24.7425 20.3569C24.7425 20.3569 23.2854 27.2831 18.9142 22.7708C16.9714 20.7653 17.871 18.7598 21.4072 18.1544C19.3841 18.5008 17.1099 17.9285 16.4857 15.6848C16.3061 15.0393 16 11.064 16 10.5269C16 7.83725 18.3437 8.68251 19.7901 9.77492Z" fill="white"/>
|
||||
<path d="M44.3863 16.3646C45.5901 16.7932 46.2292 17.7837 46.2292 18.8924C46.2292 20.7698 44.9659 21.908 42.5434 21.908H37.5942V11.3828H42.3799C44.6835 11.3828 45.7982 12.5506 45.7982 14.1028C45.7982 15.1376 45.3226 15.8915 44.3863 16.3646ZM42.2313 13.0237H39.6006V15.7732H42.2313C43.2568 15.7732 43.8067 15.2411 43.8067 14.3541C43.8067 13.5411 43.2419 13.0237 42.2313 13.0237ZM39.6006 20.2524H42.4393C43.5689 20.2524 44.1782 19.735 44.1782 18.8037C44.1782 17.828 43.5986 17.3402 42.4393 17.3402H39.6006V20.2524Z" fill="white"/>
|
||||
<path d="M49.0111 21.908H47.1385V11.3828H49.0111V21.908Z" fill="white"/>
|
||||
<path d="M55.2202 18.5524V14.2802H57.0929V21.908H55.2797V20.7993C54.7 21.6567 53.8975 22.0854 52.872 22.0854C51.252 22.0854 50.1968 21.1098 50.1968 19.3359V14.2802H52.0694V19.0254C52.0694 19.9863 52.545 20.4741 53.5111 20.4741C54.4177 20.4741 55.2202 19.8089 55.2202 18.5524Z" fill="white"/>
|
||||
<path d="M65.6167 18.2272V18.6706H59.8799C60.0137 19.9863 60.7419 20.6367 61.8714 20.6367C62.7334 20.6367 63.3131 20.2672 63.6252 19.5428H65.4235C65.0222 21.095 63.6846 22.0854 61.8566 22.0854C60.7122 22.0854 59.7907 21.7159 59.0922 20.9915C58.3937 20.2672 58.037 19.3063 58.037 18.0941C58.037 16.8967 58.3788 15.9359 59.0773 15.1967C59.7759 14.4724 60.6825 14.1028 61.8269 14.1028C62.9861 14.1028 63.9076 14.4872 64.5912 15.2411C65.2749 15.995 65.6167 17.0002 65.6167 18.2272ZM61.812 15.5515C60.7865 15.5515 60.0731 16.1428 59.8948 17.3698H63.7441C63.5806 16.2611 62.8969 15.5515 61.812 15.5515Z" fill="white"/>
|
||||
<path d="M69.7673 22.115C67.5379 22.115 66.3638 21.2428 66.2598 19.4837H68.0878C68.1918 20.4298 68.6674 20.7846 69.797 20.7846C70.8076 20.7846 71.3129 20.4741 71.3129 19.868C71.3129 19.3211 70.9562 19.055 69.8118 18.8628L68.935 18.715C67.2555 18.4341 66.4232 17.6654 66.4232 16.4089C66.4232 14.975 67.5676 14.1028 69.6038 14.1028C71.7885 14.1028 72.9181 14.9602 72.9924 16.6898H71.2238C71.1792 15.7585 70.6293 15.4332 69.6038 15.4332C68.712 15.4332 68.2662 15.7289 68.2662 16.3202C68.2662 16.8524 68.6526 17.0889 69.5443 17.2515L70.5104 17.3993C72.3681 17.7393 73.1707 18.4193 73.1707 19.7202C73.1707 21.2576 71.9223 22.115 69.7673 22.115Z" fill="white"/>
|
||||
<path d="M81.3899 21.908H79.2497L77.0204 18.3602L75.8611 19.5132V21.908H74.0182V11.3828H75.8611V17.4437L78.9822 14.2802H81.2116L78.3134 17.1628L81.3899 21.908Z" fill="white"/>
|
||||
<path d="M86.2805 16.2019L86.9047 14.2802H88.8665L85.909 22.5289C85.5968 23.3715 85.2104 23.9776 84.72 24.3176C84.2295 24.6576 83.531 24.8202 82.6095 24.8202C82.2974 24.8202 82.0299 24.8054 81.7921 24.7759V23.3124H82.5055C83.3526 23.3124 83.7688 22.795 83.7688 22.0854C83.7688 21.7306 83.6499 21.2132 83.4121 20.548L81.1827 14.2802H83.204L83.8282 16.1872C84.289 17.6211 84.6902 19.0402 85.0469 20.4446C85.3739 19.2324 85.7901 17.8132 86.2805 16.2019Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#fff" d="M6.514 2.143A1 1 0 0 0 5 3v18a1 1 0 0 0 1.514.858l15-9a1 1 0 0 0 0-1.716l-15-9Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 182 B |
@@ -9,11 +9,7 @@ export function applyTheme(theme: 'light' | 'dark') {
|
||||
document.documentElement.classList.add(theme)
|
||||
}
|
||||
|
||||
export function initSystemColorMode({additionalBodyClasses = ''} = {}) {
|
||||
if (additionalBodyClasses) {
|
||||
document.body.classList.add(additionalBodyClasses)
|
||||
}
|
||||
|
||||
export function initSystemColorMode() {
|
||||
applyTheme(
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
|
||||
@@ -20,7 +20,7 @@ export function Container({
|
||||
if (!entry) return
|
||||
|
||||
let {height} = entry.contentRect
|
||||
height += 4 // border-2 = 2px top + 2px bottom
|
||||
height += 2 // border top and bottom
|
||||
if (height !== prevHeight.current) {
|
||||
prevHeight.current = height
|
||||
window.parent.postMessage(
|
||||
@@ -37,7 +37,7 @@ export function Container({
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="w-full border-2 border-brand text-black relative transition-colors max-w-[600px] min-w-[300px] flex items-center dark:text-slate-200 rounded-[32px] overflow-hidden cursor-pointer"
|
||||
className="w-full bg-brand text-black dark:bg-brand relative transition-colors max-w-[600px] min-w-[300px] flex items-center dark:text-slate-200 rounded-[20px] cursor-pointer hover:bg-opacity-90"
|
||||
onClick={() => {
|
||||
if (ref.current && href) {
|
||||
// forwardRef requires preact/compat - let's keep it simple
|
||||
@@ -49,7 +49,9 @@ export function Container({
|
||||
}
|
||||
}}>
|
||||
{href && <Link href={href} />}
|
||||
<div className="flex-1 max-w-full">{children}</div>
|
||||
<div className="flex-1 px-[6px] pt-[6px] pb-2.5 max-w-full">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ import {ComponentChildren, h} from 'preact'
|
||||
import {useMemo} from 'preact/hooks'
|
||||
|
||||
import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg'
|
||||
import playIcon from '../../assets/play_filled_corner0_rounded.svg'
|
||||
import playIcon from '../../assets/play_filled_corner2_rounded.svg'
|
||||
import starterPackIcon from '../../assets/starterPack.svg'
|
||||
import {Globe} from '../icons/Globe'
|
||||
import {CONTENT_LABELS, labelsToInfo} from '../labels'
|
||||
import * as bsky from '../types/bsky'
|
||||
import {getRkey} from '../util/rkey'
|
||||
@@ -94,7 +93,7 @@ export function Embed({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center shrink min-w-0 min-h-0">
|
||||
<p className="text-sm shrink-0 font-semibold max-w-[70%] truncate">
|
||||
<p className="block text-sm shrink-0 font-bold max-w-[70%] line-clamp-1">
|
||||
{record.author.displayName?.trim() || record.author.handle}
|
||||
</p>
|
||||
{verification.isVerified && (
|
||||
@@ -104,7 +103,7 @@ export function Embed({
|
||||
size={12}
|
||||
/>
|
||||
)}
|
||||
<p className="text-sm text-textLight dark:text-textDimmed min-w-0 truncate ml-1">
|
||||
<p className="block line-clamp-1 text-sm text-textLight dark:text-textDimmed shrink-[10] ml-1">
|
||||
@{record.author.handle}
|
||||
</p>
|
||||
</div>
|
||||
@@ -335,18 +334,13 @@ function ExternalEmbed({
|
||||
/>
|
||||
)}
|
||||
<div className="py-3 px-4">
|
||||
<p className="font-semibold leading-tight line-clamp-3">
|
||||
{content.external.title}
|
||||
<p className="text-sm text-textLight dark:text-textDimmed line-clamp-1">
|
||||
{toNiceDomain(content.external.uri)}
|
||||
</p>
|
||||
<p className="text-sm leading-snug text-textLight dark:text-textDimmed line-clamp-2 mt-0.5">
|
||||
<p className="font-semibold line-clamp-3">{content.external.title}</p>
|
||||
<p className="text-sm text-textLight dark:text-textDimmed line-clamp-2 mt-0.5">
|
||||
{content.external.description}
|
||||
</p>
|
||||
<div className="flex flex-row items-center gap-1 border-t dark:border-slate-600 mt-1 pt-1.5">
|
||||
<Globe size={12} className="text-textLight dark:text-textDimmed" />
|
||||
<p className="text-sm leading-none text-textLight dark:text-textDimmed line-clamp-1">
|
||||
{toNiceDomain(content.external.uri)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
@@ -380,7 +374,7 @@ function GenericWithImageEmbed({
|
||||
<div className="w-8 h-8 rounded-md bg-brand shrink-0" />
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-sm">{title}</p>
|
||||
<p className="font-bold text-sm">{title}</p>
|
||||
<p className="text-textLight dark:text-textDimmed text-sm">
|
||||
{subtitle}
|
||||
</p>
|
||||
@@ -395,6 +389,7 @@ function GenericWithImageEmbed({
|
||||
)
|
||||
}
|
||||
|
||||
// just the thumbnail and a play button
|
||||
function VideoEmbed({content}: {content: AppBskyEmbedVideo.View}) {
|
||||
let aspectRatio = 1
|
||||
|
||||
@@ -403,28 +398,6 @@ function VideoEmbed({content}: {content: AppBskyEmbedVideo.View}) {
|
||||
aspectRatio = clamp(width / height, 1 / 1, 3 / 1)
|
||||
}
|
||||
|
||||
const supportsHls = useMemo(() => {
|
||||
const video = document.createElement('video')
|
||||
return video.canPlayType('application/vnd.apple.mpegurl') !== ''
|
||||
}, [])
|
||||
|
||||
if (supportsHls) {
|
||||
return (
|
||||
<video
|
||||
src={content.playlist}
|
||||
poster={content.thumbnail}
|
||||
controls
|
||||
playsinline
|
||||
preload="metadata"
|
||||
loading="lazy"
|
||||
aria-label={content.alt || undefined}
|
||||
onClickCapture={evt => evt.stopPropagation()}
|
||||
className="w-full rounded-xl bg-black"
|
||||
style={{aspectRatio: `${aspectRatio} / 1`}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full overflow-hidden rounded-xl aspect-square relative"
|
||||
|
||||
@@ -53,7 +53,7 @@ export function Post({thread}: Props) {
|
||||
return (
|
||||
<Container href={href}>
|
||||
<div
|
||||
className="flex-1 flex-col flex gap-4 bg-white dark:bg-black hover:bg-brandHover dark:hover:bg-brandHoverDark rounded-[30px] p-5"
|
||||
className="flex-1 flex-col flex gap-2 bg-neutral-50 dark:bg-black dark:hover:bg-slate-900 hover:bg-blue-50 rounded-[14px] p-4"
|
||||
lang={record?.langs?.[0]}>
|
||||
<div className="flex gap-2.5 items-center cursor-pointer w-full max-w-full ">
|
||||
<Link
|
||||
@@ -70,7 +70,7 @@ export function Post({thread}: Props) {
|
||||
<div className="flex flex-1 items-center">
|
||||
<Link
|
||||
href={`/profile/${post.author.did}`}
|
||||
className="block font-semibold text-[15px] min-[400px]:text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 text-ellipsis decoration-2">
|
||||
className="block font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 text-ellipsis decoration-2">
|
||||
{post.author.displayName?.trim() || post.author.handle}
|
||||
</Link>
|
||||
{verification.isVerified && (
|
||||
@@ -87,68 +87,72 @@ export function Post({thread}: Props) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[13px] min-[400px]:text-[15px] min-w-0">
|
||||
<Link
|
||||
href={`/profile/${post.author.did}`}
|
||||
className="text-textNeutral hover:underline line-clamp-1">
|
||||
@{post.author.handle}
|
||||
</Link>
|
||||
<span className="text-textNeutral shrink-0">·</span>
|
||||
<Link
|
||||
href={`/profile/${post.author.did}`}
|
||||
className="text-brand hover:underline shrink-0">
|
||||
Follow
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href={`/profile/${post.author.did}`}
|
||||
className="block text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1">
|
||||
@{post.author.handle}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PostContent record={record} />
|
||||
<Embed content={post.embed} labels={post.labels} />
|
||||
|
||||
<div className="flex items-end justify-between w-full">
|
||||
<div className="flex flex-col min-[400px]:gap-0.5">
|
||||
<div className="flex items-center gap-3 text-sm cursor-pointer ml-[-2px]">
|
||||
{!!post.likeCount && (
|
||||
<div className="flex items-center gap-0.5 min-[400px]:gap-1 cursor-pointer group">
|
||||
<LikeIcon className="w-5 h-5 min-[400px]:w-[22px] min-[400px]:h-[22px] text-textLight dark:text-textDimmed group-hover:text-neutral-800 dark:group-hover:text-white transition-colors" />
|
||||
<p className="text-[11px] min-[400px]:text-[15px] font-semibold text-textLight dark:text-textDimmed mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors">
|
||||
{prettyNumber(post.likeCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!!post.replyCount && (
|
||||
<div className="flex items-center gap-0.5 min-[400px]:gap-1 cursor-pointer group">
|
||||
<ReplyIcon className="w-5 h-5 min-[400px]:w-[22px] min-[400px]:h-[22px] text-textLight dark:text-textDimmed group-hover:text-neutral-800 dark:group-hover:text-white transition-colors" />
|
||||
<p className="text-[11px] min-[400px]:text-[15px] font-semibold text-textLight dark:text-textDimmed mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors">
|
||||
{prettyNumber(post.replyCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!!post.repostCount && (
|
||||
<div className="flex items-center gap-0.5 min-[400px]:gap-1 cursor-pointer group">
|
||||
<RepostIcon className="w-5 h-5 min-[400px]:w-[22px] min-[400px]:h-[22px] text-textLight dark:text-textDimmed group-hover:text-neutral-800 dark:group-hover:text-white transition-colors" />
|
||||
<p className="text-[11px] min-[400px]:text-[15px] font-semibold text-textLight dark:text-textDimmed mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors">
|
||||
{prettyNumber(post.repostCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Link href={href}>
|
||||
<time
|
||||
datetime={new Date(post.indexedAt).toISOString()}
|
||||
className="text-[11px] min-[400px]:text-[15px] text-textNeutral hover:underline">
|
||||
{niceDate(post.indexedAt)}
|
||||
</time>
|
||||
</Link>
|
||||
<div className="flex items-center justify-between w-full pt-2.5 text-sm">
|
||||
<div className="flex items-center gap-3 text-sm cursor-pointer">
|
||||
{!!post.likeCount && (
|
||||
<div className="flex items-center gap-1 cursor-pointer group">
|
||||
<LikeIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
|
||||
/>
|
||||
<p className="font-medium text-slate-600 text-neutral-600 dark:text-neutral-300 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors dark:text-slate-400">
|
||||
{prettyNumber(post.likeCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!!post.replyCount && (
|
||||
<div className="flex items-center gap-1 cursor-pointer group">
|
||||
<ReplyIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
|
||||
/>
|
||||
<p className="font-medium text-slate-600 text-neutral-600 dark:text-neutral-300 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors dark:text-slate-400">
|
||||
{prettyNumber(post.replyCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!post.repostCount && (
|
||||
<div className="flex items-center gap-1 cursor-pointer group">
|
||||
<RepostIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
|
||||
/>
|
||||
<p className="font-medium text-slate-600 dark:text-slate-400 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors">
|
||||
{prettyNumber(post.repostCount)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href={href}
|
||||
className="transition-transform hover:scale-110 shrink-0">
|
||||
<img src={logo} className="h-5 min-[400px]:h-7" />
|
||||
<Link href={href}>
|
||||
<time
|
||||
datetime={new Date(post.indexedAt).toISOString()}
|
||||
className="text-slate-500 dark:text-textDimmed text-sm hover:underline dark:text-slate-500">
|
||||
{niceDate(post.indexedAt)}
|
||||
</time>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end pt-2">
|
||||
<Link
|
||||
href={href}
|
||||
className="transition-transform hover:scale-110 shrink-0">
|
||||
<img src={logo} className="h-8" />
|
||||
</Link>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -173,7 +177,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
|
||||
<Link
|
||||
key={counter}
|
||||
href={segment.link.uri}
|
||||
className="text-brand hover:underline"
|
||||
className="text-blue-500 hover:underline"
|
||||
disableTracking={
|
||||
!segment.link.uri.startsWith('https://bsky.app') &&
|
||||
!segment.link.uri.startsWith('https://go.bsky.app')
|
||||
@@ -189,7 +193,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
|
||||
<Link
|
||||
key={counter}
|
||||
href={`/profile/${segment.mention.did}`}
|
||||
className="text-brand hover:underline">
|
||||
className="text-blue-500 hover:underline">
|
||||
{segment.text}
|
||||
</Link>,
|
||||
)
|
||||
@@ -201,7 +205,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
|
||||
<Link
|
||||
key={counter}
|
||||
href={`/hashtag/${segment.tag.tag}`}
|
||||
className="text-brand hover:underline">
|
||||
className="text-blue-500 hover:underline">
|
||||
{segment.text}
|
||||
</Link>,
|
||||
)
|
||||
@@ -213,7 +217,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<p className="text-md min-[400px]:text-lg leading-snug min-[400px]:leading-snug break-word break-words whitespace-pre-wrap">
|
||||
<p className="min-[300px]:text-lg leading-6 min-[300px]:leading-6 break-word break-words whitespace-pre-wrap">
|
||||
{richText}
|
||||
</p>
|
||||
)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {h} from 'preact'
|
||||
|
||||
export const Globe = ({
|
||||
size = 14,
|
||||
className,
|
||||
}: {
|
||||
size?: number
|
||||
className?: string
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
width={size}
|
||||
height={size}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.99 7.99 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
@@ -2,12 +2,6 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
|
||||
const root = document.getElementById('app')
|
||||
if (!root) throw new Error('No root element')
|
||||
|
||||
initSystemColorMode({additionalBodyClasses: 'dark:bg-dimmedBgDarken'})
|
||||
initSystemColorMode()
|
||||
|
||||
const agent = new AtpAgent({
|
||||
service: 'https://public.api.bsky.app',
|
||||
@@ -39,7 +39,6 @@ render(<LandingPage />, root)
|
||||
function LandingPage() {
|
||||
const [uri, setUri] = useState('')
|
||||
const [colorMode, setColorMode] = useState<ColorModeValues>('system')
|
||||
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [thread, setThread] = useState<AppBskyFeedDefs.ThreadViewPost | null>(
|
||||
@@ -119,7 +118,7 @@ function LandingPage() {
|
||||
}, [uri])
|
||||
|
||||
return (
|
||||
<main className="w-full min-h-dvh flex flex-col items-center gap-8 py-14 px-4 md:pt-32 dark:text-slate-200">
|
||||
<main className="w-full min-h-screen flex flex-col items-center gap-8 py-14 px-4 md:pt-32 dark:bg-dimmedBgDarken dark:text-slate-200">
|
||||
<Link
|
||||
href="https://bsky.social/about"
|
||||
className="transition-transform hover:scale-110">
|
||||
@@ -186,7 +185,7 @@ function LandingPage() {
|
||||
function Skeleton() {
|
||||
return (
|
||||
<Container>
|
||||
<div className="flex-1 flex-col flex gap-2 p-5 pb-8">
|
||||
<div className="flex-1 flex-col flex gap-2 pb-8">
|
||||
<div className="flex gap-2.5 items-center">
|
||||
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-100 dark:bg-slate-700 shrink-0 animate-pulse" />
|
||||
<div className="flex-1">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export function niceDate(date: number | string | Date) {
|
||||
const d = new Date(date)
|
||||
return `${d.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})} · ${d.toLocaleDateString('en-us', {
|
||||
return `${d.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})} at ${d.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})}`
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: 'rgb(0,106,255)',
|
||||
brandHover: 'rgb(245,249,255)',
|
||||
brandHoverDark: 'rgb(17,24,34)',
|
||||
brand: 'rgb(10,122,255)',
|
||||
brandLighten: 'rgb(32,139,254)',
|
||||
textLight: 'rgb(63,82,104)',
|
||||
textDimmed: 'rgb(164,179,197)',
|
||||
textNeutral: 'rgb(102,123,153)',
|
||||
textLight: 'rgb(66,87,108)',
|
||||
textDimmed: 'rgb(174,187,201)',
|
||||
dimmedBgLighten: 'rgb(30,41,54)',
|
||||
dimmedBg: 'rgb(22,30,39)',
|
||||
dimmedBgDarken: 'rgb(18,25,32)',
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "npm run test:unit && npm run test:e2e",
|
||||
"test:e2e": "./tests/infra/with-test-db.sh node --loader ts-node/esm --test ./tests/index.ts",
|
||||
"test:unit": "node --loader ts-node/esm --test ./src/*.test.ts",
|
||||
"test": "./tests/infra/with-test-db.sh node --loader ts-node/esm --test ./tests/index.ts",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -15,7 +15,6 @@ export type ServiceConfig = {
|
||||
safelinkPdsUrl?: string
|
||||
safelinkAgentIdentifier?: string
|
||||
safelinkAgentPass?: string
|
||||
metricsApiHost?: string
|
||||
}
|
||||
|
||||
export type DbConfig = {
|
||||
@@ -46,7 +45,6 @@ export type Environment = {
|
||||
safelinkPdsUrl?: string
|
||||
safelinkAgentIdentifier?: string
|
||||
safelinkAgentPass?: string
|
||||
metricsApiHost?: string
|
||||
}
|
||||
|
||||
export const readEnv = (): Environment => {
|
||||
@@ -67,7 +65,6 @@ export const readEnv = (): Environment => {
|
||||
safelinkPdsUrl: envStr('LINK_SAFELINK_PDS_URL'),
|
||||
safelinkAgentIdentifier: envStr('LINK_SAFELINK_AGENT_IDENTIFIER'),
|
||||
safelinkAgentPass: envStr('LINK_SAFELINK_AGENT_PASS'),
|
||||
metricsApiHost: envStr('LINK_METRICS_API_HOST'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +79,6 @@ export const envToCfg = (env: Environment): Config => {
|
||||
safelinkPdsUrl: env.safelinkPdsUrl,
|
||||
safelinkAgentIdentifier: env.safelinkAgentIdentifier,
|
||||
safelinkAgentPass: env.safelinkAgentPass,
|
||||
metricsApiHost: env.metricsApiHost,
|
||||
}
|
||||
if (!env.dbPostgresUrl) {
|
||||
throw new Error('Must configure postgres url (LINK_DB_POSTGRES_URL)')
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {SafelinkClient} from './cache/safelinkClient.js'
|
||||
import {type Config} from './config.js'
|
||||
import Database from './db/index.js'
|
||||
import {MetricsClient} from './metrics.js'
|
||||
|
||||
export type AppContextOptions = {
|
||||
cfg: Config
|
||||
@@ -13,7 +12,6 @@ export class AppContext {
|
||||
db: Database
|
||||
safelinkClient: SafelinkClient
|
||||
abortController = new AbortController()
|
||||
metrics: MetricsClient
|
||||
|
||||
constructor(private opts: AppContextOptions) {
|
||||
this.cfg = this.opts.cfg
|
||||
@@ -22,9 +20,6 @@ export class AppContext {
|
||||
cfg: this.opts.cfg.service,
|
||||
db: this.opts.db,
|
||||
})
|
||||
this.metrics = new MetricsClient({
|
||||
trackingEndpoint: this.opts.cfg.service.metricsApiHost,
|
||||
})
|
||||
}
|
||||
|
||||
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import escapeHTML from 'escape-html'
|
||||
|
||||
export function linkRedirectContents(link: string): string {
|
||||
// Encode characters that could break out of the single-quoted URL in meta refresh.
|
||||
// HTML entity escaping (') is insufficient because the browser decodes entities
|
||||
// before the meta refresh parser processes the URL, allowing apostrophes to
|
||||
// prematurely terminate the URL string.
|
||||
//
|
||||
// Example: "They're" with HTML escaping becomes "They're" in HTML, but after
|
||||
// the browser decodes the content attribute, the meta refresh parser sees "They're"
|
||||
// and interprets the apostrophe as the closing quote, truncating the URL to "They".
|
||||
const safeLink = link.replace(/'/g, '%27')
|
||||
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; URL='${escapeHTML(safeLink)}'" />
|
||||
<meta http-equiv="refresh" content="0; URL='${escapeHTML(link)}'" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||
|
||||
@@ -36,7 +36,6 @@ export class LinkService {
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.ctx.metrics.start()
|
||||
this.server = this.app.listen(this.ctx.cfg.service.port)
|
||||
this.server.keepAliveTimeout = 90000
|
||||
this.terminator = createHttpTerminator({server: this.server})
|
||||
@@ -47,6 +46,5 @@ export class LinkService {
|
||||
this.ctx.abortController.abort()
|
||||
await this.terminator?.terminate()
|
||||
await this.ctx.db.close()
|
||||
this.ctx.metrics.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import assert from 'node:assert'
|
||||
import {afterEach, beforeEach, describe, it, mock} from 'node:test'
|
||||
|
||||
import {httpLogger} from './logger.js'
|
||||
import {MetricsClient} from './metrics.js'
|
||||
|
||||
type TestEvents = {
|
||||
click: {button: string}
|
||||
view: {screen: string}
|
||||
}
|
||||
|
||||
describe('MetricsClient', () => {
|
||||
let fetchMock: ReturnType<typeof mock.fn>
|
||||
let fetchRequests: {body: any}[]
|
||||
let client: MetricsClient<TestEvents>
|
||||
let loggerErrorMock: ReturnType<typeof mock.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
mock.timers.enable({apis: ['setInterval', 'setTimeout']})
|
||||
fetchRequests = []
|
||||
fetchMock = mock.fn(async (_url: any, options: any) => {
|
||||
const body = JSON.parse(options.body)
|
||||
fetchRequests.push({body})
|
||||
return {ok: true, status: 200, text: async () => ''}
|
||||
})
|
||||
;(globalThis as any).fetch = fetchMock
|
||||
loggerErrorMock = mock.fn()
|
||||
httpLogger.error = loggerErrorMock as any
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
client?.stop()
|
||||
mock.timers.reset()
|
||||
mock.restoreAll()
|
||||
})
|
||||
|
||||
it('flushes events on interval', async () => {
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.track('click', {button: 'submit'})
|
||||
client.track('view', {screen: 'home'})
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 0)
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 1)
|
||||
assert.strictEqual(fetchRequests[0].body.events.length, 2)
|
||||
assert.strictEqual(fetchRequests[0].body.events[0].event, 'click')
|
||||
assert.strictEqual(fetchRequests[0].body.events[1].event, 'view')
|
||||
})
|
||||
|
||||
it('flushes when maxBatchSize is exceeded', async () => {
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.maxBatchSize = 5
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
client.track('click', {button: `btn-${i}`})
|
||||
}
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 0)
|
||||
|
||||
client.track('click', {button: 'btn-trigger'})
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 1)
|
||||
assert.strictEqual(fetchRequests[0].body.events.length, 6)
|
||||
})
|
||||
|
||||
it('logs error on failed request', async () => {
|
||||
fetchMock.mock.mockImplementation(async () => {
|
||||
return {
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: async () => 'Internal Server Error',
|
||||
}
|
||||
})
|
||||
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.track('click', {button: 'submit'})
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchMock.mock.callCount(), 1)
|
||||
assert.strictEqual(loggerErrorMock.mock.callCount(), 1)
|
||||
const call = loggerErrorMock.mock.calls[0]
|
||||
const arg = call.arguments[0] as {err: Error}
|
||||
assert.ok(arg.err instanceof Error)
|
||||
assert.strictEqual(call.arguments[1], 'Failed to send metrics')
|
||||
})
|
||||
|
||||
it('handles fetch text() error gracefully', async () => {
|
||||
fetchMock.mock.mockImplementation(async () => {
|
||||
return {
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: async () => {
|
||||
throw new Error('Failed to read response')
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.track('click', {button: 'submit'})
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchMock.mock.callCount(), 1)
|
||||
assert.strictEqual(loggerErrorMock.mock.callCount(), 1)
|
||||
const call = loggerErrorMock.mock.calls[0]
|
||||
const arg = call.arguments[0] as {err: Error}
|
||||
assert.ok(arg.err instanceof Error)
|
||||
assert.match(arg.err.message, /Unknown error/)
|
||||
assert.strictEqual(call.arguments[1], 'Failed to send metrics')
|
||||
})
|
||||
|
||||
it('flushes when stop() is called', async () => {
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.track('click', {button: 'submit'})
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 0)
|
||||
|
||||
client.stop()
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 1)
|
||||
assert.strictEqual(fetchRequests[0].body.events.length, 1)
|
||||
assert.strictEqual(fetchRequests[0].body.events[0].event, 'click')
|
||||
})
|
||||
|
||||
it('does not send if trackingEndpoint is not configured', async () => {
|
||||
client = new MetricsClient<TestEvents>({})
|
||||
client.track('click', {button: 'submit'})
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchMock.mock.callCount(), 0)
|
||||
})
|
||||
|
||||
it('start() is idempotent', async () => {
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
|
||||
client.track('click', {button: 'submit'})
|
||||
client.start()
|
||||
client.start()
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchRequests.length, 1)
|
||||
})
|
||||
|
||||
it('does not flush if queue is empty', async () => {
|
||||
client = new MetricsClient<TestEvents>({
|
||||
trackingEndpoint: 'https://test.metrics.api',
|
||||
})
|
||||
client.start()
|
||||
|
||||
mock.timers.tick(10_000)
|
||||
await flush()
|
||||
|
||||
assert.strictEqual(fetchMock.mock.callCount(), 0)
|
||||
})
|
||||
})
|
||||
|
||||
function flush() {
|
||||
return new Promise(r => setImmediate(r))
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import {httpLogger} from './logger.js'
|
||||
|
||||
/**
|
||||
* New metrics events should be added here
|
||||
*/
|
||||
type Events = {
|
||||
redirect: {
|
||||
link: string
|
||||
whitelisted: 'unknown' | 'yes'
|
||||
blocked: boolean
|
||||
warned: boolean
|
||||
utm_source?: string
|
||||
utm_medium?: string
|
||||
utm_campaign?: string
|
||||
utm_content?: string
|
||||
utm_term?: string
|
||||
}
|
||||
invalid_redirect: {
|
||||
link: string
|
||||
}
|
||||
}
|
||||
|
||||
type Event<M extends Record<string, any>> = {
|
||||
time: number
|
||||
event: keyof M
|
||||
payload: M[keyof M]
|
||||
metadata: Record<string, any>
|
||||
}
|
||||
|
||||
export type Config = {
|
||||
trackingEndpoint?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This MetricsClient is duplicated from both `social-app` and `atproto`
|
||||
* codebases.
|
||||
*/
|
||||
export class MetricsClient<M extends Record<string, any> = Events> {
|
||||
maxBatchSize = 100
|
||||
|
||||
private disabled: boolean = false
|
||||
private started: boolean = false
|
||||
private queue: Event<M>[] = []
|
||||
private flushInterval: NodeJS.Timeout | null = null
|
||||
constructor(private config: Config) {
|
||||
this.disabled = !config.trackingEndpoint
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.disabled) return
|
||||
if (this.started) return
|
||||
this.started = true
|
||||
this.flushInterval = setInterval(() => {
|
||||
this.flush()
|
||||
}, 10_000)
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.flushInterval) {
|
||||
clearInterval(this.flushInterval)
|
||||
this.flushInterval = null
|
||||
}
|
||||
this.flush()
|
||||
}
|
||||
|
||||
track<E extends keyof M>(event: E, payload: M[E]) {
|
||||
if (this.disabled) return
|
||||
|
||||
this.start()
|
||||
|
||||
/**
|
||||
* deviceId is required for sharding events in Middleman. To avoid a hot
|
||||
* shard, we generate a random anonymous IDs for this client.
|
||||
*
|
||||
* @see https://github.com/bluesky-social/tango/blob/d5819cde419d13e0d2cf837f4b30d48529d64060/middleman/handlers_tracking.go#L195
|
||||
*/
|
||||
const anonId = `anon-${crypto.randomUUID()}`
|
||||
|
||||
/**
|
||||
* Event structure is like this to ensure compat with Middleman, which
|
||||
* receives events like this from other codebases, including `social-app`.
|
||||
*/
|
||||
const e = {
|
||||
source: 'blink',
|
||||
time: Date.now(),
|
||||
event,
|
||||
payload,
|
||||
metadata: {
|
||||
base: {
|
||||
deviceId: anonId,
|
||||
sessionId: anonId,
|
||||
},
|
||||
session: {
|
||||
did: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
this.queue.push(e)
|
||||
|
||||
if (this.queue.length > this.maxBatchSize) {
|
||||
this.flush()
|
||||
}
|
||||
}
|
||||
|
||||
flush() {
|
||||
if (this.disabled) return
|
||||
if (!this.queue.length) return
|
||||
const events = this.queue.splice(0, this.queue.length)
|
||||
this.sendBatch(events)
|
||||
}
|
||||
|
||||
private async sendBatch(events: Event<M>[]) {
|
||||
if (this.disabled || !this.config.trackingEndpoint) return
|
||||
|
||||
try {
|
||||
const res = await fetch(this.config.trackingEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({events}),
|
||||
keepalive: true,
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text().catch(() => 'Unknown error')
|
||||
httpLogger.error(
|
||||
{err: new Error(`${res.status} Failed to fetch - ${errorText}`)},
|
||||
'Failed to send metrics',
|
||||
)
|
||||
} else {
|
||||
// Drain response body to allow connection reuse.
|
||||
await res.text().catch(() => {})
|
||||
}
|
||||
} catch (err) {
|
||||
httpLogger.error({err}, 'Failed to send metrics')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
url.pathname === '/redirect') || // is a redirect loop
|
||||
INTERNAL_IP_REGEX.test(url.hostname) // isn't directing to an internal location
|
||||
) {
|
||||
ctx.metrics.track('invalid_redirect', {link})
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`)
|
||||
return res.status(302).end()
|
||||
@@ -49,9 +48,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
res.type('html')
|
||||
|
||||
let html: string | undefined
|
||||
let whitelisted: 'unknown' | 'yes' = 'unknown'
|
||||
let blocked: boolean = false
|
||||
let warned: boolean = false
|
||||
|
||||
if (ctx.cfg.service.safelinkEnabled) {
|
||||
const rule = await ctx.safelinkClient.tryFindRule(link)
|
||||
@@ -59,7 +55,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
switch (rule.action) {
|
||||
case 'whitelist':
|
||||
redirectLogger.info({rule}, 'Whitelist rule matched')
|
||||
whitelisted = 'yes'
|
||||
break
|
||||
case 'block':
|
||||
html = linkWarningLayout(
|
||||
@@ -71,7 +66,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
redirectLogger.info({rule}, 'Block rule matched')
|
||||
blocked = true
|
||||
break
|
||||
case 'warn':
|
||||
html = linkWarningLayout(
|
||||
@@ -83,7 +77,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
)
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
redirectLogger.info({rule}, 'Warn rule matched')
|
||||
warned = true
|
||||
break
|
||||
default:
|
||||
redirectLogger.warn({rule}, 'Unknown rule matched')
|
||||
@@ -96,18 +89,6 @@ export default function (ctx: AppContext, app: Express) {
|
||||
html = linkRedirectContents(url.href)
|
||||
}
|
||||
|
||||
ctx.metrics.track('redirect', {
|
||||
link,
|
||||
whitelisted,
|
||||
blocked,
|
||||
warned,
|
||||
utm_source: req.query.utm_source?.toString(),
|
||||
utm_medium: req.query.utm_medium?.toString(),
|
||||
utm_campaign: req.query.utm_campaign?.toString(),
|
||||
utm_content: req.query.utm_content?.toString(),
|
||||
utm_term: req.query.utm_term?.toString(),
|
||||
})
|
||||
|
||||
return res.end(html)
|
||||
}),
|
||||
)
|
||||
|
||||
+9
-29
@@ -2,9 +2,11 @@ import assert from 'node:assert'
|
||||
import {type AddressInfo} from 'node:net'
|
||||
import {after, before, describe, it} from 'node:test'
|
||||
|
||||
import {ToolsOzoneSafelinkDefs} from '@atproto/api'
|
||||
|
||||
import {Database, envToCfg, LinkService, readEnv} from '../src/index.js'
|
||||
|
||||
describe.skip('link service', async () => {
|
||||
describe('link service', async () => {
|
||||
let linkService: LinkService
|
||||
let baseUrl: string
|
||||
before(async () => {
|
||||
@@ -16,9 +18,9 @@ describe.skip('link service', async () => {
|
||||
dbPostgresSchema: 'link_test',
|
||||
dbPostgresUrl: process.env.DB_POSTGRES_URL,
|
||||
safelinkEnabled: true,
|
||||
safelinkPdsUrl: 'http://localhost:2583',
|
||||
safelinkAgentIdentifier: 'mod-authority.test',
|
||||
safelinkAgentPass: 'hunter2',
|
||||
ozoneUrl: 'http://localhost:2583',
|
||||
ozoneAgentHandle: 'mod-authority.test',
|
||||
ozoneAgentPass: 'hunter2',
|
||||
})
|
||||
const migrateDb = Database.postgres({
|
||||
url: cfg.db.url,
|
||||
@@ -31,7 +33,6 @@ describe.skip('link service', async () => {
|
||||
const {port} = linkService.server?.address() as AddressInfo
|
||||
baseUrl = `http://localhost:${port}`
|
||||
|
||||
/*
|
||||
// Ensure blocklist, whitelist, and safelink rules are set up
|
||||
const now = new Date().toISOString()
|
||||
linkService.ctx.cfg.eventCache.smartUpdate({
|
||||
@@ -109,7 +110,6 @@ describe.skip('link service', async () => {
|
||||
comment:
|
||||
'Could be quite the mistake to get into this addicting game, but we will warn instead of block',
|
||||
})
|
||||
*/
|
||||
})
|
||||
after(async () => {
|
||||
await linkService?.destroy()
|
||||
@@ -213,7 +213,6 @@ describe.skip('link service', async () => {
|
||||
)
|
||||
})
|
||||
|
||||
/*
|
||||
it('Rule adjustment, safe redirect, 200 response for Instagram Account of teamsesh Bones', async () => {
|
||||
// Retrieve the latest event after all updates
|
||||
const result = linkService.ctx.cfg.eventCache.smartGet(
|
||||
@@ -233,7 +232,6 @@ describe.skip('link service', async () => {
|
||||
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
|
||||
)
|
||||
})
|
||||
*/
|
||||
|
||||
async function getRedirect(link: string): Promise<[number, string]> {
|
||||
const url = new URL(link)
|
||||
@@ -293,10 +291,9 @@ describe('link service no safelink', async () => {
|
||||
dbPostgresSchema: 'link_test',
|
||||
dbPostgresUrl: process.env.DB_POSTGRES_URL,
|
||||
safelinkEnabled: false,
|
||||
safelinkPdsUrl: 'http://localhost:2583',
|
||||
safelinkAgentIdentifier: 'mod-authority.test',
|
||||
safelinkAgentPass: 'hunter2',
|
||||
metricsApiHost: 'http://localhost:2584',
|
||||
ozoneUrl: 'http://localhost:2583',
|
||||
ozoneAgentHandle: 'mod-authority.test',
|
||||
ozoneAgentPass: 'hunter2',
|
||||
})
|
||||
const migrateDb = Database.postgres({
|
||||
url: cfg.db.url,
|
||||
@@ -360,21 +357,4 @@ describe('link service no safelink', async () => {
|
||||
// No blocked-site div, always safe
|
||||
assert.doesNotMatch(html, /"blocked-site"/)
|
||||
})
|
||||
|
||||
it('normal redirect with query params', async () => {
|
||||
const urlToRedirect = 'https://bsky.app/settings'
|
||||
const url = new URL(`${baseUrl}/redirect`)
|
||||
url.searchParams.set('u', urlToRedirect)
|
||||
url.searchParams.set('utm_source', 'test')
|
||||
const res = await fetch(url, {redirect: 'manual'})
|
||||
assert.strictEqual(res.status, 200)
|
||||
const html = await res.text()
|
||||
assert.match(html, /meta http-equiv="refresh"/)
|
||||
assert.match(
|
||||
html,
|
||||
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
|
||||
)
|
||||
// No blocked-site div, always safe
|
||||
assert.doesNotMatch(html, /"blocked-site"/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"ts-node": {
|
||||
"logError": true,
|
||||
"pretty": true /* <= technically not required */
|
||||
}
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import {type SVGAttributes} from 'react'
|
||||
|
||||
export function Butterfly(props: React.SVGAttributes<SVGSVGElement>) {
|
||||
export function Butterfly(props: SVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import {type ImgHTMLAttributes} from 'react'
|
||||
|
||||
// @NOTE satori does not currently support webp, see vercel/satori#273
|
||||
function detectMime(buf: Buffer): string {
|
||||
@@ -10,7 +10,7 @@ function detectMime(buf: Buffer): string {
|
||||
}
|
||||
|
||||
export function Img(
|
||||
props: Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src'> & {src: Buffer},
|
||||
props: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> & {src: Buffer},
|
||||
) {
|
||||
const {src, ...others} = props
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable bsky-internal/avoid-unwrapped-text */
|
||||
import React from 'react'
|
||||
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
|
||||
import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
|
||||
|
||||
import {Butterfly} from './Butterfly.js'
|
||||
import {Img} from './Img.js'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import assert from 'node:assert'
|
||||
|
||||
import React from 'react'
|
||||
import {type AppBskyGraphDefs, AtUri} from '@atproto/api'
|
||||
import resvg from '@resvg/resvg-js'
|
||||
import {type Express} from 'express'
|
||||
|
||||
@@ -54,7 +54,7 @@ func NewRenderer(prefix string, fs *embed.FS, debug bool) *Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
func (r Renderer) Render(w io.Writer, name string, data any, c echo.Context) error {
|
||||
func (r Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
var ctx pongo2.Context
|
||||
|
||||
if data != nil {
|
||||
|
||||
@@ -590,14 +590,6 @@ func (srv *Server) WebPost(c echo.Context) error {
|
||||
if postView.Embed.EmbedVideo_View.Thumbnail != nil {
|
||||
data["imgThumbUrls"] = []string{*postView.Embed.EmbedVideo_View.Thumbnail}
|
||||
}
|
||||
if postView.Embed.EmbedVideo_View.Playlist != "" {
|
||||
data["videoUrl"] = postView.Embed.EmbedVideo_View.Playlist
|
||||
data["videoType"] = "application/vnd.apple.mpegurl"
|
||||
if postView.Embed.EmbedVideo_View.AspectRatio != nil {
|
||||
data["videoWidth"] = postView.Embed.EmbedVideo_View.AspectRatio.Width
|
||||
data["videoHeight"] = postView.Embed.EmbedVideo_View.AspectRatio.Height
|
||||
}
|
||||
}
|
||||
} else if hasMediaImages {
|
||||
var thumbUrls []string
|
||||
for i := range postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View.Images {
|
||||
@@ -608,14 +600,6 @@ func (srv *Server) WebPost(c echo.Context) error {
|
||||
if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail != nil {
|
||||
data["imgThumbUrls"] = []string{*postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail}
|
||||
}
|
||||
if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist != "" {
|
||||
data["videoUrl"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist
|
||||
data["videoType"] = "application/vnd.apple.mpegurl"
|
||||
if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio != nil {
|
||||
data["videoWidth"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Width
|
||||
data["videoHeight"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@ type Template struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data any, c echo.Context) error {
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ func serve(cctx *cli.Context) error {
|
||||
|
||||
// Create CORS middleware for oembed
|
||||
oembedCORS := middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodOptions},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodOptions},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
||||
})
|
||||
|
||||
e.GET("/robots.txt", echo.WrapHandler(staticHandler))
|
||||
@@ -271,7 +271,7 @@ func (srv *Server) errorHandler(err error, c echo.Context) {
|
||||
code = he.Code
|
||||
}
|
||||
c.Logger().Error(err)
|
||||
data := map[string]any{
|
||||
data := map[string]interface{}{
|
||||
"statusCode": code,
|
||||
}
|
||||
c.Render(code, "error.html", data)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
module github.com/bluesky-social/social-app/bskyweb
|
||||
|
||||
go 1.26
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, viewport-fit=cover">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover">
|
||||
<meta name="referrer" content="origin-when-cross-origin">
|
||||
<!--
|
||||
Preconnect to essential domains
|
||||
|
||||
@@ -34,14 +34,6 @@
|
||||
<meta property="twitter:image" content="{{ imgThumbUrl }}">
|
||||
{% endfor %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{%- if videoUrl %}
|
||||
<meta property="og:video" content="{{ videoUrl }}">
|
||||
<meta property="og:video:type" content="{{ videoType }}">
|
||||
{%- if videoWidth %}
|
||||
<meta property="og:video:width" content="{{ videoWidth }}">
|
||||
<meta property="og:video:height" content="{{ videoHeight }}">
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% else %}
|
||||
<meta property="og:image" content="{{ postView.Author.Avatar|avatar_thumbnail }}">
|
||||
<meta property="twitter:image" content="{{ postView.Author.Avatar|avatar_thumbnail }}">
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"name": "dev-env",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh ts-node ./mock-server.ts"
|
||||
"e2e:mock-server": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh ts-node ./mock-server.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/dev-env": "^0.3.215",
|
||||
"@atproto/dev-env": "^0.3.213",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^6.0.2"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"#/*": ["./src/*"],
|
||||
"lib/*": ["./src/lib/*"],
|
||||
@@ -44,4 +43,4 @@
|
||||
"metro.config.js",
|
||||
"jest.config.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
+77
-193
@@ -64,14 +64,14 @@
|
||||
"@atproto/xrpc" "^0.7.6"
|
||||
"@atproto/xrpc-server" "^0.10.0"
|
||||
|
||||
"@atproto/api@^0.19.4":
|
||||
version "0.19.4"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.4.tgz#f3ff850baf4d85538c082fb91aa0982737eb68be"
|
||||
integrity sha512-fYNM62vdXxer0h8a9Jzl4/ag9uFIe0nTO+LkC6KTlx1yUDigrAoQMMbllIiCWj62GhUMxAkHabk/BZjjVAfKng==
|
||||
"@atproto/api@^0.19.2":
|
||||
version "0.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.3.tgz#61de8d2e31abe9eb2b4c8f4ad124ed79d4a77e89"
|
||||
integrity sha512-G8YpBpRouHdTAIagi/QQIUZOhGd1jfBQWkJy9QfxAzjjEpPvaVOSk4e1S85QzGLm/xbzVONzGkmdtiOSfP6wVg==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.18"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@atproto/xrpc" "^0.7.7"
|
||||
await-lock "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
@@ -96,23 +96,23 @@
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/bsky@^0.0.221":
|
||||
version "0.0.221"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.221.tgz#b574456225db66c866848526947473d60bb4d0e7"
|
||||
integrity sha512-feNR6xkJ9HCJbQdJU3ytsnAfaSBXJdaXIMi0tNPrsLfoqCWWbTfXIcMczXeY4SOhKGFR5oMYxE9zrRC/TTAssw==
|
||||
"@atproto/bsky@^0.0.219":
|
||||
version "0.0.219"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.219.tgz#6a9f82eb4ab999e121d04ad2b3f08ff60cc75fba"
|
||||
integrity sha512-Vm7JpIyCqd7sHzHsXqppGSaKkXKUhvdZ/UOldc247Bgmx+L/U+E6IeR028hzMr1YyDvU+bkO1hlqT8uUovOCdA==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch-node" "^0.2.0"
|
||||
"@atproto-labs/xrpc-utils" "^0.0.24"
|
||||
"@atproto/api" "^0.19.4"
|
||||
"@atproto/common" "^0.5.15"
|
||||
"@atproto/api" "^0.19.2"
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/did" "^0.3.0"
|
||||
"@atproto/identity" "^0.4.12"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/repo" "^0.8.13"
|
||||
"@atproto/repo" "^0.8.12"
|
||||
"@atproto/sync" "^0.1.40"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/xrpc-server" "^0.10.17"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@atproto/xrpc-server" "^0.10.15"
|
||||
"@bufbuild/protobuf" "^1.5.0"
|
||||
"@connectrpc/connect" "^1.1.4"
|
||||
"@connectrpc/connect-express" "^1.1.4"
|
||||
@@ -146,13 +146,13 @@
|
||||
undici "^6.19.8"
|
||||
zod "3.23.8"
|
||||
|
||||
"@atproto/bsync@^0.0.25":
|
||||
version "0.0.25"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.25.tgz#0d6056f844c0b2579d9dfc04b6727974dc03fcd6"
|
||||
integrity sha512-5tjP5QbUcNtMBw7FJeyRfA0OHQRKrg97Jva6Q26cKqLMICGYNbx0fpD7nZhGTP2/s1gd6UZkG9Zdh4GRZpbxWg==
|
||||
"@atproto/bsync@^0.0.24":
|
||||
version "0.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.24.tgz#6b0d4b02c0c0241687456ab817471d36ee81ae61"
|
||||
integrity sha512-JN+oncaPBNRjzjTPGR7Q1fkKF3cqOQ6oLRrAh9kVU04ZS3FhWUG8cQvnr8wb1PUhFb/XYpWkwDw5+GIhdb7Lfw==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@bufbuild/protobuf" "^1.5.0"
|
||||
"@connectrpc/connect" "^1.1.4"
|
||||
"@connectrpc/connect-node" "^1.1.4"
|
||||
@@ -172,16 +172,6 @@
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common-web@^0.4.19":
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.19.tgz#bbd7f84f545ebe73ca3bc00314ccf4ee66e7069e"
|
||||
integrity sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/lex-json" "^0.0.14"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
|
||||
@@ -213,17 +203,6 @@
|
||||
multiformats "^9.9.0"
|
||||
pino "^8.21.0"
|
||||
|
||||
"@atproto/common@^0.5.15":
|
||||
version "0.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.15.tgz#3c43c25d3493d868cc4281d6ac2f923b00644463"
|
||||
integrity sha512-+cdfdMPAIbH9zQGLfH1gNY2KEZsMxj0EelVQL5uJUFL+UkkAXiiqWj7J5mbax8sf02cC/afJnfkWzERNAheKoA==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.19"
|
||||
"@atproto/lex-cbor" "^0.0.15"
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
multiformats "^9.9.0"
|
||||
pino "^8.21.0"
|
||||
|
||||
"@atproto/crypto@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.0.tgz#bc73a479f9dbe06fa025301c182d7f7ab01bc568"
|
||||
@@ -244,23 +223,23 @@
|
||||
"@noble/hashes" "^1.6.1"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/dev-env@^0.3.215":
|
||||
version "0.3.215"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.215.tgz#9da8c4a73abb4501ac72c73da9536e2ec86cb46f"
|
||||
integrity sha512-zwZwGWYLgP2Zdie6/gMtxuDbSs7/UV/gPJYfOlXln1ZDoMkfFbgCTq44PWRnJpUKTzrq7gt19E0GsL7DMkppjA==
|
||||
"@atproto/dev-env@^0.3.213":
|
||||
version "0.3.213"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.213.tgz#30ca66f827d44ccabb02b359119f68549fa3edf0"
|
||||
integrity sha512-Bjhv+zzcQxhwV4I7si+yDls8sSetksILeiBemfRe3cvE4GOgs7KfsYI5+pqNmBZ5rrFPt3KUeN9vIo31LCgZOw==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.19.4"
|
||||
"@atproto/bsky" "^0.0.221"
|
||||
"@atproto/bsync" "^0.0.25"
|
||||
"@atproto/common-web" "^0.4.19"
|
||||
"@atproto/api" "^0.19.2"
|
||||
"@atproto/bsky" "^0.0.219"
|
||||
"@atproto/bsync" "^0.0.24"
|
||||
"@atproto/common-web" "^0.4.18"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/identity" "^0.4.12"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/ozone" "^0.1.167"
|
||||
"@atproto/pds" "^0.4.216"
|
||||
"@atproto/ozone" "^0.1.166"
|
||||
"@atproto/pds" "^0.4.214"
|
||||
"@atproto/sync" "^0.1.40"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/xrpc-server" "^0.10.17"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@atproto/xrpc-server" "^0.10.15"
|
||||
"@did-plc/lib" "^0.0.1"
|
||||
"@did-plc/server" "^0.0.1"
|
||||
dotenv "^16.0.3"
|
||||
@@ -309,14 +288,6 @@
|
||||
"@atproto/lex-data" "^0.0.13"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-cbor@^0.0.15":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.15.tgz#ae4558d8ce22119710ad22feb458d22774b3ca3b"
|
||||
integrity sha512-3osDicK9bAMXJlKjLKqwYrhLQ60bOguWBNjE+fuNjMuizNzC0aqaClE3d+qMsFuFq9bjEHFw+4Vr9Qmd/m6VYg==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-client@^0.0.15":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.15.tgz#c647d14e91ca3f52feebf4b34f80abb7e93b3bee"
|
||||
@@ -327,16 +298,6 @@
|
||||
"@atproto/lex-schema" "^0.0.14"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-client@^0.0.17":
|
||||
version "0.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.17.tgz#566689a288f8b2af31f4a0fa081496dfaaef7278"
|
||||
integrity sha512-lZ9clUjWgpno1XhSawQP+1/JeIYA9qBh759b/NSU0OiypQqgq7IxDvmzaBsiHK1sqjo0tyEkmG4X5Ym7YXjv0Q==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/lex-json" "^0.0.14"
|
||||
"@atproto/lex-schema" "^0.0.16"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-data@^0.0.13":
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.13.tgz#db1bcfa12d5056210f6eb7f3b8bac909909d6b9c"
|
||||
@@ -347,22 +308,12 @@
|
||||
uint8arrays "3.0.0"
|
||||
unicode-segmenter "^0.14.0"
|
||||
|
||||
"@atproto/lex-data@^0.0.14":
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.14.tgz#2f2f3c64699925a0d4785e5afd0e7731ba1d46c0"
|
||||
integrity sha512-53DUa9664SS76nGAMYopWsO10OH0AAdf7P/HSKB6Wzx3iqe6lk/K61QZnKxOG1LreYl5CfvIJU6eNf4txI6GlQ==
|
||||
"@atproto/lex-document@^0.0.15":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.15.tgz#b2f19756291a0d259cd99f5ebe4872e9133069b6"
|
||||
integrity sha512-QT2MbICG4cTFrrA19SIHpZJ33WRLdzjhDsEhSknQ4dE5CjqPf4BP9LaC4pOeW8NE5Kn92hgIm3JWNjoak8blXw==
|
||||
dependencies:
|
||||
multiformats "^9.9.0"
|
||||
tslib "^2.8.1"
|
||||
uint8arrays "3.0.0"
|
||||
unicode-segmenter "^0.14.0"
|
||||
|
||||
"@atproto/lex-document@^0.0.17":
|
||||
version "0.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.17.tgz#8460096235910bf5ec8305f03a8da6bab8d8a12b"
|
||||
integrity sha512-rQiDCSYQwze4+kaArUtmp4bjZ9rV3vYUMhjdDwmZCKodpppNEYrP5AQzyKlxBtKO+MRdLYwHDDwwvakU8atRww==
|
||||
dependencies:
|
||||
"@atproto/lex-schema" "^0.0.16"
|
||||
"@atproto/lex-schema" "^0.0.14"
|
||||
core-js "^3"
|
||||
tslib "^2.8.1"
|
||||
|
||||
@@ -374,27 +325,19 @@
|
||||
"@atproto/lex-data" "^0.0.13"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-json@^0.0.14":
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.14.tgz#717e533ab583aa5f580acb2a77d9aa3e7eddaa17"
|
||||
integrity sha512-6lPkDKqe7teEu4WrN5q7400cvZKgYS3uwUMvzG3F9XkgVYhOwSDCtouV/nSLBbpvo3l9OP0kiigtclcNcyekww==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-resolver@^0.0.19":
|
||||
version "0.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.19.tgz#806fcb71e72d0db51e2eb29c594eddb8c4087414"
|
||||
integrity sha512-oATn4RpZNLh5rp9doN5/UOYS/Cd25GOD90ohB5jnnmeoF8jTupqIYTVhntbnx1EFn+5tTlgkyXEBV+XESBUcdQ==
|
||||
"@atproto/lex-resolver@^0.0.17":
|
||||
version "0.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.17.tgz#2c474f6babeb54665656bf28b8d27a98de69deae"
|
||||
integrity sha512-6nI5bYZUYh50ZI8r4erLRP9EbNcW226VShpVN3vHyOSgTje4VP1RTcvBhROBAPj4rL3vc+Oa8OiL6IQXkYrQBg==
|
||||
dependencies:
|
||||
"@atproto-labs/did-resolver" "^0.2.6"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/lex-client" "^0.0.17"
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/lex-document" "^0.0.17"
|
||||
"@atproto/lex-schema" "^0.0.16"
|
||||
"@atproto/repo" "^0.8.13"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/lex-client" "^0.0.15"
|
||||
"@atproto/lex-data" "^0.0.13"
|
||||
"@atproto/lex-document" "^0.0.15"
|
||||
"@atproto/lex-schema" "^0.0.14"
|
||||
"@atproto/repo" "^0.8.12"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-schema@^0.0.14":
|
||||
@@ -406,17 +349,6 @@
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-schema@^0.0.16":
|
||||
version "0.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.16.tgz#8362932e239b7eaa7c5d6982d06c3147e3afd138"
|
||||
integrity sha512-O+IorivZHJPeV3kU3NDD2yI8ATfckOphgvDfeiyKHRTxRUKS+lHMCpGUiSTC3fJrfMvYITrruUVViUHVEScrbA==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@standard-schema/spec" "^1.1.0"
|
||||
iso-datestring-validator "^2.2.2"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.2.tgz#f6152a2119df953236ca127c4b30e332265e81e7"
|
||||
@@ -450,28 +382,28 @@
|
||||
optionalDependencies:
|
||||
"@atproto/oauth-provider-api" "0.3.7"
|
||||
|
||||
"@atproto/oauth-provider@^0.15.14":
|
||||
version "0.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.14.tgz#d969018b4ad5c0dd5863150cb8c1b65458738589"
|
||||
integrity sha512-arA3O+Ye1YBhoIUnZtn8wfatnVnwiZrGyNkxhH0nqGbh/RRfwA5W0tgnSDq0VMclLkrPY/OnZ4v3oo9N81yWGg==
|
||||
"@atproto/oauth-provider@^0.15.12":
|
||||
version "0.15.12"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.12.tgz#9dbbfdd6808399d9d7ff8993ac888938fbf4c515"
|
||||
integrity sha512-Ri4aVx2I4lOKxViB92jwPhAs/NctWEwV0tgYSHcaRpvqr2SVlC2LxTVjUq14ohdbVfv4VFRzj0vZypEX+mclHg==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch" "^0.2.3"
|
||||
"@atproto-labs/fetch-node" "^0.2.0"
|
||||
"@atproto-labs/pipe" "^0.1.1"
|
||||
"@atproto-labs/simple-store" "^0.3.0"
|
||||
"@atproto-labs/simple-store-memory" "^0.1.4"
|
||||
"@atproto/common" "^0.5.15"
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/did" "^0.3.0"
|
||||
"@atproto/jwk" "^0.6.0"
|
||||
"@atproto/jwk-jose" "^0.1.11"
|
||||
"@atproto/lex-document" "^0.0.17"
|
||||
"@atproto/lex-resolver" "^0.0.19"
|
||||
"@atproto/lex-document" "^0.0.15"
|
||||
"@atproto/lex-resolver" "^0.0.17"
|
||||
"@atproto/oauth-provider-api" "0.3.7"
|
||||
"@atproto/oauth-provider-frontend" "0.2.9"
|
||||
"@atproto/oauth-provider-ui" "0.4.3"
|
||||
"@atproto/oauth-scopes" "^0.3.2"
|
||||
"@atproto/oauth-types" "^0.6.3"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@hapi/accept" "^6.0.3"
|
||||
"@hapi/address" "^5.1.1"
|
||||
"@hapi/bourne" "^3.0.0"
|
||||
@@ -510,20 +442,20 @@
|
||||
"@atproto/jwk" "^0.6.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/ozone@^0.1.167":
|
||||
version "0.1.167"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.167.tgz#c3974bbe06f0926f165f90d5ba51ba9ce2032fff"
|
||||
integrity sha512-AFquyND8zsskjkDc3WrQObUnZlEky05pFo0YYLy5JoqQN0WXIePLgGY0SC0EIokfF8iYXJE1ZM1u/dgA7DHqGQ==
|
||||
"@atproto/ozone@^0.1.166":
|
||||
version "0.1.166"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.166.tgz#9e65d6f67ef1fe285d0880e5f5a1b282dc63bd70"
|
||||
integrity sha512-XZ77P/V/tt3SqTQYRsi5nM3P2h+QaNT7Nz3GTf+TMLVolACufRHPoDgp/PoTuS2FaLj1ndHnGyIPvZi/o8he6g==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.19.4"
|
||||
"@atproto/api" "^0.19.2"
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/identity" "^0.4.12"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@atproto/ws-client" "^0.0.4"
|
||||
"@atproto/xrpc" "^0.7.7"
|
||||
"@atproto/xrpc-server" "^0.10.16"
|
||||
"@atproto/xrpc-server" "^0.10.15"
|
||||
"@did-plc/lib" "^0.0.1"
|
||||
compression "^1.7.4"
|
||||
cors "^2.8.5"
|
||||
@@ -541,30 +473,30 @@
|
||||
undici "^6.14.1"
|
||||
ws "^8.12.0"
|
||||
|
||||
"@atproto/pds@^0.4.216":
|
||||
version "0.4.216"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.216.tgz#4d9c73a529bd00893753aba1e7bb33f99b9aaaf4"
|
||||
integrity sha512-yPNatCb2kvudRp5DMbPemN1+uMsLOJDydw2PCBMxuzdimOf10PsekOhhZxtfGGRvk2NbvKoyfUvkROoFmTr+ew==
|
||||
"@atproto/pds@^0.4.214":
|
||||
version "0.4.214"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.214.tgz#c68d55ec0b00a4e35f4801c826d44b65cd62f984"
|
||||
integrity sha512-bTWeWg3H0TlELfE2eI2ySQuC6ojsCBSSmPtCXBh3Td9TFNpIoZQ/tYLUJMtMXaiVUi6HxzXQL1/iuYfC4Y+ZYQ==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch-node" "^0.2.0"
|
||||
"@atproto-labs/simple-store" "^0.3.0"
|
||||
"@atproto-labs/simple-store-memory" "^0.1.4"
|
||||
"@atproto-labs/simple-store-redis" "^0.0.1"
|
||||
"@atproto-labs/xrpc-utils" "^0.0.24"
|
||||
"@atproto/api" "^0.19.4"
|
||||
"@atproto/api" "^0.19.2"
|
||||
"@atproto/aws" "^0.2.31"
|
||||
"@atproto/common" "^0.5.15"
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/identity" "^0.4.12"
|
||||
"@atproto/lex-cbor" "^0.0.15"
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/lex-cbor" "^0.0.14"
|
||||
"@atproto/lex-data" "^0.0.13"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/oauth-provider" "^0.15.14"
|
||||
"@atproto/oauth-provider" "^0.15.12"
|
||||
"@atproto/oauth-scopes" "^0.3.2"
|
||||
"@atproto/repo" "^0.8.13"
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
"@atproto/repo" "^0.8.12"
|
||||
"@atproto/syntax" "^0.5.0"
|
||||
"@atproto/xrpc" "^0.7.7"
|
||||
"@atproto/xrpc-server" "^0.10.17"
|
||||
"@atproto/xrpc-server" "^0.10.15"
|
||||
"@did-plc/lib" "^0.0.4"
|
||||
"@hapi/address" "^5.1.1"
|
||||
better-sqlite3 "^10.0.0"
|
||||
@@ -608,21 +540,6 @@
|
||||
varint "^6.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/repo@^0.8.13":
|
||||
version "0.8.13"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.8.13.tgz#70160b8b3f78b6addcba7cf3e3ae06306e6b6641"
|
||||
integrity sha512-VS8XHaBMGdq60xwRI5zQmXzsMF1hU7NKPjmkdr65tJdrv2z0VW77mG01Ui19Xh9O0mUc/LG6GEhwVrabB9Txow==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.5.14"
|
||||
"@atproto/common-web" "^0.4.18"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@ipld/dag-cbor" "^7.0.0"
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
varint "^6.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/sync@^0.1.40":
|
||||
version "0.1.40"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/sync/-/sync-0.1.40.tgz#b8b467ac4fbf2e682d36cd5697508f993e9e645a"
|
||||
@@ -645,13 +562,6 @@
|
||||
dependencies:
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/syntax@^0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.1.tgz#78257b903a0723720dca32110379208791ac3c24"
|
||||
integrity sha512-J8DJjgKgACIyCTbpfvoTnf7+ofTx1kxTGO7KAftkC+jczaMdQhKdgIBAg2DaYy+80cvYGTHy5q/HI9qMAwGbWw==
|
||||
dependencies:
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/ws-client@^0.0.4":
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ws-client/-/ws-client-0.0.4.tgz#9e436c0e72abea5da0d5a7e8ec862cec0fdb10cd"
|
||||
@@ -681,27 +591,6 @@
|
||||
rate-limiter-flexible "^2.4.1"
|
||||
ws "^8.12.0"
|
||||
|
||||
"@atproto/xrpc-server@^0.10.16", "@atproto/xrpc-server@^0.10.17":
|
||||
version "0.10.17"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.17.tgz#1016d4a6d97966a3f80e8785e5d5cba7b68c1c60"
|
||||
integrity sha512-FjexO6P/LRTx6/FdiWTzycFF4TgACW9npsFOitnocydTCOLYouP0OvUwdkxkreFC7qerT4+ARKpqrxRzyj0MNA==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.5.15"
|
||||
"@atproto/crypto" "^0.4.5"
|
||||
"@atproto/lex-cbor" "^0.0.15"
|
||||
"@atproto/lex-client" "^0.0.17"
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
"@atproto/lex-json" "^0.0.14"
|
||||
"@atproto/lex-schema" "^0.0.16"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/ws-client" "^0.0.4"
|
||||
"@atproto/xrpc" "^0.7.7"
|
||||
express "^4.17.2"
|
||||
http-errors "^2.0.0"
|
||||
mime-types "^2.1.35"
|
||||
rate-limiter-flexible "^2.4.1"
|
||||
ws "^8.12.0"
|
||||
|
||||
"@atproto/xrpc@^0.7.6", "@atproto/xrpc@^0.7.7":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11"
|
||||
@@ -2257,11 +2146,6 @@
|
||||
dependencies:
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@standard-schema/spec@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
|
||||
integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||
@@ -4262,10 +4146,10 @@ typed-emitter@^2.1.0:
|
||||
optionalDependencies:
|
||||
rxjs "^7.5.2"
|
||||
|
||||
typescript@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.2.tgz#0b1bfb15f68c64b97032f3d78abbf98bdbba501f"
|
||||
integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==
|
||||
typescript@^5.9.3:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.19.3"
|
||||
|
||||
@@ -47,7 +47,8 @@ Every night, a GitHub action will run `yarn intl:extract` to update the english
|
||||
### Release process
|
||||
|
||||
1. Pull main and create a branch.
|
||||
1. Run `yarn intl:release` to fetch all translation updates from Crowdin and extract all `.po` files so that they're synced with the latest code. Commit that.
|
||||
1. Run `yarn intl:pull` to fetch all translation updates from Crowdin. Commit.
|
||||
1. Run `yarn intl:extract:all` to ensure all `.po` files are synced with the current state of the code. Commit.
|
||||
1. Create a PR, ensure the translations all look correct, and merge.
|
||||
1. If needed:
|
||||
1. Merge all approved translation PRs (contributions from outside crowdin).
|
||||
|
||||
+1
-23
@@ -37,7 +37,6 @@ export default defineConfig(
|
||||
'*.e2e.ts',
|
||||
'*.e2e.tsx',
|
||||
'eslint.config.mjs',
|
||||
'.jscodeshift/**',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -47,6 +46,7 @@ export default defineConfig(
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
reactHooks.configs.flat.recommended,
|
||||
// @ts-expect-error https://github.com/un-ts/eslint-plugin-import-x/issues/439
|
||||
importX.flatConfigs.recommended,
|
||||
importX.flatConfigs.typescript,
|
||||
importX.flatConfigs['react-native'],
|
||||
@@ -61,7 +61,6 @@ export default defineConfig(
|
||||
'react-native': reactNative,
|
||||
'react-native-a11y': reactNativeA11y,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
// @ts-expect-error - not sure why
|
||||
lingui,
|
||||
'react-compiler': reactCompiler,
|
||||
'bsky-internal': bskyInternal,
|
||||
@@ -127,7 +126,6 @@ export default defineConfig(
|
||||
*/
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
'react/hook-use-state': 'warn',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'react-native/no-inline-styles': 'off',
|
||||
@@ -190,18 +188,6 @@ export default defineConfig(
|
||||
*/
|
||||
ignore: ['^#\/locale\/locales\/.+\/messages'],
|
||||
}],
|
||||
'import-x/no-extraneous-dependencies': ['error', {
|
||||
'whitelist': [
|
||||
// test files only
|
||||
'@jest/globals',
|
||||
// we only use a really simple util from this, and we know it will be present
|
||||
'expo-modules-core',
|
||||
// this is a dep for @atproto/api, but we absolutely need them in sync, so just
|
||||
// rely on the transient version
|
||||
'@atproto/common-web',
|
||||
]
|
||||
}],
|
||||
'import-x/no-nodejs-modules': 'error',
|
||||
|
||||
/**
|
||||
* TypeScript-specific rules
|
||||
@@ -250,14 +236,6 @@ export default defineConfig(
|
||||
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
|
||||
'@typescript-eslint/await-thenable': 'warn',
|
||||
|
||||
"no-restricted-imports": ["error", {
|
||||
"paths": [{
|
||||
"name": "react",
|
||||
"importNames": ["React", "default"],
|
||||
"message": "React is already in the global type namespace. Use named imports for runtime modules."
|
||||
}]
|
||||
}],
|
||||
|
||||
/**
|
||||
* Turn off rules that we haven't enforced thus far
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,6 @@ function getTagName(node) {
|
||||
return reversedIdentifiers.reverse().join('.')
|
||||
}
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
|
||||
@@ -3,7 +3,6 @@ const BANNED_IMPORTS = [
|
||||
'@fortawesome/free-solid-svg-icons',
|
||||
]
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
|
||||
@@ -10,7 +10,6 @@ const BANNED_IMPORT_PREFIXES = [
|
||||
'view/',
|
||||
]
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
|
||||
@@ -9,7 +9,6 @@ jest.mock('@react-native-async-storage/async-storage', () =>
|
||||
require('@react-native-async-storage/async-storage/jest/async-storage-mock'),
|
||||
)
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => {
|
||||
// eslint-disable-next-line import-x/no-nodejs-modules
|
||||
const {EventEmitter} = require('events')
|
||||
return {
|
||||
__esModule: true,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {render} from '@testing-library/react-native'
|
||||
|
||||
import {ThemeProvider} from '../src/lib/ThemeContext'
|
||||
import {type RootStoreModel, RootStoreProvider} from '../src/state'
|
||||
|
||||
const customRender = (ui: any, rootStore: RootStoreModel) =>
|
||||
render(
|
||||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<ThemeProvider theme="light">
|
||||
<SafeAreaProvider>{ui}</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
</GestureHandlerRootView>,
|
||||
)
|
||||
|
||||
// re-export everything
|
||||
export * from '@testing-library/react-native'
|
||||
|
||||
// override render method
|
||||
export {customRender as render}
|
||||
+3
-13
@@ -33,27 +33,17 @@ class BottomSheetView(
|
||||
private var eventDispatcher: EventDispatcher? = null
|
||||
|
||||
// Native content height observation (eliminates JS bridge round-trip)
|
||||
private var contentLayoutListener: OnLayoutChangeListener? = null
|
||||
private var contentLayoutListener: View.OnLayoutChangeListener? = null
|
||||
private var observedChildren: List<View> = emptyList()
|
||||
private var lastObservedContentHeight: Float = 0f
|
||||
private var pendingLayoutUpdate: Boolean = false
|
||||
|
||||
private val screenHeight: Float =
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
// API 35+: edge-to-edge is mandatory, heightPixels is the full display
|
||||
context.resources.displayMetrics.heightPixels.toFloat()
|
||||
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||
// API 30-34: heightPixels may exclude nav bar, use currentWindowMetrics
|
||||
} else {
|
||||
val wm = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
|
||||
wm.currentWindowMetrics.bounds.height().toFloat()
|
||||
} else {
|
||||
// API < 30: currentWindowMetrics not available, use getRealSize
|
||||
// which includes system bars (heightPixels may exclude them)
|
||||
val wm = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
|
||||
val size = android.graphics.Point()
|
||||
@Suppress("DEPRECATION")
|
||||
wm.defaultDisplay.getRealSize(size)
|
||||
size.y.toFloat()
|
||||
}
|
||||
|
||||
private fun getNavigationBarHeight(): Int {
|
||||
@@ -365,7 +355,7 @@ class BottomSheetView(
|
||||
|
||||
val innerViewGroup = this.innerView as? ViewGroup ?: return
|
||||
|
||||
val listener = OnLayoutChangeListener { _, _, top, _, bottom, _, _, oldTop, oldBottom ->
|
||||
val listener = View.OnLayoutChangeListener { _, _, top, _, bottom, _, _, oldTop, oldBottom ->
|
||||
val newHeight = bottom - top
|
||||
val oldHeight = oldBottom - oldTop
|
||||
if (newHeight != oldHeight) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import {Component, createRef} from 'react'
|
||||
import {type ComponentType, type ContextType, type RefObject} from 'react'
|
||||
import {
|
||||
Dimensions,
|
||||
type LayoutChangeEvent,
|
||||
@@ -21,9 +22,9 @@ import {
|
||||
Context as PortalContext,
|
||||
} from './BottomSheetPortal'
|
||||
|
||||
const NativeView: React.ComponentType<
|
||||
const NativeView: ComponentType<
|
||||
BottomSheetViewProps & {
|
||||
ref: React.RefObject<any>
|
||||
ref: RefObject<any>
|
||||
style: StyleProp<ViewStyle>
|
||||
}
|
||||
> = requireNativeViewManager('BottomSheet')
|
||||
@@ -39,14 +40,14 @@ const IS_IOS15 =
|
||||
const IS_NON_E2E_ANDROID =
|
||||
Platform.OS === 'android' && Number(Platform.Version) < 35
|
||||
|
||||
export class BottomSheetNativeComponent extends React.Component<
|
||||
export class BottomSheetNativeComponent extends Component<
|
||||
BottomSheetViewProps,
|
||||
{
|
||||
open: boolean
|
||||
viewHeight?: number
|
||||
}
|
||||
> {
|
||||
ref = React.createRef<any>()
|
||||
ref = createRef<any>()
|
||||
|
||||
static contextType = PortalContext
|
||||
|
||||
@@ -79,7 +80,7 @@ export class BottomSheetNativeComponent extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const Portal = this.context as React.ContextType<typeof PortalContext>
|
||||
const Portal = this.context as ContextType<typeof PortalContext>
|
||||
if (!Portal) {
|
||||
throw new Error(
|
||||
'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.',
|
||||
@@ -139,7 +140,7 @@ function BottomSheetNativeComponentInner({
|
||||
onStateChange: (
|
||||
event: NativeSyntheticEvent<{state: BottomSheetState}>,
|
||||
) => void
|
||||
nativeViewRef: React.RefObject<View>
|
||||
nativeViewRef: RefObject<View>
|
||||
onLayout?: (event: LayoutChangeEvent) => void
|
||||
}) {
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import React from 'react'
|
||||
import {createContext, useContext, useMemo} from 'react'
|
||||
import {type ElementType, type ReactNode} from 'react'
|
||||
|
||||
import {createPortalGroup_INTERNAL} from './lib/Portal'
|
||||
|
||||
type PortalContext = React.ElementType<{children: React.ReactNode}>
|
||||
type PortalContext = ElementType<{children: ReactNode}>
|
||||
|
||||
export const Context = React.createContext({} as PortalContext)
|
||||
export const Context = createContext({} as PortalContext)
|
||||
Context.displayName = 'BottomSheetPortalContext'
|
||||
|
||||
export const useBottomSheetPortal_INTERNAL = () => React.useContext(Context)
|
||||
export const useBottomSheetPortal_INTERNAL = () => useContext(Context)
|
||||
|
||||
export function BottomSheetPortalProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const portal = React.useMemo(() => {
|
||||
export function BottomSheetPortalProvider({children}: {children: ReactNode}) {
|
||||
const portal = useMemo(() => {
|
||||
return createPortalGroup_INTERNAL()
|
||||
}, [])
|
||||
|
||||
@@ -32,7 +29,7 @@ const defaultPortal = createPortalGroup_INTERNAL()
|
||||
|
||||
export const BottomSheetOutlet = defaultPortal.Outlet
|
||||
|
||||
export function BottomSheetProvider({children}: {children: React.ReactNode}) {
|
||||
export function BottomSheetProvider({children}: {children: ReactNode}) {
|
||||
return (
|
||||
<Context.Provider value={defaultPortal.Portal}>
|
||||
<defaultPortal.Provider>{children}</defaultPortal.Provider>
|
||||
|
||||
+9
-9
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import {createContext, useContext, useEffect, useMemo, useState} from 'react'
|
||||
import {type ReactNode} from 'react'
|
||||
|
||||
import {BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types'
|
||||
import {type BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types'
|
||||
import {BackgroundNotificationHandler} from './ExpoBackgroundNotificationHandlerModule'
|
||||
|
||||
interface BackgroundNotificationPreferencesContext {
|
||||
@@ -11,30 +12,29 @@ interface BackgroundNotificationPreferencesContext {
|
||||
) => void
|
||||
}
|
||||
|
||||
const Context = React.createContext<BackgroundNotificationPreferencesContext>(
|
||||
const Context = createContext<BackgroundNotificationPreferencesContext>(
|
||||
{} as BackgroundNotificationPreferencesContext,
|
||||
)
|
||||
export const useBackgroundNotificationPreferences = () =>
|
||||
React.useContext(Context)
|
||||
export const useBackgroundNotificationPreferences = () => useContext(Context)
|
||||
|
||||
export function BackgroundNotificationPreferencesProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [preferences, setPreferences] =
|
||||
React.useState<BackgroundNotificationHandlerPreferences>({
|
||||
useState<BackgroundNotificationHandlerPreferences>({
|
||||
playSoundChat: true,
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
const prefs = await BackgroundNotificationHandler.getAllPrefsAsync()
|
||||
setPreferences(prefs)
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const value = React.useMemo(
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
preferences,
|
||||
setPref: async <
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react'
|
||||
import {createRef, PureComponent} from 'react'
|
||||
import {type ComponentType, type RefObject} from 'react'
|
||||
import {requireNativeModule} from 'expo'
|
||||
import {requireNativeViewManager} from 'expo-modules-core'
|
||||
|
||||
import {GifViewProps} from './GifView.types'
|
||||
import {type GifViewProps} from './GifView.types'
|
||||
|
||||
const NativeModule = requireNativeModule('ExpoBlueskyGifView')
|
||||
const NativeView: React.ComponentType<
|
||||
GifViewProps & {ref: React.RefObject<any>}
|
||||
> = requireNativeViewManager('ExpoBlueskyGifView')
|
||||
const NativeView: ComponentType<GifViewProps & {ref: RefObject<any>}> =
|
||||
requireNativeViewManager('ExpoBlueskyGifView')
|
||||
|
||||
export class GifView extends React.PureComponent<GifViewProps> {
|
||||
export class GifView extends PureComponent<GifViewProps> {
|
||||
// TODO native types, should all be the same as those in this class
|
||||
private nativeRef: React.RefObject<any> = React.createRef()
|
||||
private nativeRef: RefObject<any> = createRef()
|
||||
|
||||
constructor(props: GifViewProps | Readonly<GifViewProps>) {
|
||||
super(props)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from 'react'
|
||||
import {createRef, PureComponent} from 'react'
|
||||
import {type RefObject} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
|
||||
import {GifViewProps} from './GifView.types'
|
||||
import {type GifViewProps} from './GifView.types'
|
||||
|
||||
export class GifView extends React.PureComponent<GifViewProps> {
|
||||
private readonly videoPlayerRef: React.RefObject<HTMLMediaElement> =
|
||||
React.createRef()
|
||||
export class GifView extends PureComponent<GifViewProps> {
|
||||
private readonly videoPlayerRef: RefObject<HTMLMediaElement> = createRef()
|
||||
private isLoaded = false
|
||||
|
||||
constructor(props: GifViewProps | Readonly<GifViewProps>) {
|
||||
|
||||
+33
-42
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.121.0",
|
||||
"version": "1.119.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -52,7 +52,7 @@
|
||||
"lint-native": "swiftlint ./modules && ktlint ./modules",
|
||||
"lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules",
|
||||
"typecheck": "tsc --project ./tsconfig.check.json",
|
||||
"e2e:mock-server": "cd dev-env && yarn start",
|
||||
"e2e:mock-server": "cd dev-env && yarn e2e:mock-server",
|
||||
"e2e:build": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
|
||||
"e2e:build-android": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:android",
|
||||
"e2e:start": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo start -c",
|
||||
@@ -70,7 +70,6 @@
|
||||
"intl:pull": "crowdin download translations --verbose -b main",
|
||||
"intl:push": "crowdin push translations --verbose -b main",
|
||||
"intl:push-sources": "crowdin push sources --verbose -b main",
|
||||
"intl:release": "yarn intl:pull && yarn intl:extract:all",
|
||||
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
|
||||
"update-extensions": "bash scripts/updateExtensions.sh",
|
||||
"export": "npx expo export --dump-sourcemap && yarn upload-native-sourcemaps",
|
||||
@@ -81,16 +80,13 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.19.9",
|
||||
"@atproto/api": "^0.19.3",
|
||||
"@bitdrift/react-native": "^0.6.8",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@bsky.app/alf": "^0.1.7",
|
||||
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
||||
"@bsky.app/expo-scroll-edge-effect": "^0.1.4",
|
||||
"@bsky.app/expo-translate-text": "^0.2.9",
|
||||
"@bsky.app/expo-translate-text": "^0.2.7",
|
||||
"@bsky.app/react-native-mmkv": "2.12.5",
|
||||
"@bsky.app/sift": "^0.3.2",
|
||||
"@bsky.app/tapper": "^0.5.0",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
@@ -112,6 +108,7 @@
|
||||
"@ipld/dag-cbor": "^9.2.0",
|
||||
"@lingui/core": "^5.9.2",
|
||||
"@lingui/react": "^5.9.2",
|
||||
"@mattermost/react-native-paste-input": "mattermost/react-native-paste-input",
|
||||
"@miblanchard/react-native-slider": "^2.6.0",
|
||||
"@mozzius/expo-dynamic-app-icon": "^1.8.0",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
@@ -119,9 +116,9 @@
|
||||
"@react-navigation/native": "^7.1.33",
|
||||
"@react-navigation/native-stack": "^7.14.4",
|
||||
"@sentry/react-native": "~6.20.0",
|
||||
"@tanstack/query-async-storage-persister": "^5.96.2",
|
||||
"@tanstack/react-query": "^5.96.2",
|
||||
"@tanstack/react-query-persist-client": "^5.96.2",
|
||||
"@tanstack/query-async-storage-persister": "^5.25.0",
|
||||
"@tanstack/react-query": "5.25.0",
|
||||
"@tanstack/react-query-persist-client": "^5.25.0",
|
||||
"@tiptap/core": "^2.9.1",
|
||||
"@tiptap/extension-document": "^2.9.1",
|
||||
"@tiptap/extension-hard-break": "^2.9.1",
|
||||
@@ -146,7 +143,7 @@
|
||||
"emoji-mart": "^5.6.0",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"expo": "^54.0.33",
|
||||
"expo": "^54.0.27",
|
||||
"expo-application": "~7.0.8",
|
||||
"expo-blur": "~15.0.8",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
@@ -155,35 +152,32 @@
|
||||
"expo-contacts": "^15.0.10",
|
||||
"expo-dev-client": "~6.0.20",
|
||||
"expo-device": "~8.0.10",
|
||||
"expo-file-system": "~19.0.21",
|
||||
"expo-font": "~14.0.11",
|
||||
"expo-glass-effect": "55.0.8",
|
||||
"expo-file-system": "~19.0.20",
|
||||
"expo-font": "~14.0.10",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
"expo-image-manipulator": "~14.0.8",
|
||||
"expo-image-picker": "~17.0.10",
|
||||
"expo-image-picker": "~17.0.9",
|
||||
"expo-intent-launcher": "~13.0.8",
|
||||
"expo-keep-awake": "~15.0.8",
|
||||
"expo-linear-gradient": "~15.0.8",
|
||||
"expo-linking": "~8.0.11",
|
||||
"expo-linking": "~8.0.10",
|
||||
"expo-localization": "~17.0.8",
|
||||
"expo-location": "~19.0.8",
|
||||
"expo-media-library": "~18.2.1",
|
||||
"expo-notifications": "~0.32.16",
|
||||
"expo-paste-input": "^0.1.15",
|
||||
"expo-notifications": "~0.32.14",
|
||||
"expo-privacy-sensitive": "^0.1.0",
|
||||
"expo-screen-orientation": "~9.0.8",
|
||||
"expo-sharing": "~14.0.8",
|
||||
"expo-sms": "^14.0.7",
|
||||
"expo-splash-screen": "~31.0.13",
|
||||
"expo-splash-screen": "~31.0.12",
|
||||
"expo-system-ui": "~6.0.9",
|
||||
"expo-updates": "~29.0.16",
|
||||
"expo-video": "~3.0.16",
|
||||
"expo-updates": "~29.0.14",
|
||||
"expo-video": "~3.0.15",
|
||||
"expo-video-thumbnails": "^10.0.8",
|
||||
"expo-web-browser": "~15.0.10",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"fuse.js": "^7.1.0",
|
||||
"hls.js": "^1.6.2",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"js-sha256": "^0.9.0",
|
||||
@@ -204,7 +198,6 @@
|
||||
"react": "19.1.0",
|
||||
"react-compiler-runtime": "^19.1.0-rc.1",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hotkeys-hook": "5.2.4",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-is": "19",
|
||||
"react-keyed-flatten-children": "^5.0.0",
|
||||
@@ -215,7 +208,7 @@
|
||||
"react-native-drawer-layout": "^4.2.2",
|
||||
"react-native-edge-to-edge": "^1.6.0",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-keyboard-controller": "^1.21.5",
|
||||
"react-native-keyboard-controller": "^1.20.7",
|
||||
"react-native-pager-view": "6.8.0",
|
||||
"react-native-progress": "bluesky-social/react-native-progress",
|
||||
"react-native-qrcode-styled": "^0.3.3",
|
||||
@@ -245,12 +238,13 @@
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@crowdin/cli": "^4.14.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@expo/config-plugins": "~54.0.1",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.2",
|
||||
"@lingui/cli": "^5.9.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@react-native/babel-preset": "0.81.5",
|
||||
"@react-native/eslint-config": "^0.81.5",
|
||||
"@react-native/typescript-config": "^0.81.5",
|
||||
"@sentry/webpack-plugin": "^3.2.2",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
@@ -264,24 +258,24 @@
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
||||
"babel-preset-expo": "~54.0.10",
|
||||
"babel-preset-expo": "~54.0.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-bsky-internal": "link:./eslint",
|
||||
"eslint-plugin-import-x": "^4.16.2",
|
||||
"eslint-plugin-lingui": "^0.12.0",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-lingui": "^0.11.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"eslint-plugin-react-native-a11y": "^3.5.1",
|
||||
"eslint-plugin-simple-import-sort": "^13.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"file-loader": "6.2.0",
|
||||
"globals": "^17.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~54.0.17",
|
||||
"jest-expo": "~54.0.14",
|
||||
"jest-junit": "^16.0.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"lockfile-lint": "^4.14.0",
|
||||
@@ -290,22 +284,20 @@
|
||||
"react-refresh": "^0.14.0",
|
||||
"svgo": "^3.3.2",
|
||||
"ts-plugin-sort-import-suggestions": "^1.0.4",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"webpack-bundle-analyzer": "^4.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@react-native/babel-preset": "0.81.5",
|
||||
"@react-native/normalize-colors": "0.81.5",
|
||||
"**/@expo/image-utils": "0.8.12",
|
||||
"**/@expo/image-utils": "0.8.7",
|
||||
"**/@react-native-async-storage/async-storage": "2.2.0",
|
||||
"**/expo-constants": "18.0.8",
|
||||
"**/expo-device": "7.1.4",
|
||||
"**/multiformats": "9.9.0",
|
||||
"unicode-segmenter": "0.14.5",
|
||||
"@types/estree": "1.0.6",
|
||||
"metro": "0.83.3",
|
||||
"metro-core": "0.83.3",
|
||||
"metro-config": "0.83.3",
|
||||
"metro-runtime": "0.83.3",
|
||||
"metro-source-map": "0.83.3"
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo/ios",
|
||||
@@ -328,8 +320,7 @@
|
||||
],
|
||||
"modulePathIgnorePatterns": [
|
||||
"__tests__/.*/__mocks__",
|
||||
"__e2e__/.*",
|
||||
"bskylink/.*"
|
||||
"__e2e__/.*"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"<rootDir>/node_modules/",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||
index 4ed2307..ede1181 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||
@@ -54,7 +54,7 @@ class PasteTextInputManager(context: ReactApplicationContext) : ReactTextInputMa
|
||||
}
|
||||
|
||||
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
|
||||
- val map = super.getExportedCustomBubblingEventTypeConstants()!!
|
||||
+ val map = super.getExportedCustomBubblingEventTypeConstants().toMutableMap()
|
||||
map["onPaste"] = MapBuilder.of(
|
||||
"phasedRegistrationNames",
|
||||
MapBuilder.of("bubbled", "onPaste")
|
||||
@@ -0,0 +1,264 @@
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
|
||||
index e916023..5049c33 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
// Created by Elias Nahum on 04-11-20.
|
||||
// Copyright © 2020 Facebook. All rights reserved.
|
||||
+// Updated to remove parent’s default text view
|
||||
//
|
||||
|
||||
#import "PasteInputView.h"
|
||||
@@ -12,49 +13,78 @@
|
||||
|
||||
@implementation PasteInputView
|
||||
{
|
||||
- PasteInputTextView *_backedTextInputView;
|
||||
+ // We'll store the custom text view in this ivar
|
||||
+ PasteInputTextView *_customBackedTextView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
+ // Must call the super’s designated initializer
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
- _backedTextInputView = [[PasteInputTextView alloc] initWithFrame:self.bounds];
|
||||
- _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
- _backedTextInputView.textInputDelegate = self;
|
||||
+ // 1. The parent (RCTMultilineTextInputView) has already created
|
||||
+ // its own _backedTextInputView = [RCTUITextView new] in super init.
|
||||
+ // We can remove that subview:
|
||||
|
||||
- [self addSubview:_backedTextInputView];
|
||||
- }
|
||||
+ id<RCTBackedTextInputViewProtocol> parentInputView = super.backedTextInputView;
|
||||
+ if ([parentInputView isKindOfClass:[UIView class]]) {
|
||||
+ UIView *parentSubview = (UIView *)parentInputView;
|
||||
+ if (parentSubview.superview == self) {
|
||||
+ [parentSubview removeFromSuperview];
|
||||
+ }
|
||||
+ }
|
||||
|
||||
+ // 2. Now create our custom PasteInputTextView
|
||||
+ _customBackedTextView = [[PasteInputTextView alloc] initWithFrame:self.bounds];
|
||||
+ _customBackedTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
+ _customBackedTextView.textInputDelegate = self;
|
||||
+
|
||||
+ // Optional: disable inline predictions for iOS 17+
|
||||
+ if (@available(iOS 17.0, *)) {
|
||||
+ _customBackedTextView.inlinePredictionType = UITextInlinePredictionTypeNo;
|
||||
+ }
|
||||
+
|
||||
+ // 3. Add your custom text view as the only subview
|
||||
+ [self addSubview:_customBackedTextView];
|
||||
+ }
|
||||
return self;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * Override the parent's accessor so that anywhere in RN that calls
|
||||
+ * `self.backedTextInputView` will get the custom PasteInputTextView.
|
||||
+ */
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
- return _backedTextInputView;
|
||||
+ return _customBackedTextView;
|
||||
}
|
||||
|
||||
-- (void)setDisableCopyPaste:(BOOL)disableCopyPaste {
|
||||
- _backedTextInputView.disableCopyPaste = disableCopyPaste;
|
||||
+#pragma mark - Setters for React Props
|
||||
+
|
||||
+- (void)setDisableCopyPaste:(BOOL)disableCopyPaste
|
||||
+{
|
||||
+ _customBackedTextView.disableCopyPaste = disableCopyPaste;
|
||||
}
|
||||
|
||||
-- (void)setOnPaste:(RCTDirectEventBlock)onPaste {
|
||||
- _backedTextInputView.onPaste = onPaste;
|
||||
+- (void)setOnPaste:(RCTDirectEventBlock)onPaste
|
||||
+{
|
||||
+ _customBackedTextView.onPaste = onPaste;
|
||||
}
|
||||
|
||||
-- (void)setSmartPunctuation:(NSString *)smartPunctuation {
|
||||
- if ([smartPunctuation isEqualToString:@"enable"]) {
|
||||
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeYes];
|
||||
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeYes];
|
||||
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes];
|
||||
- } else if ([smartPunctuation isEqualToString:@"disable"]) {
|
||||
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeNo];
|
||||
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeNo];
|
||||
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
|
||||
- } else {
|
||||
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeDefault];
|
||||
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeDefault];
|
||||
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault];
|
||||
- }
|
||||
+- (void)setSmartPunctuation:(NSString *)smartPunctuation
|
||||
+{
|
||||
+ if ([smartPunctuation isEqualToString:@"enable"]) {
|
||||
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeYes];
|
||||
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeYes];
|
||||
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes];
|
||||
+ } else if ([smartPunctuation isEqualToString:@"disable"]) {
|
||||
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeNo];
|
||||
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeNo];
|
||||
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
|
||||
+ } else {
|
||||
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeDefault];
|
||||
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeDefault];
|
||||
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault];
|
||||
+ }
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
@@ -62,7 +92,6 @@ - (void)setSmartPunctuation:(NSString *)smartPunctuation {
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
RCTDirectEventBlock onScroll = self.onScroll;
|
||||
-
|
||||
if (onScroll) {
|
||||
CGPoint contentOffset = scrollView.contentOffset;
|
||||
CGSize contentSize = scrollView.contentSize;
|
||||
@@ -71,22 +100,22 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
|
||||
onScroll(@{
|
||||
@"contentOffset": @{
|
||||
- @"x": @(contentOffset.x),
|
||||
- @"y": @(contentOffset.y)
|
||||
+ @"x": @(contentOffset.x),
|
||||
+ @"y": @(contentOffset.y)
|
||||
},
|
||||
@"contentInset": @{
|
||||
- @"top": @(contentInset.top),
|
||||
- @"left": @(contentInset.left),
|
||||
- @"bottom": @(contentInset.bottom),
|
||||
- @"right": @(contentInset.right)
|
||||
+ @"top": @(contentInset.top),
|
||||
+ @"left": @(contentInset.left),
|
||||
+ @"bottom": @(contentInset.bottom),
|
||||
+ @"right": @(contentInset.right)
|
||||
},
|
||||
@"contentSize": @{
|
||||
- @"width": @(contentSize.width),
|
||||
- @"height": @(contentSize.height)
|
||||
+ @"width": @(contentSize.width),
|
||||
+ @"height": @(contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
- @"width": @(size.width),
|
||||
- @"height": @(size.height)
|
||||
+ @"width": @(size.width),
|
||||
+ @"height": @(size.height)
|
||||
},
|
||||
@"zoomScale": @(scrollView.zoomScale ?: 1),
|
||||
});
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm
|
||||
index dd50053..2ed7017 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm
|
||||
@@ -122,8 +122,8 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
|
||||
const auto &newTextInputProps = static_cast<const PasteTextInputProps &>(*props);
|
||||
|
||||
// Traits:
|
||||
- if (newTextInputProps.traits.multiline != oldTextInputProps.traits.multiline) {
|
||||
- [self _setMultiline:newTextInputProps.traits.multiline];
|
||||
+ if (newTextInputProps.multiline != oldTextInputProps.multiline) {
|
||||
+ [self _setMultiline:newTextInputProps.multiline];
|
||||
}
|
||||
|
||||
if (newTextInputProps.traits.autocapitalizationType != oldTextInputProps.traits.autocapitalizationType) {
|
||||
@@ -421,7 +421,7 @@ - (void)textInputDidChangeSelection
|
||||
return;
|
||||
}
|
||||
const auto &props = static_cast<const PasteTextInputProps &>(*_props);
|
||||
- if (props.traits.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
+ if (props.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
[self textInputDidChange];
|
||||
_ignoreNextTextInputCall = YES;
|
||||
}
|
||||
@@ -708,11 +708,11 @@ - (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldTe
|
||||
- (SubmitBehavior)getSubmitBehavior
|
||||
{
|
||||
const auto &props = static_cast<const PasteTextInputProps &>(*_props);
|
||||
- const SubmitBehavior submitBehaviorDefaultable = props.traits.submitBehavior;
|
||||
+ const SubmitBehavior submitBehaviorDefaultable = props.submitBehavior;
|
||||
|
||||
// We should always have a non-default `submitBehavior`, but in case we don't, set it based on multiline.
|
||||
if (submitBehaviorDefaultable == SubmitBehavior::Default) {
|
||||
- return props.traits.multiline ? SubmitBehavior::Newline : SubmitBehavior::BlurAndSubmit;
|
||||
+ return props.multiline ? SubmitBehavior::Newline : SubmitBehavior::BlurAndSubmit;
|
||||
}
|
||||
|
||||
return submitBehaviorDefaultable;
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp
|
||||
index 29e094f..7ef519a 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp
|
||||
@@ -22,8 +22,7 @@ PasteTextInputProps::PasteTextInputProps(
|
||||
const PropsParserContext &context,
|
||||
const PasteTextInputProps &sourceProps,
|
||||
const RawProps& rawProps)
|
||||
- : ViewProps(context, sourceProps, rawProps),
|
||||
- BaseTextProps(context, sourceProps, rawProps),
|
||||
+ : BaseTextInputProps(context, sourceProps, rawProps),
|
||||
traits(convertRawProp(context, rawProps, sourceProps.traits, {})),
|
||||
smartPunctuation(convertRawProp(context, rawProps, "smartPunctuation", sourceProps.smartPunctuation, {})),
|
||||
disableCopyPaste(convertRawProp(context, rawProps, "disableCopyPaste", sourceProps.disableCopyPaste, {false})),
|
||||
@@ -133,7 +132,7 @@ TextAttributes PasteTextInputProps::getEffectiveTextAttributes(Float fontSizeMul
|
||||
ParagraphAttributes PasteTextInputProps::getEffectiveParagraphAttributes() const {
|
||||
auto result = paragraphAttributes;
|
||||
|
||||
- if (!traits.multiline) {
|
||||
+ if (!multiline) {
|
||||
result.maximumNumberOfLines = 1;
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h
|
||||
index 723d00c..31cfe66 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <react/renderer/components/iostextinput/conversions.h>
|
||||
#include <react/renderer/components/iostextinput/primitives.h>
|
||||
#include <react/renderer/components/text/BaseTextProps.h>
|
||||
+#include <react/renderer/components/textinput/BaseTextInputProps.h>
|
||||
#include <react/renderer/components/view/ViewProps.h>
|
||||
#include <react/renderer/core/Props.h>
|
||||
#include <react/renderer/core/PropsParserContext.h>
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
-class PasteTextInputProps final : public ViewProps, public BaseTextProps {
|
||||
+class PasteTextInputProps final : public BaseTextInputProps {
|
||||
public:
|
||||
PasteTextInputProps() = default;
|
||||
PasteTextInputProps(const PropsParserContext& context, const PasteTextInputProps& sourceProps, const RawProps& rawProps);
|
||||
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp
|
||||
index 31e07e3..7f0ebfb 100644
|
||||
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp
|
||||
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp
|
||||
@@ -91,20 +91,11 @@ void PasteTextInputShadowNode::updateStateIfNeeded(
|
||||
const auto& state = getStateData();
|
||||
|
||||
react_native_assert(textLayoutManager_);
|
||||
- react_native_assert(
|
||||
- (!state.layoutManager || state.layoutManager == textLayoutManager_) &&
|
||||
- "`StateData` refers to a different `TextLayoutManager`");
|
||||
-
|
||||
- if (state.reactTreeAttributedString == reactTreeAttributedString &&
|
||||
- state.layoutManager == textLayoutManager_) {
|
||||
- return;
|
||||
- }
|
||||
|
||||
auto newState = TextInputState{};
|
||||
newState.attributedStringBox = AttributedStringBox{reactTreeAttributedString};
|
||||
newState.paragraphAttributes = getConcreteProps().paragraphAttributes;
|
||||
newState.reactTreeAttributedString = reactTreeAttributedString;
|
||||
- newState.layoutManager = textLayoutManager_;
|
||||
newState.mostRecentEventCount = getConcreteProps().mostRecentEventCount;
|
||||
setStateData(std::move(newState));
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
diff --git a/node_modules/expo-glass-effect/ios/GlassContainer.swift b/node_modules/expo-glass-effect/ios/GlassContainer.swift
|
||||
index 61fb67c..b2d111e 100644
|
||||
--- a/node_modules/expo-glass-effect/ios/GlassContainer.swift
|
||||
+++ b/node_modules/expo-glass-effect/ios/GlassContainer.swift
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022-present 650 Industries. All rights reserved.
|
||||
|
||||
import ExpoModulesCore
|
||||
+import React
|
||||
|
||||
public final class GlassContainer: ExpoView {
|
||||
private var containerEffect: Any?
|
||||
@@ -46,11 +47,19 @@ public final class GlassContainer: ExpoView {
|
||||
}
|
||||
}
|
||||
|
||||
- public override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
+ // Paper: redirect children into the container effect's contentView
|
||||
+ public override func didUpdateReactSubviews() {
|
||||
+ for subview in self.reactSubviews() {
|
||||
+ containerEffectView.contentView.addSubview(subview)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Fabric: redirect children into the container effect's contentView
|
||||
+ @objc public func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
containerEffectView.contentView.insertSubview(childComponentView, at: index)
|
||||
}
|
||||
|
||||
- public override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
+ @objc public func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
childComponentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/expo-glass-effect/ios/GlassView.swift b/node_modules/expo-glass-effect/ios/GlassView.swift
|
||||
index 35cd8f3..9587306 100644
|
||||
--- a/node_modules/expo-glass-effect/ios/GlassView.swift
|
||||
+++ b/node_modules/expo-glass-effect/ios/GlassView.swift
|
||||
@@ -271,11 +271,19 @@ public final class GlassView: ExpoView {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
- public override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
+ // Paper: redirect children into the glass effect's contentView
|
||||
+ public override func didUpdateReactSubviews() {
|
||||
+ for subview in self.reactSubviews() {
|
||||
+ glassEffectView.contentView.addSubview(subview)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Fabric: redirect children into the glass effect's contentView
|
||||
+ @objc public func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
glassEffectView.contentView.insertSubview(childComponentView, at: index)
|
||||
}
|
||||
|
||||
- public override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
+ @objc public func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
||||
childComponentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# expo-glass-effect patch
|
||||
|
||||
Patches in support for Expo SDK 54. Please delete when we update Expo
|
||||
@@ -0,0 +1,44 @@
|
||||
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt
|
||||
index d300fc2..0890878 100644
|
||||
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt
|
||||
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/activityresult/AppContextActivityResultLauncher.kt
|
||||
@@ -3,8 +3,8 @@ package expo.modules.kotlin.activityresult
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import java.io.Serializable
|
||||
+import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
-import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
/**
|
||||
* A launcher for a previously-[AppContextActivityResultCaller.registerForActivityResult] prepared call
|
||||
@@ -22,8 +22,12 @@ abstract class AppContextActivityResultLauncher<I : Serializable, O> {
|
||||
*/
|
||||
abstract fun launch(input: I, callback: ActivityResultCallback<O>)
|
||||
|
||||
- suspend fun launch(input: I): O = suspendCoroutine { continuation ->
|
||||
- launch(input) { output -> continuation.resume(output) }
|
||||
+ suspend fun launch(input: I): O = suspendCancellableCoroutine { continuation ->
|
||||
+ launch(input) { output ->
|
||||
+ if (continuation.isActive) {
|
||||
+ continuation.resume(output)
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
abstract val contract: AppContextActivityResultContract<I, O>
|
||||
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15..afe138d 100644
|
||||
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
@@ -1,15 +0,0 @@
|
||||
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15..afe138d 100644
|
||||
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
@@ -0,0 +1,992 @@
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock
|
||||
new file mode 100644
|
||||
index 0000000..883ef6a
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin
|
||||
new file mode 100644
|
||||
index 0000000..f76dd23
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock
|
||||
new file mode 100644
|
||||
index 0000000..774caf7
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/8.10/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
|
||||
new file mode 100644
|
||||
index 0000000..a3c1514
|
||||
Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties
|
||||
new file mode 100644
|
||||
index 0000000..0e5b4da
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties
|
||||
@@ -0,0 +1,2 @@
|
||||
+#Thu Apr 24 20:44:36 PDT 2025
|
||||
+gradle.version=8.10
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/config.properties b/node_modules/expo-notifications/android/.gradle/config.properties
|
||||
new file mode 100644
|
||||
index 0000000..0bd71c6
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.gradle/config.properties
|
||||
@@ -0,0 +1,2 @@
|
||||
+#Thu Apr 24 20:44:32 PDT 2025
|
||||
+java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
|
||||
diff --git a/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties b/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/.gitignore b/node_modules/expo-notifications/android/.idea/.gitignore
|
||||
new file mode 100644
|
||||
index 0000000..26d3352
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/.gitignore
|
||||
@@ -0,0 +1,3 @@
|
||||
+# Default ignored files
|
||||
+/shelf/
|
||||
+/workspace.xml
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml
|
||||
new file mode 100644
|
||||
index 0000000..4a53bee
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="AndroidProjectSystem">
|
||||
+ <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml
|
||||
new file mode 100644
|
||||
index 0000000..9e9ba09
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml
|
||||
@@ -0,0 +1,607 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="DeviceStreaming">
|
||||
+ <option name="deviceSelectionList">
|
||||
+ <list>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="27" />
|
||||
+ <option name="brand" value="DOCOMO" />
|
||||
+ <option name="codename" value="F01L" />
|
||||
+ <option name="id" value="F01L" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="FUJITSU" />
|
||||
+ <option name="name" value="F-01L" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1280" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="OnePlus" />
|
||||
+ <option name="codename" value="OP5552L1" />
|
||||
+ <option name="id" value="OP5552L1" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="OnePlus" />
|
||||
+ <option name="name" value="CPH2415" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2412" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="OPPO" />
|
||||
+ <option name="codename" value="OP573DL1" />
|
||||
+ <option name="id" value="OP573DL1" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="OPPO" />
|
||||
+ <option name="name" value="CPH2557" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="28" />
|
||||
+ <option name="brand" value="DOCOMO" />
|
||||
+ <option name="codename" value="SH-01L" />
|
||||
+ <option name="id" value="SH-01L" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="SHARP" />
|
||||
+ <option name="name" value="AQUOS sense2 SH-01L" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2160" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="Lenovo" />
|
||||
+ <option name="codename" value="TB370FU" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="TB370FU" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Lenovo" />
|
||||
+ <option name="name" value="Tab P12" />
|
||||
+ <option name="screenDensity" value="340" />
|
||||
+ <option name="screenX" value="1840" />
|
||||
+ <option name="screenY" value="2944" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a15" />
|
||||
+ <option name="id" value="a15" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="A15" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a35x" />
|
||||
+ <option name="id" value="a35x" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="A35" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="31" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="a51" />
|
||||
+ <option name="id" value="a51" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy A51" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="akita" />
|
||||
+ <option name="id" value="akita" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="arcfox" />
|
||||
+ <option name="id" value="arcfox" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="razr plus 2024" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="1272" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="austin" />
|
||||
+ <option name="id" value="austin" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g 5G (2022)" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="b0q" />
|
||||
+ <option name="id" value="b0q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S22 Ultra" />
|
||||
+ <option name="screenDensity" value="600" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3088" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="32" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="bluejay" />
|
||||
+ <option name="id" value="bluejay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 6a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="caiman" />
|
||||
+ <option name="id" value="caiman" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="960" />
|
||||
+ <option name="screenY" value="2142" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="comet" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="comet" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro Fold" />
|
||||
+ <option name="screenDensity" value="390" />
|
||||
+ <option name="screenX" value="2076" />
|
||||
+ <option name="screenY" value="2152" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="29" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="crownqlteue" />
|
||||
+ <option name="id" value="crownqlteue" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Note9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2220" />
|
||||
+ <option name="screenY" value="1080" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="dm2q" />
|
||||
+ <option name="id" value="dm2q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="S23 Plus" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="dm3q" />
|
||||
+ <option name="id" value="dm3q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S23 Ultra" />
|
||||
+ <option name="screenDensity" value="600" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3088" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="e1q" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="e1q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S24" />
|
||||
+ <option name="screenDensity" value="480" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="e3q" />
|
||||
+ <option name="id" value="e3q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S24 Ultra" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="3120" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="eos" />
|
||||
+ <option name="id" value="eos" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Eos" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="384" />
|
||||
+ <option name="screenY" value="384" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix" />
|
||||
+ <option name="id" value="felix" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix" />
|
||||
+ <option name="id" value="felix" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="felix_camera" />
|
||||
+ <option name="id" value="felix_camera" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="2208" />
|
||||
+ <option name="screenY" value="1840" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="fogona" />
|
||||
+ <option name="id" value="fogona" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g play - 2024" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="g0q" />
|
||||
+ <option name="id" value="g0q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-S906U1" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gta9pwifi" />
|
||||
+ <option name="id" value="gta9pwifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-X210" />
|
||||
+ <option name="screenDensity" value="240" />
|
||||
+ <option name="screenX" value="1200" />
|
||||
+ <option name="screenY" value="1920" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts7xllite" />
|
||||
+ <option name="id" value="gts7xllite" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-T738U" />
|
||||
+ <option name="screenDensity" value="340" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts8uwifi" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="gts8uwifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S8 Ultra" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="1848" />
|
||||
+ <option name="screenY" value="2960" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts8wifi" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="gts8wifi" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S8" />
|
||||
+ <option name="screenDensity" value="274" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="gts9fe" />
|
||||
+ <option name="id" value="gts9fe" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="1440" />
|
||||
+ <option name="screenY" value="2304" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="husky" />
|
||||
+ <option name="id" value="husky" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8 Pro" />
|
||||
+ <option name="screenDensity" value="390" />
|
||||
+ <option name="screenX" value="1008" />
|
||||
+ <option name="screenY" value="2244" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="java" />
|
||||
+ <option name="id" value="java" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="G20" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="komodo" />
|
||||
+ <option name="id" value="komodo" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9 Pro XL" />
|
||||
+ <option name="screenDensity" value="360" />
|
||||
+ <option name="screenX" value="1008" />
|
||||
+ <option name="screenY" value="2244" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="lynx" />
|
||||
+ <option name="id" value="lynx" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 7a" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="motorola" />
|
||||
+ <option name="codename" value="maui" />
|
||||
+ <option name="id" value="maui" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Motorola" />
|
||||
+ <option name="name" value="moto g play - 2023" />
|
||||
+ <option name="screenDensity" value="280" />
|
||||
+ <option name="screenX" value="720" />
|
||||
+ <option name="screenY" value="1600" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="o1q" />
|
||||
+ <option name="id" value="o1q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S21" />
|
||||
+ <option name="screenDensity" value="421" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="31" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="oriole" />
|
||||
+ <option name="id" value="oriole" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 6" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="panther" />
|
||||
+ <option name="id" value="panther" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 7" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="q5q" />
|
||||
+ <option name="id" value="q5q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Z Fold5" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1812" />
|
||||
+ <option name="screenY" value="2176" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="q6q" />
|
||||
+ <option name="id" value="q6q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy Z Fold6" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1856" />
|
||||
+ <option name="screenY" value="2160" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="r11" />
|
||||
+ <option name="formFactor" value="Wear OS" />
|
||||
+ <option name="id" value="r11" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Watch" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="384" />
|
||||
+ <option name="screenY" value="384" />
|
||||
+ <option name="type" value="WEAR_OS" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="r11q" />
|
||||
+ <option name="id" value="r11q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="SM-S711U" />
|
||||
+ <option name="screenDensity" value="450" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="30" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="redfin" />
|
||||
+ <option name="id" value="redfin" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 5" />
|
||||
+ <option name="screenDensity" value="440" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2340" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="shiba" />
|
||||
+ <option name="id" value="shiba" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 8" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="samsung" />
|
||||
+ <option name="codename" value="t2q" />
|
||||
+ <option name="id" value="t2q" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Samsung" />
|
||||
+ <option name="name" value="Galaxy S21 Plus" />
|
||||
+ <option name="screenDensity" value="394" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2400" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="33" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tangorpro" />
|
||||
+ <option name="formFactor" value="Tablet" />
|
||||
+ <option name="id" value="tangorpro" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel Tablet" />
|
||||
+ <option name="screenDensity" value="320" />
|
||||
+ <option name="screenX" value="1600" />
|
||||
+ <option name="screenY" value="2560" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="34" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tokay" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="tokay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2424" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ <PersistentDeviceSelectionData>
|
||||
+ <option name="api" value="35" />
|
||||
+ <option name="brand" value="google" />
|
||||
+ <option name="codename" value="tokay" />
|
||||
+ <option name="default" value="true" />
|
||||
+ <option name="id" value="tokay" />
|
||||
+ <option name="labId" value="google" />
|
||||
+ <option name="manufacturer" value="Google" />
|
||||
+ <option name="name" value="Pixel 9" />
|
||||
+ <option name="screenDensity" value="420" />
|
||||
+ <option name="screenX" value="1080" />
|
||||
+ <option name="screenY" value="2424" />
|
||||
+ </PersistentDeviceSelectionData>
|
||||
+ </list>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/gradle.xml b/node_modules/expo-notifications/android/.idea/gradle.xml
|
||||
new file mode 100644
|
||||
index 0000000..b838237
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/gradle.xml
|
||||
@@ -0,0 +1,12 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="GradleSettings">
|
||||
+ <option name="linkedExternalProjectsSettings">
|
||||
+ <GradleProjectSettings>
|
||||
+ <option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
+ </GradleProjectSettings>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/migrations.xml b/node_modules/expo-notifications/android/.idea/migrations.xml
|
||||
new file mode 100644
|
||||
index 0000000..f8051a6
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/migrations.xml
|
||||
@@ -0,0 +1,10 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ProjectMigrations">
|
||||
+ <option name="MigrateToGradleLocalJavaHome">
|
||||
+ <set>
|
||||
+ <option value="$PROJECT_DIR$" />
|
||||
+ </set>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/misc.xml b/node_modules/expo-notifications/android/.idea/misc.xml
|
||||
new file mode 100644
|
||||
index 0000000..3040d03
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/misc.xml
|
||||
@@ -0,0 +1,10 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
+ <component name="ProjectRootManager">
|
||||
+ <output url="file://$PROJECT_DIR$/build/classes" />
|
||||
+ </component>
|
||||
+ <component name="ProjectType">
|
||||
+ <option name="id" value="Android" />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/runConfigurations.xml b/node_modules/expo-notifications/android/.idea/runConfigurations.xml
|
||||
new file mode 100644
|
||||
index 0000000..16660f1
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/runConfigurations.xml
|
||||
@@ -0,0 +1,17 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="RunConfigurationProducerService">
|
||||
+ <option name="ignoredProducers">
|
||||
+ <set>
|
||||
+ <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
+ <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
+ </set>
|
||||
+ </option>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/.idea/workspace.xml b/node_modules/expo-notifications/android/.idea/workspace.xml
|
||||
new file mode 100644
|
||||
index 0000000..df26928
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/.idea/workspace.xml
|
||||
@@ -0,0 +1,47 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="AutoImportSettings">
|
||||
+ <option name="autoReloadType" value="NONE" />
|
||||
+ </component>
|
||||
+ <component name="ChangeListManager">
|
||||
+ <list default="true" id="fed6a9c0-2e93-4b6e-953a-d1cd1e93b59f" name="Changes" comment="" />
|
||||
+ <option name="SHOW_DIALOG" value="false" />
|
||||
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
+ <option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
+ </component>
|
||||
+ <component name="ClangdSettings">
|
||||
+ <option name="formatViaClangd" value="false" />
|
||||
+ </component>
|
||||
+ <component name="ProjectColorInfo"><![CDATA[{
|
||||
+ "associatedIndex": 4
|
||||
+}]]></component>
|
||||
+ <component name="ProjectId" id="2wCjuanPzVGKP91vdmftQVgUlaM" />
|
||||
+ <component name="ProjectViewState">
|
||||
+ <option name="hideEmptyMiddlePackages" value="true" />
|
||||
+ <option name="showLibraryContents" value="true" />
|
||||
+ </component>
|
||||
+ <component name="PropertiesComponent"><![CDATA[{
|
||||
+ "keyToString": {
|
||||
+ "RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
+ "RunOnceActivity.cidr.known.project.marker": "true",
|
||||
+ "RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||
+ "android.gradle.sync.needed": "true",
|
||||
+ "cf.first.check.clang-format": "false",
|
||||
+ "cidr.known.project.marker": "true",
|
||||
+ "kotlin-language-version-configured": "true",
|
||||
+ "last_opened_file_path": "/Users/hailey/bsky/social-app/node_modules/expo-notifications/android"
|
||||
+ }
|
||||
+}]]></component>
|
||||
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
+ <component name="TaskManager">
|
||||
+ <task active="true" id="Default" summary="Default task">
|
||||
+ <changelist id="fed6a9c0-2e93-4b6e-953a-d1cd1e93b59f" name="Changes" comment="" />
|
||||
+ <created>1745552672693</created>
|
||||
+ <option name="number" value="Default" />
|
||||
+ <option name="presentableId" value="Default" />
|
||||
+ <updated>1745552672693</updated>
|
||||
+ </task>
|
||||
+ <servers />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
|
||||
index bc479ee..1ebfa00 100644
|
||||
--- a/node_modules/expo-notifications/android/build.gradle
|
||||
+++ b/node_modules/expo-notifications/android/build.gradle
|
||||
@@ -42,6 +42,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:24.0.1'
|
||||
|
||||
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
|
||||
+ implementation project(':expo-background-notification-handler')
|
||||
|
||||
if (project.findProject(':expo-modules-test-core')) {
|
||||
testImplementation project(':expo-modules-test-core')
|
||||
diff --git a/node_modules/expo-notifications/android/local.properties b/node_modules/expo-notifications/android/local.properties
|
||||
new file mode 100644
|
||||
index 0000000..ab4c86d
|
||||
--- /dev/null
|
||||
+++ b/node_modules/expo-notifications/android/local.properties
|
||||
@@ -0,0 +1,8 @@
|
||||
+## This file must *NOT* be checked into Version Control Systems,
|
||||
+# as it contains information specific to your local configuration.
|
||||
+#
|
||||
+# Location of the SDK. This is only used by Gradle.
|
||||
+# For customization when using a Version Control System, please read the
|
||||
+# header note.
|
||||
+#Thu Apr 24 20:44:32 PDT 2025
|
||||
+sdk.dir=/Users/hailey/Library/Android/sdk
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
index 7b99e6c..45a450d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
@@ -15,6 +15,7 @@ import org.json.JSONObject
|
||||
* This interface exists to provide a common API for both classes.
|
||||
* */
|
||||
interface INotificationContent : Parcelable {
|
||||
+ val channelId: String?
|
||||
val title: String?
|
||||
val text: String?
|
||||
val subText: String?
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
index 191b64e..fe8b3c5 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation;
|
||||
* Refactoring this class may require a migration strategy for the data stored in SharedPreferences.
|
||||
*/
|
||||
public class NotificationContent implements Parcelable, Serializable, INotificationContent {
|
||||
+ private String mChannelId;
|
||||
private String mTitle;
|
||||
private String mText;
|
||||
private String mSubtitle;
|
||||
@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
};
|
||||
|
||||
+ @Nullable
|
||||
+ public String getChannelId() {
|
||||
+ return mChannelId;
|
||||
+ }
|
||||
+
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
|
||||
protected NotificationContent(Parcel in) {
|
||||
+ mChannelId = in.readString();
|
||||
mTitle = in.readString();
|
||||
mText = in.readString();
|
||||
mSubtitle = in.readString();
|
||||
@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
+ dest.writeString(mChannelId);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mText);
|
||||
dest.writeString(mSubtitle);
|
||||
@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
private static final long serialVersionUID = 397666843266836802L;
|
||||
|
||||
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
|
||||
+ out.writeObject(mChannelId);
|
||||
out.writeObject(mTitle);
|
||||
out.writeObject(mText);
|
||||
out.writeObject(mSubtitle);
|
||||
@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
useDefaultVibrationPattern();
|
||||
}
|
||||
|
||||
+ public Builder setChannelId(String channelId) {
|
||||
+ content.mChannelId = channelId;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
public Builder setTitle(String title) {
|
||||
content.mTitle = title;
|
||||
return this;
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
index 3af254c..3c77e9d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
@@ -11,6 +11,9 @@ import org.json.JSONObject
|
||||
* */
|
||||
@JvmInline
|
||||
value class NotificationData(private val data: Map<String, String>) {
|
||||
+ val channelId: String?
|
||||
+ get() = data["channelId"]
|
||||
+
|
||||
val title: String?
|
||||
get() = data["title"]
|
||||
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
index d2cc6cf..6a48ff2 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot
|
||||
return remoteMessage.notification?.imageUrl != null
|
||||
}
|
||||
|
||||
+ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId
|
||||
+
|
||||
override val title = remoteMessage.notification?.title ?: notificationData.title
|
||||
|
||||
override val text = remoteMessage.notification?.body ?: notificationData.message
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
index 98f003f..2f745e8 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder(
|
||||
builder.setOngoing(content.isSticky)
|
||||
|
||||
// see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates
|
||||
+ content.channelId?.let {
|
||||
+ builder.setChannelId(it)
|
||||
+ }
|
||||
builder.setContentTitle(content.title)
|
||||
builder.setContentText(content.text)
|
||||
builder.setSubText(content.subText)
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
index 90ca4ff..9d4cb09 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
@@ -3,6 +3,9 @@ package expo.modules.notifications.service.delegates
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface
|
||||
+import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule
|
||||
import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
|
||||
import expo.modules.notifications.notifications.RemoteMessageSerializer
|
||||
import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
|
||||
@@ -18,7 +21,7 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate {
|
||||
+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface{
|
||||
companion object {
|
||||
// Unfortunately we cannot save state between instances of a service other way
|
||||
// than by static properties. Fortunately, using weak references we can
|
||||
@@ -105,8 +108,19 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
|
||||
DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage)
|
||||
val notification = createNotification(remoteMessage)
|
||||
DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
|
||||
- NotificationsService.receive(context, notification)
|
||||
- runTaskManagerTasks(context.applicationContext, RemoteMessageSerializer.toBundle(remoteMessage))
|
||||
+ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) {
|
||||
+ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage)
|
||||
+ } else {
|
||||
+ NotificationsService.receive(context, notification)
|
||||
+ runTaskManagerTasks(
|
||||
+ context.applicationContext,
|
||||
+ RemoteMessageSerializer.toBundle(remoteMessage)
|
||||
+ )
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ override fun showMessage(remoteMessage: RemoteMessage) {
|
||||
+ NotificationsService.receive(context, createNotification(remoteMessage))
|
||||
}
|
||||
|
||||
protected fun createNotification(remoteMessage: RemoteMessage): Notification {
|
||||
@@ -1,170 +0,0 @@
|
||||
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
|
||||
index bc479ee..1ebfa00 100644
|
||||
--- a/node_modules/expo-notifications/android/build.gradle
|
||||
+++ b/node_modules/expo-notifications/android/build.gradle
|
||||
@@ -42,6 +42,7 @@ dependencies {
|
||||
implementation 'com.google.firebase:firebase-messaging:24.0.1'
|
||||
|
||||
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
|
||||
+ implementation project(':expo-background-notification-handler')
|
||||
|
||||
if (project.findProject(':expo-modules-test-core')) {
|
||||
testImplementation project(':expo-modules-test-core')
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
index 7b99e6c..45a450d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt
|
||||
@@ -15,6 +15,7 @@ import org.json.JSONObject
|
||||
* This interface exists to provide a common API for both classes.
|
||||
* */
|
||||
interface INotificationContent : Parcelable {
|
||||
+ val channelId: String?
|
||||
val title: String?
|
||||
val text: String?
|
||||
val subText: String?
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
index 191b64e..fe8b3c5 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
|
||||
@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation;
|
||||
* Refactoring this class may require a migration strategy for the data stored in SharedPreferences.
|
||||
*/
|
||||
public class NotificationContent implements Parcelable, Serializable, INotificationContent {
|
||||
+ private String mChannelId;
|
||||
private String mTitle;
|
||||
private String mText;
|
||||
private String mSubtitle;
|
||||
@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
};
|
||||
|
||||
+ @Nullable
|
||||
+ public String getChannelId() {
|
||||
+ return mChannelId;
|
||||
+ }
|
||||
+
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
}
|
||||
|
||||
protected NotificationContent(Parcel in) {
|
||||
+ mChannelId = in.readString();
|
||||
mTitle = in.readString();
|
||||
mText = in.readString();
|
||||
mSubtitle = in.readString();
|
||||
@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
+ dest.writeString(mChannelId);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mText);
|
||||
dest.writeString(mSubtitle);
|
||||
@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
private static final long serialVersionUID = 397666843266836802L;
|
||||
|
||||
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
|
||||
+ out.writeObject(mChannelId);
|
||||
out.writeObject(mTitle);
|
||||
out.writeObject(mText);
|
||||
out.writeObject(mSubtitle);
|
||||
@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat
|
||||
useDefaultVibrationPattern();
|
||||
}
|
||||
|
||||
+ public Builder setChannelId(String channelId) {
|
||||
+ content.mChannelId = channelId;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
public Builder setTitle(String title) {
|
||||
content.mTitle = title;
|
||||
return this;
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
index 3af254c..3c77e9d 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt
|
||||
@@ -11,6 +11,9 @@ import org.json.JSONObject
|
||||
* */
|
||||
@JvmInline
|
||||
value class NotificationData(private val data: Map<String, String>) {
|
||||
+ val channelId: String?
|
||||
+ get() = data["channelId"]
|
||||
+
|
||||
val title: String?
|
||||
get() = data["title"]
|
||||
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
index d2cc6cf..6a48ff2 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt
|
||||
@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot
|
||||
return remoteMessage.notification?.imageUrl != null
|
||||
}
|
||||
|
||||
+ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId
|
||||
+
|
||||
override val title = remoteMessage.notification?.title ?: notificationData.title
|
||||
|
||||
override val text = remoteMessage.notification?.body ?: notificationData.message
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
index 98f003f..2f745e8 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt
|
||||
@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder(
|
||||
builder.setOngoing(content.isSticky)
|
||||
|
||||
// see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates
|
||||
+ content.channelId?.let {
|
||||
+ builder.setChannelId(it)
|
||||
+ }
|
||||
builder.setContentTitle(content.title)
|
||||
builder.setContentText(content.text)
|
||||
builder.setSubText(content.subText)
|
||||
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
index 90ca4ff..9d4cb09 100644
|
||||
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
|
||||
@@ -3,6 +3,9 @@ package expo.modules.notifications.service.delegates
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler
|
||||
+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface
|
||||
+import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule
|
||||
import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
|
||||
import expo.modules.notifications.notifications.RemoteMessageSerializer
|
||||
import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer
|
||||
@@ -18,7 +21,7 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate {
|
||||
+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface{
|
||||
companion object {
|
||||
// Unfortunately we cannot save state between instances of a service other way
|
||||
// than by static properties. Fortunately, using weak references we can
|
||||
@@ -105,8 +108,19 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
|
||||
DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage)
|
||||
val notification = createNotification(remoteMessage)
|
||||
DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification)
|
||||
- NotificationsService.receive(context, notification)
|
||||
- runTaskManagerTasks(context.applicationContext, RemoteMessageSerializer.toBundle(remoteMessage))
|
||||
+ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) {
|
||||
+ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage)
|
||||
+ } else {
|
||||
+ NotificationsService.receive(context, notification)
|
||||
+ runTaskManagerTasks(
|
||||
+ context.applicationContext,
|
||||
+ RemoteMessageSerializer.toBundle(remoteMessage)
|
||||
+ )
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ override fun showMessage(remoteMessage: RemoteMessage) {
|
||||
+ NotificationsService.receive(context, createNotification(remoteMessage))
|
||||
}
|
||||
|
||||
protected fun createNotification(remoteMessage: RemoteMessage): Notification {
|
||||
@@ -1,16 +0,0 @@
|
||||
diff --git a/node_modules/react-native/third-party-podspecs/fmt.podspec b/node_modules/react-native/third-party-podspecs/fmt.podspec
|
||||
index 2f38990..9b02e48 100644
|
||||
--- a/node_modules/react-native/third-party-podspecs/fmt.podspec
|
||||
+++ b/node_modules/react-native/third-party-podspecs/fmt.podspec
|
||||
@@ -26,4 +26,11 @@ Pod::Spec.new do |spec|
|
||||
spec.public_header_files = "include/fmt/*.h"
|
||||
spec.header_mappings_dir = "include"
|
||||
spec.source_files = ["include/fmt/*.h", "src/format.cc"]
|
||||
+
|
||||
+ # TODO: Remove after upgrading React Native past 0.83.x
|
||||
+ # Fix fmt 11.0.2 consteval build error with Xcode 26.4 (facebook/react-native#55601)
|
||||
+ # Fixed in RN 0.84+ which bumps fmt to a compatible version.
|
||||
+ spec.prepare_command = <<~SCRIPT
|
||||
+ perl -i -pe 's/^# define FMT_USE_CONSTEVAL 1$/# define FMT_USE_CONSTEVAL 0/' include/fmt/base.h
|
||||
+ SCRIPT
|
||||
end
|
||||
@@ -1,48 +0,0 @@
|
||||
diff --git a/node_modules/react-native-keyboard-controller/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts b/node_modules/react-native-keyboard-controller/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts
|
||||
index 24a25ae..2c5ff6d 100644
|
||||
--- a/node_modules/react-native-keyboard-controller/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts
|
||||
+++ b/node_modules/react-native-keyboard-controller/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
-import { Platform } from "react-native";
|
||||
import { scrollTo, useAnimatedReaction } from "react-native-reanimated";
|
||||
|
||||
-import { IS_FABRIC } from "../../../architecture";
|
||||
import { isScrollAtEnd, shouldShiftContent } from "../useChatKeyboard/helpers";
|
||||
|
||||
import type { KeyboardLiftBehavior } from "../useChatKeyboard/types";
|
||||
@@ -52,7 +50,6 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
|
||||
scroll,
|
||||
layout,
|
||||
size,
|
||||
- contentOffsetY,
|
||||
inverted,
|
||||
keyboardLiftBehavior,
|
||||
freeze,
|
||||
@@ -62,20 +59,14 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
|
||||
(target: number) => {
|
||||
"worklet";
|
||||
|
||||
- if (contentOffsetY && IS_FABRIC) {
|
||||
- // eslint-disable-next-line react-compiler/react-compiler
|
||||
- contentOffsetY.value = target;
|
||||
- } else if (Platform.OS === "android") {
|
||||
- // Defer scrollTo so the animatedProps inset commit lands first;
|
||||
- // otherwise the native ScrollView clamps to the old range.
|
||||
- requestAnimationFrame(() => {
|
||||
- scrollTo(scrollViewRef, 0, target, false);
|
||||
- });
|
||||
- } else {
|
||||
+ // Always defer scrollTo so the animatedProps inset commit lands first;
|
||||
+ // otherwise the native ScrollView clamps contentOffset to the old
|
||||
+ // contentInset range (iOS Fabric) or the old contentInsetBottom (Android).
|
||||
+ requestAnimationFrame(() => {
|
||||
scrollTo(scrollViewRef, 0, target, false);
|
||||
- }
|
||||
+ });
|
||||
},
|
||||
- [scrollViewRef, contentOffsetY],
|
||||
+ [scrollViewRef],
|
||||
);
|
||||
|
||||
useAnimatedReaction(
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withEntitlementsPlist} = require('expo/config-plugins')
|
||||
const {withEntitlementsPlist} = require('@expo/config-plugins')
|
||||
|
||||
const withAppEntitlements = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withEntitlementsPlist(config, async config => {
|
||||
config.modResults['com.apple.security.application-groups'] = [
|
||||
`group.app.bsky`,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withExtensionEntitlements = (config, {extensionName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const extensionEntitlementsPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withExtensionInfoPlist = (config, {extensionName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const plistPath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
@@ -6,6 +6,7 @@ const withExtensionViewController = (
|
||||
config,
|
||||
{controllerName, extensionName},
|
||||
) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const controllerPath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withPlugins} = require('expo/config-plugins')
|
||||
const {withPlugins} = require('@expo/config-plugins')
|
||||
const {withAppEntitlements} = require('./withAppEntitlements')
|
||||
const {withXcodeTarget} = require('./withXcodeTarget')
|
||||
const {withExtensionEntitlements} = require('./withExtensionEntitlements')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withSounds = (config, {extensionName, soundFiles}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
for (const file of soundFiles) {
|
||||
const soundPath = path.join(config.modRequest.projectRoot, 'assets', file)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject, IOSConfig} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const PBXFile = require('xcode/lib/pbxFile')
|
||||
|
||||
const withXcodeTarget = (
|
||||
config,
|
||||
{extensionName, controllerName, soundFiles},
|
||||
) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
let pbxProject = config.modResults
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withEntitlementsPlist} = require('expo/config-plugins')
|
||||
const {withEntitlementsPlist} = require('@expo/config-plugins')
|
||||
|
||||
const withAppEntitlements = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withEntitlementsPlist(config, async config => {
|
||||
config.modResults['com.apple.security.application-groups'] = [
|
||||
`group.app.bsky`,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withExtensionEntitlements = (config, {extensionName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const extensionEntitlementsPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withExtensionInfoPlist = (config, {extensionName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const plistPath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
@@ -6,6 +6,7 @@ const withExtensionViewController = (
|
||||
config,
|
||||
{controllerName, extensionName},
|
||||
) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const controllerPath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withAndroidManifest} = require('expo/config-plugins')
|
||||
const {withAndroidManifest} = require('@expo/config-plugins')
|
||||
|
||||
const withIntentFilters = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withAndroidManifest(config, config => {
|
||||
const intents = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withPlugins} = require('expo/config-plugins')
|
||||
const {withPlugins} = require('@expo/config-plugins')
|
||||
const {withAppEntitlements} = require('./withAppEntitlements')
|
||||
const {withXcodeTarget} = require('./withXcodeTarget')
|
||||
const {withExtensionEntitlements} = require('./withExtensionEntitlements')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
|
||||
const withXcodeTarget = (config, {extensionName, controllerName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const pbxProject = config.modResults
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withEntitlementsPlist} = require('expo/config-plugins')
|
||||
const {withEntitlementsPlist} = require('@expo/config-plugins')
|
||||
|
||||
const withAppEntitlements = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withEntitlementsPlist(config, async config => {
|
||||
config.modResults['com.apple.security.application-groups'] = [
|
||||
`group.app.bsky`,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withClipEntitlements = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const entitlementsPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const {withInfoPlist} = require('expo/config-plugins')
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withClipInfoPlist = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const targetPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const FILES = ['AppDelegate.swift', 'ViewController.swift']
|
||||
|
||||
const withFiles = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const basePath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withPlugins} = require('expo/config-plugins')
|
||||
const {withPlugins} = require('@expo/config-plugins')
|
||||
const {withAppEntitlements} = require('./withAppEntitlements')
|
||||
const {withClipEntitlements} = require('./withClipEntitlements')
|
||||
const {withClipInfoPlist} = require('./withClipInfoPlist')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const {withXcodeProject} = require('expo/config-plugins')
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
|
||||
const BUILD_PHASE_FILES = ['AppDelegate.swift', 'ViewController.swift']
|
||||
|
||||
const withXcodeTarget = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const pbxProject = config.modResults
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Based on https://github.com/expo/expo/pull/33957
|
||||
// Could be removed once the app has been updated to Expo 53
|
||||
const {withAndroidStyles} = require('@expo/config-plugins')
|
||||
|
||||
module.exports = function withAndroidDayNightThemePlugin(appConfig) {
|
||||
const cleanupList = new Set([
|
||||
'colorPrimary',
|
||||
'android:editTextBackground',
|
||||
'android:textColor',
|
||||
'android:editTextStyle',
|
||||
])
|
||||
|
||||
return withAndroidStyles(appConfig, config => {
|
||||
config.modResults.resources.style = config.modResults.resources.style
|
||||
?.map(style => {
|
||||
if (style.$.name === 'AppTheme' && style.item != null) {
|
||||
style.item = style.item.filter(item => !cleanupList.has(item.$.name))
|
||||
}
|
||||
return style
|
||||
})
|
||||
.filter(style => {
|
||||
return style.$.name !== 'ResetEditText'
|
||||
})
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
const {withAndroidManifest} = require('expo/config-plugins')
|
||||
const {withAndroidManifest} = require('@expo/config-plugins')
|
||||
|
||||
const withProcessTextQuery = config =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
withAndroidManifest(config, config => {
|
||||
const manifest = config.modResults.manifest
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {withProjectBuildGradle} = require('expo/config-plugins')
|
||||
const {withProjectBuildGradle} = require('@expo/config-plugins')
|
||||
|
||||
const jitpackRepository = "maven { url 'https://www.jitpack.io' }"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user