Merge branch 'main' into hailey/oauth
This commit is contained in:
@@ -62,38 +62,55 @@ jobs:
|
||||
- name: 🏗️ EAS Build
|
||||
run: yarn use-build-number-with-bump eas build -p android --profile ${{ inputs.profile || 'testflight-android' }} --local --output build.aab --non-interactive
|
||||
|
||||
- name: 🚀 Deploy
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
run: eas submit -p android --non-interactive --path build.aab
|
||||
|
||||
- name: ✍️ Rename bundle
|
||||
- name: ✍️ Rename Testflight bundle
|
||||
if: ${{ inputs.profile != 'production' }}
|
||||
run: mv build.aab build.apk
|
||||
|
||||
- name: ⏰ Get a timestamp
|
||||
id: timestamp
|
||||
if: ${{ inputs.profile != 'production' }}
|
||||
uses: nanzm/get-time-action@master
|
||||
with:
|
||||
format: 'MM-DD-HH-mm-ss'
|
||||
|
||||
- name: 🚀 Upload Artifact
|
||||
id: upload-artifact
|
||||
- name: 🚀 Upload Production Artifact
|
||||
id: upload-artifact-production
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
retention-days: 30
|
||||
compression-level: 6
|
||||
name: build-${{ steps.timestamp.outputs.time }}.aab
|
||||
path: build.aab
|
||||
|
||||
- name: 🚀 Upload Testflight Artifact
|
||||
id: upload-artifact-testflight
|
||||
if: ${{ inputs.profile != 'production' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
retention-days: 30
|
||||
compression-level: 0
|
||||
compression-level: 6
|
||||
name: build-${{ steps.timestamp.outputs.time }}.apk
|
||||
path: build.apk
|
||||
|
||||
- name: 🔔 Notify Slack
|
||||
- name: 🔔 Notify Slack of Production Build
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
uses: slackapi/slack-github-action@v1.25.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Android build is ready for submission. This is a production build! Download the artifact here: ${{ steps.upload-artifact-production.outputs.artifact-url }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- name: 🔔 Notify Slack of Testflight Build
|
||||
if: ${{ inputs.profile != 'production' }}
|
||||
uses: slackapi/slack-github-action@v1.25.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "Android build is ready for testing. Download the artifact here: ${{ steps.upload-artifact.outputs.artifact-url }}"
|
||||
"text": "Android build is ready for testing. Download the artifact here: ${{ steps.upload-artifact-testflight.outputs.artifact-url }}"
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {createServer as createHTTPServer} from 'node:http'
|
||||
import {parse} from 'node:url'
|
||||
|
||||
import {createServer, TestPDS} from '../jest/test-pds'
|
||||
|
||||
async function main() {
|
||||
@@ -14,8 +15,7 @@ async function main() {
|
||||
await server?.close()
|
||||
console.log('Starting new server')
|
||||
const inviteRequired = url?.query && 'invite' in url.query
|
||||
const phoneRequired = url?.query && 'phone' in url.query
|
||||
server = await createServer({inviteRequired, phoneRequired})
|
||||
server = await createServer({inviteRequired})
|
||||
console.log('Listening at', server.pdsUrl)
|
||||
if (url?.query) {
|
||||
if ('users' in url.query) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {beforeAll, describe, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, loginAsAlice, createServer} from '../util'
|
||||
|
||||
import {createServer, loginAsAlice, openApp} from '../util'
|
||||
|
||||
describe('Home screen', () => {
|
||||
beforeAll(async () => {
|
||||
@@ -68,19 +69,16 @@ describe('Home screen', () => {
|
||||
).not.toExist()
|
||||
})
|
||||
|
||||
it('Can report posts', async () => {
|
||||
const carlaPosts = by.id('feedItem-by-carla.test')
|
||||
await element(by.id('postDropdownBtn').withAncestor(carlaPosts))
|
||||
.atIndex(0)
|
||||
.tap()
|
||||
await element(by.text('Report post')).tap()
|
||||
await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
await element(
|
||||
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
).tap()
|
||||
await element(by.id('sendReportBtn')).tap()
|
||||
await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
})
|
||||
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
|
||||
// it('Can report posts', async () => {
|
||||
// const carlaPosts = by.id('feedItem-by-carla.test')
|
||||
// await element(by.id('postDropdownBtn').withAncestor(carlaPosts))
|
||||
// .atIndex(0)
|
||||
// .tap()
|
||||
// await element(by.text('Report post')).tap()
|
||||
// await element(by.id('com.atproto.moderation.defs#reasonSpam')).tap()
|
||||
// await element(by.id('sendReportBtn')).tap()
|
||||
// })
|
||||
|
||||
it('Can swipe between feeds', async () => {
|
||||
await element(by.id('homeScreen')).swipe('left', 'fast', 0.75)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {beforeAll, describe, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, loginAsAlice, createServer} from '../util'
|
||||
|
||||
import {createServer, loginAsAlice, openApp} from '../util'
|
||||
|
||||
describe('invite-codes', () => {
|
||||
let service: string
|
||||
@@ -1,52 +0,0 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, loginAsAlice, createServer} from '../util'
|
||||
|
||||
describe('invite-codes', () => {
|
||||
let service: string
|
||||
let inviteCode = ''
|
||||
beforeAll(async () => {
|
||||
service = await createServer('?users&invite&phone')
|
||||
await openApp({permissions: {notifications: 'YES'}})
|
||||
})
|
||||
|
||||
it('I can fetch invite codes', async () => {
|
||||
await loginAsAlice()
|
||||
await element(by.id('e2eOpenInviteCodesModal')).tap()
|
||||
await expect(element(by.id('inviteCodesModal'))).toBeVisible()
|
||||
const attrs = await element(by.id('inviteCode-0-code')).getAttributes()
|
||||
inviteCode = attrs.text
|
||||
await element(by.id('closeBtn')).tap()
|
||||
await element(by.id('e2eSignOut')).tap()
|
||||
})
|
||||
|
||||
it('I can create a new account with the invite code', async () => {
|
||||
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||
await element(by.id('createAccountButton')).tap()
|
||||
await device.takeScreenshot('1- opened create account screen')
|
||||
await element(by.id('selectServiceButton')).tap()
|
||||
await device.takeScreenshot('2- selected other server')
|
||||
await element(by.id('customSelectBtn')).tap()
|
||||
await element(by.id('customServerTextInput')).typeText(service)
|
||||
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||
await element(by.id('doneBtn')).tap()
|
||||
await device.takeScreenshot('3- input test server URL')
|
||||
await element(by.id('inviteCodeInput')).typeText(inviteCode)
|
||||
await element(by.id('emailInput')).typeText('example@test.com')
|
||||
await element(by.id('passwordInput')).typeText('hunter2')
|
||||
await device.takeScreenshot('4- entered account details')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
await element(by.id('phoneInput')).typeText('2345551234')
|
||||
await element(by.id('requestCodeBtn')).tap()
|
||||
await device.takeScreenshot('5- requested code')
|
||||
await element(by.id('codeInput')).typeText('000000')
|
||||
await device.takeScreenshot('6- entered code')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
await element(by.id('handleInput')).typeText('e2e-test')
|
||||
await device.takeScreenshot('7- entered handle')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
await expect(element(by.id('onboardingInterests'))).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {beforeAll, describe, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, loginAsAlice, createServer, sleep} from '../util'
|
||||
|
||||
import {createServer, loginAsAlice, openApp, sleep} from '../util'
|
||||
|
||||
describe('Profile screen', () => {
|
||||
beforeAll(async () => {
|
||||
@@ -124,16 +125,17 @@ describe('Profile screen', () => {
|
||||
await expect(element(by.id('profileHeaderAlert'))).not.toExist()
|
||||
})
|
||||
|
||||
it('Can report another user', async () => {
|
||||
await element(by.id('profileHeaderDropdownBtn')).tap()
|
||||
await element(by.text('Report Account')).tap()
|
||||
await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
await element(
|
||||
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
).tap()
|
||||
await element(by.id('sendReportBtn')).tap()
|
||||
await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
})
|
||||
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
|
||||
// it('Can report another user', async () => {
|
||||
// await element(by.id('profileHeaderDropdownBtn')).tap()
|
||||
// await element(by.text('Report Account')).tap()
|
||||
// await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
// await element(
|
||||
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
// ).tap()
|
||||
// await element(by.id('sendReportBtn')).tap()
|
||||
// await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
// })
|
||||
|
||||
it('Can like posts', async () => {
|
||||
await element(by.id('postsFeed-flatlist')).swipe(
|
||||
@@ -179,15 +181,16 @@ describe('Profile screen', () => {
|
||||
).not.toExist()
|
||||
})
|
||||
|
||||
it('Can report posts', async () => {
|
||||
const posts = by.id('feedItem-by-bob.test')
|
||||
await element(by.id('postDropdownBtn').withAncestor(posts)).atIndex(0).tap()
|
||||
await element(by.text('Report post')).tap()
|
||||
await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
await element(
|
||||
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
).tap()
|
||||
await element(by.id('sendReportBtn')).tap()
|
||||
await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
})
|
||||
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
|
||||
// it('Can report posts', async () => {
|
||||
// const posts = by.id('feedItem-by-bob.test')
|
||||
// await element(by.id('postDropdownBtn').withAncestor(posts)).atIndex(0).tap()
|
||||
// await element(by.text('Report post')).tap()
|
||||
// await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
// await element(
|
||||
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
// ).tap()
|
||||
// await element(by.id('sendReportBtn')).tap()
|
||||
// await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
// })
|
||||
})
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, createServer} from '../util'
|
||||
|
||||
describe('Create account', () => {
|
||||
let service: string
|
||||
beforeAll(async () => {
|
||||
service = await createServer('?phone')
|
||||
await openApp({permissions: {notifications: 'YES'}})
|
||||
})
|
||||
|
||||
it('I can create a new account with text verification', async () => {
|
||||
console.log('SERVICE IS', service)
|
||||
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||
|
||||
await element(by.id('createAccountButton')).tap()
|
||||
await device.takeScreenshot('1- opened create account screen')
|
||||
await element(by.id('selectServiceButton')).tap()
|
||||
await device.takeScreenshot('2- selected other server')
|
||||
await element(by.id('customSelectBtn')).tap()
|
||||
await element(by.id('customServerTextInput')).typeText(service)
|
||||
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||
await element(by.id('doneBtn')).tap()
|
||||
await device.takeScreenshot('3- input test server URL')
|
||||
await element(by.id('emailInput')).typeText('text-verification@test.com')
|
||||
await element(by.id('passwordInput')).typeText('hunter2')
|
||||
await device.takeScreenshot('4- entered account details')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('handleInput')).typeText('text-verification-test')
|
||||
await device.takeScreenshot('5- entered handle')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('phoneInput')).typeText('8042221111')
|
||||
await element(by.id('requestCodeBtn')).tap()
|
||||
await device.takeScreenshot('6- requested code')
|
||||
|
||||
await element(by.id('codeInput')).typeText('000000')
|
||||
await device.takeScreenshot('7- entered code')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await expect(element(by.id('onboardingInterests'))).toBeVisible()
|
||||
})
|
||||
|
||||
it('failed text verification correctly goes back to the code input screen', async () => {
|
||||
await element(by.id('e2eSignOut')).tap()
|
||||
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||
|
||||
await element(by.id('createAccountButton')).tap()
|
||||
await device.takeScreenshot('1- opened create account screen')
|
||||
await element(by.id('selectServiceButton')).tap()
|
||||
await device.takeScreenshot('2- selected other server')
|
||||
await element(by.id('customSelectBtn')).tap()
|
||||
await element(by.id('customServerTextInput')).typeText(service)
|
||||
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||
await element(by.id('doneBtn')).tap()
|
||||
await device.takeScreenshot('3- input test server URL')
|
||||
await element(by.id('emailInput')).typeText('text-verification2@test.com')
|
||||
await element(by.id('passwordInput')).typeText('hunter2')
|
||||
await device.takeScreenshot('4- entered account details')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('phoneInput')).typeText('8042221111')
|
||||
await element(by.id('requestCodeBtn')).tap()
|
||||
await device.takeScreenshot('5- requested code')
|
||||
|
||||
await element(by.id('codeInput')).typeText('111111')
|
||||
await device.takeScreenshot('6- entered code')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('handleInput')).typeText('text-verification-test2')
|
||||
await device.takeScreenshot('7- entered handle')
|
||||
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await expect(element(by.id('codeInput'))).toBeVisible()
|
||||
await device.takeScreenshot('8- got error')
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-env detox/detox */
|
||||
|
||||
import {describe, beforeAll, it} from '@jest/globals'
|
||||
import {beforeAll, describe, it} from '@jest/globals'
|
||||
import {expect} from 'detox'
|
||||
import {openApp, loginAsAlice, createServer} from '../util'
|
||||
|
||||
import {createServer, loginAsAlice, openApp} from '../util'
|
||||
|
||||
describe('Thread screen', () => {
|
||||
beforeAll(async () => {
|
||||
@@ -102,27 +103,29 @@ describe('Thread screen', () => {
|
||||
).not.toExist()
|
||||
})
|
||||
|
||||
it('Can report the root post', async () => {
|
||||
const post = by.id('postThreadItem-by-bob.test')
|
||||
await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
|
||||
await element(by.text('Report post')).tap()
|
||||
await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
await element(
|
||||
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
).tap()
|
||||
await element(by.id('sendReportBtn')).tap()
|
||||
await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
})
|
||||
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
|
||||
// it('Can report the root post', async () => {
|
||||
// const post = by.id('postThreadItem-by-bob.test')
|
||||
// await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
|
||||
// await element(by.text('Report post')).tap()
|
||||
// await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
// await element(
|
||||
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
// ).tap()
|
||||
// await element(by.id('sendReportBtn')).tap()
|
||||
// await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
// })
|
||||
|
||||
it('Can report a reply post', async () => {
|
||||
const post = by.id('postThreadItem-by-carla.test')
|
||||
await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
|
||||
await element(by.text('Report post')).tap()
|
||||
await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
await element(
|
||||
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
).tap()
|
||||
await element(by.id('sendReportBtn')).tap()
|
||||
await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
})
|
||||
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
|
||||
// it('Can report a reply post', async () => {
|
||||
// const post = by.id('postThreadItem-by-carla.test')
|
||||
// await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
|
||||
// await element(by.text('Report post')).tap()
|
||||
// await expect(element(by.id('reportModal'))).toBeVisible()
|
||||
// await element(
|
||||
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
|
||||
// ).tap()
|
||||
// await element(by.id('sendReportBtn')).tap()
|
||||
// await expect(element(by.id('reportModal'))).not.toBeVisible()
|
||||
// })
|
||||
})
|
||||
|
||||
+59
-96
@@ -1,8 +1,8 @@
|
||||
import {AtUri, BskyAgent} from '@atproto/api'
|
||||
import {TestBsky, TestNetwork} from '@atproto/dev-env'
|
||||
import fs from 'fs'
|
||||
import net from 'net'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import {TestNetwork, TestPds} from '@atproto/dev-env'
|
||||
import {AtUri, BskyAgent} from '@atproto/api'
|
||||
|
||||
export interface TestUser {
|
||||
email: string
|
||||
@@ -55,12 +55,8 @@ class StringIdGenerator {
|
||||
const ids = new StringIdGenerator()
|
||||
|
||||
export async function createServer(
|
||||
{
|
||||
inviteRequired,
|
||||
phoneRequired,
|
||||
}: {inviteRequired: boolean; phoneRequired: boolean} = {
|
||||
{inviteRequired}: {inviteRequired: boolean} = {
|
||||
inviteRequired: false,
|
||||
phoneRequired: false,
|
||||
},
|
||||
): Promise<TestPDS> {
|
||||
const port = 3000
|
||||
@@ -69,23 +65,11 @@ export async function createServer(
|
||||
const pdsUrl = `http://localhost:${port}`
|
||||
const id = ids.next()
|
||||
|
||||
const phoneParams = phoneRequired
|
||||
? {
|
||||
phoneVerificationRequired: true,
|
||||
phoneVerificationProvider: 'twilio',
|
||||
twilioAccountSid: 'ACXXXXXXX',
|
||||
twilioAuthToken: 'AUTH',
|
||||
twilioServiceSid: 'VAXXXXXXXX',
|
||||
}
|
||||
: {}
|
||||
|
||||
const testNet = await TestNetwork.create({
|
||||
pds: {
|
||||
port,
|
||||
hostname: 'localhost',
|
||||
dbPostgresSchema: `pds_${id}`,
|
||||
inviteRequired,
|
||||
...phoneParams,
|
||||
},
|
||||
bsky: {
|
||||
dbPostgresSchema: `bsky_${id}`,
|
||||
@@ -94,36 +78,33 @@ export async function createServer(
|
||||
},
|
||||
plc: {port: port2},
|
||||
})
|
||||
mockTwilio(testNet.pds)
|
||||
|
||||
// add the test mod authority
|
||||
if (!phoneRequired) {
|
||||
const agent = new BskyAgent({service: pdsUrl})
|
||||
const res = await agent.api.com.atproto.server.createAccount({
|
||||
email: 'mod-authority@test.com',
|
||||
handle: 'mod-authority.test',
|
||||
password: 'hunter2',
|
||||
})
|
||||
agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`)
|
||||
await agent.api.app.bsky.actor.profile.create(
|
||||
{repo: res.data.did},
|
||||
{
|
||||
displayName: 'Dev-env Moderation',
|
||||
description: `The pretend version of mod.bsky.app`,
|
||||
},
|
||||
)
|
||||
const agent = new BskyAgent({service: pdsUrl})
|
||||
const res = await agent.api.com.atproto.server.createAccount({
|
||||
email: 'mod-authority@test.com',
|
||||
handle: 'mod-authority.test',
|
||||
password: 'hunter2',
|
||||
})
|
||||
agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`)
|
||||
await agent.api.app.bsky.actor.profile.create(
|
||||
{repo: res.data.did},
|
||||
{
|
||||
displayName: 'Dev-env Moderation',
|
||||
description: `The pretend version of mod.bsky.app`,
|
||||
},
|
||||
)
|
||||
|
||||
await agent.api.app.bsky.labeler.service.create(
|
||||
{repo: res.data.did, rkey: 'self'},
|
||||
{
|
||||
policies: {
|
||||
labelValues: ['!hide', '!warn'],
|
||||
labelValueDefinitions: [],
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
await agent.api.app.bsky.labeler.service.create(
|
||||
{repo: res.data.did, rkey: 'self'},
|
||||
{
|
||||
policies: {
|
||||
labelValues: ['!hide', '!warn'],
|
||||
labelValueDefinitions: [],
|
||||
},
|
||||
)
|
||||
}
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
|
||||
const pic = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'assets', 'default-avatar.png'),
|
||||
@@ -181,7 +162,7 @@ class Mocker {
|
||||
const inviteRes = await agent.api.com.atproto.server.createInviteCode(
|
||||
{useCount: 1},
|
||||
{
|
||||
headers: this.pds.adminAuthHeaders('admin'),
|
||||
headers: this.pds.adminAuthHeaders(),
|
||||
encoding: 'application/json',
|
||||
},
|
||||
)
|
||||
@@ -192,8 +173,6 @@ class Mocker {
|
||||
email,
|
||||
handle: name + '.test',
|
||||
password: 'hunter2',
|
||||
verificationPhone: '1234567890',
|
||||
verificationCode: '000000',
|
||||
})
|
||||
await agent.upsertProfile(async () => {
|
||||
const blob = await agent.uploadBlob(this.pic, {
|
||||
@@ -358,7 +337,7 @@ class Mocker {
|
||||
await agent.api.com.atproto.server.createInviteCode(
|
||||
{useCount: 1, forAccount},
|
||||
{
|
||||
headers: this.pds.adminAuthHeaders('admin'),
|
||||
headers: this.pds.adminAuthHeaders(),
|
||||
encoding: 'application/json',
|
||||
},
|
||||
)
|
||||
@@ -373,18 +352,11 @@ class Mocker {
|
||||
if (!ctx) {
|
||||
throw new Error('Invalid appview')
|
||||
}
|
||||
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||
await labelSrvc.createLabels([
|
||||
{
|
||||
// @ts-ignore
|
||||
src: ctx.cfg.labelerDid,
|
||||
uri: did,
|
||||
cid: '',
|
||||
val: label,
|
||||
neg: false,
|
||||
cts: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
await createLabel(this.bsky, {
|
||||
uri: did,
|
||||
cid: '',
|
||||
val: label,
|
||||
})
|
||||
}
|
||||
|
||||
async labelProfile(label: string, user: string) {
|
||||
@@ -403,18 +375,11 @@ class Mocker {
|
||||
if (!ctx) {
|
||||
throw new Error('Invalid appview')
|
||||
}
|
||||
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||
await labelSrvc.createLabels([
|
||||
{
|
||||
// @ts-ignore
|
||||
src: ctx.cfg.labelerDid,
|
||||
uri: profile.uri,
|
||||
cid: profile.cid,
|
||||
val: label,
|
||||
neg: false,
|
||||
cts: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
await createLabel(this.bsky, {
|
||||
uri: profile.uri,
|
||||
cid: profile.cid,
|
||||
val: label,
|
||||
})
|
||||
}
|
||||
|
||||
async labelPost(label: string, {uri, cid}: {uri: string; cid: string}) {
|
||||
@@ -422,18 +387,11 @@ class Mocker {
|
||||
if (!ctx) {
|
||||
throw new Error('Invalid appview')
|
||||
}
|
||||
const labelSrvc = ctx.services.label(ctx.db.getPrimary())
|
||||
await labelSrvc.createLabels([
|
||||
{
|
||||
// @ts-ignore
|
||||
src: ctx.cfg.labelerDid,
|
||||
uri,
|
||||
cid,
|
||||
val: label,
|
||||
neg: false,
|
||||
cts: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
await createLabel(this.bsky, {
|
||||
uri,
|
||||
cid,
|
||||
val: label,
|
||||
})
|
||||
}
|
||||
|
||||
async createMuteList(user: string, name: string): Promise<string> {
|
||||
@@ -484,14 +442,19 @@ async function getPort(start = 3000) {
|
||||
throw new Error('Unable to find an available port')
|
||||
}
|
||||
|
||||
export const mockTwilio = (pds: TestPds) => {
|
||||
if (!pds.ctx.phoneVerifier) return
|
||||
|
||||
pds.ctx.phoneVerifier.sendCode = async (_number: string) => {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
pds.ctx.phoneVerifier.verifyCode = async (_number: string, code: string) => {
|
||||
return code === '000000'
|
||||
}
|
||||
const createLabel = async (
|
||||
bsky: TestBsky,
|
||||
opts: {uri: string; cid: string; val: string},
|
||||
) => {
|
||||
await bsky.db.db
|
||||
.insertInto('label')
|
||||
.values({
|
||||
uri: opts.uri,
|
||||
cid: opts.cid,
|
||||
val: opts.val,
|
||||
cts: new Date().toISOString(),
|
||||
neg: false,
|
||||
src: 'did:example:labeler',
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
+1
-1
@@ -192,7 +192,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@atproto/dev-env": "^0.2.28",
|
||||
"@atproto/dev-env": "^0.3.4",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
|
||||
+6
-1
@@ -539,8 +539,10 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
|
||||
const {currentAccount} = useSession()
|
||||
const {openModal} = useModalControls()
|
||||
const prevLoggedRouteName = React.useRef<string | undefined>(undefined)
|
||||
|
||||
function onReady() {
|
||||
prevLoggedRouteName.current = getCurrentRouteName()
|
||||
initAnalytics(currentAccount)
|
||||
|
||||
if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) {
|
||||
@@ -555,7 +557,10 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||
linking={LINKING}
|
||||
theme={theme}
|
||||
onStateChange={() => {
|
||||
logEvent('router:navigate', {})
|
||||
logEvent('router:navigate', {
|
||||
from: prevLoggedRouteName.current,
|
||||
})
|
||||
prevLoggedRouteName.current = getCurrentRouteName()
|
||||
}}
|
||||
onReady={() => {
|
||||
attachRouteToLogEvents(getCurrentRouteName)
|
||||
|
||||
@@ -39,8 +39,7 @@ export function useDialogControl(): DialogOuterProps['control'] {
|
||||
control.current.open()
|
||||
},
|
||||
close: cb => {
|
||||
control.current.close()
|
||||
cb?.()
|
||||
control.current.close(cb)
|
||||
},
|
||||
}),
|
||||
[id, control],
|
||||
|
||||
@@ -83,7 +83,7 @@ export function Outer({
|
||||
const sheetOptions = nativeOptions?.sheet || {}
|
||||
const hasSnapPoints = !!sheetOptions.snapPoints
|
||||
const insets = useSafeAreaInsets()
|
||||
const closeCallback = React.useRef<() => void>()
|
||||
const closeCallbacks = React.useRef<(() => void)[]>([])
|
||||
const {setDialogIsOpen} = useDialogStateControlContext()
|
||||
|
||||
/*
|
||||
@@ -96,22 +96,51 @@ export function Outer({
|
||||
*/
|
||||
const isOpen = openIndex > -1
|
||||
|
||||
const callQueuedCallbacks = React.useCallback(() => {
|
||||
for (const cb of closeCallbacks.current) {
|
||||
try {
|
||||
cb()
|
||||
} catch (e: any) {
|
||||
logger.error('Error running close callback', e)
|
||||
}
|
||||
}
|
||||
|
||||
closeCallbacks.current = []
|
||||
}, [])
|
||||
|
||||
const open = React.useCallback<DialogControlProps['open']>(
|
||||
({index} = {}) => {
|
||||
// Run any leftover callbacks that might have been queued up before calling `.open()`
|
||||
callQueuedCallbacks()
|
||||
|
||||
setDialogIsOpen(control.id, true)
|
||||
// can be set to any index of `snapPoints`, but `0` is the first i.e. "open"
|
||||
setOpenIndex(index || 0)
|
||||
sheet.current?.snapToIndex(index || 0)
|
||||
},
|
||||
[setOpenIndex, setDialogIsOpen, control.id],
|
||||
[setDialogIsOpen, control.id, callQueuedCallbacks],
|
||||
)
|
||||
|
||||
// This is the function that we call when we want to dismiss the dialog.
|
||||
const close = React.useCallback<DialogControlProps['close']>(cb => {
|
||||
if (cb && typeof cb === 'function') {
|
||||
closeCallback.current = cb
|
||||
if (typeof cb === 'function') {
|
||||
closeCallbacks.current.push(cb)
|
||||
}
|
||||
sheet.current?.close()
|
||||
}, [])
|
||||
|
||||
// This is the actual thing we are doing once we "confirm" the dialog. We want the dialog's close animation to
|
||||
// happen before we run this. It is passed to the `BottomSheet` component.
|
||||
const onCloseAnimationComplete = React.useCallback(() => {
|
||||
// This removes the dialog from our list of stored dialogs. Not super necessary on iOS, but on Android this
|
||||
// tells us that we need to toggle the accessibility overlay setting
|
||||
setDialogIsOpen(control.id, false)
|
||||
setOpenIndex(-1)
|
||||
|
||||
callQueuedCallbacks()
|
||||
onClose?.()
|
||||
}, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen])
|
||||
|
||||
useImperativeHandle(
|
||||
control.ref,
|
||||
() => ({
|
||||
@@ -121,21 +150,6 @@ export function Outer({
|
||||
[open, close],
|
||||
)
|
||||
|
||||
const onCloseInner = React.useCallback(() => {
|
||||
try {
|
||||
closeCallback.current?.()
|
||||
} catch (e: any) {
|
||||
logger.error(`Dialog closeCallback failed`, {
|
||||
message: e.message,
|
||||
})
|
||||
} finally {
|
||||
closeCallback.current = undefined
|
||||
}
|
||||
setDialogIsOpen(control.id, false)
|
||||
onClose?.()
|
||||
setOpenIndex(-1)
|
||||
}, [control.id, onClose, setDialogIsOpen])
|
||||
|
||||
const context = React.useMemo(() => ({close}), [close])
|
||||
|
||||
return (
|
||||
@@ -163,7 +177,7 @@ export function Outer({
|
||||
backdropComponent={Backdrop}
|
||||
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
|
||||
handleStyle={{display: 'none'}}
|
||||
onClose={onCloseInner}>
|
||||
onClose={onCloseAnimationComplete}>
|
||||
<Context.Provider value={context}>
|
||||
<View
|
||||
style={[
|
||||
|
||||
@@ -33,40 +33,41 @@ export function Outer({
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const [isVisible, setIsVisible] = React.useState(true)
|
||||
const {setDialogIsOpen} = useDialogStateControlContext()
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
setIsOpen(true)
|
||||
setDialogIsOpen(control.id, true)
|
||||
setIsOpen(true)
|
||||
}, [setIsOpen, setDialogIsOpen, control.id])
|
||||
|
||||
const onCloseInner = React.useCallback(async () => {
|
||||
setIsVisible(false)
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
setIsOpen(false)
|
||||
setIsVisible(true)
|
||||
setDialogIsOpen(control.id, false)
|
||||
onClose?.()
|
||||
}, [control.id, onClose, setDialogIsOpen])
|
||||
|
||||
const close = React.useCallback<DialogControlProps['close']>(
|
||||
cb => {
|
||||
setDialogIsOpen(control.id, false)
|
||||
setIsOpen(false)
|
||||
|
||||
try {
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb()
|
||||
// This timeout ensures that the callback runs at the same time as it would on native. I.e.
|
||||
// console.log('Step 1') -> close(() => console.log('Step 3')) -> console.log('Step 2')
|
||||
// This should always output 'Step 1', 'Step 2', 'Step 3', but without the timeout it would output
|
||||
// 'Step 1', 'Step 3', 'Step 2'.
|
||||
setTimeout(cb)
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(`Dialog closeCallback failed`, {
|
||||
message: e.message,
|
||||
})
|
||||
} finally {
|
||||
onCloseInner()
|
||||
}
|
||||
|
||||
onClose?.()
|
||||
},
|
||||
[onCloseInner],
|
||||
[control.id, onClose, setDialogIsOpen],
|
||||
)
|
||||
|
||||
const handleBackgroundPress = React.useCallback(async () => {
|
||||
close()
|
||||
}, [close])
|
||||
|
||||
useImperativeHandle(
|
||||
control.ref,
|
||||
() => ({
|
||||
@@ -103,7 +104,7 @@ export function Outer({
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityHint={undefined}
|
||||
accessibilityLabel={_(msg`Close active dialog`)}
|
||||
onPress={onCloseInner}>
|
||||
onPress={handleBackgroundPress}>
|
||||
<View
|
||||
style={[
|
||||
web(a.fixed),
|
||||
@@ -113,17 +114,15 @@ export function Outer({
|
||||
gtMobile ? a.p_lg : a.p_md,
|
||||
{overflowY: 'auto'},
|
||||
]}>
|
||||
{isVisible && (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
// exiting={FadeOut.duration(150)}
|
||||
style={[
|
||||
web(a.fixed),
|
||||
a.inset_0,
|
||||
{opacity: 0.8, backgroundColor: t.palette.black},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
// exiting={FadeOut.duration(150)}
|
||||
style={[
|
||||
web(a.fixed),
|
||||
a.inset_0,
|
||||
{opacity: 0.8, backgroundColor: t.palette.black},
|
||||
]}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
@@ -135,7 +134,7 @@ export function Outer({
|
||||
minHeight: web('calc(90vh - 36px)') || undefined,
|
||||
},
|
||||
]}>
|
||||
{isVisible ? children : null}
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
@@ -123,6 +123,13 @@ export function Action({
|
||||
cta,
|
||||
testID,
|
||||
}: {
|
||||
/**
|
||||
* Callback to run when the action is pressed. The method is called _after_
|
||||
* the dialog closes.
|
||||
*
|
||||
* Note: The dialog will close automatically when the action is pressed, you
|
||||
* should NOT close the dialog as a side effect of this method.
|
||||
*/
|
||||
onPress: () => void
|
||||
color?: ButtonColor
|
||||
/**
|
||||
@@ -165,6 +172,13 @@ export function Basic({
|
||||
description: string
|
||||
cancelButtonCta?: string
|
||||
confirmButtonCta?: string
|
||||
/**
|
||||
* Callback to run when the Confirm button is pressed. The method is called
|
||||
* _after_ the dialog closes.
|
||||
*
|
||||
* Note: The dialog will close automatically when the action is pressed, you
|
||||
* should NOT close the dialog as a side effect of this method.
|
||||
*/
|
||||
onConfirm: () => void
|
||||
confirmButtonColor?: ButtonColor
|
||||
}>) {
|
||||
|
||||
@@ -90,6 +90,7 @@ export function SelectReportOptionView({
|
||||
return (
|
||||
<Button
|
||||
key={reportOption.reason}
|
||||
testID={reportOption.reason}
|
||||
label={_(msg`Create report for ${reportOption.title}`)}
|
||||
onPress={() => props.onSelectReportOption(reportOption)}>
|
||||
<ReportOptionButton
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||
|
||||
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
||||
|
||||
import {atoms as a, useTheme, native} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
|
||||
import {ReportDialogProps} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {atoms as a, native, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {ReportDialogProps} from './types'
|
||||
|
||||
export function SubmitView({
|
||||
params,
|
||||
@@ -208,6 +206,7 @@ export function SubmitView({
|
||||
))}
|
||||
|
||||
<Button
|
||||
testID="sendReportBtn"
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="negative"
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {
|
||||
type EmbedPlayerSource,
|
||||
embedPlayerSources,
|
||||
externalEmbedLabels,
|
||||
} from '#/lib/strings/embed-player'
|
||||
import {useSetExternalEmbedPref} from '#/state/preferences'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Button, ButtonText} from '../Button'
|
||||
import {Text} from '../Typography'
|
||||
|
||||
export function EmbedConsentDialog({
|
||||
control,
|
||||
source,
|
||||
onAccept,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
source: EmbedPlayerSource
|
||||
onAccept: () => void
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const setExternalEmbedPref = useSetExternalEmbedPref()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const onShowAllPress = useCallback(() => {
|
||||
for (const key of embedPlayerSources) {
|
||||
setExternalEmbedPref(key, 'show')
|
||||
}
|
||||
onAccept()
|
||||
control.close()
|
||||
}, [control, onAccept, setExternalEmbedPref])
|
||||
|
||||
const onShowPress = useCallback(() => {
|
||||
setExternalEmbedPref(source, 'show')
|
||||
onAccept()
|
||||
control.close()
|
||||
}, [control, onAccept, setExternalEmbedPref, source])
|
||||
|
||||
const onHidePress = useCallback(() => {
|
||||
setExternalEmbedPref(source, 'hide')
|
||||
control.close()
|
||||
}, [control, setExternalEmbedPref, source])
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`External Media`)}
|
||||
style={[gtMobile ? {width: 'auto', maxWidth: 400} : a.w_full]}>
|
||||
<View style={a.gap_sm}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>External Media</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.mt_sm, a.mb_2xl, a.gap_lg]}>
|
||||
<Text>
|
||||
<Trans>
|
||||
This content is hosted by {externalEmbedLabels[source]}. Do you
|
||||
want to enable external media?
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
<Trans>
|
||||
External media may allow websites to collect information about
|
||||
you and your device. No information is sent or requested until
|
||||
you press the "play" button.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={a.gap_md}>
|
||||
<Button
|
||||
style={gtMobile && a.flex_1}
|
||||
label={_(msg`Enable external media`)}
|
||||
onPress={onShowAllPress}
|
||||
onAccessibilityEscape={control.close}
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Enable external media</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
style={gtMobile && a.flex_1}
|
||||
label={_(msg`Enable this source only`)}
|
||||
onPress={onShowPress}
|
||||
onAccessibilityEscape={control.close}
|
||||
color="secondary"
|
||||
size="medium"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label={_(msg`No thanks`)}
|
||||
onAccessibilityEscape={control.close}
|
||||
onPress={onHidePress}
|
||||
color="secondary"
|
||||
size="medium"
|
||||
variant="ghost">
|
||||
<ButtonText>
|
||||
<Trans>No thanks</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export function HostingProvider({
|
||||
onSelect={onSelectServiceUrl}
|
||||
/>
|
||||
<Button
|
||||
testID="loginSelectServiceButton"
|
||||
label={toNiceDomain(serviceUrl)}
|
||||
accessibilityHint={_(msg`Press to change hosting provider`)}
|
||||
variant="solid"
|
||||
|
||||
@@ -80,8 +80,10 @@ export const HITSLOP_30 = createHitslop(30)
|
||||
export const BACK_HITSLOP = HITSLOP_30
|
||||
export const MAX_POST_LINES = 25
|
||||
|
||||
export const BSKY_APP_ACCOUNT_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur'
|
||||
|
||||
export const BSKY_FEED_OWNER_DIDS = [
|
||||
'did:plc:z72i7hdynmk6r22z27h6tvur',
|
||||
BSKY_APP_ACCOUNT_DID,
|
||||
'did:plc:vpkhqolt662uhesyj6nxm7ys',
|
||||
'did:plc:q6gjnaw2blty4crticxkmujt',
|
||||
]
|
||||
|
||||
@@ -44,6 +44,12 @@ export type LogEvents = {
|
||||
}
|
||||
'onboarding:moderation:nextPressed': {}
|
||||
'onboarding:finished:nextPressed': {}
|
||||
'home:feedDisplayed': {
|
||||
feedUrl: string
|
||||
feedType: string
|
||||
index: number
|
||||
reason: 'focus' | 'tabbar-click' | 'pager-swipe' | 'desktop-sidebar-click'
|
||||
}
|
||||
'feed:endReached': {
|
||||
feedUrl: string
|
||||
feedType: string
|
||||
|
||||
+865
-754
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+865
-754
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+2562
-1168
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+872
-762
File diff suppressed because it is too large
Load Diff
+1188
-1231
File diff suppressed because it is too large
Load Diff
+865
-754
File diff suppressed because it is too large
Load Diff
+422
-396
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2748
-1171
File diff suppressed because it is too large
Load Diff
+864
-753
File diff suppressed because it is too large
Load Diff
+1129
-1573
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,11 +34,6 @@ export const PRIMARY_FEEDS: FeedConfig[] = [
|
||||
uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot',
|
||||
gradient: tokens.gradients.midnight,
|
||||
},
|
||||
{
|
||||
default: IS_PROD, // these feeds are only available in prod
|
||||
uri: 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm',
|
||||
gradient: tokens.gradients.midnight,
|
||||
},
|
||||
]
|
||||
|
||||
const SECONDARY_FEEDS: FeedConfig[] = [
|
||||
@@ -130,16 +125,6 @@ export function StepAlgoFeeds() {
|
||||
<Trans>We recommend our "Discover" feed:</Trans>
|
||||
</Text>
|
||||
<FeedCard config={PRIMARY_FEEDS[0]} />
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.pt_4xl,
|
||||
a.pb_lg,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>We also think you'll like "For You" by Skygaze:</Trans>
|
||||
</Text>
|
||||
<FeedCard config={PRIMARY_FEEDS[1]} />
|
||||
</Toggle.Group>
|
||||
|
||||
<Toggle.Group
|
||||
|
||||
@@ -4,6 +4,7 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||
import {BSKY_APP_ACCOUNT_DID} from '#/lib/constants'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useSetSaveFeedsMutation} from '#/state/queries/preferences'
|
||||
@@ -55,7 +56,9 @@ export function StepFinished() {
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
bulkWriteFollows(suggestedAccountsStepResults.accountDids),
|
||||
bulkWriteFollows(
|
||||
suggestedAccountsStepResults.accountDids.concat(BSKY_APP_ACCOUNT_DID),
|
||||
),
|
||||
// these must be serial
|
||||
(async () => {
|
||||
await getAgent().setInterestsPref({tags: selectedInterests})
|
||||
|
||||
@@ -3,7 +3,6 @@ import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {EmbedPlayerSource} from '#/lib/strings/embed-player'
|
||||
import {GalleryModel} from '#/state/models/media/gallery'
|
||||
import {ImageModel} from '#/state/models/media/image'
|
||||
import {ThreadgateSetting} from '../queries/threadgate'
|
||||
@@ -125,12 +124,6 @@ export interface LinkWarningModal {
|
||||
share?: boolean
|
||||
}
|
||||
|
||||
export interface EmbedConsentModal {
|
||||
name: 'embed-consent'
|
||||
source: EmbedPlayerSource
|
||||
onAccept: () => void
|
||||
}
|
||||
|
||||
export interface InAppBrowserConsentModal {
|
||||
name: 'in-app-browser-consent'
|
||||
href: string
|
||||
@@ -169,7 +162,6 @@ export type Modal =
|
||||
|
||||
// Generic
|
||||
| LinkWarningModal
|
||||
| EmbedConsentModal
|
||||
| InAppBrowserConsentModal
|
||||
|
||||
const ModalContext = React.createContext<{
|
||||
|
||||
@@ -507,9 +507,7 @@ export const ComposePost = observer(function ComposePost({
|
||||
control={discardPromptControl}
|
||||
title={_(msg`Discard draft?`)}
|
||||
description={_(msg`Are you sure you'd like to discard this draft?`)}
|
||||
onConfirm={() => {
|
||||
discardPromptControl.close(onClose)
|
||||
}}
|
||||
onConfirm={onClose}
|
||||
confirmButtonCta={_(msg`Discard`)}
|
||||
confirmButtonColor="negative"
|
||||
/>
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {
|
||||
EmbedPlayerSource,
|
||||
embedPlayerSources,
|
||||
externalEmbedLabels,
|
||||
} from '#/lib/strings/embed-player'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSetExternalEmbedPref} from '#/state/preferences/external-embeds-prefs'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {colors, gradients, s} from 'lib/styles'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ScrollView} from './util'
|
||||
|
||||
export const snapPoints = [450]
|
||||
|
||||
export function Component({
|
||||
onAccept,
|
||||
source,
|
||||
}: {
|
||||
onAccept: () => void
|
||||
source: EmbedPlayerSource
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {closeModal} = useModalControls()
|
||||
const {_} = useLingui()
|
||||
const setExternalEmbedPref = useSetExternalEmbedPref()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
|
||||
const onShowAllPress = React.useCallback(() => {
|
||||
for (const key of embedPlayerSources) {
|
||||
setExternalEmbedPref(key, 'show')
|
||||
}
|
||||
onAccept()
|
||||
closeModal()
|
||||
}, [closeModal, onAccept, setExternalEmbedPref])
|
||||
|
||||
const onShowPress = React.useCallback(() => {
|
||||
setExternalEmbedPref(source, 'show')
|
||||
onAccept()
|
||||
closeModal()
|
||||
}, [closeModal, onAccept, setExternalEmbedPref, source])
|
||||
|
||||
const onHidePress = React.useCallback(() => {
|
||||
setExternalEmbedPref(source, 'hide')
|
||||
closeModal()
|
||||
}, [closeModal, setExternalEmbedPref, source])
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
testID="embedConsentModal"
|
||||
style={[
|
||||
s.flex1,
|
||||
pal.view,
|
||||
isMobile
|
||||
? {paddingHorizontal: 20, paddingTop: 10}
|
||||
: {paddingHorizontal: 30},
|
||||
]}>
|
||||
<Text style={[pal.text, styles.title]}>
|
||||
<Trans>External Media</Trans>
|
||||
</Text>
|
||||
|
||||
<Text style={pal.text}>
|
||||
<Trans>
|
||||
This content is hosted by {externalEmbedLabels[source]}. Do you want
|
||||
to enable external media?
|
||||
</Trans>
|
||||
</Text>
|
||||
<View style={[s.mt10]} />
|
||||
<Text style={pal.textLight}>
|
||||
<Trans>
|
||||
External media may allow websites to collect information about you and
|
||||
your device. No information is sent or requested until you press the
|
||||
"play" button.
|
||||
</Trans>
|
||||
</Text>
|
||||
<View style={[s.mt20]} />
|
||||
<TouchableOpacity
|
||||
testID="enableAllBtn"
|
||||
onPress={onShowAllPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(
|
||||
msg`Show embeds from ${externalEmbedLabels[source]}`,
|
||||
)}
|
||||
accessibilityHint=""
|
||||
onAccessibilityEscape={closeModal}>
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={[styles.btn]}>
|
||||
<Text style={[s.white, s.bold, s.f18]}>
|
||||
<Trans>Enable External Media</Trans>
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
<View style={[s.mt10]} />
|
||||
<TouchableOpacity
|
||||
testID="enableSourceBtn"
|
||||
onPress={onShowPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(
|
||||
msg`Never load embeds from ${externalEmbedLabels[source]}`,
|
||||
)}
|
||||
accessibilityHint=""
|
||||
onAccessibilityEscape={closeModal}>
|
||||
<View style={[styles.btn, pal.btn]}>
|
||||
<Text style={[pal.text, s.bold, s.f18]}>
|
||||
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={[s.mt10]} />
|
||||
<TouchableOpacity
|
||||
testID="disableSourceBtn"
|
||||
onPress={onHidePress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(
|
||||
msg`Never load embeds from ${externalEmbedLabels[source]}`,
|
||||
)}
|
||||
accessibilityHint=""
|
||||
onAccessibilityEscape={closeModal}>
|
||||
<View style={[styles.btn, pal.btn]}>
|
||||
<Text style={[pal.text, s.bold, s.f18]}>
|
||||
<Trans>No thanks</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 24,
|
||||
marginBottom: 12,
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
borderRadius: 32,
|
||||
padding: 14,
|
||||
backgroundColor: colors.gray1,
|
||||
},
|
||||
})
|
||||
@@ -15,7 +15,6 @@ import * as ChangePasswordModal from './ChangePassword'
|
||||
import * as CreateOrEditListModal from './CreateOrEditList'
|
||||
import * as DeleteAccountModal from './DeleteAccount'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as EmbedConsentModal from './EmbedConsent'
|
||||
import * as InAppBrowserConsentModal from './InAppBrowserConsent'
|
||||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||
@@ -116,9 +115,6 @@ export function ModalsContainer() {
|
||||
} else if (activeModal?.name === 'link-warning') {
|
||||
snapPoints = LinkWarningModal.snapPoints
|
||||
element = <LinkWarningModal.Component {...activeModal} />
|
||||
} else if (activeModal?.name === 'embed-consent') {
|
||||
snapPoints = EmbedConsentModal.snapPoints
|
||||
element = <EmbedConsentModal.Component {...activeModal} />
|
||||
} else if (activeModal?.name === 'in-app-browser-consent') {
|
||||
snapPoints = InAppBrowserConsentModal.snapPoints
|
||||
element = <InAppBrowserConsentModal.Component {...activeModal} />
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
import React from 'react'
|
||||
import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native'
|
||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
||||
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import type {Modal as ModalIface} from '#/state/modals'
|
||||
import {useModalControls, useModals} from '#/state/modals'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
|
||||
import {useModals, useModalControls} from '#/state/modals'
|
||||
import type {Modal as ModalIface} from '#/state/modals'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as AddAppPassword from './AddAppPasswords'
|
||||
import * as AltTextImageModal from './AltImage'
|
||||
import * as ChangeEmailModal from './ChangeEmail'
|
||||
import * as ChangeHandleModal from './ChangeHandle'
|
||||
import * as ChangePasswordModal from './ChangePassword'
|
||||
import * as CreateOrEditListModal from './CreateOrEditList'
|
||||
import * as UserAddRemoveLists from './UserAddRemoveLists'
|
||||
import * as ListAddUserModal from './ListAddRemoveUsers'
|
||||
import * as CropImageModal from './crop-image/CropImage.web'
|
||||
import * as DeleteAccountModal from './DeleteAccount'
|
||||
import * as EditImageModal from './EditImage'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
import * as LinkWarningModal from './LinkWarning'
|
||||
import * as ListAddUserModal from './ListAddRemoveUsers'
|
||||
import * as RepostModal from './Repost'
|
||||
import * as SelfLabelModal from './SelfLabel'
|
||||
import * as ThreadgateModal from './Threadgate'
|
||||
import * as CropImageModal from './crop-image/CropImage.web'
|
||||
import * as AltTextImageModal from './AltImage'
|
||||
import * as EditImageModal from './EditImage'
|
||||
import * as ChangeHandleModal from './ChangeHandle'
|
||||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as AddAppPassword from './AddAppPasswords'
|
||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
import * as UserAddRemoveLists from './UserAddRemoveLists'
|
||||
import * as VerifyEmailModal from './VerifyEmail'
|
||||
import * as ChangeEmailModal from './ChangeEmail'
|
||||
import * as ChangePasswordModal from './ChangePassword'
|
||||
import * as LinkWarningModal from './LinkWarning'
|
||||
import * as EmbedConsentModal from './EmbedConsent'
|
||||
|
||||
export function ModalsContainer() {
|
||||
const {isModalActive, activeModals} = useModals()
|
||||
@@ -112,8 +111,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||
element = <ChangePasswordModal.Component />
|
||||
} else if (modal.name === 'link-warning') {
|
||||
element = <LinkWarningModal.Component {...modal} />
|
||||
} else if (modal.name === 'embed-consent') {
|
||||
element = <EmbedConsentModal.Component {...modal} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import React, {forwardRef} from 'react'
|
||||
import {Animated, View} from 'react-native'
|
||||
import PagerView, {
|
||||
PagerViewOnPageSelectedEvent,
|
||||
PagerViewOnPageScrollEvent,
|
||||
PagerViewOnPageSelectedEvent,
|
||||
PageScrollStateChangedNativeEvent,
|
||||
} from 'react-native-pager-view'
|
||||
|
||||
import {LogEvents} from '#/lib/statsig/events'
|
||||
import {s} from 'lib/styles'
|
||||
|
||||
export type PageSelectedEvent = PagerViewOnPageSelectedEvent
|
||||
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
|
||||
|
||||
export interface PagerRef {
|
||||
setPage: (index: number) => void
|
||||
setPage: (
|
||||
index: number,
|
||||
reason: LogEvents['home:feedDisplayed']['reason'],
|
||||
) => void
|
||||
}
|
||||
|
||||
export interface RenderTabBarFnProps {
|
||||
@@ -25,7 +30,10 @@ interface Props {
|
||||
initialPage?: number
|
||||
renderTabBar: RenderTabBarFn
|
||||
onPageSelected?: (index: number) => void
|
||||
onPageSelecting?: (index: number) => void
|
||||
onPageSelecting?: (
|
||||
index: number,
|
||||
reason: LogEvents['home:feedDisplayed']['reason'],
|
||||
) => void
|
||||
onPageScrollStateChanged?: (
|
||||
scrollState: 'idle' | 'dragging' | 'settling',
|
||||
) => void
|
||||
@@ -51,7 +59,13 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
|
||||
const pagerView = React.useRef<PagerView>(null)
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => pagerView.current?.setPage(index),
|
||||
setPage: (
|
||||
index: number,
|
||||
reason: LogEvents['home:feedDisplayed']['reason'],
|
||||
) => {
|
||||
pagerView.current?.setPage(index)
|
||||
onPageSelecting?.(index, reason)
|
||||
},
|
||||
}))
|
||||
|
||||
const onPageSelectedInner = React.useCallback(
|
||||
@@ -79,14 +93,14 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
|
||||
// -prf
|
||||
if (scrollState.current === 'settling') {
|
||||
if (lastDirection.current === -1 && offset < lastOffset.current) {
|
||||
onPageSelecting?.(position)
|
||||
onPageSelecting?.(position, 'pager-swipe')
|
||||
setSelectedPage(position)
|
||||
lastDirection.current = 0
|
||||
} else if (
|
||||
lastDirection.current === 1 &&
|
||||
offset > lastOffset.current
|
||||
) {
|
||||
onPageSelecting?.(position + 1)
|
||||
onPageSelecting?.(position + 1, 'pager-swipe')
|
||||
setSelectedPage(position + 1)
|
||||
lastDirection.current = 0
|
||||
}
|
||||
@@ -113,7 +127,7 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
|
||||
const onTabBarSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
pagerView.current?.setPage(index)
|
||||
onPageSelecting?.(index)
|
||||
onPageSelecting?.(index, 'tabbar-click')
|
||||
},
|
||||
[pagerView, onPageSelecting],
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react'
|
||||
import {flushSync} from 'react-dom'
|
||||
import {View} from 'react-native'
|
||||
import {flushSync} from 'react-dom'
|
||||
|
||||
import {LogEvents} from '#/lib/statsig/events'
|
||||
import {s} from 'lib/styles'
|
||||
|
||||
export interface RenderTabBarFnProps {
|
||||
@@ -14,7 +16,10 @@ interface Props {
|
||||
initialPage?: number
|
||||
renderTabBar: RenderTabBarFn
|
||||
onPageSelected?: (index: number) => void
|
||||
onPageSelecting?: (index: number) => void
|
||||
onPageSelecting?: (
|
||||
index: number,
|
||||
reason: LogEvents['home:feedDisplayed']['reason'],
|
||||
) => void
|
||||
}
|
||||
export const Pager = React.forwardRef(function PagerImpl(
|
||||
{
|
||||
@@ -31,11 +36,16 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
const anchorRef = React.useRef(null)
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setPage: (index: number) => onTabBarSelect(index),
|
||||
setPage: (
|
||||
index: number,
|
||||
reason: LogEvents['home:feedDisplayed']['reason'],
|
||||
) => {
|
||||
onTabBarSelect(index, reason)
|
||||
},
|
||||
}))
|
||||
|
||||
const onTabBarSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
(index: number, reason: LogEvents['home:feedDisplayed']['reason']) => {
|
||||
const scrollY = window.scrollY
|
||||
// We want to determine if the tabbar is already "sticking" at the top (in which
|
||||
// case we should preserve and restore scroll), or if it is somewhere below in the
|
||||
@@ -54,7 +64,7 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
flushSync(() => {
|
||||
setSelectedPage(index)
|
||||
onPageSelected?.(index)
|
||||
onPageSelecting?.(index)
|
||||
onPageSelecting?.(index, reason)
|
||||
})
|
||||
if (isSticking) {
|
||||
const restoredScrollY = scrollYs.current[index]
|
||||
@@ -73,7 +83,7 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
{renderTabBar({
|
||||
selectedPage,
|
||||
tabBarAnchor: <View ref={anchorRef} />,
|
||||
onSelect: onTabBarSelect,
|
||||
onSelect: e => onTabBarSelect(e, 'tabbar-click'),
|
||||
})}
|
||||
{React.Children.map(children, (child, i) => (
|
||||
<View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {EmbedPlayerParams, getGifDims} from 'lib/strings/embed-player'
|
||||
import React from 'react'
|
||||
import {Image, ImageLoadEventData} from 'expo-image'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
GestureResponderEvent,
|
||||
@@ -9,13 +7,17 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {isIOS, isNative, isWeb} from '#/platform/detection'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {useExternalEmbedsPrefs} from 'state/preferences'
|
||||
import {useModalControls} from 'state/modals'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {Image, ImageLoadEventData} from 'expo-image'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {EmbedPlayerParams, getGifDims} from '#/lib/strings/embed-player'
|
||||
import {isIOS, isNative, isWeb} from '#/platform/detection'
|
||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||
|
||||
export function ExternalGifEmbed({
|
||||
link,
|
||||
@@ -25,8 +27,9 @@ export function ExternalGifEmbed({
|
||||
params: EmbedPlayerParams
|
||||
}) {
|
||||
const externalEmbedsPrefs = useExternalEmbedsPrefs()
|
||||
const {openModal} = useModalControls()
|
||||
|
||||
const {_} = useLingui()
|
||||
const consentDialogControl = useDialogControl()
|
||||
|
||||
const thumbHasLoaded = React.useRef(false)
|
||||
const viewWidth = React.useRef(0)
|
||||
@@ -57,11 +60,7 @@ export function ExternalGifEmbed({
|
||||
|
||||
// Show consent if this is the first load
|
||||
if (externalEmbedsPrefs?.[params.source] === undefined) {
|
||||
openModal({
|
||||
name: 'embed-consent',
|
||||
source: params.source,
|
||||
onAccept: load,
|
||||
})
|
||||
consentDialogControl.open()
|
||||
return
|
||||
}
|
||||
// If the player isn't active, we want to activate it and prefetch the gif
|
||||
@@ -84,7 +83,13 @@ export function ExternalGifEmbed({
|
||||
}
|
||||
})
|
||||
},
|
||||
[externalEmbedsPrefs, isPlayerActive, load, openModal, params.source],
|
||||
[
|
||||
consentDialogControl,
|
||||
externalEmbedsPrefs,
|
||||
isPlayerActive,
|
||||
load,
|
||||
params.source,
|
||||
],
|
||||
)
|
||||
|
||||
const onLoad = React.useCallback((e: ImageLoadEventData) => {
|
||||
@@ -98,47 +103,55 @@ export function ExternalGifEmbed({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[
|
||||
{height: imageDims.height},
|
||||
styles.topRadius,
|
||||
styles.gifContainer,
|
||||
]}
|
||||
onPress={onPlayPress}
|
||||
onLayout={onLayout}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint={_(msg`Plays the GIF`)}
|
||||
accessibilityLabel={_(msg`Play ${link.title}`)}>
|
||||
{(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
|
||||
<View style={[styles.layer, styles.overlayLayer]}>
|
||||
<View style={[styles.overlayContainer, styles.topRadius]}>
|
||||
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
|
||||
<FontAwesomeIcon icon="play" size={42} color="white" />
|
||||
) : (
|
||||
// Activity indicator while gif loads
|
||||
<ActivityIndicator size="large" color="white" />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
!isPrefetched || (isWeb && !isAnimating)
|
||||
? link.thumb
|
||||
: params.playerUri,
|
||||
}} // Web uses the thumb to control playback
|
||||
style={{flex: 1}}
|
||||
ref={imageRef}
|
||||
onLoad={onLoad}
|
||||
autoplay={isAnimating}
|
||||
contentFit="contain"
|
||||
accessibilityIgnoresInvertColors
|
||||
accessibilityLabel={link.title}
|
||||
accessibilityHint={link.title}
|
||||
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
|
||||
<>
|
||||
<EmbedConsentDialog
|
||||
control={consentDialogControl}
|
||||
source={params.source}
|
||||
onAccept={load}
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
style={[
|
||||
{height: imageDims.height},
|
||||
styles.topRadius,
|
||||
styles.gifContainer,
|
||||
]}
|
||||
onPress={onPlayPress}
|
||||
onLayout={onLayout}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint={_(msg`Plays the GIF`)}
|
||||
accessibilityLabel={_(msg`Play ${link.title}`)}>
|
||||
{(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
|
||||
<View style={[styles.layer, styles.overlayLayer]}>
|
||||
<View style={[styles.overlayContainer, styles.topRadius]}>
|
||||
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
|
||||
<FontAwesomeIcon icon="play" size={42} color="white" />
|
||||
) : (
|
||||
// Activity indicator while gif loads
|
||||
<ActivityIndicator size="large" color="white" />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
!isPrefetched || (isWeb && !isAnimating)
|
||||
? link.thumb
|
||||
: params.playerUri,
|
||||
}} // Web uses the thumb to control playback
|
||||
style={{flex: 1}}
|
||||
ref={imageRef}
|
||||
onLoad={onLoad}
|
||||
autoplay={isAnimating}
|
||||
contentFit="contain"
|
||||
accessibilityIgnoresInvertColors
|
||||
accessibilityLabel={link.title}
|
||||
accessibilityHint={link.title}
|
||||
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,20 +13,23 @@ import Animated, {
|
||||
useAnimatedRef,
|
||||
useFrameCallback,
|
||||
} from 'react-native-reanimated'
|
||||
import {Image} from 'expo-image'
|
||||
import {WebView} from 'react-native-webview'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {WebView} from 'react-native-webview'
|
||||
import {Image} from 'expo-image'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {EmbedPlayerParams, getPlayerAspect} from 'lib/strings/embed-player'
|
||||
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||
import {EventStopper} from '../EventStopper'
|
||||
import {isNative} from 'platform/detection'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {useExternalEmbedsPrefs} from 'state/preferences'
|
||||
import {useModalControls} from 'state/modals'
|
||||
|
||||
interface ShouldStartLoadRequest {
|
||||
url: string
|
||||
@@ -48,7 +51,7 @@ function PlaceholderOverlay({
|
||||
if (isPlayerActive && !isLoading) return null
|
||||
|
||||
return (
|
||||
<View style={[styles.layer, styles.overlayLayer]}>
|
||||
<View style={[a.absolute, a.inset_0, styles.overlayLayer]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Play Video`)}
|
||||
@@ -89,7 +92,7 @@ function Player({
|
||||
if (!isPlayerActive) return null
|
||||
|
||||
return (
|
||||
<EventStopper style={[styles.layer, styles.playerLayer]}>
|
||||
<EventStopper style={[a.absolute, a.inset_0, styles.playerLayer]}>
|
||||
<WebView
|
||||
javaScriptEnabled={true}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
@@ -119,7 +122,7 @@ export function ExternalPlayer({
|
||||
const insets = useSafeAreaInsets()
|
||||
const windowDims = useWindowDimensions()
|
||||
const externalEmbedsPrefs = useExternalEmbedsPrefs()
|
||||
const {openModal} = useModalControls()
|
||||
const consentDialogControl = useDialogControl()
|
||||
|
||||
const [isPlayerActive, setPlayerActive] = React.useState(false)
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
@@ -187,37 +190,47 @@ export function ExternalPlayer({
|
||||
event.preventDefault()
|
||||
|
||||
if (externalEmbedsPrefs?.[params.source] === undefined) {
|
||||
openModal({
|
||||
name: 'embed-consent',
|
||||
source: params.source,
|
||||
onAccept: () => {
|
||||
setPlayerActive(true)
|
||||
},
|
||||
})
|
||||
consentDialogControl.open()
|
||||
return
|
||||
}
|
||||
|
||||
setPlayerActive(true)
|
||||
},
|
||||
[externalEmbedsPrefs, openModal, params.source],
|
||||
[externalEmbedsPrefs, consentDialogControl, params.source],
|
||||
)
|
||||
|
||||
const onAcceptConsent = React.useCallback(() => {
|
||||
setPlayerActive(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Animated.View ref={viewRef} collapsable={false} style={[aspect]}>
|
||||
{link.thumb && (!isPlayerActive || isLoading) && (
|
||||
<Image
|
||||
style={[{flex: 1}, styles.topRadius]}
|
||||
source={{uri: link.thumb}}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
)}
|
||||
<PlaceholderOverlay
|
||||
isLoading={isLoading}
|
||||
isPlayerActive={isPlayerActive}
|
||||
onPress={onPlayPress}
|
||||
<>
|
||||
<EmbedConsentDialog
|
||||
control={consentDialogControl}
|
||||
source={params.source}
|
||||
onAccept={onAcceptConsent}
|
||||
/>
|
||||
<Player isPlayerActive={isPlayerActive} params={params} onLoad={onLoad} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View ref={viewRef} collapsable={false} style={[aspect]}>
|
||||
{link.thumb && (!isPlayerActive || isLoading) && (
|
||||
<Image
|
||||
style={[a.flex_1, styles.topRadius]}
|
||||
source={{uri: link.thumb}}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
)}
|
||||
<PlaceholderOverlay
|
||||
isLoading={isLoading}
|
||||
isPlayerActive={isPlayerActive}
|
||||
onPress={onPlayPress}
|
||||
/>
|
||||
<Player
|
||||
isPlayerActive={isPlayerActive}
|
||||
params={params}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,13 +239,6 @@ const styles = StyleSheet.create({
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
},
|
||||
layer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
overlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from 'react'
|
||||
import {ActivityIndicator, AppState, StyleSheet, View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {logEvent, LogEvents, useGate} from '#/lib/statsig/statsig'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {FeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
||||
@@ -79,7 +80,7 @@ function HomeScreenReady({
|
||||
// This is supposed to only happen on the web when you use the right nav.
|
||||
if (selectedIndex !== lastPagerReportedIndexRef.current) {
|
||||
lastPagerReportedIndexRef.current = selectedIndex
|
||||
pagerRef.current?.setPage(selectedIndex)
|
||||
pagerRef.current?.setPage(selectedIndex, 'desktop-sidebar-click')
|
||||
}
|
||||
}, [selectedIndex])
|
||||
|
||||
@@ -96,6 +97,17 @@ function HomeScreenReady({
|
||||
}, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]),
|
||||
)
|
||||
|
||||
useFocusEffect(
|
||||
useNonReactiveCallback(() => {
|
||||
logEvent('home:feedDisplayed', {
|
||||
index: selectedIndex,
|
||||
feedType: selectedFeed.split('|')[0],
|
||||
feedUrl: selectedFeed,
|
||||
reason: 'focus',
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const disableMinShellOnForegrounding = useGate(
|
||||
'disable_min_shell_on_foregrounding',
|
||||
)
|
||||
@@ -123,6 +135,19 @@ function HomeScreenReady({
|
||||
[setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
|
||||
)
|
||||
|
||||
const onPageSelecting = React.useCallback(
|
||||
(index: number, reason: LogEvents['home:feedDisplayed']['reason']) => {
|
||||
const feed = allFeeds[index]
|
||||
logEvent('home:feedDisplayed', {
|
||||
index,
|
||||
feedType: feed.split('|')[0],
|
||||
feedUrl: feed,
|
||||
reason,
|
||||
})
|
||||
},
|
||||
[allFeeds],
|
||||
)
|
||||
|
||||
const onPressSelected = React.useCallback(() => {
|
||||
emitSoftReset()
|
||||
}, [])
|
||||
@@ -175,6 +200,7 @@ function HomeScreenReady({
|
||||
ref={pagerRef}
|
||||
testID="homeScreen"
|
||||
initialPage={selectedIndex}
|
||||
onPageSelecting={onPageSelecting}
|
||||
onPageSelected={onPageSelected}
|
||||
onPageScrollStateChanged={onPageScrollStateChanged}
|
||||
renderTabBar={renderTabBar}>
|
||||
|
||||
@@ -6,12 +6,13 @@ import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {H3, P} from '#/components/Typography'
|
||||
import {H3, P, Text} from '#/components/Typography'
|
||||
|
||||
export function Dialogs() {
|
||||
const scrollable = Dialog.useDialogControl()
|
||||
const basic = Dialog.useDialogControl()
|
||||
const prompt = Prompt.usePromptControl()
|
||||
const testDialog = Dialog.useDialogControl()
|
||||
const {closeAllDialogs} = useDialogStateControlContext()
|
||||
|
||||
return (
|
||||
@@ -60,6 +61,15 @@ export function Dialogs() {
|
||||
<ButtonText>Open prompt</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={testDialog.open}
|
||||
label="one">
|
||||
<ButtonText>Open Tester</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Prompt.Outer control={prompt}>
|
||||
<Prompt.TitleText>This is a prompt</Prompt.TitleText>
|
||||
<Prompt.DescriptionText>
|
||||
@@ -122,6 +132,131 @@ export function Dialogs() {
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
|
||||
<Dialog.Outer control={testDialog}>
|
||||
<Dialog.Handle />
|
||||
|
||||
<Dialog.ScrollableInner
|
||||
accessibilityDescribedBy="dialog-description"
|
||||
accessibilityLabelledBy="dialog-title">
|
||||
<View style={[a.relative, a.gap_md, a.w_full]}>
|
||||
<Text>
|
||||
Watch the console logs to test each of these dialog edge cases.
|
||||
Functionality should be consistent across both native and web. If
|
||||
not then *sad face* something is wrong.
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
testDialog.close(() => {
|
||||
console.log('close callback')
|
||||
})
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>Normal Use (Should just log)</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
testDialog.close(() => {
|
||||
console.log('close callback')
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
testDialog.open()
|
||||
}, 100)
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>
|
||||
Calls `.open()` in 100ms (Should log when the animation switches
|
||||
to open)
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
setTimeout(() => {
|
||||
testDialog.open()
|
||||
}, 2e3)
|
||||
|
||||
testDialog.close(() => {
|
||||
console.log('close callback')
|
||||
})
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>
|
||||
Calls `.open()` in 2000ms (Should log after close animation and
|
||||
not log on open)
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
testDialog.close(() => {
|
||||
console.log('close callback')
|
||||
})
|
||||
setTimeout(() => {
|
||||
testDialog.close(() => {
|
||||
console.log('close callback after 100ms')
|
||||
})
|
||||
}, 100)
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>
|
||||
Calls `.close()` then again in 100ms (should log twice)
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
testDialog.close(() => {
|
||||
console.log('close callback')
|
||||
})
|
||||
testDialog.close(() => {
|
||||
console.log('close callback 2')
|
||||
})
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>
|
||||
Call `close()` twice immediately (should just log twice)
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
console.log('Step 1')
|
||||
testDialog.close(() => {
|
||||
console.log('Step 3')
|
||||
})
|
||||
console.log('Step 2')
|
||||
}}
|
||||
label="Close It">
|
||||
<ButtonText>
|
||||
Log before `close()`, after `close()` and in the `close()`
|
||||
callback. Should be an order of 1 2 3
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,28 +46,14 @@
|
||||
multiformats "^9.9.0"
|
||||
tlds "^1.234.0"
|
||||
|
||||
"@atproto/api@^0.9.5":
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.9.5.tgz#630e5d9520bba38d0cd348c8028ddbb73bd074f8"
|
||||
integrity sha512-4vlwTbiWSkCV0DkfNMawiH+26Fv7txPr4x0vwq6KPIBz28UHPK9UyPseLKxi6/Aok74aPr8ySJ4+nfcmwcp08Q==
|
||||
"@atproto/aws@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.0.tgz#17f3faf744824457cabd62f87be8bf08cacf8029"
|
||||
integrity sha512-F09SHiC9CX3ydfrvYZbkpfES48UGCQNnznNVgJ3QyKSN8ON+BoWmGCpAFtn3AWeEoU0w9h0hypNvUm5nORv+5g==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/xrpc" "^0.4.1"
|
||||
multiformats "^9.9.0"
|
||||
tlds "^1.234.0"
|
||||
typed-emitter "^2.1.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/aws@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.1.6.tgz#c6ecbfd92b325f3c5433688534d47f43358b415b"
|
||||
integrity sha512-ZWfETjv9ku/5gxs3SVVW1sbGsQMmdnwmjp3RCxXFhMgNkbNL2l7xb17nRQ1VibsFLBWpp0+MeFTZYlSEzAYfCw==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/repo" "^0.3.6"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/repo" "^0.4.0"
|
||||
"@aws-sdk/client-cloudfront" "^3.261.0"
|
||||
"@aws-sdk/client-kms" "^3.196.0"
|
||||
"@aws-sdk/client-s3" "^3.224.0"
|
||||
@@ -77,71 +63,58 @@
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/bsky@^0.0.28":
|
||||
version "0.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.28.tgz#d9516f682883ceba60f52e3944d93dbd81375a7e"
|
||||
integrity sha512-Sq5/UWyxtIf/7UCPIHTcvE1ZUgWm+kYMkQVYRkMnNK5nN1G3/nY8Sm21qcVO/jxItOyua64XFoRss1zJf8G+Bw==
|
||||
"@atproto/bsky@^0.0.44":
|
||||
version "0.0.44"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.44.tgz#990d6061d557cdf891d43656543ebb611f57bd82"
|
||||
integrity sha512-SVOnvdUlDf9sKI1Tto+IY1tVS4/9VRoTTiI08ezvK9sew9sQVUVurwYI5E3EtAbEi3ukBPZ9+Cuoh3Me65iyjQ==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.9.5"
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/identity" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/repo" "^0.3.6"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/xrpc-server" "^0.4.2"
|
||||
"@atproto/api" "^0.12.2"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/identity" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
"@atproto/repo" "^0.4.0"
|
||||
"@atproto/syntax" "^0.3.0"
|
||||
"@atproto/xrpc-server" "^0.5.1"
|
||||
"@bufbuild/protobuf" "^1.5.0"
|
||||
"@connectrpc/connect" "^1.1.4"
|
||||
"@connectrpc/connect-express" "^1.1.4"
|
||||
"@connectrpc/connect-node" "^1.1.4"
|
||||
"@did-plc/lib" "^0.0.1"
|
||||
"@isaacs/ttlcache" "^1.4.1"
|
||||
compression "^1.7.4"
|
||||
cors "^2.8.5"
|
||||
express "^4.17.2"
|
||||
express-async-errors "^3.1.1"
|
||||
form-data "^4.0.0"
|
||||
http-errors "^2.0.0"
|
||||
http-terminator "^3.2.0"
|
||||
ioredis "^5.3.2"
|
||||
jose "^5.0.1"
|
||||
kysely "^0.22.0"
|
||||
multiformats "^9.9.0"
|
||||
murmurhash "^2.0.1"
|
||||
p-queue "^6.6.2"
|
||||
pg "^8.10.0"
|
||||
pino "^8.15.0"
|
||||
pino-http "^8.2.1"
|
||||
sharp "^0.32.6"
|
||||
structured-headers "^1.0.1"
|
||||
typed-emitter "^2.1.0"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/bsync@^0.0.0":
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.0.tgz#b08160ee8aca7d9fd9d8dc34a4719227b518df9d"
|
||||
integrity sha512-gv0dOnKGPhB0xyqLJhu3U3osZAPXLnaZQTRzwZlC5tm/Yc+c8myv2E3nIF+3Ojekh/cbg9SC8qRae4pEL0WHYg==
|
||||
"@atproto/bsync@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.3.tgz#2b0b8ef3686cf177846a80088317f2e89d1bf88f"
|
||||
integrity sha512-tJRwNgXzfNV57lzgWPvjtb1OMlMJH9SpsMeYhIii16zcaFUWwsb474BicKpkGRT+iCvtYzBT6gWlZE2Ijnhf7w==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/syntax" "^0.3.0"
|
||||
"@bufbuild/protobuf" "^1.5.0"
|
||||
"@connectrpc/connect" "^1.1.4"
|
||||
"@connectrpc/connect-express" "^1.1.4"
|
||||
"@connectrpc/connect-node" "^1.1.4"
|
||||
http-terminator "^3.2.0"
|
||||
kysely "^0.22.0"
|
||||
pg "^8.10.0"
|
||||
pino "^8.15.0"
|
||||
pino-http "^8.2.1"
|
||||
typed-emitter "^2.1.0"
|
||||
|
||||
"@atproto/common-web@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.2.3.tgz#c44c1e177ae8309d5116347d49850209e8e478cc"
|
||||
integrity sha512-k9VKGYUqjsRlI3wS31XyCbeb2U7ddS4X/eFgzos2CE5rIbk/uQGyKH+0Jcn1JIwRkvI1BemyNuUVrS8Ok3wiuw==
|
||||
dependencies:
|
||||
graphemer "^1.4.0"
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/common-web@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb"
|
||||
@@ -172,18 +145,17 @@
|
||||
pino "^8.6.1"
|
||||
zod "^3.14.2"
|
||||
|
||||
"@atproto/common@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.3.3.tgz#bc0059929e528032a55aa32fd180c6e992959dd6"
|
||||
integrity sha512-ETYsHpQoytW3yJ1BoMDCZh3tdokV3HbZ2ThXq+EWbMxbGNsRDREgJK3JXJMHapf8PrnZZpE2VdWM9NHvlcmnQg==
|
||||
"@atproto/common@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.0.tgz#d77696c7eb545426df727837d9ee333b429fe7ef"
|
||||
integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@ipld/dag-cbor" "^7.0.3"
|
||||
cbor-x "^1.5.1"
|
||||
iso-datestring-validator "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
pino "^8.15.0"
|
||||
zod "3.21.4"
|
||||
|
||||
"@atproto/crypto@0.1.0":
|
||||
version "0.1.0"
|
||||
@@ -196,63 +168,49 @@
|
||||
one-webcrypto "^1.0.3"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/crypto@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.3.0.tgz#a79e05a85129810755f3456e9d419b49824407d7"
|
||||
integrity sha512-bhcxRTL4fgRY2YX/St0x4o0oDUp18QIPD7ek+7v8UKA0HpsCGQYbo8w9d9hUvwwty5X5p00cYF2tbggUWaPy7A==
|
||||
"@atproto/crypto@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.0.tgz#dcdd6bf5ba98261ae0ff3b96d7b8695c1ef788e6"
|
||||
integrity sha512-Kj/4VgJ7hzzXvE42L0rjzP6lM0tai+OfPnP1rxJ+UZg/YUDtuewL4uapnVoWXvlNceKgaLZH98g5n9gXBVTe5Q==
|
||||
dependencies:
|
||||
"@noble/curves" "^1.1.0"
|
||||
"@noble/hashes" "^1.3.1"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/dev-env@^0.2.28":
|
||||
version "0.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.2.28.tgz#c7ed2f34af28fab7c02da85155f0e2dcd8ce447a"
|
||||
integrity sha512-RD6USl0m7usHl1MyuCf3dZPuaJFnlEfo3Eni5mRZQNTPgS1ItZcrjEYsd8djmA2RmndCOC2FMBNhemALN6uRMw==
|
||||
"@atproto/dev-env@^0.3.4":
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.4.tgz#153b7be8268b2dcfc8d0ba4abc5fd60ad7a6e241"
|
||||
integrity sha512-ix33GBQ1hjesoieTQKx38VGxZWNKeXCnaMdalr0/SAFwaDPCqMOrvUTPCx8VWClgAd0qYMcBM98+0lBTohW1qQ==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.9.5"
|
||||
"@atproto/bsky" "^0.0.28"
|
||||
"@atproto/bsync" "^0.0.0"
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/identity" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/ozone" "^0.0.7"
|
||||
"@atproto/pds" "^0.3.16"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/xrpc-server" "^0.4.2"
|
||||
"@atproto/api" "^0.12.2"
|
||||
"@atproto/bsky" "^0.0.44"
|
||||
"@atproto/bsync" "^0.0.3"
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/identity" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
"@atproto/ozone" "^0.1.6"
|
||||
"@atproto/pds" "^0.4.13"
|
||||
"@atproto/syntax" "^0.3.0"
|
||||
"@atproto/xrpc-server" "^0.5.1"
|
||||
"@did-plc/lib" "^0.0.1"
|
||||
"@did-plc/server" "^0.0.1"
|
||||
axios "^0.27.2"
|
||||
better-sqlite3 "^7.6.2"
|
||||
chalk "^5.0.1"
|
||||
dotenv "^16.0.3"
|
||||
express "^4.18.2"
|
||||
get-port "^6.1.2"
|
||||
get-port "^5.1.1"
|
||||
multiformats "^9.9.0"
|
||||
sharp "^0.32.6"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/identity@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.3.2.tgz#8a0536bc19ccbc45a04df84c3f30d86f58f964ee"
|
||||
integrity sha512-xZSyB3gHn/avwdAIV+mECvjjvMYXxPSvSgVBUsETMvMY72H9d84utDD58y5aAU/9mL+founaNZmniKDaR633CQ==
|
||||
"@atproto/identity@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.0.tgz#f8a4d450a20606d221c4ec05b856c0ce55f0a3a7"
|
||||
integrity sha512-KKdVlqBgkFuTUx3KFiiQe0LuK9kopej1bhKm6SHRPEYbSEPFmRZQMY9TAjWJQrvQt8DpQzz6kVGjASFEjd3teQ==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
axios "^0.27.2"
|
||||
|
||||
"@atproto/lexicon@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.3.1.tgz#5d7275d041883a1c930404e3274a6fe7affc151f"
|
||||
integrity sha512-yLy6GUNP4pn0mGUIyUHvN0UeBza0S03AgjTXVR6KliC4ut2+7SjNMe4cI4G1M8/bJMaccC6ooQSm2kvwiOdr3A==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
iso-datestring-validator "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/lexicon@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.0.tgz#63e8829945d80c25524882caa8ed27b1151cc576"
|
||||
@@ -264,50 +222,50 @@
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/ozone@^0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.0.7.tgz#bfad82bc1d0900e79401a82f13581f707415505a"
|
||||
integrity sha512-XffjEBoNV0uXimtrnGdn3PTy0BAMGLrIExa8XuIDH5ZKOUmYlyepWA0VG0IhNIWWXOSdDltw0mFi9D5ViXsBow==
|
||||
"@atproto/ozone@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.6.tgz#b54c68360af19bfe6914d74b58759df0729461de"
|
||||
integrity sha512-uAXhXdO75vU/VVGGrsifZfaq6h7cMbEdS3bH8GCJfgwtxOlCU0elV2YM88GHBfVGJ0ghYKNki+Dhvpe8i+Fe1Q==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.9.5"
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/identity" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/xrpc-server" "^0.4.2"
|
||||
"@atproto/api" "^0.12.2"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/identity" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
"@atproto/syntax" "^0.3.0"
|
||||
"@atproto/xrpc" "^0.5.0"
|
||||
"@atproto/xrpc-server" "^0.5.1"
|
||||
"@did-plc/lib" "^0.0.1"
|
||||
axios "^1.6.7"
|
||||
compression "^1.7.4"
|
||||
cors "^2.8.5"
|
||||
express "^4.17.2"
|
||||
express-async-errors "^3.1.1"
|
||||
http-terminator "^3.2.0"
|
||||
kysely "^0.22.0"
|
||||
multiformats "^9.9.0"
|
||||
p-queue "^6.6.2"
|
||||
pg "^8.10.0"
|
||||
pino "^8.15.0"
|
||||
pino-http "^8.2.1"
|
||||
typed-emitter "^2.1.0"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/pds@^0.3.16":
|
||||
version "0.3.16"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.3.16.tgz#5eb740934c1dc4cafeb6c57c2b857777c7cdfc0d"
|
||||
integrity sha512-+DwRYn3FBiCd/Nu/F3+onoFdtL67zYcVDYIl1Aq6jOXNzMyzQW+Z4Y33OsKz5SMHJMuri4wef2iy63nCdSGxtw==
|
||||
"@atproto/pds@^0.4.13":
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.13.tgz#9235d2c748d142a06d78da143ff1ad7e150b2d97"
|
||||
integrity sha512-86fmaSFBP1HML0U85bsYkd06oO6XFFA/+VpRMeABy7cUShvvlkVq8anxp301Qaf89t+AM/tvjICqQ2syW8bgfA==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.9.5"
|
||||
"@atproto/aws" "^0.1.6"
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/identity" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/repo" "^0.3.6"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/xrpc" "^0.4.1"
|
||||
"@atproto/xrpc-server" "^0.4.2"
|
||||
"@atproto/api" "^0.12.2"
|
||||
"@atproto/aws" "^0.2.0"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/identity" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
"@atproto/repo" "^0.4.0"
|
||||
"@atproto/syntax" "^0.3.0"
|
||||
"@atproto/xrpc" "^0.5.0"
|
||||
"@atproto/xrpc-server" "^0.5.1"
|
||||
"@did-plc/lib" "^0.0.4"
|
||||
better-sqlite3 "^7.6.2"
|
||||
better-sqlite3 "^9.4.0"
|
||||
bytes "^3.1.2"
|
||||
compression "^1.7.4"
|
||||
cors "^2.8.5"
|
||||
@@ -315,9 +273,8 @@
|
||||
express "^4.17.2"
|
||||
express-async-errors "^3.1.1"
|
||||
file-type "^16.5.4"
|
||||
form-data "^4.0.0"
|
||||
glob "^10.3.10"
|
||||
handlebars "^4.7.7"
|
||||
http-errors "^2.0.0"
|
||||
http-terminator "^3.2.0"
|
||||
ioredis "^5.3.2"
|
||||
jose "^5.0.1"
|
||||
@@ -327,7 +284,6 @@
|
||||
nodemailer "^6.8.0"
|
||||
nodemailer-html-to-text "^3.2.0"
|
||||
p-queue "^6.6.2"
|
||||
pg "^8.10.0"
|
||||
pino "^8.15.0"
|
||||
pino-http "^8.2.1"
|
||||
sharp "^0.32.6"
|
||||
@@ -335,43 +291,34 @@
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/repo@^0.3.6":
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.3.6.tgz#1732a5fdc71899be819b32b9c63c03815af4a6ce"
|
||||
integrity sha512-coCYHl/0V3ucyJ2Rgx/l0MK37ui3doph7AsDn8UFxrDeZrecOgkKu66/Zo3PtdmaAQfUPDWdx4cifKeX7IRD5Q==
|
||||
"@atproto/repo@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.4.0.tgz#e5d3195a8e4233c9bf060737b18ddee905af2d9a"
|
||||
integrity sha512-LB0DF/D8r8hB+qiGB0sWZuq7TSJYbWel+t572aCrLeCOmbRgnLkGPLUTOOUvLFYv8xz1BPZTbI8hy/vcUV79VA==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/identity" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/syntax" "^0.1.5"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
"@ipld/car" "^3.2.3"
|
||||
"@ipld/dag-cbor" "^7.0.0"
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/syntax@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.1.5.tgz#85b6488a33da3b864e8ac22a61b5586b271206ee"
|
||||
integrity sha512-pbY5lOnThoAbsmrdbN9LC/dNmckfqODJiX9zjW2t3BIHYFeGBc6w9bK3Vre8A0Hg8yWkQpv6gaBLu+ykgi2DJQ==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.2.3"
|
||||
|
||||
"@atproto/syntax@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.0.tgz#fafa2dbea9add37253005cb663e7373e05e618b3"
|
||||
integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
|
||||
|
||||
"@atproto/xrpc-server@^0.4.2":
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.4.2.tgz#23efd89086b85933f1b0cc00c86e895adcaac315"
|
||||
integrity sha512-/m8rmFQFqFJ7WaVskPx27DLPeQfRCeEBMCdNxtyJZXElQZJMgcX5382SxAqsI3fVaW3EVwcQp0VuTNFOKFgHVg==
|
||||
"@atproto/xrpc-server@^0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.5.1.tgz#f63c86ba60bd5b9c5a641ea57191ff83d9db41fd"
|
||||
integrity sha512-SXU6dscVe5iYxPeV79QIFs/yEEu7LLOzyHGoHG1kSNO6DjwxXTdcWOc8GSYGV6H+7VycOoPZPkyD9q4teJlj/w==
|
||||
dependencies:
|
||||
"@atproto/common" "^0.3.3"
|
||||
"@atproto/crypto" "^0.3.0"
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
"@atproto/common" "^0.4.0"
|
||||
"@atproto/crypto" "^0.4.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
cbor-x "^1.5.1"
|
||||
express "^4.17.2"
|
||||
http-errors "^2.0.0"
|
||||
@@ -381,14 +328,6 @@
|
||||
ws "^8.12.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/xrpc@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.4.1.tgz#2fb7e81a159b019339bbcdcf4e7ce8dc4e83bef0"
|
||||
integrity sha512-EMRGiu6oDvFL03Hk2rG/WCL3QK0GjZs9psH80JVf8z2nfdsGON6yn0hw3jvRB26CBXqi58U8Uicyq8Ej5pVTAA==
|
||||
dependencies:
|
||||
"@atproto/lexicon" "^0.3.1"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/xrpc@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032"
|
||||
@@ -3727,6 +3666,18 @@
|
||||
cborg "^1.6.0"
|
||||
multiformats "^9.5.4"
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
|
||||
integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
|
||||
dependencies:
|
||||
string-width "^5.1.2"
|
||||
string-width-cjs "npm:string-width@^4.2.0"
|
||||
strip-ansi "^7.0.1"
|
||||
strip-ansi-cjs "npm:strip-ansi@^6.0.1"
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@isaacs/ttlcache@^1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2"
|
||||
@@ -4489,6 +4440,11 @@
|
||||
mkdirp "^1.0.4"
|
||||
rimraf "^3.0.2"
|
||||
|
||||
"@pkgjs/parseargs@^0.11.0":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@^0.5.11", "@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
|
||||
version "0.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#7c2268cedaa0644d677e8c4f377bc8fb304f714a"
|
||||
@@ -8772,6 +8728,15 @@ axios@^1.3.4:
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axios@^1.6.7:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
|
||||
integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axobject-query@^3.1.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
||||
@@ -9110,13 +9075,13 @@ better-opn@~3.0.2:
|
||||
dependencies:
|
||||
open "^8.0.4"
|
||||
|
||||
better-sqlite3@^7.6.2:
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-7.6.2.tgz#47cd8cad5b9573cace535f950ac321166bc31384"
|
||||
integrity sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==
|
||||
better-sqlite3@^9.4.0:
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-9.4.5.tgz#1d3422443a9924637cb06cc3ccc941b2ae932c65"
|
||||
integrity sha512-uFVyoyZR9BNcjSca+cp3MWCv6upAv+tbMC4SWM51NIMhoQOm4tjIkyxFO/ZsYdGAF61WJBgdzyJcz4OokJi0gQ==
|
||||
dependencies:
|
||||
bindings "^1.5.0"
|
||||
prebuild-install "^7.1.0"
|
||||
prebuild-install "^7.1.1"
|
||||
|
||||
bfj@^7.0.2:
|
||||
version "7.0.2"
|
||||
@@ -9598,7 +9563,7 @@ cborg@^1.6.0:
|
||||
resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1"
|
||||
integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==
|
||||
|
||||
chalk@5.3.0, chalk@^5.0.1:
|
||||
chalk@5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
|
||||
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
|
||||
@@ -10267,7 +10232,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
@@ -12690,6 +12655,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0:
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
follow-redirects@^1.15.6:
|
||||
version "1.15.6"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||
|
||||
fontfaceobserver@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8"
|
||||
@@ -12702,6 +12672,14 @@ for-each@^0.3.3:
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
|
||||
integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
fork-ts-checker-webpack-plugin@^6.5.0:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3"
|
||||
@@ -12908,10 +12886,10 @@ get-port@^3.2.0:
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
|
||||
integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==
|
||||
|
||||
get-port@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-6.1.2.tgz#c1228abb67ba0e17fb346da33b15187833b9c08a"
|
||||
integrity sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==
|
||||
get-port@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
|
||||
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
|
||||
|
||||
get-stream@^4.0.0:
|
||||
version "4.1.0"
|
||||
@@ -12986,6 +12964,17 @@ glob@7.1.6:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^10.3.10:
|
||||
version "10.3.12"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b"
|
||||
integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==
|
||||
dependencies:
|
||||
foreground-child "^3.1.0"
|
||||
jackspeak "^2.3.6"
|
||||
minimatch "^9.0.1"
|
||||
minipass "^7.0.4"
|
||||
path-scurry "^1.10.2"
|
||||
|
||||
glob@^6.0.1:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
|
||||
@@ -14177,6 +14166,15 @@ iterator.prototype@^1.1.0:
|
||||
has-tostringtag "^1.0.0"
|
||||
reflect.getprototypeof "^1.0.3"
|
||||
|
||||
jackspeak@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
|
||||
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
|
||||
dependencies:
|
||||
"@isaacs/cliui" "^8.0.2"
|
||||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.8.7"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
|
||||
@@ -15915,6 +15913,11 @@ lower-case@^2.0.2:
|
||||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
lru-cache@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
|
||||
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
|
||||
|
||||
lru-cache@^4.0.1:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
@@ -16439,6 +16442,13 @@ minimatch@^5.0.1:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^9.0.1:
|
||||
version "9.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
|
||||
integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
@@ -16477,6 +16487,11 @@ minipass@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
||||
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
||||
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
|
||||
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
|
||||
|
||||
minizlib@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
@@ -16573,11 +16588,6 @@ multipipe@^4.0.0:
|
||||
duplexer2 "^0.1.2"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
murmurhash@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/murmurhash/-/murmurhash-2.0.1.tgz#4097720e08cf978872194ad84ea5be2dec9b610f"
|
||||
integrity sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==
|
||||
|
||||
mute-stream@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
@@ -17387,6 +17397,14 @@ path-parse@^1.0.5, path-parse@^1.0.7:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-scurry@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7"
|
||||
integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==
|
||||
dependencies:
|
||||
lru-cache "^10.2.0"
|
||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
@@ -20120,6 +20138,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||
|
||||
simple-concat@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||
@@ -20518,7 +20541,7 @@ string-natural-compare@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -20527,7 +20550,7 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.0, string-width@^5.0.1:
|
||||
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
@@ -20627,6 +20650,13 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
@@ -20634,13 +20664,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@@ -20713,6 +20736,11 @@ structured-headers@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1"
|
||||
integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==
|
||||
|
||||
structured-headers@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-1.0.1.tgz#1821e434e0fe45bdd78f07c779b16519ab520415"
|
||||
integrity sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA==
|
||||
|
||||
style-loader@^3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff"
|
||||
@@ -22330,19 +22358,19 @@ workbox-window@6.6.1:
|
||||
"@types/trusted-types" "^2.0.2"
|
||||
workbox-core "6.6.1"
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
|
||||
Reference in New Issue
Block a user