diff --git a/.eslintrc.js b/.eslintrc.js index a999fd24b0..29136d5dd0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,7 @@ module.exports = { }, }, ], + 'bsky-internal/use-typed-gates': 'error', 'simple-import-sort/imports': [ 'warn', { diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml new file mode 100644 index 0000000000..f7f24af9f7 --- /dev/null +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -0,0 +1,57 @@ +name: build-and-push-embedr-aws +on: + push: + branches: + - main + - bnewbold/embedr + - bnewbold/embedr-rebase + +env: + REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} + USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} + PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }} + IMAGE_NAME: embed + +jobs: + embedr-container-aws: + if: github.repository == 'bluesky-social/social-app' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v1 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.USERNAME}} + password: ${{ env.PASSWORD }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,enable=true,priority=100,prefix=,suffix=,format=long + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + file: ./Dockerfile.embedr + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index dec4e0e7e8..9a26b82c21 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -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 }} diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 65a633c8ad..2e07335eef 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -18,14 +18,13 @@ on: description: Runtime version (in x.x.x format) that this update is for required: true -concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} - cancel-in-progress: true - jobs: bundleDeploy: name: Bundle and Deploy EAS Update runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-deploy + cancel-in-progress: true outputs: fingerprint-is-different: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different }} @@ -166,6 +165,9 @@ jobs: buildIfNecessaryIOS: name: Build and Submit iOS runs-on: macos-14 + concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-ios + cancel-in-progress: false needs: [bundleDeploy] # Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be # available here @@ -231,6 +233,9 @@ jobs: buildIfNecessaryAndroid: name: Build and Submit Android runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-android + cancel-in-progress: false needs: [ bundleDeploy ] # Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be # available here diff --git a/Dockerfile b/Dockerfile index 3ad05b6ec6..568cbf7b41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,3 +72,5 @@ CMD ["/usr/bin/bskyweb"] LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app LABEL org.opencontainers.image.description="bsky.app Web App" LABEL org.opencontainers.image.licenses=MIT + +# NOOP diff --git a/Dockerfile.embedr b/Dockerfile.embedr new file mode 100644 index 0000000000..c70251658b --- /dev/null +++ b/Dockerfile.embedr @@ -0,0 +1,78 @@ +FROM golang:1.21-bullseye AS build-env + +WORKDIR /usr/src/social-app + +ENV DEBIAN_FRONTEND=noninteractive + +# Node +ENV NODE_VERSION=18 +ENV NVM_DIR=/usr/share/nvm + +# Go +ENV GODEBUG="netdns=go" +ENV GOOS="linux" +ENV GOARCH="amd64" +ENV CGO_ENABLED=1 +ENV GOEXPERIMENT="loopvar" + +COPY . . + +# +# Generate the JavaScript webpack. NOTE: this will change +# +RUN mkdir --parents $NVM_DIR && \ + wget \ + --output-document=/tmp/nvm-install.sh \ + https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh && \ + bash /tmp/nvm-install.sh + +RUN \. "$NVM_DIR/nvm.sh" && \ + nvm install $NODE_VERSION && \ + nvm use $NODE_VERSION && \ + npm install --global yarn && \ + yarn && \ + cd bskyembed && yarn install --frozen-lockfile && cd .. && \ + yarn intl:build && \ + yarn build-embed + +# DEBUG +RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bskyembed/dist + +# hack around issue with empty directory and go:embed +RUN touch bskyweb/static/js/empty.txt + +# +# Generate the embedr Go binary. +# +RUN cd bskyweb/ && \ + go mod download && \ + go mod verify + +RUN cd bskyweb/ && \ + go build \ + -v \ + -trimpath \ + -tags timetzdata \ + -o /embedr \ + ./cmd/embedr + +FROM debian:bullseye-slim + +ENV GODEBUG=netdns=go +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install --yes \ + dumb-init \ + ca-certificates + +ENTRYPOINT ["dumb-init", "--"] + +WORKDIR /embedr +COPY --from=build-env /embedr /usr/bin/embedr + +CMD ["/usr/bin/embedr"] + +LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app +LABEL org.opencontainers.image.description="embed.bsky.app Web App" +LABEL org.opencontainers.image.licenses=MIT diff --git a/Makefile b/Makefile index c90abb783e..a40d37610e 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,11 @@ build-web: ## Compile web bundle, copy to bskyweb directory yarn intl:build yarn build-web +.PHONY: build-web-embed +build-web-embed: ## Compile web embed bundle, copy to bskyweb/embedr* directories + yarn intl:build + yarn build-embed + .PHONY: test test: ## Run all tests NODE_ENV=test EXPO_PUBLIC_ENV=test yarn test @@ -28,6 +33,7 @@ lint: ## Run style checks and verify syntax .PHONY: deps deps: ## Installs dependent libs using 'yarn install' yarn install --frozen-lockfile + cd bskyembed && yarn install --frozen-lockfile .PHONY: nvm-setup nvm-setup: ## Use NVM to install and activate node+yarn diff --git a/__e2e__/mock-server.ts b/__e2e__/mock-server.ts index 1bf240ccbd..b5f13a87fd 100644 --- a/__e2e__/mock-server.ts +++ b/__e2e__/mock-server.ts @@ -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) { diff --git a/__e2e__/tests/composer.test.ts b/__e2e__/tests/composer.test.ts index 34f8ae39ec..06781410f6 100644 --- a/__e2e__/tests/composer.test.ts +++ b/__e2e__/tests/composer.test.ts @@ -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('Composer', () => { beforeAll(async () => { @@ -41,7 +42,6 @@ describe('Composer', () => { await element(by.id('composerTextInput')).typeText( 'Post with a https://example.com link card', ) - await element(by.id('addLinkCardBtn')).tap() await element(by.id('composerPublishBtn')).tap() await expect(element(by.id('composeFAB'))).toBeVisible() }) @@ -72,7 +72,6 @@ describe('Composer', () => { await element(by.id('composerTextInput')).typeText( 'Reply with a https://example.com link card', ) - await element(by.id('addLinkCardBtn')).tap() await element(by.id('composerPublishBtn')).tap() await expect(element(by.id('composeFAB'))).toBeVisible() }) @@ -104,7 +103,6 @@ describe('Composer', () => { await element(by.id('composerTextInput')).typeText( 'QP with a https://example.com link card', ) - await element(by.id('addLinkCardBtn')).tap() await element(by.id('composerPublishBtn')).tap() await expect(element(by.id('composeFAB'))).toBeVisible() }) diff --git a/__e2e__/tests/home-screen.test.ts b/__e2e__/tests/home-screen.test.ts index a83a34edc6..b594c46978 100644 --- a/__e2e__/tests/home-screen.test.ts +++ b/__e2e__/tests/home-screen.test.ts @@ -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) diff --git a/__e2e__/tests/invite-codes.test.ts b/__e2e__/tests/invite-codes.test.skip.ts similarity index 94% rename from __e2e__/tests/invite-codes.test.ts rename to __e2e__/tests/invite-codes.test.skip.ts index 7eb8d9a3e1..9f00f05255 100644 --- a/__e2e__/tests/invite-codes.test.ts +++ b/__e2e__/tests/invite-codes.test.skip.ts @@ -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 diff --git a/__e2e__/tests/invites-and-text-verification.test.skip.ts b/__e2e__/tests/invites-and-text-verification.test.skip.ts deleted file mode 100644 index 863b31107b..0000000000 --- a/__e2e__/tests/invites-and-text-verification.test.skip.ts +++ /dev/null @@ -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() - }) -}) diff --git a/__e2e__/tests/profile-screen.test.ts b/__e2e__/tests/profile-screen.test.ts index 13d0fa8efb..7c3207ec83 100644 --- a/__e2e__/tests/profile-screen.test.ts +++ b/__e2e__/tests/profile-screen.test.ts @@ -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() + // }) }) diff --git a/__e2e__/tests/text-verification.test.skip.ts b/__e2e__/tests/text-verification.test.skip.ts deleted file mode 100644 index bd19e66b27..0000000000 --- a/__e2e__/tests/text-verification.test.skip.ts +++ /dev/null @@ -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') - }) -}) diff --git a/__e2e__/tests/thread-screen.test.ts b/__e2e__/tests/thread-screen.test.ts index 646c828fd3..b99da11a67 100644 --- a/__e2e__/tests/thread-screen.test.ts +++ b/__e2e__/tests/thread-screen.test.ts @@ -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() + // }) }) diff --git a/__e2e__/util.ts b/__e2e__/util.ts index a7869a2e1d..70fbdb6010 100644 --- a/__e2e__/util.ts +++ b/__e2e__/util.ts @@ -1,5 +1,5 @@ -import {resolveConfig} from 'detox/internals' import {execSync} from 'child_process' +import {resolveConfig} from 'detox/internals' import http from 'http' const platform = device.getPlatform() @@ -52,7 +52,7 @@ export async function login( if (await isVisible('chooseAccountForm')) { await element(by.id('chooseNewAccountBtn')).tap() } - await element(by.id('loginSelectServiceButton')).tap() + await element(by.id('selectServiceButton')).tap() if (takeScreenshots) { await device.takeScreenshot('2- opened service selector') } diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index f003e5acc0..2f603a521d 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -1,17 +1,18 @@ import {RichText} from '@atproto/api' + +import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' +import {cleanError} from '../../src/lib/strings/errors' +import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' +import {enforceLen, pluralize} from '../../src/lib/strings/helpers' +import {detectLinkables} from '../../src/lib/strings/rich-text-detection' +import {shortenLinks} from '../../src/lib/strings/rich-text-manip' +import {ago} from '../../src/lib/strings/time' import { makeRecordUri, toNiceDomain, - toShortUrl, toShareUrl, + toShortUrl, } from '../../src/lib/strings/url-helpers' -import {pluralize, enforceLen} from '../../src/lib/strings/helpers' -import {ago} from '../../src/lib/strings/time' -import {detectLinkables} from '../../src/lib/strings/rich-text-detection' -import {shortenLinks} from '../../src/lib/strings/rich-text-manip' -import {makeValidHandle, createFullHandle} from '../../src/lib/strings/handles' -import {cleanError} from '../../src/lib/strings/errors' -import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' describe('detectLinkables', () => { const inputs = [ @@ -395,6 +396,7 @@ describe('parseEmbedPlayerFromUrl', () => { 'https://youtube.com/watch?v=videoId&feature=share', 'https://youtube.com/shorts/videoId', 'https://m.youtube.com/watch?v=videoId', + 'https://music.youtube.com/watch?v=videoId', 'https://youtube.com/shorts/', 'https://youtube.com/', @@ -434,6 +436,8 @@ describe('parseEmbedPlayerFromUrl', () => { 'https://giphy.com/gif/some-random-gif-name-gifId', 'https://giphy.com/gifs/', + 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100', + 'https://media.giphy.com/media/gifId/giphy.webp', 'https://media0.giphy.com/media/gifId/giphy.webp', 'https://media1.giphy.com/media/gifId/giphy.gif', @@ -456,6 +460,12 @@ describe('parseEmbedPlayerFromUrl', () => { 'https://tenor.com/view', 'https://tenor.com/view/gifId.gif', 'https://tenor.com/intl/view/gifId.gif', + + 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100', + 'https://media.tenor.com/someID_AAAAC/someName.gif', + 'https://media.tenor.com/someID/someName.gif', + 'https://media.tenor.com/someID', + 'https://media.tenor.com', ] const outputs = [ @@ -495,6 +505,11 @@ describe('parseEmbedPlayerFromUrl', () => { source: 'youtube', playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', }, + { + type: 'youtube_video', + source: 'youtube', + playerUri: 'https://bsky.app/iframe/youtube.html?videoId=videoId&start=0', + }, undefined, undefined, @@ -621,18 +636,17 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, undefined, undefined, - { type: 'giphy_gif', source: 'giphy', isGif: true, hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + metaUri: 'https://giphy.com/gifs/39248209509382934029', + playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp', }, { type: 'giphy_gif', @@ -640,7 +654,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -648,7 +662,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -656,7 +670,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -664,7 +678,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -672,7 +686,15 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, undefined, undefined, @@ -684,7 +706,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { @@ -693,7 +715,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -701,7 +723,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -709,7 +731,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -717,32 +739,30 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, + undefined, + undefined, + undefined, + undefined, + undefined, + { type: 'tenor_gif', source: 'tenor', isGif: true, hideDetails: true, - playerUri: 'https://tenor.com/view/gifId.gif', + playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif', + dimensions: { + width: 100, + height: 100, + }, }, undefined, undefined, - { - type: 'tenor_gif', - source: 'tenor', - isGif: true, - hideDetails: true, - playerUri: 'https://tenor.com/view/gifId.gif', - }, - { - type: 'tenor_gif', - source: 'tenor', - isGif: true, - hideDetails: true, - playerUri: 'https://tenor.com/intl/view/gifId.gif', - }, + undefined, + undefined, ] it('correctly grabs the correct id from uri', () => { diff --git a/__tests__/lib/strings/url-helpers.test.ts b/__tests__/lib/strings/url-helpers.test.ts index fb4b8f7555..0b1b750281 100644 --- a/__tests__/lib/strings/url-helpers.test.ts +++ b/__tests__/lib/strings/url-helpers.test.ts @@ -1,10 +1,10 @@ -import {it, describe, expect} from '@jest/globals' +import {describe, expect, it} from '@jest/globals' import { - linkRequiresWarning, isPossiblyAUrl, - splitApexDomain, isTrustedUrl, + linkRequiresWarning, + splitApexDomain, } from '../../../src/lib/strings/url-helpers' describe('linkRequiresWarning', () => { @@ -170,6 +170,7 @@ describe('isTrustedUrl', () => { ['https://google.com', false], ['https://docs.google.com', false], ['https://google.com/#', false], + ['https://blueskywebxzendesk.com', false], ] it.each(cases)('given input uri %p, returns %p', (str, expected) => { diff --git a/app.config.js b/app.config.js index 9036d5e331..6275c6d2f4 100644 --- a/app.config.js +++ b/app.config.js @@ -35,15 +35,20 @@ module.exports = function (config) { */ const PLATFORM = process.env.EAS_BUILD_PLATFORM - const DIST_BUILD_NUMBER = - PLATFORM === 'android' - ? process.env.BSKY_ANDROID_VERSION_CODE - : process.env.BSKY_IOS_BUILD_NUMBER - const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development' const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight' + const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production' - const UPDATES_CHANNEL = IS_TESTFLIGHT ? 'testflight' : 'production' + const UPDATES_CHANNEL = IS_TESTFLIGHT + ? 'testflight' + : IS_PRODUCTION + ? 'production' + : undefined + const UPDATES_ENABLED = !!UPDATES_CHANNEL + + const SENTRY_DIST = `${PLATFORM}.${VERSION}.${IS_TESTFLIGHT ? 'tf' : ''}${ + IS_DEV ? 'dev' : '' + }` return { expo: { @@ -89,6 +94,11 @@ module.exports = function (config) { barStyle: 'light-content', backgroundColor: '#00000000', }, + // Dark nav bar in light mode is better than light nav bar in dark mode + androidNavigationBar: { + barStyle: 'light-content', + backgroundColor: DARK_SPLASH_CONFIG_ANDROID.backgroundColor, + }, android: { icon: './assets/icon.png', adaptiveIcon: { @@ -126,14 +136,12 @@ module.exports = function (config) { }, updates: { url: 'https://updates.bsky.app/manifest', - // TODO Eventually we want to enable this for all environments, but for now it will only be used for - // TestFlight builds - enabled: IS_TESTFLIGHT, + enabled: UPDATES_ENABLED, fallbackToCacheTimeout: 30000, - codeSigningCertificate: IS_TESTFLIGHT + codeSigningCertificate: UPDATES_ENABLED ? './code-signing/certificate.pem' : undefined, - codeSigningMetadata: IS_TESTFLIGHT + codeSigningMetadata: UPDATES_ENABLED ? { keyid: 'main', alg: 'rsa-v1_5-sha256', @@ -151,7 +159,7 @@ module.exports = function (config) { { ios: { deploymentTarget: '13.4', - newArchEnabled: false, + newArchEnabled: true, }, android: { compileSdkVersion: 34, @@ -208,7 +216,7 @@ module.exports = function (config) { organization: 'blueskyweb', project: 'react-native', release: VERSION, - dist: `${PLATFORM}.${VERSION}.${DIST_BUILD_NUMBER}`, + dist: SENTRY_DIST, }, }, ], diff --git a/assets/icons/alien_stroke2_corner0_rounded.svg b/assets/icons/alien_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..595308c97b --- /dev/null +++ b/assets/icons/alien_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/apple_stroke2_corner0_rounded.svg b/assets/icons/apple_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..3c7f051a3c --- /dev/null +++ b/assets/icons/apple_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/arrowLeft_stroke2_corner0_rounded.svg b/assets/icons/arrowLeft_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..96b5c16f50 --- /dev/null +++ b/assets/icons/arrowLeft_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/atom_stroke2_corner0_rounded.svg b/assets/icons/atom_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..723fdeab6d --- /dev/null +++ b/assets/icons/atom_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bubble_filled_stroke2_corner2_rounded.svg b/assets/icons/bubble_filled_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..3dbc8ca61d --- /dev/null +++ b/assets/icons/bubble_filled_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/bubble_stroke2_corner2_rounded.svg b/assets/icons/bubble_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..e0399eda87 --- /dev/null +++ b/assets/icons/bubble_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/celebrate_stroke2_corner0_rounded.svg b/assets/icons/celebrate_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..3ea7bc8d2a --- /dev/null +++ b/assets/icons/celebrate_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/codeBrackets_stroke2_corner0_rounded.svg b/assets/icons/codeBrackets_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..0cc239210e --- /dev/null +++ b/assets/icons/codeBrackets_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/coffee_stroke2_corner0_rounded.svg b/assets/icons/coffee_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..b734ef606a --- /dev/null +++ b/assets/icons/coffee_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/emojiArc_stroke2_corner0_rounded.svg b/assets/icons/emojiArc_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..3d09228e71 --- /dev/null +++ b/assets/icons/emojiArc_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/emojiHeartEyes_stroke2_corner0_rounded.svg b/assets/icons/emojiHeartEyes_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..4aecb86c54 --- /dev/null +++ b/assets/icons/emojiHeartEyes_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/envelope_filled_stroke2_corner0_rounded.svg b/assets/icons/envelope_filled_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..3810bf334f --- /dev/null +++ b/assets/icons/envelope_filled_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/explosion_stroke2_corner0_rounded.svg b/assets/icons/explosion_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..11544bf79c --- /dev/null +++ b/assets/icons/explosion_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/gameController_stroke2_corner0_rounded.svg b/assets/icons/gameController_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..e530c802b0 --- /dev/null +++ b/assets/icons/gameController_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/gifSquare_stroke2_corner0_rounded.svg b/assets/icons/gifSquare_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..47b9df9846 --- /dev/null +++ b/assets/icons/gifSquare_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/gif_stroke2_corner0_rounded.svg b/assets/icons/gif_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..519acfd4d2 --- /dev/null +++ b/assets/icons/gif_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/image_stroke2_corner0_rounded.svg b/assets/icons/image_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..389020b0d1 --- /dev/null +++ b/assets/icons/image_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/lab_stroke2_corner0_rounded.svg b/assets/icons/lab_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..466809194e --- /dev/null +++ b/assets/icons/lab_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/leaf_stroke2_corner0_rounded.svg b/assets/icons/leaf_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..16b379f98c --- /dev/null +++ b/assets/icons/leaf_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/musicNote_stroke2_corner0_rounded.svg b/assets/icons/musicNote_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..2dcc2e3b29 --- /dev/null +++ b/assets/icons/musicNote_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/pencil_stroke2_corner0_rounded.svg b/assets/icons/pencil_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..7341989894 --- /dev/null +++ b/assets/icons/pencil_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/peopleRemove2_stroke2_corner0_rounded.svg b/assets/icons/peopleRemove2_stroke2_corner0_rounded.svg index 2e798cbe29..daec6f5579 100644 --- a/assets/icons/peopleRemove2_stroke2_corner0_rounded.svg +++ b/assets/icons/peopleRemove2_stroke2_corner0_rounded.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/piggyBank_stroke2_corner0_rounded.svg b/assets/icons/piggyBank_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..36d3060102 --- /dev/null +++ b/assets/icons/piggyBank_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/pizza_stroke2_corner0_rounded.svg b/assets/icons/pizza_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..e63351b897 --- /dev/null +++ b/assets/icons/pizza_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/poop_stroke2_corner0_rounded.svg b/assets/icons/poop_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..daa6c11126 --- /dev/null +++ b/assets/icons/poop_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/repost_stroke2_corner2_rounded.svg b/assets/icons/repost_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..c6d84cd5e6 --- /dev/null +++ b/assets/icons/repost_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/rose_stroke2_corner0_rounded.svg b/assets/icons/rose_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..2d269855bd --- /dev/null +++ b/assets/icons/rose_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/settingsSliderVertical_stroke2_corner0_rounded.svg b/assets/icons/settingsSliderVertical_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..518261bca9 --- /dev/null +++ b/assets/icons/settingsSliderVertical_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/shaka_stroke2_corner0_rounded.svg b/assets/icons/shaka_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..32af469e4a --- /dev/null +++ b/assets/icons/shaka_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/timesLarge_stroke2_corner0_rounded.svg b/assets/icons/timesLarge_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..68403f5984 --- /dev/null +++ b/assets/icons/timesLarge_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/ufo_stroke2_corner0_rounded.svg b/assets/icons/ufo_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..115c589e0a --- /dev/null +++ b/assets/icons/ufo_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/zap_stroke2_corner0_rounded.svg b/assets/icons/zap_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..06a88ad8d2 --- /dev/null +++ b/assets/icons/zap_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bskyembed/.eslintrc b/bskyembed/.eslintrc new file mode 100644 index 0000000000..339900dd09 --- /dev/null +++ b/bskyembed/.eslintrc @@ -0,0 +1,20 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "simple-import-sort"], + "extends": [ + "eslint:recommended", + "preact", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "rules": { + "simple-import-sort/imports": "warn", + "simple-import-sort/exports": "warn" + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest", + "project": "./tsconfig.json" + } +} \ No newline at end of file diff --git a/bskyembed/.gitignore b/bskyembed/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/bskyembed/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg b/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..afb8f245f7 --- /dev/null +++ b/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg b/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..9962a20bdb --- /dev/null +++ b/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/bskyembed/assets/circleInfo_stroke2_corner0_rounded.svg b/bskyembed/assets/circleInfo_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..6e098673dd --- /dev/null +++ b/bskyembed/assets/circleInfo_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/bskyembed/assets/heart2_filled_stroke2_corner0_rounded.svg b/bskyembed/assets/heart2_filled_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..18d351c7bc --- /dev/null +++ b/bskyembed/assets/heart2_filled_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/bskyembed/assets/logo.svg b/bskyembed/assets/logo.svg new file mode 100644 index 0000000000..f671e1128d --- /dev/null +++ b/bskyembed/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/bskyembed/assets/repost_stroke2_corner2_rounded.svg b/bskyembed/assets/repost_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..a31e504a12 --- /dev/null +++ b/bskyembed/assets/repost_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/bskyembed/index.html b/bskyembed/index.html new file mode 100644 index 0000000000..61d0c7d17a --- /dev/null +++ b/bskyembed/index.html @@ -0,0 +1,19 @@ + + + + + + Bluesky Embed + + + + + + + + + +
+ + + diff --git a/bskyembed/package.json b/bskyembed/package.json new file mode 100644 index 0000000000..f610e8c064 --- /dev/null +++ b/bskyembed/package.json @@ -0,0 +1,29 @@ +{ + "name": "bskyembed", + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build-snippet": "tsc --project tsconfig.snippet.json", + "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src" + }, + "dependencies": { + "@atproto/api": "^0.12.2", + "@preact/preset-vite": "^2.8.2", + "@vitejs/plugin-legacy": "^5.3.2", + "preact": "^10.4.8", + "terser": "^5.30.3" + }, + "devDependencies": { + "autoprefixer": "^10.4.19", + "eslint": "^8.19.0", + "eslint-config-preact": "^1.3.0", + "eslint-plugin-simple-import-sort": "^12.0.0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^4.0.5", + "vite": "^5.2.8", + "vite-tsconfig-paths": "^4.3.2" + } +} diff --git a/bskyembed/post.html b/bskyembed/post.html new file mode 100644 index 0000000000..5f550495f2 --- /dev/null +++ b/bskyembed/post.html @@ -0,0 +1,19 @@ + + + + + + Bluesky Embed + + + + + + + + + +
+ + + diff --git a/bskyembed/postcss.config.cjs b/bskyembed/postcss.config.cjs new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/bskyembed/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/bskyembed/snippet/embed.ts b/bskyembed/snippet/embed.ts new file mode 100644 index 0000000000..380cda5fb9 --- /dev/null +++ b/bskyembed/snippet/embed.ts @@ -0,0 +1,100 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +interface Window { + bluesky: { + scan: (element?: Pick) => void + } +} + +const EMBED_URL = 'https://embed.bsky.app' + +window.bluesky = window.bluesky || { + scan, +} + +/** + * Listen for messages from the Bluesky embed iframe and adjust the height of + * the iframe accordingly. + */ +window.addEventListener('message', event => { + if (event.origin !== EMBED_URL) { + return + } + + const id = (event.data as {id: string}).id + if (!id) { + return + } + + const embed = document.querySelector( + `[data-bluesky-id="${id}"]`, + ) + + if (!embed) { + return + } + + const height = (event.data as {height: number}).height + if (height) { + embed.style.height = `${height}px` + } +}) + +/** + * Scan the document for all elements with the data-bluesky-aturi attribute, + * and initialize them as Bluesky embeds. + * + * @param element Only scan this specific element @default document @optional + * @returns + */ +function scan(node = document) { + const embeds = node.querySelectorAll('[data-bluesky-uri]') + + for (let i = 0; i < embeds.length; i++) { + const id = String(Math.random()).slice(2) + + const embed = embeds[i] + const aturi = embed.getAttribute('data-bluesky-uri') + + if (!aturi) { + continue + } + + const ref_url = location.origin + location.pathname + + const searchParams = new URLSearchParams() + searchParams.set('id', id) + if (ref_url.startsWith('http')) { + searchParams.set('ref_url', encodeURIComponent(ref_url)) + } + + const iframe = document.createElement('iframe') + iframe.setAttribute('data-bluesky-id', id) + iframe.src = `${EMBED_URL}/embed/${aturi.slice( + 'at://'.length, + )}?${searchParams.toString()}` + iframe.width = '100%' + iframe.style.border = 'none' + iframe.style.display = 'block' + iframe.style.flexGrow = '1' + iframe.frameBorder = '0' + iframe.scrolling = 'no' + + const container = document.createElement('div') + container.style.maxWidth = '600px' + container.style.width = '100%' + container.style.marginTop = '10px' + container.style.marginBottom = '10px' + container.style.display = 'flex' + container.className = 'bluesky-embed' + + container.appendChild(iframe) + + embed.replaceWith(container) + } +} + +if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { + scan() +} else { + document.addEventListener('DOMContentLoaded', () => scan()) +} diff --git a/bskyembed/src/components/container.tsx b/bskyembed/src/components/container.tsx new file mode 100644 index 0000000000..5b1b2b7fb4 --- /dev/null +++ b/bskyembed/src/components/container.tsx @@ -0,0 +1,55 @@ +import {ComponentChildren, h} from 'preact' +import {useEffect, useRef} from 'preact/hooks' + +import {Link} from './link' + +export function Container({ + children, + href, +}: { + children: ComponentChildren + href?: string +}) { + const ref = useRef(null) + const prevHeight = useRef(0) + + useEffect(() => { + if (ref.current) { + const observer = new ResizeObserver(entries => { + const entry = entries[0] + if (!entry) return + + let {height} = entry.contentRect + height += 2 // border top and bottom + if (height !== prevHeight.current) { + prevHeight.current = height + window.parent.postMessage( + {height, id: new URLSearchParams(window.location.search).get('id')}, + '*', + ) + } + }) + observer.observe(ref.current) + return () => observer.disconnect() + } + }, []) + + return ( +
{ + if (ref.current && href) { + // forwardRef requires preact/compat - let's keep it simple + // to keep the bundle size down + const anchor = ref.current.querySelector('a') + if (anchor) { + anchor.click() + } + } + }}> + {href && } +
{children}
+
+ ) +} diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx new file mode 100644 index 0000000000..4457defce4 --- /dev/null +++ b/bskyembed/src/components/embed.tsx @@ -0,0 +1,351 @@ +import { + AppBskyEmbedExternal, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyFeedDefs, + AppBskyFeedPost, + AppBskyGraphDefs, + AppBskyLabelerDefs, +} from '@atproto/api' +import {ComponentChildren, h} from 'preact' +import {useMemo} from 'preact/hooks' + +import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' +import {CONTENT_LABELS, labelsToInfo} from '../labels' +import {getRkey} from '../utils' +import {Link} from './link' + +export function Embed({ + content, + labels, + hideRecord, +}: { + content: AppBskyFeedDefs.PostView['embed'] + labels: AppBskyFeedDefs.PostView['labels'] + hideRecord?: boolean +}) { + const labelInfo = useMemo(() => labelsToInfo(labels), [labels]) + + if (!content) return null + + try { + // Case 1: Image + if (AppBskyEmbedImages.isView(content)) { + return + } + + // Case 2: External link + if (AppBskyEmbedExternal.isView(content)) { + return + } + + // Case 3: Record (quote or linked post) + if (AppBskyEmbedRecord.isView(content)) { + if (hideRecord) { + return null + } + + const record = content.record + + // Case 3.1: Post + if (AppBskyEmbedRecord.isViewRecord(record)) { + const pwiOptOut = !!record.author.labels?.find( + label => label.val === '!no-unauthenticated', + ) + if (pwiOptOut) { + return ( + + The author of the quoted post has requested their posts not be + displayed on external sites. + + ) + } + + let text + if (AppBskyFeedPost.isRecord(record.value)) { + text = record.value.text + } + + const isAuthorLabeled = record.author.labels?.some(label => + CONTENT_LABELS.includes(label.val), + ) + + return ( + +
+
+ +
+

+ {record.author.displayName} + + @{record.author.handle} + +

+
+ {text &&

{text}

} + {record.embeds?.map(embed => ( + + ))} + + ) + } + + // Case 3.2: List + if (AppBskyGraphDefs.isListView(record)) { + return ( + + ) + } + + // Case 3.3: Feed + if (AppBskyFeedDefs.isGeneratorView(record)) { + return ( + + ) + } + + // Case 3.4: Labeler + if (AppBskyLabelerDefs.isLabelerView(record)) { + return ( + + ) + } + + // Case 3.5: Post not found + if (AppBskyEmbedRecord.isViewNotFound(record)) { + return Quoted post not found, it may have been deleted. + } + + // Case 3.6: Post blocked + if (AppBskyEmbedRecord.isViewBlocked(record)) { + return The quoted post is blocked. + } + + throw new Error('Unknown embed type') + } + + // Case 4: Record with media + if ( + AppBskyEmbedRecordWithMedia.isView(content) && + AppBskyEmbedRecord.isViewRecord(content.record.record) + ) { + return ( +
+ + +
+ ) + } + + throw new Error('Unsupported embed type') + } catch (err) { + return ( + {err instanceof Error ? err.message : 'An error occurred'} + ) + } +} + +function Info({children}: {children: ComponentChildren}) { + return ( +
+ +

{children}

+
+ ) +} + +function ImageEmbed({ + content, + labelInfo, +}: { + content: AppBskyEmbedImages.View + labelInfo?: string +}) { + if (labelInfo) { + return {labelInfo} + } + + switch (content.images.length) { + case 1: + return ( + {content.images[0].alt} + ) + case 2: + return ( +
+ {content.images.map((image, i) => ( + {image.alt} + ))} +
+ ) + case 3: + return ( +
+ {content.images[0].alt} +
+ {content.images.slice(1).map((image, i) => ( + {image.alt} + ))} +
+
+ ) + case 4: + return ( +
+ {content.images.map((image, i) => ( + {image.alt} + ))} +
+ ) + default: + return null + } +} + +function ExternalEmbed({ + content, + labelInfo, +}: { + content: AppBskyEmbedExternal.View + labelInfo?: string +}) { + function toNiceDomain(url: string): string { + try { + const urlp = new URL(url) + return urlp.host ? urlp.host : url + } catch (e) { + return url + } + } + + if (labelInfo) { + return {labelInfo} + } + + return ( + + {content.external.thumb && ( + + )} +
+

+ {toNiceDomain(content.external.uri)} +

+

{content.external.title}

+

+ {content.external.description} +

+
+ + ) +} + +function GenericWithImage({ + title, + subtitle, + href, + image, + description, +}: { + title: string + subtitle: string + href: string + image?: string + description?: string +}) { + return ( + +
+ {image ? ( + {title} + ) : ( +
+ )} +
+

{title}

+

{subtitle}

+
+
+ {description &&

{description}

} + + ) +} diff --git a/bskyembed/src/components/link.tsx b/bskyembed/src/components/link.tsx new file mode 100644 index 0000000000..64c2c9a83b --- /dev/null +++ b/bskyembed/src/components/link.tsx @@ -0,0 +1,32 @@ +import {h} from 'preact' + +export function Link({ + href, + className, + ...props +}: { + href: string + className?: string +} & h.JSX.HTMLAttributes) { + const searchParam = new URLSearchParams(window.location.search) + const ref_url = searchParam.get('ref_url') + + const newSearchParam = new URLSearchParams() + newSearchParam.set('ref_src', 'embed') + if (ref_url) { + newSearchParam.set('ref_url', ref_url) + } + + return ( + evt.stopPropagation()} + className={`cursor-pointer ${className || ''}`} + {...props} + /> + ) +} diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx new file mode 100644 index 0000000000..3f2c745bdd --- /dev/null +++ b/bskyembed/src/components/post.tsx @@ -0,0 +1,160 @@ +import {AppBskyFeedDefs, AppBskyFeedPost, RichText} from '@atproto/api' +import {h} from 'preact' + +import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg' +import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg' +import logo from '../../assets/logo.svg' +import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg' +import {CONTENT_LABELS} from '../labels' +import {getRkey, niceDate} from '../utils' +import {Container} from './container' +import {Embed} from './embed' +import {Link} from './link' + +interface Props { + thread: AppBskyFeedDefs.ThreadViewPost +} + +export function Post({thread}: Props) { + const post = thread.post + + const isAuthorLabeled = post.author.labels?.some(label => + CONTENT_LABELS.includes(label.val), + ) + + let record: AppBskyFeedPost.Record | null = null + if (AppBskyFeedPost.isRecord(post.record)) { + record = post.record + } + + const href = `/profile/${post.author.did}/post/${getRkey(post)}` + return ( + +
+
+ +
+ +
+ +
+ +

{post.author.displayName}

+ + +

@{post.author.handle}

+ +
+
+ + + +
+ + + + + +
+ {!!post.likeCount && ( +
+ +

+ {post.likeCount} +

+
+ )} + {!!post.repostCount && ( +
+ +

+ {post.repostCount} +

+
+ )} +
+ +

Reply

+
+
+

+ {post.replyCount + ? `Read ${post.replyCount} ${ + post.replyCount > 1 ? 'replies' : 'reply' + } on Bluesky` + : `View on Bluesky`} +

+

+ View on Bluesky +

+
+
+ + ) +} + +function PostContent({record}: {record: AppBskyFeedPost.Record | null}) { + if (!record) return null + + const rt = new RichText({ + text: record.text, + facets: record.facets, + }) + + const richText = [] + + let counter = 0 + for (const segment of rt.segments()) { + if (segment.isLink() && segment.link) { + richText.push( + + {segment.text} + , + ) + } else if (segment.isMention() && segment.mention) { + richText.push( + + {segment.text} + , + ) + } else if (segment.isTag() && segment.tag) { + richText.push( + + {segment.text} + , + ) + } else { + richText.push(segment.text) + } + + counter++ + } + + return ( +

+ {richText} +

+ ) +} diff --git a/bskyembed/src/index.css b/bskyembed/src/index.css new file mode 100644 index 0000000000..23457ec28d --- /dev/null +++ b/bskyembed/src/index.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.break-word { + word-break: break-word; +} \ No newline at end of file diff --git a/bskyembed/src/labels.ts b/bskyembed/src/labels.ts new file mode 100644 index 0000000000..ff3d91bc7b --- /dev/null +++ b/bskyembed/src/labels.ts @@ -0,0 +1,21 @@ +import {AppBskyFeedDefs} from '@atproto/api' + +export const CONTENT_LABELS = ['porn', 'sexual', 'nudity', 'graphic-media'] + +export function labelsToInfo( + labels?: AppBskyFeedDefs.PostView['labels'], +): string | undefined { + const label = labels?.find(label => CONTENT_LABELS.includes(label.val)) + + switch (label?.val) { + case 'porn': + case 'sexual': + return 'Adult Content' + case 'nudity': + return 'Non-sexual Nudity' + case 'graphic-media': + return 'Graphic Media' + default: + return undefined + } +} diff --git a/bskyembed/src/screens/landing.tsx b/bskyembed/src/screens/landing.tsx new file mode 100644 index 0000000000..72612db0ef --- /dev/null +++ b/bskyembed/src/screens/landing.tsx @@ -0,0 +1,294 @@ +import '../index.css' + +import {AppBskyFeedDefs, AppBskyFeedPost, AtUri, BskyAgent} from '@atproto/api' +import {h, render} from 'preact' +import {useEffect, useMemo, useRef, useState} from 'preact/hooks' + +import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg' +import logo from '../../assets/logo.svg' +import {Container} from '../components/container' +import {Link} from '../components/link' +import {Post} from '../components/post' +import {niceDate} from '../utils' + +const DEFAULT_POST = 'https://bsky.app/profile/emilyliu.me/post/3jzn6g7ixgq2y' +const DEFAULT_URI = + 'at://did:plc:vjug55kidv6sye7ykr5faxxn/app.bsky.feed.post/3jzn6g7ixgq2y' + +export const EMBED_SERVICE = 'https://embed.bsky.app' +export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` + +const root = document.getElementById('app') +if (!root) throw new Error('No root element') + +const agent = new BskyAgent({ + service: 'https://public.api.bsky.app', +}) + +render(, root) + +function LandingPage() { + const [uri, setUri] = useState('') + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + const [thread, setThread] = useState( + null, + ) + + useEffect(() => { + void (async () => { + setError(null) + setThread(null) + setLoading(true) + try { + let atUri = DEFAULT_URI + + if (uri) { + if (uri.startsWith('at://')) { + atUri = uri + } else { + try { + const urlp = new URL(uri) + if (!urlp.hostname.endsWith('bsky.app')) { + throw new Error('Invalid hostname') + } + const split = urlp.pathname.slice(1).split('/') + if (split.length < 4) { + throw new Error('Invalid pathname') + } + const [profile, didOrHandle, type, rkey] = split + if (profile !== 'profile' || type !== 'post') { + throw new Error('Invalid profile or type') + } + + let did = didOrHandle + if (!didOrHandle.startsWith('did:')) { + const resolution = await agent.resolveHandle({ + handle: didOrHandle, + }) + if (!resolution.data.did) { + throw new Error('No DID found') + } + did = resolution.data.did + } + + atUri = `at://${did}/app.bsky.feed.post/${rkey}` + } catch (err) { + console.log(err) + throw new Error('Invalid Bluesky URL') + } + } + } + + const {data} = await agent.getPostThread({ + uri: atUri, + depth: 0, + parentHeight: 0, + }) + + if (!AppBskyFeedDefs.isThreadViewPost(data.thread)) { + throw new Error('Post not found') + } + const pwiOptOut = !!data.thread.post.author.labels?.find( + label => label.val === '!no-unauthenticated', + ) + if (pwiOptOut) { + throw new Error( + 'The author of this post has requested their posts not be displayed on external sites.', + ) + } + setThread(data.thread) + } catch (err) { + console.error(err) + setError(err instanceof Error ? err.message : 'Invalid Bluesky URL') + } finally { + setLoading(false) + } + })() + }, [uri]) + + return ( +
+ + + + +

Embed a Bluesky Post

+ + setUri(e.currentTarget.value)} + className="border rounded-lg py-3 w-full max-w-[600px] px-4" + placeholder={DEFAULT_POST} + /> + + + + {loading ? ( + + ) : ( +
+ {!error && thread && uri && } + {!error && thread && } + {error && ( +
+

{error}

+
+ )} +
+ )} +
+ ) +} + +function Skeleton() { + return ( + +
+
+
+
+
+
+
+
+
+
+
+
+ + ) +} + +function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { + const ref = useRef(null) + const [copied, setCopied] = useState(false) + + // reset copied state after 2 seconds + useEffect(() => { + if (copied) { + const timeout = setTimeout(() => { + setCopied(false) + }, 2000) + return () => clearTimeout(timeout) + } + }, [copied]) + + const snippet = useMemo(() => { + const record = thread.post.record + + if (!AppBskyFeedPost.isRecord(record)) { + return '' + } + + const lang = record.langs && record.langs.length > 0 ? record.langs[0] : '' + const profileHref = toShareUrl( + ['/profile', thread.post.author.did].join('/'), + ) + const urip = new AtUri(thread.post.uri) + const href = toShareUrl( + ['/profile', thread.post.author.did, 'post', urip.rkey].join('/'), + ) + + // x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x + // DO NOT ADD ANY NEW INTERPOLATIONS BELOW WITHOUT ESCAPING THEM! + // Also, keep this code synced with the app code in Embed.tsx. + // x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x + return `

${escapeHtml(record.text)}${ + record.embed + ? `

[image or embed]` + : '' + }

— ${escapeHtml( + thread.post.author.displayName || thread.post.author.handle, + )} (
@${escapeHtml( + thread.post.author.handle, + )}) ${escapeHtml( + niceDate(thread.post.indexedAt), + )}
` + }, [thread]) + + return ( +
+ { + ref.current?.select() + }} + /> + +
+ ) +} + +function toShareUrl(path: string) { + return `https://bsky.app${path}?ref_src=embed` +} + +/** + * Based on a snippet of code from React, which itself was based on the escape-html library. + * Copyright (c) Meta Platforms, Inc. and affiliates + * Copyright (c) 2012-2013 TJ Holowaychuk + * Copyright (c) 2015 Andreas Lubbe + * Copyright (c) 2015 Tiancheng "Timothy" Gu + * Licensed as MIT. + */ +const matchHtmlRegExp = /["'&<>]/ +function escapeHtml(string: string) { + const str = String(string) + const match = matchHtmlRegExp.exec(str) + if (!match) { + return str + } + let escape + let html = '' + let index + let lastIndex = 0 + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&' + break + case 39: // ' + escape = ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break + default: + continue + } + if (lastIndex !== index) { + html += str.slice(lastIndex, index) + } + lastIndex = index + 1 + html += escape + } + return lastIndex !== index ? html + str.slice(lastIndex, index) : html +} diff --git a/bskyembed/src/screens/post.tsx b/bskyembed/src/screens/post.tsx new file mode 100644 index 0000000000..365227cd47 --- /dev/null +++ b/bskyembed/src/screens/post.tsx @@ -0,0 +1,85 @@ +import '../index.css' + +import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' +import {h, render} from 'preact' + +import logo from '../../assets/logo.svg' +import {Container} from '../components/container' +import {Link} from '../components/link' +import {Post} from '../components/post' +import {getRkey} from '../utils' + +const root = document.getElementById('app') +if (!root) throw new Error('No root element') + +const agent = new BskyAgent({ + service: 'https://public.api.bsky.app', +}) + +const uri = `at://${window.location.pathname.slice('/embed/'.length)}` +if (!uri) { + throw new Error('No uri in path') +} + +agent + .getPostThread({ + uri, + depth: 0, + parentHeight: 0, + }) + .then(({data}) => { + if (!AppBskyFeedDefs.isThreadViewPost(data.thread)) { + throw new Error('Expected a ThreadViewPost') + } + const pwiOptOut = !!data.thread.post.author.labels?.find( + label => label.val === '!no-unauthenticated', + ) + if (pwiOptOut) { + render(, root) + } else { + render(, root) + } + }) + .catch(err => { + console.error(err) + render(, root) + }) + +function PwiOptOut({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { + const href = `/profile/${thread.post.author.did}/post/${getRkey(thread.post)}` + return ( + + + + +
+

+ The author of this post has requested their posts not be displayed on + external sites. +

+ + View on Bluesky + +
+
+ ) +} + +function ErrorMessage() { + return ( + + + + +

+ Post not found, it may have been deleted. +

+
+ ) +} diff --git a/bskyembed/src/utils.ts b/bskyembed/src/utils.ts new file mode 100644 index 0000000000..1f6fd5061c --- /dev/null +++ b/bskyembed/src/utils.ts @@ -0,0 +1,18 @@ +import {AtUri} from '@atproto/api' + +export function niceDate(date: number | string | Date) { + const d = new Date(date) + return `${d.toLocaleDateString('en-us', { + year: 'numeric', + month: 'short', + day: 'numeric', + })} at ${d.toLocaleTimeString(undefined, { + hour: 'numeric', + minute: '2-digit', + })}` +} + +export function getRkey({uri}: {uri: string}): string { + const at = new AtUri(uri) + return at.rkey +} diff --git a/bskyembed/tailwind.config.cjs b/bskyembed/tailwind.config.cjs new file mode 100644 index 0000000000..092e8c2cb7 --- /dev/null +++ b/bskyembed/tailwind.config.cjs @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + brand: 'rgb(10,122,255)', + textLight: 'rgb(66,87,108)', + }, + }, + }, + plugins: [], +} diff --git a/bskyembed/tsconfig.json b/bskyembed/tsconfig.json new file mode 100644 index 0000000000..44c516ed11 --- /dev/null +++ b/bskyembed/tsconfig.json @@ -0,0 +1,24 @@ + +{ + "compilerOptions": { + "target": "ES5", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "types": ["vite/client"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "downlevelIteration": true + }, + "include": ["src"] +} diff --git a/bskyembed/tsconfig.snippet.json b/bskyembed/tsconfig.snippet.json new file mode 100644 index 0000000000..a6b6071dd6 --- /dev/null +++ b/bskyembed/tsconfig.snippet.json @@ -0,0 +1,10 @@ + +{ + "compilerOptions": { + "target": "ES5", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "strict": true, + "outDir": "dist" + }, + "include": ["snippet"], +} diff --git a/bskyembed/vite.config.ts b/bskyembed/vite.config.ts new file mode 100644 index 0000000000..9acc9d5ee4 --- /dev/null +++ b/bskyembed/vite.config.ts @@ -0,0 +1,27 @@ +import {resolve} from 'node:path' + +import preact from '@preact/preset-vite' +import legacy from '@vitejs/plugin-legacy' +import type {UserConfig} from 'vite' +import paths from 'vite-tsconfig-paths' + +const config: UserConfig = { + plugins: [ + preact(), + paths(), + legacy({ + targets: ['defaults', 'not IE 11'], + }), + ], + build: { + assetsDir: 'static', + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + post: resolve(__dirname, 'post.html'), + }, + }, + }, +} + +export default config diff --git a/bskyembed/yarn.lock b/bskyembed/yarn.lock new file mode 100644 index 0000000000..60efe36845 --- /dev/null +++ b/bskyembed/yarn.lock @@ -0,0 +1,4194 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@atproto/api@^0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.2.tgz#5df6d4f60dea0395c84fdebd9e81a7e853edf130" + integrity sha512-UVzCiDZH2j0wrr/O8nb1edD5cYLVqB5iujueXUCbHS3rAwIxgmyLtA3Hzm2QYsGPo/+xsIg1fNvpq9rNT6KWUA== + dependencies: + "@atproto/common-web" "^0.3.0" + "@atproto/lexicon" "^0.4.0" + "@atproto/syntax" "^0.3.0" + "@atproto/xrpc" "^0.5.0" + multiformats "^9.9.0" + tlds "^1.234.0" + +"@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" + integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA== + dependencies: + graphemer "^1.4.0" + multiformats "^9.9.0" + uint8arrays "3.0.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" + integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ== + dependencies: + "@atproto/common-web" "^0.3.0" + "@atproto/syntax" "^0.3.0" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + zod "^3.21.4" + +"@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@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032" + integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog== + dependencies: + "@atproto/lexicon" "^0.4.0" + zod "^3.21.4" + +"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + +"@babel/core@^7.13.16", "@babel/core@^7.22.1", "@babel/core@^7.23.9": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/eslint-parser@^7.13.14": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" + integrity sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ== + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.1" + +"@babel/generator@^7.24.1", "@babel/generator@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" + integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" + integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" + integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" + integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.12.13": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz#71d9ad06063a6ac5430db126b5df48c70ee885fa" + integrity sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" + integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-attributes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" + integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.23.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" + integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-async-generator-functions@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" + integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" + integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== + dependencies: + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + +"@babel/plugin-transform-block-scoped-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" + integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-class-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz#5bc8fc160ed96378184bc10042af47f50884dcb1" + integrity sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" + integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/template" "^7.24.0" + +"@babel/plugin-transform-destructuring@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz#b1e8243af4a0206841973786292b8c8dd8447345" + integrity sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-dotall-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" + integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-duplicate-keys@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" + integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-dynamic-import@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" + integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" + integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-export-namespace-from@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" + integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" + integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" + integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-json-strings@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" + integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" + integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" + integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" + integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-modules-amd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" + integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" + integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/plugin-transform-modules-umd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" + integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" + integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" + integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz#5a3ce73caf0e7871a02e1c31e8b473093af241ff" + integrity sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.1" + +"@babel/plugin-transform-object-super@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" + integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + +"@babel/plugin-transform-optional-catch-binding@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" + integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6" + integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz#983c15d114da190506c75b616ceb0f817afcc510" + integrity sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-private-methods@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-private-property-in-object@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz#756443d400274f8fb7896742962cc1b9f25c1f6a" + integrity sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" + integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.5" + +"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" + integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.23.3" + "@babel/types" "^7.23.4" + +"@babel/plugin-transform-regenerator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" + integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" + integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-shorthand-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" + integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" + integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" + integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-template-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" + integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-typeof-symbol@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz#6831f78647080dec044f7e9f68003d99424f94c7" + integrity sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-escapes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" + integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-property-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" + integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" + integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-sets-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" + integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/preset-env@^7.23.9": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== + dependencies: + "@babel/compat-data" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" + "@babel/plugin-transform-classes" "^7.24.1" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.1" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.1" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.1" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.1" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.8.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.4.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@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" + +"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@mdn/browser-compat-data@^5.2.34", "@mdn/browser-compat-data@^5.3.13": + version "5.5.19" + resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.19.tgz#5c661edd669ee990dbdf2e1a8ee3c9c1c6fa7117" + integrity sha512-ntKBZtwWCy4XvJosdTJKqIMdmzgbxjopfoiMxgpzsml3dXqA7MIHCE/amidfQc06a6KvmMrpiVuYHIBt2feDog== + +"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": + version "5.1.1-v1" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" + integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== + dependencies: + eslint-scope "5.1.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@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== + +"@preact/preset-vite@^2.8.2": + version "2.8.2" + resolved "https://registry.yarnpkg.com/@preact/preset-vite/-/preset-vite-2.8.2.tgz#8113a37c8f7dfa0156fe1e6811bdc2b7ea6a3490" + integrity sha512-m3tl+M8IO8jgiHnk+7LSTFl8axdPXloewi7iGVLdmCwf34XOzEUur0bZVewW4DUbUipFjTS2CXu27+5f/oexBA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@prefresh/vite" "^2.4.1" + "@rollup/pluginutils" "^4.1.1" + babel-plugin-transform-hook-names "^1.0.2" + debug "^4.3.4" + kolorist "^1.8.0" + magic-string "0.30.5" + node-html-parser "^6.1.10" + resolve "^1.22.8" + source-map "^0.7.4" + stack-trace "^1.0.0-pre2" + +"@prefresh/babel-plugin@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@prefresh/babel-plugin/-/babel-plugin-0.5.1.tgz#3161bbbf12dd39a5fe08514349898fa6a20525b7" + integrity sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg== + +"@prefresh/core@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@prefresh/core/-/core-1.5.2.tgz#750e1936d82f3b0a1199d3cda5c35e3443128490" + integrity sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA== + +"@prefresh/utils@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@prefresh/utils/-/utils-1.2.0.tgz#cbdfe549b207041e38bb6cc382408b30cd24fec8" + integrity sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ== + +"@prefresh/vite@^2.4.1": + version "2.4.5" + resolved "https://registry.yarnpkg.com/@prefresh/vite/-/vite-2.4.5.tgz#8e6ecdb36510b8497c346a5a7f55e0bc9b9b5f6b" + integrity sha512-iForDVJ2M8gQYnm5pHumvTEJjGGc7YNYC0GVKnHFL+GvFfKHfH9Rpq67nUAzNbjuLEpqEOUuQVQajMazWu2ZNQ== + dependencies: + "@babel/core" "^7.22.1" + "@prefresh/babel-plugin" "0.5.1" + "@prefresh/core" "^1.5.1" + "@prefresh/utils" "^1.2.0" + "@rollup/pluginutils" "^4.2.1" + +"@rollup/pluginutils@^4.1.1", "@rollup/pluginutils@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + +"@rollup/rollup-android-arm-eabi@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz#ca0501dd836894216cb9572848c5dde4bfca3bec" + integrity sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA== + +"@rollup/rollup-android-arm64@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz#154ca7e4f815d2e442ffc62ee7f64aee8b2547b0" + integrity sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ== + +"@rollup/rollup-darwin-arm64@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz#02b522ab6ccc2c504634651985ff8e657b42c055" + integrity sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q== + +"@rollup/rollup-darwin-x64@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz#217737f9f73de729fdfd7d529afebb6c8283f554" + integrity sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA== + +"@rollup/rollup-linux-arm-gnueabihf@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz#a87e478ab3f697c7f4e74c8b1cac1e0667f8f4be" + integrity sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g== + +"@rollup/rollup-linux-arm64-gnu@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz#4da6830eca27e5f4ca15f9197e5660952ca185c6" + integrity sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w== + +"@rollup/rollup-linux-arm64-musl@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz#0b0ed35720aebc8f5e501d370a9ea0f686ead1e0" + integrity sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz#024ad04d162726f25e62915851f7df69a9677c17" + integrity sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw== + +"@rollup/rollup-linux-riscv64-gnu@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz#180694d1cd069ddbe22022bb5b1bead3b7de581c" + integrity sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw== + +"@rollup/rollup-linux-s390x-gnu@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz#f7b4e2b0ca49be4e34f9ef0b548c926d94edee87" + integrity sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA== + +"@rollup/rollup-linux-x64-gnu@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz#0aaf79e5b9ccf7db3084fe6c3f2d2873a27d5af4" + integrity sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA== + +"@rollup/rollup-linux-x64-musl@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz#38f0a37ca5015eb07dff86a1b6f94279c179f4ed" + integrity sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g== + +"@rollup/rollup-win32-arm64-msvc@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz#84d48c55740ede42c77373f76e85f368633a0cc3" + integrity sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA== + +"@rollup/rollup-win32-ia32-msvc@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz#c1e0bc39e20e760f0a526ddf14ae0543af796605" + integrity sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg== + +"@rollup/rollup-win32-x64-msvc@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz#299eee74b7d87e116083ac5b1ce8dd9434668294" + integrity sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew== + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/semver@^7.3.12": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@typescript-eslint/experimental-utils@^5.0.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741" + integrity sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw== + dependencies: + "@typescript-eslint/utils" "5.62.0" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-legacy@^5.3.2": + version "5.3.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-legacy/-/plugin-legacy-5.3.2.tgz#f890db6014898c36af85b8ad52c680ef026b8aa8" + integrity sha512-8moCOrIMaZ/Rjln0Q6GsH6s8fAt1JOI3k8nmfX4tXUxE5KAExVctSyOBk+A25GClsdSWqIk2yaUthH3KJ2X4tg== + dependencies: + "@babel/core" "^7.23.9" + "@babel/preset-env" "^7.23.9" + browserslist "^4.23.0" + browserslist-to-esbuild "^2.1.1" + core-js "^3.36.0" + magic-string "^0.30.7" + regenerator-runtime "^0.14.1" + systemjs "^6.14.3" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.8.2, acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.6, array-includes@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlast@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.toreversed@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" + integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" + integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.1.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +ast-metadata-inferer@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz#0f94c3425e310d8da45823ab2161142e3f134343" + integrity sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA== + dependencies: + "@mdn/browser-compat-data" "^5.2.34" + +autoprefixer@^10.4.19: + version "10.4.19" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" + integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.1" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" + integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + +babel-plugin-transform-hook-names@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz#0d75c2d78e8bbcdb258241131562b9cf07f010f3" + integrity sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist-to-esbuild@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz#50dc4c55a6889ba22c7b1bd820032f81b822faf0" + integrity sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw== + dependencies: + meow "^13.0.0" + +browserslist@^4.21.10, browserslist@^4.22.2, browserslist@^4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30001524, caniuse-lite@^1.0.30001587: + version "1.0.30001606" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001606.tgz#b4d5f67ab0746a3b8b5b6d1f06e39c51beb39a9e" + integrity sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg== + +caniuse-lite@^1.0.30001599: + version "1.0.30001607" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz#b91e8e033f6bca4e13d3d45388d87fa88931d9a5" + integrity sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.31.0, core-js-compat@^3.36.1: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" + integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== + dependencies: + browserslist "^4.23.0" + +core-js@^3.36.0: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.1.tgz#c97a7160ebd00b2de19e62f4bbd3406ab720e578" + integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA== + +cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.4.668: + version "1.4.728" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.728.tgz#ac54d9d1b38752b920ec737a48c83dec2bf45ea1" + integrity sha512-Ud1v7hJJYIqehlUJGqR6PF1Ek8l80zWwxA6nGxigBsGJ9f9M2fciHyrIiNMerSHSH3p+0/Ia7jIlnDkt41h5cw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.0.17: + version "1.0.18" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" + integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-preact@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-preact/-/eslint-config-preact-1.3.0.tgz#17b72813078f4d1d4d2b79938ec21f92338bc9c0" + integrity sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw== + dependencies: + "@babel/core" "^7.13.16" + "@babel/eslint-parser" "^7.13.14" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-decorators" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + eslint-plugin-compat "^4.0.0" + eslint-plugin-jest "^25.2.4" + eslint-plugin-react "^7.27.0" + eslint-plugin-react-hooks "^4.3.0" + +eslint-plugin-compat@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz#eeaf80daa1afe495c88a47e9281295acae45c0aa" + integrity sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w== + dependencies: + "@mdn/browser-compat-data" "^5.3.13" + ast-metadata-inferer "^0.8.0" + browserslist "^4.21.10" + caniuse-lite "^1.0.30001524" + find-up "^5.0.0" + lodash.memoize "^4.1.2" + semver "^7.5.4" + +eslint-plugin-jest@^25.2.4: + version "25.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" + integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + +eslint-plugin-react-hooks@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react@^7.27.0: + version "7.34.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" + integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlast "^1.2.4" + array.prototype.flatmap "^1.3.2" + array.prototype.toreversed "^1.1.2" + array.prototype.tosorted "^1.1.3" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.17" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + object.hasown "^1.1.3" + object.values "^1.1.7" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.10" + +eslint-plugin-simple-import-sort@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.0.0.tgz#3cfa05d74509bd4dc329a956938823812194dbb6" + integrity sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ== + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.19.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + 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" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +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@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +iso-datestring-validator@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" + integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== + +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +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" + +jiti@^1.21.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kolorist@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c" + integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lilconfig@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" + integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +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@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +magic-string@0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +magic-string@^0.30.7: + version "0.30.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +meow@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" + integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +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" + +"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== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +multiformats@^9.4.2, multiformats@^9.9.0: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-html-parser@^6.1.10: + version "6.1.13" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.13.tgz#a1df799b83df5c6743fcd92740ba14682083b7e4" + integrity sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg== + dependencies: + css-select "^5.1.0" + he "1.2.0" + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.hasown@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== + dependencies: + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.values@^1.1.6, object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "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-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-selector-parser@^6.0.11: + version "6.0.16" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" + integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.23, postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +preact@^10.4.8: + version "10.20.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.1.tgz#1bc598ab630d8612978f7533da45809a8298542b" + integrity sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.14.0, regenerator-runtime@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.1.7, resolve@^1.14.2, resolve@^1.22.2, resolve@^1.22.8: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.13.0: + version "4.14.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.1.tgz#228d5159c3f4d8745bd24819d734bc6c6ca87c09" + integrity sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.14.1" + "@rollup/rollup-android-arm64" "4.14.1" + "@rollup/rollup-darwin-arm64" "4.14.1" + "@rollup/rollup-darwin-x64" "4.14.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.14.1" + "@rollup/rollup-linux-arm64-gnu" "4.14.1" + "@rollup/rollup-linux-arm64-musl" "4.14.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.14.1" + "@rollup/rollup-linux-riscv64-gnu" "4.14.1" + "@rollup/rollup-linux-s390x-gnu" "4.14.1" + "@rollup/rollup-linux-x64-gnu" "4.14.1" + "@rollup/rollup-linux-x64-musl" "4.14.1" + "@rollup/rollup-win32-arm64-msvc" "4.14.1" + "@rollup/rollup-win32-ia32-msvc" "4.14.1" + "@rollup/rollup-win32-x64-msvc" "4.14.1" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +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== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +stack-trace@^1.0.0-pre2: + version "1.0.0-pre2" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-1.0.0-pre2.tgz#46a83a79f1b287807e9aaafc6a5dd8bcde626f9c" + integrity sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A== + +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs + 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== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +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== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.matchall@^4.0.10: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^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@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +sucrase@^3.32.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +systemjs@^6.14.3: + version "6.14.3" + resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.14.3.tgz#c1d6e4ff5f9ff7106e5bb3d451360b1a066bde8a" + integrity sha512-hQv45irdhXudAOr8r6SVSpJSGtogdGZUbJBRKCE5nsIS7tsxxvnIHqT4IOPWj+P+HcSzeWzHlGCGpmhPDIKe+w== + +tailwindcss@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.3.tgz#be48f5283df77dfced705451319a5dffb8621519" + integrity sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.0" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + +terser@^5.30.3: + version "5.30.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2" + integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tlds@^1.234.0: + version "1.252.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.252.0.tgz#71d9617f4ef4cc7347843bee72428e71b8b0f419" + integrity sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsconfck@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.3.tgz#d9bda0e87d05b1c360e996c9050473c7e6f8084f" + integrity sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA== + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typescript@^4.0.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +uint8arrays@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" + integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== + dependencies: + multiformats "^9.4.2" + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vite-tsconfig-paths@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9" + integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + +vite@^5.2.8: + version "5.2.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa" + integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +which-collection@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +"wrap-ansi-cjs@npm: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@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^2.3.4: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed" + integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.21.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== diff --git a/bskyweb/.gitignore b/bskyweb/.gitignore index 1d945e1dab..fad122a280 100644 --- a/bskyweb/.gitignore +++ b/bskyweb/.gitignore @@ -3,12 +3,22 @@ test-coverage.out # Don't check in the binary. /bskyweb +/embedr # Don't accidentally commit JS-generated code static/js/*.js static/js/*.map static/js/*.js.LICENSE.txt +static/js/empty.txt templates/scripts.html +templates/*-embed.html +static/embed/*.html +static/embed/assets/*.js +static/embed/assets/*.css +embedr-static/post-*.js +embedr-static/post-*.css +embedr-static/index-*.js +embedr-static/polyfills-*.js # Don't ignore this file !.gitignore diff --git a/bskyweb/Makefile b/bskyweb/Makefile index 6f979fa849..bb2da525fe 100644 --- a/bskyweb/Makefile +++ b/bskyweb/Makefile @@ -14,6 +14,7 @@ help: ## Print info about all commands .PHONY: build build: ## Build all executables go build ./cmd/bskyweb + go build ./cmd/embedr .PHONY: test test: ## Run all tests @@ -43,3 +44,7 @@ check: ## Compile everything, checking syntax (does not output binaries) .PHONY: run-dev-bskyweb run-dev-bskyweb: .env ## Runs 'bskyweb' for local dev GOLOG_LOG_LEVEL=info go run ./cmd/bskyweb serve + +.PHONY: run-dev-embedr +run-dev-embedr: .env ## Runs 'embedr' for local dev + GOLOG_LOG_LEVEL=info go run ./cmd/embedr serve diff --git a/bskyweb/README.embed.md b/bskyweb/README.embed.md new file mode 100644 index 0000000000..8f19ef0226 --- /dev/null +++ b/bskyweb/README.embed.md @@ -0,0 +1,52 @@ + +## oEmbed + + + +* URL scheme: `https://bsky.app/profile/*/post/*` +* API endpoint: `https://embed.bsky.app/oembed` + +Request params: + +- `url` (required): support both AT-URI and bsky.app URL +- `maxwidth` (optional): [220..550], 325 is default +- `maxheight` (not supported!) +- `format` (optional): only `json` supported + +Response format: + +- `type` (required): "rich" +- `version` (required): "1.0" +- `author_name` (optional): display name +- `author_url` (optional): profile URL +- `provider_name` (optional): "Bluesky Social" +- `provider_url` (optional): "https://bsky.app" +- `cache_age` (optional, integer seconds): 86400 (24 hours) (?) +- `width` (required): ? +- `height` (required): ? + +Not used: + +- title (optional): A text title, describing the resource. +- thumbnail_url (optional): A URL to a thumbnail image representing the resource. The thumbnail must respect any maxwidth and maxheight parameters. If this parameter is present, thumbnail_width and thumbnail_height must also be present. +- thumbnail_width (optional): The width of the optional thumbnail. If this parameter is present, thumbnail_url and thumbnail_height must also be present. +- thumbnail_height (optional): The height of the optional thumbnail. If this parameter is present, thumbnail_url and thumbnail_width must also be present. + +Only `json` is supported; `xml` is a 501. + +``` + +``` + + +## iframe URL + +`https://embed.bsky.app/embed//app.bsky.feed.post/` +`https://embed.bsky.app/static/embed.js` + +``` +
+

{{ post-text }}

+ — US Department of the Interior (@Interior) May 5, 2014 +
+``` diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 54a3925c6d..54580d6431 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -170,6 +170,9 @@ func serve(cctx *cli.Context) error { // home e.GET("/", server.WebHome) + // download + e.GET("/download", server.Download) + // generic routes e.GET("/hashtag/:tag", server.WebGeneric) e.GET("/search", server.WebGeneric) @@ -187,6 +190,7 @@ func serve(cctx *cli.Context) error { e.GET("/settings/saved-feeds", server.WebGeneric) e.GET("/settings/threads", server.WebGeneric) e.GET("/settings/external-embeds", server.WebGeneric) + e.GET("/settings/accessibility", server.WebGeneric) e.GET("/sys/debug", server.WebGeneric) e.GET("/sys/debug-mod", server.WebGeneric) e.GET("/sys/log", server.WebGeneric) @@ -196,6 +200,8 @@ func serve(cctx *cli.Context) error { e.GET("/support/community-guidelines", server.WebGeneric) e.GET("/support/copyright", server.WebGeneric) e.GET("/intent/compose", server.WebGeneric) + e.GET("/messages", server.WebGeneric) + e.GET("/messages/:conversation", server.WebGeneric) // profile endpoints; only first populates info e.GET("/profile/:handleOrDID", server.WebProfile) @@ -271,6 +277,20 @@ func (srv *Server) errorHandler(err error, c echo.Context) { c.Render(code, "error.html", data) } +// Handler for redirecting to the download page. +func (srv *Server) Download(c echo.Context) error { + ua := c.Request().UserAgent() + if strings.Contains(ua, "Android") { + return c.Redirect(http.StatusFound, "https://play.google.com/store/apps/details?id=xyz.blueskyweb.app") + } + + if strings.Contains(ua, "iPhone") || strings.Contains(ua, "iPad") || strings.Contains(ua, "iPod") { + return c.Redirect(http.StatusFound, "https://apps.apple.com/tr/app/bluesky-social/id6444370199") + } + + return c.Redirect(http.StatusFound, "/") +} + // handler for endpoint that have no specific server-side handling func (srv *Server) WebGeneric(c echo.Context) error { data := pongo2.Context{} diff --git a/bskyweb/cmd/embedr/.gitignore b/bskyweb/cmd/embedr/.gitignore new file mode 100644 index 0000000000..c810652a10 --- /dev/null +++ b/bskyweb/cmd/embedr/.gitignore @@ -0,0 +1 @@ +/bskyweb diff --git a/bskyweb/cmd/embedr/handlers.go b/bskyweb/cmd/embedr/handlers.go new file mode 100644 index 0000000000..a3767eeca9 --- /dev/null +++ b/bskyweb/cmd/embedr/handlers.go @@ -0,0 +1,213 @@ +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + appbsky "github.com/bluesky-social/indigo/api/bsky" + "github.com/bluesky-social/indigo/atproto/syntax" + + "github.com/labstack/echo/v4" +) + +var ErrPostNotFound = errors.New("post not found") +var ErrPostNotPublic = errors.New("post is not publicly accessible") + +func (srv *Server) getBlueskyPost(ctx context.Context, did syntax.DID, rkey syntax.RecordKey) (*appbsky.FeedDefs_PostView, error) { + + // fetch the post post (with extra context) + uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey) + tpv, err := appbsky.FeedGetPostThread(ctx, srv.xrpcc, 1, 0, uri) + if err != nil { + log.Warnf("failed to fetch post: %s\t%v", uri, err) + // TODO: detect 404, specifically? + return nil, ErrPostNotFound + } + + if tpv.Thread.FeedDefs_BlockedPost != nil { + return nil, ErrPostNotPublic + } else if tpv.Thread.FeedDefs_ThreadViewPost.Post == nil { + return nil, ErrPostNotFound + } + + postView := tpv.Thread.FeedDefs_ThreadViewPost.Post + for _, label := range postView.Author.Labels { + if label.Src == postView.Author.Did && label.Val == "!no-unauthenticated" { + return nil, ErrPostNotPublic + } + } + return postView, nil +} + +func (srv *Server) WebHome(c echo.Context) error { + return c.Render(http.StatusOK, "home.html", nil) +} + +type OEmbedResponse struct { + Type string `json:"type"` + Version string `json:"version"` + AuthorName string `json:"author_name,omitempty"` + AuthorURL string `json:"author_url,omitempty"` + ProviderName string `json:"provider_url,omitempty"` + CacheAge int `json:"cache_age,omitempty"` + Width *int `json:"width"` + Height *int `json:"height"` + HTML string `json:"html,omitempty"` +} + +func (srv *Server) parseBlueskyURL(ctx context.Context, raw string) (*syntax.ATURI, error) { + + if raw == "" { + return nil, fmt.Errorf("empty url") + } + + // first try simple AT-URI + uri, err := syntax.ParseATURI(raw) + if nil == err { + return &uri, nil + } + + // then try bsky.app post URL + u, err := url.Parse(raw) + if err != nil { + return nil, err + } + if u.Hostname() != "bsky.app" { + return nil, fmt.Errorf("only bsky.app URLs currently supported") + } + pathParts := strings.Split(u.Path, "/") // NOTE: pathParts[0] will be empty string + if len(pathParts) != 5 || pathParts[1] != "profile" || pathParts[3] != "post" { + return nil, fmt.Errorf("only bsky.app post URLs currently supported") + } + atid, err := syntax.ParseAtIdentifier(pathParts[2]) + if err != nil { + return nil, err + } + rkey, err := syntax.ParseRecordKey(pathParts[4]) + if err != nil { + return nil, err + } + var did syntax.DID + if atid.IsHandle() { + ident, err := srv.dir.Lookup(ctx, *atid) + if err != nil { + return nil, err + } + did = ident.DID + } else { + did, err = atid.AsDID() + if err != nil { + return nil, err + } + } + + // TODO: don't really need to re-parse here, if we had test coverage + aturi, err := syntax.ParseATURI(fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey)) + if err != nil { + return nil, err + } else { + return &aturi, nil + } +} + +func (srv *Server) WebOEmbed(c echo.Context) error { + formatParam := c.QueryParam("format") + if formatParam != "" && formatParam != "json" { + return c.String(http.StatusNotImplemented, "Unsupported oEmbed format: "+formatParam) + } + + // TODO: do we actually do something with width? + width := 600 + maxWidthParam := c.QueryParam("maxwidth") + if maxWidthParam != "" { + maxWidthInt, err := strconv.Atoi(maxWidthParam) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid maxwidth (expected integer)") + } + if maxWidthInt < 220 { + width = 220 + } else if maxWidthInt > 600 { + width = 600 + } else { + width = maxWidthInt + } + } + // NOTE: maxheight ignored + + aturi, err := srv.parseBlueskyURL(c.Request().Context(), c.QueryParam("url")) + if err != nil { + return c.String(http.StatusBadRequest, fmt.Sprintf("Expected 'url' to be bsky.app URL or AT-URI: %v", err)) + } + if aturi.Collection() != syntax.NSID("app.bsky.feed.post") { + return c.String(http.StatusNotImplemented, "Only posts (app.bsky.feed.post records) can be embedded currently") + } + did, err := aturi.Authority().AsDID() + if err != nil { + return err + } + + post, err := srv.getBlueskyPost(c.Request().Context(), did, aturi.RecordKey()) + if err == ErrPostNotFound { + return c.String(http.StatusNotFound, fmt.Sprintf("%v", err)) + } else if err == ErrPostNotPublic { + return c.String(http.StatusForbidden, fmt.Sprintf("%v", err)) + } else if err != nil { + return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err)) + } + + html, err := srv.postEmbedHTML(post) + if err != nil { + return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err)) + } + data := OEmbedResponse{ + Type: "rich", + Version: "1.0", + AuthorName: "@" + post.Author.Handle, + AuthorURL: fmt.Sprintf("https://bsky.app/profile/%s", post.Author.Handle), + ProviderName: "Bluesky Social", + CacheAge: 86400, + Width: &width, + Height: nil, + HTML: html, + } + if post.Author.DisplayName != nil { + data.AuthorName = fmt.Sprintf("%s (@%s)", *post.Author.DisplayName, post.Author.Handle) + } + return c.JSON(http.StatusOK, data) +} + +func (srv *Server) WebPostEmbed(c echo.Context) error { + + // sanity check arguments. don't 4xx, just let app handle if not expected format + rkeyParam := c.Param("rkey") + rkey, err := syntax.ParseRecordKey(rkeyParam) + if err != nil { + return c.String(http.StatusBadRequest, fmt.Sprintf("Invalid RecordKey: %v", err)) + } + didParam := c.Param("did") + did, err := syntax.ParseDID(didParam) + if err != nil { + return c.String(http.StatusBadRequest, fmt.Sprintf("Invalid DID: %v", err)) + } + _ = rkey + _ = did + + // NOTE: this request was't really necessary; the JS will do the same fetch + /* + postView, err := srv.getBlueskyPost(ctx, did, rkey) + if err == ErrPostNotFound { + return c.String(http.StatusNotFound, fmt.Sprintf("%v", err)) + } else if err == ErrPostNotPublic { + return c.String(http.StatusForbidden, fmt.Sprintf("%v", err)) + } else if err != nil { + return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err)) + } + */ + + return c.Render(http.StatusOK, "postEmbed.html", nil) +} diff --git a/bskyweb/cmd/embedr/main.go b/bskyweb/cmd/embedr/main.go new file mode 100644 index 0000000000..9f75ed69af --- /dev/null +++ b/bskyweb/cmd/embedr/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "os" + + _ "github.com/joho/godotenv/autoload" + + logging "github.com/ipfs/go-log" + "github.com/urfave/cli/v2" +) + +var log = logging.Logger("embedr") + +func init() { + logging.SetAllLoggers(logging.LevelDebug) + //logging.SetAllLoggers(logging.LevelWarn) +} + +func main() { + run(os.Args) +} + +func run(args []string) { + + app := cli.App{ + Name: "embedr", + Usage: "web server for embed.bsky.app post embeds", + } + + app.Commands = []*cli.Command{ + &cli.Command{ + Name: "serve", + Usage: "run the server", + Action: serve, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "appview-host", + Usage: "method, hostname, and port of PDS instance", + Value: "https://public.api.bsky.app", + EnvVars: []string{"ATP_APPVIEW_HOST"}, + }, + &cli.StringFlag{ + Name: "http-address", + Usage: "Specify the local IP/port to bind to", + Required: false, + Value: ":8100", + EnvVars: []string{"HTTP_ADDRESS"}, + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Enable debug mode", + Value: false, + Required: false, + EnvVars: []string{"DEBUG"}, + }, + }, + }, + } + app.RunAndExitOnError() +} diff --git a/bskyweb/cmd/embedr/render.go b/bskyweb/cmd/embedr/render.go new file mode 100644 index 0000000000..cc8f0759a0 --- /dev/null +++ b/bskyweb/cmd/embedr/render.go @@ -0,0 +1,16 @@ +package main + +import ( + "html/template" + "io" + + "github.com/labstack/echo/v4" +) + +type Template struct { + templates *template.Template +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} diff --git a/bskyweb/cmd/embedr/server.go b/bskyweb/cmd/embedr/server.go new file mode 100644 index 0000000000..904b4df9a2 --- /dev/null +++ b/bskyweb/cmd/embedr/server.go @@ -0,0 +1,236 @@ +package main + +import ( + "context" + "errors" + "fmt" + "html/template" + "io/fs" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/bluesky-social/indigo/atproto/identity" + "github.com/bluesky-social/indigo/util/cliutil" + "github.com/bluesky-social/indigo/xrpc" + "github.com/bluesky-social/social-app/bskyweb" + + "github.com/klauspost/compress/gzhttp" + "github.com/klauspost/compress/gzip" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/urfave/cli/v2" +) + +type Server struct { + echo *echo.Echo + httpd *http.Server + xrpcc *xrpc.Client + dir identity.Directory +} + +func serve(cctx *cli.Context) error { + debug := cctx.Bool("debug") + httpAddress := cctx.String("http-address") + appviewHost := cctx.String("appview-host") + + // Echo + e := echo.New() + + // create a new session (no auth) + xrpcc := &xrpc.Client{ + Client: cliutil.NewHttpClient(), + Host: appviewHost, + } + + // httpd + var ( + httpTimeout = 2 * time.Minute + httpMaxHeaderBytes = 2 * (1024 * 1024) + gzipMinSizeBytes = 1024 * 2 + gzipCompressionLevel = gzip.BestSpeed + gzipExceptMIMETypes = []string{"image/png"} + ) + + // Wrap the server handler in a gzip handler to compress larger responses. + gzipHandler, err := gzhttp.NewWrapper( + gzhttp.MinSize(gzipMinSizeBytes), + gzhttp.CompressionLevel(gzipCompressionLevel), + gzhttp.ExceptContentTypes(gzipExceptMIMETypes), + ) + if err != nil { + return err + } + + // + // server + // + server := &Server{ + echo: e, + xrpcc: xrpcc, + dir: identity.DefaultDirectory(), + } + + // Create the HTTP server. + server.httpd = &http.Server{ + Handler: gzipHandler(server), + Addr: httpAddress, + WriteTimeout: httpTimeout, + ReadTimeout: httpTimeout, + MaxHeaderBytes: httpMaxHeaderBytes, + } + + e.HideBanner = true + + tmpl := &Template{ + templates: template.Must(template.ParseFS(bskyweb.EmbedrTemplateFS, "embedr-templates/*.html")), + } + e.Renderer = tmpl + e.HTTPErrorHandler = server.errorHandler + + e.IPExtractor = echo.ExtractIPFromXFFHeader() + + // SECURITY: Do not modify without due consideration. + e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ + ContentTypeNosniff: "nosniff", + // diable XFrameOptions; we're embedding here! + HSTSMaxAge: 31536000, // 365 days + // TODO: + // ContentSecurityPolicy + // XSSProtection + })) + e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + // Don't log requests for static content. + Skipper: func(c echo.Context) bool { + return strings.HasPrefix(c.Request().URL.Path, "/static") + }, + })) + e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ + Skipper: middleware.DefaultSkipper, + Store: middleware.NewRateLimiterMemoryStoreWithConfig( + middleware.RateLimiterMemoryStoreConfig{ + Rate: 10, // requests per second + Burst: 30, // allow bursts + ExpiresIn: 3 * time.Minute, // garbage collect entries older than 3 minutes + }, + ), + IdentifierExtractor: func(ctx echo.Context) (string, error) { + id := ctx.RealIP() + return id, nil + }, + DenyHandler: func(c echo.Context, identifier string, err error) error { + return c.String(http.StatusTooManyRequests, "Your request has been rate limited. Please try again later. Contact support@bsky.app if you believe this was a mistake.\n") + }, + })) + + // redirect trailing slash to non-trailing slash. + // all of our current endpoints have no trailing slash. + e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{ + RedirectCode: http.StatusFound, + })) + + // + // configure routes + // + // static files + staticHandler := http.FileServer(func() http.FileSystem { + if debug { + log.Debugf("serving static file from the local file system") + return http.FS(os.DirFS("embedr-static")) + } + fsys, err := fs.Sub(bskyweb.EmbedrStaticFS, "embedr-static") + if err != nil { + log.Fatal(err) + } + return http.FS(fsys) + }()) + + e.GET("/robots.txt", echo.WrapHandler(staticHandler)) + e.GET("/ips-v4", echo.WrapHandler(staticHandler)) + e.GET("/ips-v6", echo.WrapHandler(staticHandler)) + e.GET("/.well-known/*", echo.WrapHandler(staticHandler)) + e.GET("/security.txt", func(c echo.Context) error { + return c.Redirect(http.StatusMovedPermanently, "/.well-known/security.txt") + }) + e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler)), func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + path := c.Request().URL.Path + maxAge := 1 * (60 * 60) // default is 1 hour + + // Cache javascript and images files for 1 week, which works because + // they're always versioned (e.g. /static/js/main.64c14927.js) + if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") { + maxAge = 7 * (60 * 60 * 24) // 1 week + } + + c.Response().Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge)) + return next(c) + } + }) + + // actual routes + e.GET("/", server.WebHome) + e.GET("/iframe-resize.js", echo.WrapHandler(staticHandler)) + e.GET("/embed.js", echo.WrapHandler(staticHandler)) + e.GET("/oembed", server.WebOEmbed) + e.GET("/embed/:did/app.bsky.feed.post/:rkey", server.WebPostEmbed) + + // Start the server. + log.Infof("starting server address=%s", httpAddress) + go func() { + if err := server.httpd.ListenAndServe(); err != nil { + if !errors.Is(err, http.ErrServerClosed) { + log.Errorf("HTTP server shutting down unexpectedly: %s", err) + } + } + }() + + // Wait for a signal to exit. + log.Info("registering OS exit signal handler") + quit := make(chan struct{}) + exitSignals := make(chan os.Signal, 1) + signal.Notify(exitSignals, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-exitSignals + log.Infof("received OS exit signal: %s", sig) + + // Shut down the HTTP server. + if err := server.Shutdown(); err != nil { + log.Errorf("HTTP server shutdown error: %s", err) + } + + // Trigger the return that causes an exit. + close(quit) + }() + <-quit + log.Infof("graceful shutdown complete") + return nil +} + +func (srv *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + srv.echo.ServeHTTP(rw, req) +} + +func (srv *Server) Shutdown() error { + log.Info("shutting down") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + return srv.httpd.Shutdown(ctx) +} + +func (srv *Server) errorHandler(err error, c echo.Context) { + code := http.StatusInternalServerError + if he, ok := err.(*echo.HTTPError); ok { + code = he.Code + } + c.Logger().Error(err) + data := map[string]interface{}{ + "statusCode": code, + } + c.Render(code, "error.html", data) +} diff --git a/bskyweb/cmd/embedr/snippet.go b/bskyweb/cmd/embedr/snippet.go new file mode 100644 index 0000000000..b93acb2cd1 --- /dev/null +++ b/bskyweb/cmd/embedr/snippet.go @@ -0,0 +1,79 @@ +package main + +import ( + "bytes" + "fmt" + "html/template" + + appbsky "github.com/bluesky-social/indigo/api/bsky" + "github.com/bluesky-social/indigo/atproto/syntax" +) + +func (srv *Server) postEmbedHTML(postView *appbsky.FeedDefs_PostView) (string, error) { + // ensure that there isn't an injection from the URI + aturi, err := syntax.ParseATURI(postView.Uri) + if err != nil { + log.Error("bad AT-URI in reponse", "aturi", aturi, "err", err) + return "", err + } + + post, ok := postView.Record.Val.(*appbsky.FeedPost) + if !ok { + log.Error("bad post record value", "err", err) + return "", err + } + + const tpl = `
{{ .PostText }}

{{ .PostAuthor }} {{ .PostIndexedAt }}
` + + t, err := template.New("snippet").Parse(tpl) + if err != nil { + log.Error("template parse error", "err", err) + return "", err + } + + sortAt := postView.IndexedAt + createdAt, err := syntax.ParseDatetime(post.CreatedAt) + if nil == err && createdAt.String() < sortAt { + sortAt = createdAt.String() + } + + var lang string + if len(post.Langs) > 0 { + lang = post.Langs[0] + } + var authorName string + if postView.Author.DisplayName != nil { + authorName = fmt.Sprintf("%s (@%s)", *postView.Author.DisplayName, postView.Author.Handle) + } else { + authorName = fmt.Sprintf("@%s", postView.Author.Handle) + } + data := struct { + PostURI template.URL + PostCID string + PostLang string + PostText string + PostAuthor string + PostIndexedAt string + ProfileURL template.URL + PostURL template.URL + WidgetURL template.URL + }{ + PostURI: template.URL(postView.Uri), + PostCID: postView.Cid, + PostLang: lang, + PostText: post.Text, + PostAuthor: authorName, + PostIndexedAt: sortAt, + ProfileURL: template.URL(fmt.Sprintf("https://bsky.app/profile/%s?ref_src=embed", aturi.Authority())), + PostURL: template.URL(fmt.Sprintf("https://bsky.app/profile/%s/post/%s?ref_src=embed", aturi.Authority(), aturi.RecordKey())), + WidgetURL: template.URL("https://embed.bsky.app/static/embed.js"), + } + + var buf bytes.Buffer + err = t.Execute(&buf, data) + if err != nil { + log.Error("template parse error", "err", err) + return "", err + } + return buf.String(), nil +} diff --git a/bskyweb/embedr-static/.well-known/security.txt b/bskyweb/embedr-static/.well-known/security.txt new file mode 100644 index 0000000000..8173cb72d6 --- /dev/null +++ b/bskyweb/embedr-static/.well-known/security.txt @@ -0,0 +1,4 @@ +Contact: mailto:security@bsky.app +Preferred-Languages: en +Canonical: https://bsky.app/.well-known/security.txt +Acknowledgements: https://github.com/bluesky-social/atproto/blob/main/CONTRIBUTORS.md diff --git a/bskyweb/embedr-static/embed.js b/bskyweb/embedr-static/embed.js new file mode 100644 index 0000000000..15964a76c3 --- /dev/null +++ b/bskyweb/embedr-static/embed.js @@ -0,0 +1 @@ +/* embed javascript widget will go here */ diff --git a/bskyweb/embedr-static/favicon-16x16.png b/bskyweb/embedr-static/favicon-16x16.png new file mode 100644 index 0000000000..ea256e0569 Binary files /dev/null and b/bskyweb/embedr-static/favicon-16x16.png differ diff --git a/bskyweb/embedr-static/favicon-32x32.png b/bskyweb/embedr-static/favicon-32x32.png new file mode 100644 index 0000000000..a5ca7eed1e Binary files /dev/null and b/bskyweb/embedr-static/favicon-32x32.png differ diff --git a/bskyweb/embedr-static/favicon.png b/bskyweb/embedr-static/favicon.png new file mode 100644 index 0000000000..ddf55f4c81 Binary files /dev/null and b/bskyweb/embedr-static/favicon.png differ diff --git a/bskyweb/embedr-static/iframe-resize.js b/bskyweb/embedr-static/iframe-resize.js new file mode 100644 index 0000000000..6bf2793df5 --- /dev/null +++ b/bskyweb/embedr-static/iframe-resize.js @@ -0,0 +1 @@ +/* script to resize embed ifame would go here? */ diff --git a/bskyweb/embedr-static/ips-v4 b/bskyweb/embedr-static/ips-v4 new file mode 100644 index 0000000000..087996ef9a --- /dev/null +++ b/bskyweb/embedr-static/ips-v4 @@ -0,0 +1,30 @@ +13.59.225.103/32 +3.18.47.21/32 +18.191.104.94/32 +3.129.134.255/32 +3.129.237.113/32 +3.138.56.230/32 +44.218.10.163/32 +54.89.116.251/32 +44.217.166.202/32 +54.208.221.149/32 +54.166.110.54/32 +54.208.146.65/32 +3.129.234.15/32 +3.138.168.48/32 +3.23.53.192/32 +52.14.89.53/32 +3.18.126.246/32 +3.136.69.4/32 +3.22.137.152/32 +3.132.247.113/32 +3.141.186.104/32 +18.222.43.214/32 +3.14.35.197/32 +3.23.182.70/32 +18.224.144.69/32 +3.129.98.29/32 +3.130.134.20/32 +3.17.197.213/32 +18.223.234.21/32 +3.20.248.177/32 diff --git a/bskyweb/embedr-static/ips-v6 b/bskyweb/embedr-static/ips-v6 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bskyweb/embedr-static/robots.txt b/bskyweb/embedr-static/robots.txt new file mode 100644 index 0000000000..4f8510d18d --- /dev/null +++ b/bskyweb/embedr-static/robots.txt @@ -0,0 +1,9 @@ +# Hello Friends! +# If you are considering bulk or automated crawling, you may want to look in +# to our protocol (API), including a firehose of updates. See: https://atproto.com/ + +# By default, may crawl anything on this domain. HTTP 429 ("backoff") status +# codes are used for rate-limiting. Up to a handful concurrent requests should +# be ok. +User-Agent: * +Allow: / diff --git a/bskyweb/embedr-templates/error.html b/bskyweb/embedr-templates/error.html new file mode 100644 index 0000000000..5aa04c83bf --- /dev/null +++ b/bskyweb/embedr-templates/error.html @@ -0,0 +1 @@ +placeholder! diff --git a/bskyweb/embedr-templates/home.html b/bskyweb/embedr-templates/home.html new file mode 100644 index 0000000000..f938c32d6e --- /dev/null +++ b/bskyweb/embedr-templates/home.html @@ -0,0 +1,8 @@ + + + + +

embed.bsky.app homepage

+

could redirect to bsky.app? or show a "create embed" widget? + + diff --git a/bskyweb/embedr-templates/oembed.html b/bskyweb/embedr-templates/oembed.html new file mode 100644 index 0000000000..646f0a482c --- /dev/null +++ b/bskyweb/embedr-templates/oembed.html @@ -0,0 +1 @@ +oembed JSON response will go here diff --git a/bskyweb/embedr-templates/postEmbed.html b/bskyweb/embedr-templates/postEmbed.html new file mode 100644 index 0000000000..6329b3a199 --- /dev/null +++ b/bskyweb/embedr-templates/postEmbed.html @@ -0,0 +1 @@ +embed post HTML will go here diff --git a/bskyweb/static.go b/bskyweb/static.go index a67d189f57..38adb83335 100644 --- a/bskyweb/static.go +++ b/bskyweb/static.go @@ -4,3 +4,6 @@ import "embed" //go:embed static/* var StaticFS embed.FS + +//go:embed embedr-static/* +var EmbedrStaticFS embed.FS diff --git a/bskyweb/templates.go b/bskyweb/templates.go index ce3fa29af7..a66965aba4 100644 --- a/bskyweb/templates.go +++ b/bskyweb/templates.go @@ -4,3 +4,6 @@ import "embed" //go:embed templates/* var TemplateFS embed.FS + +//go:embed embedr-templates/* +var EmbedrTemplateFS embed.FS diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index 34e5901069..cb0cea24b4 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -235,6 +235,17 @@ inset:0; animation: rotate 500ms linear infinite; } + + @keyframes avatarHoverFadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes avatarHoverFadeOut { + from { opacity: 1; } + to { opacity: 0; } + } + {% include "scripts.html" %} diff --git a/bskyweb/templates/post.html b/bskyweb/templates/post.html index af6b768b38..d1fbea0ac3 100644 --- a/bskyweb/templates/post.html +++ b/bskyweb/templates/post.html @@ -36,6 +36,8 @@ + + {% endif -%} {%- endblock %} diff --git a/docs/build.md b/docs/build.md index d1f9f93b5a..deab91a5ba 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,7 +2,8 @@ ## App Build -- Set up your environment [using the react native instructions](https://reactnative.dev/docs/environment-setup). +- Set up your environment [using the expo instructions](https://docs.expo.dev/guides/local-app-development/). + - make sure that the JAVA_HOME points to the zulu-17 directory in your `.zshrc` or `.bashrc` file: `export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home`. DO NOT use another JDK or you will encounter build errors. - If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods: - Check if you've installed Cocoapods through `homebrew`. If you have, remove it: - `brew info cocoapods` @@ -14,7 +15,7 @@ - `rbenv global 2.7.6` - Add `eval "$(rbenv init - zsh)"` to your `~/.zshrc` - From inside the project directory: - - `bundler install` + - `bundler install` (this will install Cocoapods) - Setup your environment [for e2e testing using detox](https://wix.github.io/Detox/docs/introduction/getting-started): - `yarn global add detox-cli` - `brew tap wix/brew` @@ -27,7 +28,7 @@ - `git clone git@github.com:bluesky-social/atproto.git` - `cd atproto` - `brew install pnpm` - - `brew install jq` + - optional: `brew install jq` - `pnpm i` - `pnpm build` - Start the docker daemon (on MacOS this entails starting the Docker Desktop app) @@ -38,11 +39,22 @@ - Xcode must be installed for this to run. - A simulator must be preconfigured in Xcode settings. - if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`. + - if the simulator download keeps failing you can download it from the developer website. + - [Apple Developer](https://developer.apple.com/download/all/?q=Simulator%20Runtime) + - `xcode-select -s /Applications/Xcode.app` + - `xcodebuild -runFirstLaunch` + - `xcrun simctl runtime add "~/Downloads/iOS_17.4_Simulator_Runtime.dmg"` (adapt the path to the downloaded file) - In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`. - - Pods must be installed: - - From the project directory root: `cd ios && pod install`. - Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator. + - Make sure you do have a certificate: open Xcode > Settings > Accounts > (sign-in) > Manage Certificates > + > Apple Development > Done. + - If you still encounter issues, try `rm -rf ios` before trying to build again (`yarn ios`) - Android: `yarn android` + - Install "Android Studio" + - Make sure you have the Android SDK installed (Android Studio > Tools > Android SDK). + - In "SDK Platforms": "Android x" (where x is Android's current version). + - In "SDK Tools": "Android SDK Build-Tools" and "Android Emulator" are required. + - Add `export ANDROID_HOME=/Users//Library/Android/sdk` to your `.zshrc` or `.bashrc` (and restart your terminal). + - Setup an emulator (Android Studio > Tools > Device Manager). - Web: `yarn web` - If you are cloning or forking this repo as an open-source developer, please check the tips below as well - Run e2e tests @@ -83,11 +95,10 @@ To run the build with Go, use staging credentials, your own, or any other accoun ``` cd social-app yarn && yarn build-web -cp ./web-build/static/js/*.* bskyweb/static/js/ cd bskyweb/ go mod tidy go build -v -tags timetzdata -o bskyweb ./cmd/bskyweb -./bskyweb serve --pds-host=https://staging.bsky.dev --handle= --password= +./bskyweb serve --appview-host=https://public.api.bsky.app ``` On build success, access the application at [http://localhost:8100/](http://localhost:8100/). Subsequent changes require re-running the above steps in order to be reflected. diff --git a/eslint/index.js b/eslint/index.js index daf5bd81d9..bb31a942d1 100644 --- a/eslint/index.js +++ b/eslint/index.js @@ -3,5 +3,6 @@ module.exports = { rules: { 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), + 'use-typed-gates': require('./use-typed-gates'), }, } diff --git a/eslint/use-typed-gates.js b/eslint/use-typed-gates.js new file mode 100644 index 0000000000..b245072ba5 --- /dev/null +++ b/eslint/use-typed-gates.js @@ -0,0 +1,31 @@ +'use strict' + +exports.create = function create(context) { + return { + ImportSpecifier(node) { + if ( + !node.local || + node.local.type !== 'Identifier' || + node.local.name !== 'useGate' + ) { + return + } + if ( + node.parent.type !== 'ImportDeclaration' || + !node.parent.source || + node.parent.source.type !== 'Literal' + ) { + return + } + const source = node.parent.source.value + if (source.startsWith('statsig') || source.startsWith('@statsig')) { + context.report({ + node, + message: + "Use useGate() from '#/lib/statsig/statsig' instead of the one on npm.", + }) + } + // TODO: Verify gate() call results aren't stored in variables. + }, + } +} diff --git a/jest/test-pds.ts b/jest/test-pds.ts index b70a9abf08..1c52d944c6 100644 --- a/jest/test-pds.ts +++ b/jest/test-pds.ts @@ -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 { 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 { @@ -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() } diff --git a/metro.config.js b/metro.config.js index a49d95f9aa..792262ffc4 100644 --- a/metro.config.js +++ b/metro.config.js @@ -10,15 +10,6 @@ cfg.transformer.getTransformOptions = async () => ({ transform: { experimentalImportSupport: true, inlineRequires: true, - nonInlinedRequires: [ - // We can remove this option and rely on the default after - // https://github.com/facebook/metro/pull/1126 is released. - 'React', - 'react', - 'react/jsx-dev-runtime', - 'react/jsx-runtime', - 'react-native', - ], }, }) diff --git a/modules/expo-bluesky-gif-view/android/build.gradle b/modules/expo-bluesky-gif-view/android/build.gradle new file mode 100644 index 0000000000..c209a35aec --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/build.gradle @@ -0,0 +1,98 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'expo.modules.blueskygifview' +version = '0.5.0' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 33) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } + } + + namespace "expo.modules.blueskygifview" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.5.0" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + def GLIDE_VERSION = "4.13.2" + + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + + // Keep glide version up to date with expo-image so that we don't have duplicate deps + implementation 'com.github.bumptech.glide:glide:4.13.2' +} diff --git a/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml b/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bdae66c8f5 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt new file mode 100644 index 0000000000..5d20848453 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt @@ -0,0 +1,37 @@ +package expo.modules.blueskygifview + +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Animatable +import androidx.appcompat.widget.AppCompatImageView + +class AppCompatImageViewExtended(context: Context, private val parent: GifView): AppCompatImageView(context) { + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (this.drawable is Animatable) { + if (!parent.isLoaded) { + parent.isLoaded = true + parent.firePlayerStateChange() + } + + if (!parent.isPlaying) { + this.pause() + } + } + } + + fun pause() { + val drawable = this.drawable + if (drawable is Animatable) { + drawable.stop() + } + } + + fun play() { + val drawable = this.drawable + if (drawable is Animatable) { + drawable.start() + } + } +} \ No newline at end of file diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt new file mode 100644 index 0000000000..625e1d45f9 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt @@ -0,0 +1,54 @@ +package expo.modules.blueskygifview + +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBlueskyGifViewModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoBlueskyGifView") + + AsyncFunction("prefetchAsync") { sources: List -> + val activity = appContext.currentActivity ?: return@AsyncFunction + val glide = Glide.with(activity) + + sources.forEach { source -> + glide + .download(source) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .submit() + } + } + + View(GifView::class) { + Events( + "onPlayerStateChange" + ) + + Prop("source") { view: GifView, source: String -> + view.source = source + } + + Prop("placeholderSource") { view: GifView, source: String -> + view.placeholderSource = source + } + + Prop("autoplay") { view: GifView, autoplay: Boolean -> + view.autoplay = autoplay + } + + AsyncFunction("playAsync") { view: GifView -> + view.play() + } + + AsyncFunction("pauseAsync") { view: GifView -> + view.pause() + } + + AsyncFunction("toggleAsync") { view: GifView -> + view.toggle() + } + } + } +} diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt new file mode 100644 index 0000000000..be5830df7a --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt @@ -0,0 +1,180 @@ +package expo.modules.blueskygifview + + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView + +class GifView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { + // Events + private val onPlayerStateChange by EventDispatcher() + + // Glide + private val activity = appContext.currentActivity ?: throw Exceptions.MissingActivity() + private val glide = Glide.with(activity) + val imageView = AppCompatImageViewExtended(context, this) + var isPlaying = true + var isLoaded = false + + // Requests + private var placeholderRequest: Target? = null + private var webpRequest: Target? = null + + // Props + var placeholderSource: String? = null + var source: String? = null + var autoplay: Boolean = true + set(value) { + field = value + + if (value) { + this.play() + } else { + this.pause() + } + } + + + // + + init { + this.setBackgroundColor(Color.TRANSPARENT) + + this.imageView.setBackgroundColor(Color.TRANSPARENT) + this.imageView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + + this.addView(this.imageView) + } + + override fun onAttachedToWindow() { + if (this.imageView.drawable == null || this.imageView.drawable !is Animatable) { + this.load() + } else if (this.isPlaying) { + this.imageView.play() + } + super.onAttachedToWindow() + } + + override fun onDetachedFromWindow() { + this.imageView.pause() + super.onDetachedFromWindow() + } + + // + + // + + private fun load() { + if (placeholderSource == null || source == null) { + return + } + + this.webpRequest = glide.load(source) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(false) + .listener(object: RequestListener { + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: com.bumptech.glide.load.DataSource?, + isFirstResource: Boolean + ): Boolean { + if (placeholderRequest != null) { + glide.clear(placeholderRequest) + } + return false + } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return true + } + }) + .into(this.imageView) + + if (this.imageView.drawable == null || this.imageView.drawable !is Animatable) { + this.placeholderRequest = glide.load(placeholderSource) + .diskCacheStrategy(DiskCacheStrategy.DATA) + // Let's not bloat the memory cache with placeholders + .skipMemoryCache(true) + .listener(object: RequestListener { + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: com.bumptech.glide.load.DataSource?, + isFirstResource: Boolean + ): Boolean { + // Incase this request finishes after the webp, let's just not set + // the drawable. This shouldn't happen because the request should get cancelled + if (imageView.drawable == null) { + imageView.setImageDrawable(resource) + } + return true + } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return true + } + }) + .submit() + } + } + + // + + // + + fun play() { + this.imageView.play() + this.isPlaying = true + this.firePlayerStateChange() + } + + fun pause() { + this.imageView.pause() + this.isPlaying = false + this.firePlayerStateChange() + } + + fun toggle() { + if (this.isPlaying) { + this.pause() + } else { + this.play() + } + } + + // + + // + + fun firePlayerStateChange() { + onPlayerStateChange(mapOf( + "isPlaying" to this.isPlaying, + "isLoaded" to this.isLoaded, + )) + } + + // +} diff --git a/modules/expo-bluesky-gif-view/expo-module.config.json b/modules/expo-bluesky-gif-view/expo-module.config.json new file mode 100644 index 0000000000..0756c8e24c --- /dev/null +++ b/modules/expo-bluesky-gif-view/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["ios", "android", "web"], + "ios": { + "modules": ["ExpoBlueskyGifViewModule"] + }, + "android": { + "modules": ["expo.modules.blueskygifview.ExpoBlueskyGifViewModule"] + } +} diff --git a/modules/expo-bluesky-gif-view/index.ts b/modules/expo-bluesky-gif-view/index.ts new file mode 100644 index 0000000000..0244a54914 --- /dev/null +++ b/modules/expo-bluesky-gif-view/index.ts @@ -0,0 +1 @@ +export {GifView} from './src/GifView' diff --git a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec new file mode 100644 index 0000000000..ddd0877b24 --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'ExpoBlueskyGifView' + s.version = '1.0.0' + s.summary = 'A simple GIF player for Bluesky' + s.description = 'A simple GIF player for Bluesky' + s.author = '' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'SDWebImage', '~> 5.17.0' + s.dependency 'SDWebImageWebPCoder', '~> 0.13.0' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift new file mode 100644 index 0000000000..7c7132290d --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift @@ -0,0 +1,47 @@ +import ExpoModulesCore +import SDWebImage +import SDWebImageWebPCoder + +public class ExpoBlueskyGifViewModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoBlueskyGifView") + + OnCreate { + SDImageCodersManager.shared.addCoder(SDImageGIFCoder.shared) + } + + AsyncFunction("prefetchAsync") { (sources: [URL]) in + SDWebImagePrefetcher.shared.prefetchURLs(sources, context: Util.createContext(), progress: nil) + } + + View(GifView.self) { + Events( + "onPlayerStateChange" + ) + + Prop("source") { (view: GifView, prop: String) in + view.source = prop + } + + Prop("placeholderSource") { (view: GifView, prop: String) in + view.placeholderSource = prop + } + + Prop("autoplay") { (view: GifView, prop: Bool) in + view.autoplay = prop + } + + AsyncFunction("toggleAsync") { (view: GifView) in + view.toggle() + } + + AsyncFunction("playAsync") { (view: GifView) in + view.play() + } + + AsyncFunction("pauseAsync") { (view: GifView) in + view.pause() + } + } + } +} diff --git a/modules/expo-bluesky-gif-view/ios/GifView.swift b/modules/expo-bluesky-gif-view/ios/GifView.swift new file mode 100644 index 0000000000..de722d7a63 --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/GifView.swift @@ -0,0 +1,185 @@ +import ExpoModulesCore +import SDWebImage +import SDWebImageWebPCoder + +typealias SDWebImageContext = [SDWebImageContextOption: Any] + +public class GifView: ExpoView, AVPlayerViewControllerDelegate { + // Events + private let onPlayerStateChange = EventDispatcher() + + // SDWebImage + private let imageView = SDAnimatedImageView(frame: .zero) + private let imageManager = SDWebImageManager( + cache: SDImageCache.shared, + loader: SDImageLoadersManager.shared + ) + private var isPlaying = true + private var isLoaded = false + + // Requests + private var webpOperation: SDWebImageCombinedOperation? + private var placeholderOperation: SDWebImageCombinedOperation? + + // Props + var source: String? = nil + var placeholderSource: String? = nil + var autoplay = true { + didSet { + if !autoplay { + self.pause() + } else { + self.play() + } + } + } + + // MARK: - Lifecycle + + public required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + self.clipsToBounds = true + + self.imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.imageView.layer.masksToBounds = false + self.imageView.backgroundColor = .clear + self.imageView.contentMode = .scaleToFill + + // We have to explicitly set this to false. If we don't, every time + // the view comes into the viewport, it will start animating again + self.imageView.autoPlayAnimatedImage = false + + self.addSubview(self.imageView) + } + + public override func willMove(toWindow newWindow: UIWindow?) { + if newWindow == nil { + // Don't cancel the placeholder operation, because we really want that to complete for + // when we scroll back up + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + } else if self.imageView.image == nil { + self.load() + } + } + + // MARK: - Loading + + private func load() { + guard let source = self.source, let placeholderSource = self.placeholderSource else { + return + } + + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + + // We only need to start an operation for the placeholder if it doesn't exist + // in the cache already. Cache key is by default the absolute URL of the image. + // See: + // https://github.com/SDWebImage/SDWebImage/blob/master/Docs/HowToUse.md#using-asynchronous-image-caching-independently + if !SDImageCache.shared.diskImageDataExists(withKey: source), + let url = URL(string: placeholderSource) + { + self.placeholderOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + + if let url = URL(string: source) { + self.webpOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + } + + private func setImage(_ image: UIImage) { + if self.imageView.image == nil || image.sd_isAnimated { + self.imageView.image = image + } + + if image.sd_isAnimated { + self.firePlayerStateChange() + if isPlaying { + self.imageView.startAnimating() + } + } + } + + // MARK: - Loading blocks + + private func onProgress(_ receivedSize: Int, _ expectedSize: Int, _ imageUrl: URL?) {} + + private func onLoaded( + _ image: UIImage?, + _ data: Data?, + _ error: Error?, + _ cacheType: SDImageCacheType, + _ finished: Bool, + _ imageUrl: URL? + ) { + guard finished else { + return + } + + if let placeholderSource = self.placeholderSource, + imageUrl?.absoluteString == placeholderSource, + self.imageView.image == nil, + let image = image + { + self.setImage(image) + return + } + + if let source = self.source, + imageUrl?.absoluteString == source, + // UIImage perf suckssss if the image is animated + let data = data, + let animatedImage = SDAnimatedImage(data: data) + { + self.placeholderOperation?.cancel() + self.isPlaying = self.autoplay + self.isLoaded = true + self.setImage(animatedImage) + self.firePlayerStateChange() + } + } + + // MARK: - Playback Controls + + func play() { + self.imageView.startAnimating() + self.isPlaying = true + self.firePlayerStateChange() + } + + func pause() { + self.imageView.stopAnimating() + self.isPlaying = false + self.firePlayerStateChange() + } + + func toggle() { + if self.isPlaying { + self.pause() + } else { + self.play() + } + } + + // MARK: - Util + + private func firePlayerStateChange() { + onPlayerStateChange([ + "isPlaying": self.isPlaying, + "isLoaded": self.isLoaded + ]) + } +} diff --git a/modules/expo-bluesky-gif-view/ios/Util.swift b/modules/expo-bluesky-gif-view/ios/Util.swift new file mode 100644 index 0000000000..55ed4152aa --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/Util.swift @@ -0,0 +1,17 @@ +import SDWebImage + +class Util { + static func createContext() -> SDWebImageContext { + var context = SDWebImageContext() + + // SDAnimatedImage for some reason has issues whenever loaded from memory. Instead, we + // will just use the disk. SDWebImage will manage this cache for us, so we don't need + // to worry about clearing it. + context[.originalQueryCacheType] = SDImageCacheType.disk.rawValue + context[.originalStoreCacheType] = SDImageCacheType.disk.rawValue + context[.queryCacheType] = SDImageCacheType.disk.rawValue + context[.storeCacheType] = SDImageCacheType.disk.rawValue + + return context + } +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.tsx b/modules/expo-bluesky-gif-view/src/GifView.tsx new file mode 100644 index 0000000000..87258de17b --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import {requireNativeModule} from 'expo' +import {requireNativeViewManager} from 'expo-modules-core' + +import {GifViewProps} from './GifView.types' + +const NativeModule = requireNativeModule('ExpoBlueskyGifView') +const NativeView: React.ComponentType< + GifViewProps & {ref: React.RefObject} +> = requireNativeViewManager('ExpoBlueskyGifView') + +export class GifView extends React.PureComponent { + // TODO native types, should all be the same as those in this class + private nativeRef: React.RefObject = React.createRef() + + constructor(props: GifViewProps | Readonly) { + super(props) + } + + static async prefetchAsync(sources: string[]): Promise { + return await NativeModule.prefetchAsync(sources) + } + + async playAsync(): Promise { + await this.nativeRef.current.playAsync() + } + + async pauseAsync(): Promise { + await this.nativeRef.current.pauseAsync() + } + + async toggleAsync(): Promise { + await this.nativeRef.current.toggleAsync() + } + + render() { + return + } +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.types.ts b/modules/expo-bluesky-gif-view/src/GifView.types.ts new file mode 100644 index 0000000000..29ec277f2b --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.types.ts @@ -0,0 +1,15 @@ +import {ViewProps} from 'react-native' + +export interface GifViewStateChangeEvent { + nativeEvent: { + isPlaying: boolean + isLoaded: boolean + } +} + +export interface GifViewProps extends ViewProps { + autoplay?: boolean + source?: string + placeholderSource?: string + onPlayerStateChange?: (event: GifViewStateChangeEvent) => void +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.web.tsx b/modules/expo-bluesky-gif-view/src/GifView.web.tsx new file mode 100644 index 0000000000..c197e01a1d --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.web.tsx @@ -0,0 +1,82 @@ +import * as React from 'react' +import {StyleSheet} from 'react-native' + +import {GifViewProps} from './GifView.types' + +export class GifView extends React.PureComponent { + private readonly videoPlayerRef: React.RefObject = + React.createRef() + private isLoaded = false + + constructor(props: GifViewProps | Readonly) { + super(props) + } + + componentDidUpdate(prevProps: Readonly) { + if (prevProps.autoplay !== this.props.autoplay) { + if (this.props.autoplay) { + this.playAsync() + } else { + this.pauseAsync() + } + } + } + + static async prefetchAsync(_: string[]): Promise { + console.warn('prefetchAsync is not supported on web') + } + + private firePlayerStateChangeEvent = () => { + this.props.onPlayerStateChange?.({ + nativeEvent: { + isPlaying: !this.videoPlayerRef.current?.paused, + isLoaded: this.isLoaded, + }, + }) + } + + private onLoad = () => { + // Prevent multiple calls to onLoad because onCanPlay will fire after each loop + if (this.isLoaded) { + return + } + + this.isLoaded = true + this.firePlayerStateChangeEvent() + } + + async playAsync(): Promise { + this.videoPlayerRef.current?.play() + } + + async pauseAsync(): Promise { + this.videoPlayerRef.current?.pause() + } + + async toggleAsync(): Promise { + if (this.videoPlayerRef.current?.paused) { + await this.playAsync() + } else { + await this.pauseAsync() + } + } + + render() { + return ( +

+ {props.children} + {isVisible && ( + +
+
+ +
+
+
+ )} +
+ ) +} + +let Card = ({did, hide}: {did: string; hide: () => void}): React.ReactNode => { + const t = useTheme() + + const profile = useProfileQuery({did}) + const moderationOpts = useModerationOpts() + + const data = profile.data + + return ( + + {data && moderationOpts ? ( + + ) : ( + + + + )} + + ) +} +Card = React.memo(Card) + +function Inner({ + profile, + moderationOpts, + hide, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed + moderationOpts: ModerationOpts + hide: () => void +}) { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const moderation = React.useMemo( + () => moderateProfile(profile, moderationOpts), + [profile, moderationOpts], + ) + const [descriptionRT] = useRichText(profile.description ?? '') + const profileShadow = useProfileShadow(profile) + const {follow, unfollow} = useFollowMethods({ + profile: profileShadow, + logContext: 'ProfileHoverCard', + }) + const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy + const following = formatCount(profile.followsCount || 0) + const followers = formatCount(profile.followersCount || 0) + const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') + const profileURL = makeProfileLink({ + did: profile.did, + handle: profile.handle, + }) + const isMe = React.useMemo( + () => currentAccount?.did === profile.did, + [currentAccount, profile], + ) + + return ( + + + + + + + {!isMe && ( + + )} + + + + + + {sanitizeDisplayName( + profile.displayName || sanitizeHandle(profile.handle), + moderation.ui('displayName'), + )} + + + + + + + {!blockHide && ( + <> + + + + {followers} + + {pluralizedFollowers} + + + + + + {following} + following + + + + + {profile.description?.trim() && !moderation.ui('profileView').blur ? ( + + + + ) : undefined} + + )} + + ) +} diff --git a/src/components/ProfileHoverCard/types.ts b/src/components/ProfileHoverCard/types.ts new file mode 100644 index 0000000000..a62279c96c --- /dev/null +++ b/src/components/ProfileHoverCard/types.ts @@ -0,0 +1,7 @@ +import React from 'react' + +export type ProfileHoverCardProps = { + children: React.ReactElement + did: string + inline?: boolean +} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index c92fe26523..0a171674de 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -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 }>) { diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx index c676983485..8219b20951 100644 --- a/src/components/ReportDialog/SelectReportOptionView.tsx +++ b/src/components/ReportDialog/SelectReportOptionView.tsx @@ -90,6 +90,7 @@ export function SelectReportOptionView({ return ( + + + + ) +} + +/** + * Based on a snippet of code from React, which itself was based on the escape-html library. + * Copyright (c) Meta Platforms, Inc. and affiliates + * Copyright (c) 2012-2013 TJ Holowaychuk + * Copyright (c) 2015 Andreas Lubbe + * Copyright (c) 2015 Tiancheng "Timothy" Gu + * Licensed as MIT. + */ +const matchHtmlRegExp = /["'&<>]/ +function escapeHtml(string: string) { + const str = String(string) + const match = matchHtmlRegExp.exec(str) + if (!match) { + return str + } + let escape + let html = '' + let index + let lastIndex = 0 + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&' + break + case 39: // ' + escape = ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break + default: + continue + } + if (lastIndex !== index) { + html += str.slice(lastIndex, index) + } + lastIndex = index + 1 + html += escape + } + return lastIndex !== index ? html + str.slice(lastIndex, index) : html +} diff --git a/src/components/dialogs/EmbedConsent.tsx b/src/components/dialogs/EmbedConsent.tsx new file mode 100644 index 0000000000..c3fefd9f09 --- /dev/null +++ b/src/components/dialogs/EmbedConsent.tsx @@ -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 ( + + + + + + + External Media + + + + + + This content is hosted by {externalEmbedLabels[source]}. Do you + want to enable external media? + + + + + + 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. + + + + + + + + + + + + ) +} diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx new file mode 100644 index 0000000000..024188ec44 --- /dev/null +++ b/src/components/dialogs/GifSelect.tsx @@ -0,0 +1,305 @@ +import React, {useCallback, useMemo, useRef, useState} from 'react' +import {Keyboard, TextInput, View} from 'react-native' +import {Image} from 'expo-image' +import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logEvent} from '#/lib/statsig/statsig' +import {cleanError} from '#/lib/strings/errors' +import {isWeb} from '#/platform/detection' +import { + Gif, + useFeaturedGifsQuery, + useGifSearchQuery, +} from '#/state/queries/tenor' +import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' +import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import {useThrottledValue} from '#/components/hooks/useThrottledValue' +import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' +import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' +import {Button, ButtonIcon, ButtonText} from '../Button' +import {ListFooter, ListMaybePlaceholder} from '../Lists' + +export function GifSelectDialog({ + control, + onClose, + onSelectGif: onSelectGifProp, +}: { + control: Dialog.DialogControlProps + onClose: () => void + onSelectGif: (gif: Gif) => void +}) { + const onSelectGif = useCallback( + (gif: Gif) => { + control.close(() => onSelectGifProp(gif)) + }, + [control, onSelectGifProp], + ) + + const renderErrorBoundary = useCallback( + (error: any) => , + [], + ) + + return ( + + + + + + + ) +} + +function GifList({ + control, + onSelectGif, +}: { + control: Dialog.DialogControlProps + onSelectGif: (gif: Gif) => void +}) { + const {_} = useLingui() + const t = useTheme() + const {gtMobile} = useBreakpoints() + const textInputRef = useRef(null) + const listRef = useRef(null) + const [undeferredSearch, setSearch] = useState('') + const search = useThrottledValue(undeferredSearch, 500) + + const isSearching = search.length > 0 + + const trendingQuery = useFeaturedGifsQuery() + const searchQuery = useGifSearchQuery(search) + + const { + data, + fetchNextPage, + isFetchingNextPage, + hasNextPage, + error, + isLoading, + isError, + refetch, + } = isSearching ? searchQuery : trendingQuery + + const flattenedData = useMemo(() => { + return data?.pages.flatMap(page => page.results) || [] + }, [data]) + + const renderItem = useCallback( + ({item}: {item: Gif}) => { + return + }, + [onSelectGif], + ) + + const onEndReached = React.useCallback(() => { + if (isFetchingNextPage || !hasNextPage || error) return + fetchNextPage() + }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) + + const hasData = flattenedData.length > 0 + + const onGoBack = useCallback(() => { + if (isSearching) { + // clear the input and reset the state + textInputRef.current?.clear() + setSearch('') + } else { + control.close() + } + }, [control, isSearching]) + + const listHeader = useMemo(() => { + return ( + + {/* cover top corners */} + + + {!gtMobile && isWeb && ( + + )} + + + + { + setSearch(text) + listRef.current?.scrollToOffset({offset: 0, animated: false}) + }} + returnKeyType="search" + clearButtonMode="while-editing" + inputRef={textInputRef} + maxLength={50} + onKeyPress={({nativeEvent}) => { + if (nativeEvent.key === 'Escape') { + control.close() + } + }} + /> + + + ) + }, [gtMobile, t.atoms.bg, _, control]) + + return ( + <> + {gtMobile && } + + {listHeader} + {!hasData && ( + + )} + + } + stickyHeaderIndices={[0]} + onEndReached={onEndReached} + onEndReachedThreshold={4} + keyExtractor={(item: Gif) => item.id} + // @ts-expect-error web only + style={isWeb && {minHeight: '100vh'}} + onScrollBeginDrag={() => Keyboard.dismiss()} + ListFooterComponent={ + hasData ? ( + + ) : null + } + /> + + ) +} + +function GifPreview({ + gif, + onSelectGif, +}: { + gif: Gif + onSelectGif: (gif: Gif) => void +}) { + const {gtTablet} = useBreakpoints() + const {_} = useLingui() + const t = useTheme() + + const onPress = useCallback(() => { + logEvent('composer:gif:select', {}) + onSelectGif(gif) + }, [onSelectGif, gif]) + + return ( + + ) +} + +function DialogError({details}: {details?: string}) { + const {_} = useLingui() + const control = Dialog.useDialogContext() + + return ( + + + + + + ) +} diff --git a/src/components/dialogs/Signin.tsx b/src/components/dialogs/Signin.tsx new file mode 100644 index 0000000000..b9c939e94b --- /dev/null +++ b/src/components/dialogs/Signin.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {isNative} from '#/platform/detection' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import {useCloseAllActiveElements} from '#/state/util' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {Text} from '#/components/Typography' + +export function SigninDialog() { + const {signinDialogControl: control} = useGlobalDialogsControlContext() + return ( + + + + + ) +} + +function SigninDialogInner({}: {control: Dialog.DialogOuterProps['control']}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const {requestSwitchToAccount} = useLoggedOutViewControls() + const closeAllActiveElements = useCloseAllActiveElements() + + const showSignIn = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'none'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + const showCreateAccount = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'new'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + return ( + + + + + + + + + + + + Sign in or create your account to join the conversation! + + + + + + + + + + {isNative && } + + + + + ) +} diff --git a/src/components/dialogs/SwitchAccount.tsx b/src/components/dialogs/SwitchAccount.tsx index 645113d4af..55628a790d 100644 --- a/src/components/dialogs/SwitchAccount.tsx +++ b/src/components/dialogs/SwitchAccount.tsx @@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {type SessionAccount, useSession} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' -import {useCloseAllActiveElements} from '#/state/util' import {atoms as a} from '#/alf' import * as Dialog from '#/components/Dialog' import {AccountList} from '../AccountList' @@ -21,23 +20,25 @@ export function SwitchAccountDialog({ const {currentAccount} = useSession() const {onPressSwitchAccount} = useAccountSwitcher() const {setShowLoggedOut} = useLoggedOutViewControls() - const closeAllActiveElements = useCloseAllActiveElements() const onSelectAccount = useCallback( (account: SessionAccount) => { - if (account.did === currentAccount?.did) { - control.close() + if (account.did !== currentAccount?.did) { + control.close(() => { + onPressSwitchAccount(account, 'SwitchAccount') + }) } else { - onPressSwitchAccount(account, 'SwitchAccount') + control.close() } }, [currentAccount, control, onPressSwitchAccount], ) const onPressAddAccount = useCallback(() => { - setShowLoggedOut(true) - closeAllActiveElements() - }, [setShowLoggedOut, closeAllActiveElements]) + control.close(() => { + setShowLoggedOut(true) + }) + }, [setShowLoggedOut, control]) return ( diff --git a/src/components/forms/HostingProvider.tsx b/src/components/forms/HostingProvider.tsx index f2d11062a5..6cbabe2911 100644 --- a/src/components/forms/HostingProvider.tsx +++ b/src/components/forms/HostingProvider.tsx @@ -41,6 +41,7 @@ export function HostingProvider({ onSelect={onSelectServiceUrl} /> ) : ( - - - ) - - return ( - <> - - - {hasFeeds ? ( - } - keyExtractor={item => item.uri} - style={{flex: 1}} - /> - ) : isLoading ? ( - - - - ) : ( - - )} - - - - - - - - Check out some recommended feeds. Tap + to add them to your list - of pinned feeds. - - - - {hasFeeds ? ( - } - keyExtractor={item => item.uri} - style={{flex: 1}} - /> - ) : isLoading ? ( - - - - ) : ( - - - - )} - - - - - - - {item.likeCount || 0} - - - - - - - ) -} diff --git a/src/view/com/auth/onboarding/RecommendedFollows.tsx b/src/view/com/auth/onboarding/RecommendedFollows.tsx deleted file mode 100644 index d275f6c90e..0000000000 --- a/src/view/com/auth/onboarding/RecommendedFollows.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import React from 'react' -import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {AppBskyActorDefs, moderateProfile} from '@atproto/api' -import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' -import {Text} from 'view/com/util/text/Text' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout' -import {Button} from 'view/com/util/forms/Button' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {usePalette} from 'lib/hooks/usePalette' -import {RecommendedFollowsItem} from './RecommendedFollowsItem' -import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' -import {useGetSuggestedFollowersByActor} from '#/state/queries/suggested-follows' -import {useModerationOpts} from '#/state/queries/preferences' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -type Props = { - next: () => void -} -export function RecommendedFollows({next}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - const {isTabletOrMobile} = useWebMediaQueries() - const {data: suggestedFollows} = useSuggestedFollowsQuery() - const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor() - const [additionalSuggestions, setAdditionalSuggestions] = React.useState<{ - [did: string]: AppBskyActorDefs.ProfileView[] - }>({}) - const existingDids = React.useRef([]) - const moderationOpts = useModerationOpts() - - const title = ( - <> - - - Follow some - - - Recommended - - - Users - - - - - Follow some users to get started. We can recommend you more users - based on who you find interesting. - - - - - - - ) - - const suggestions = React.useMemo(() => { - if (!suggestedFollows) return [] - - const additional = Object.entries(additionalSuggestions) - const items = suggestedFollows.pages.flatMap(page => page.actors) - - outer: while (additional.length) { - const additionalAccount = additional.shift() - - if (!additionalAccount) break - - const [followedUser, relatedAccounts] = additionalAccount - - for (let i = 0; i < items.length; i++) { - if (items[i].did === followedUser) { - items.splice(i + 1, 0, ...relatedAccounts) - continue outer - } - } - } - - existingDids.current = items.map(i => i.did) - - return items - }, [suggestedFollows, additionalSuggestions]) - - const onFollowStateChange = React.useCallback( - async ({following, did}: {following: boolean; did: string}) => { - if (following) { - try { - const {suggestions: results} = await getSuggestedFollowsByActor(did) - - if (results.length) { - const deduped = results.filter( - r => !existingDids.current.find(did => did === r.did), - ) - setAdditionalSuggestions(s => ({ - ...s, - [did]: deduped.slice(0, 3), - })) - } - } catch (e) { - logger.error('RecommendedFollows: failed to get suggestions', { - message: e, - }) - } - } - - // not handling the unfollow case - }, - [existingDids, getSuggestedFollowsByActor, setAdditionalSuggestions], - ) - - return ( - <> - - - {!suggestedFollows || !moderationOpts ? ( - - ) : ( - ( - - )} - keyExtractor={item => item.did} - style={{flex: 1}} - /> - )} - - - - - - - - - - Check out some recommended users. Follow them to see similar - users. - - - - {!suggestedFollows || !moderationOpts ? ( - - ) : ( - ( - - )} - keyExtractor={item => item.did} - style={{flex: 1}} - /> - )} - - - - ) -} - -const styles = StyleSheet.create({ - row: { - flexDirection: 'row', - columnGap: 20, - alignItems: 'center', - marginVertical: 20, - }, - rowText: { - flex: 1, - }, - spacer: { - height: 20, - }, -}) diff --git a/src/view/com/auth/onboarding/WelcomeMobile.tsx b/src/view/com/auth/onboarding/WelcomeMobile.tsx deleted file mode 100644 index b8659d56cd..0000000000 --- a/src/view/com/auth/onboarding/WelcomeMobile.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react' -import {Pressable, StyleSheet, View} from 'react-native' -import {Text} from 'view/com/util/text/Text' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Button} from 'view/com/util/forms/Button' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' - -type Props = { - next: () => void - skip: () => void -} - -export function WelcomeMobile({next, skip}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - - return ( - - { - return ( - - - Skip - - - - ) - }} - /> - - - - Welcome to{' '} - Bluesky - - - - - - - - Bluesky is public. - - - - Your posts, likes, and blocks are public. Mutes are private. - - - - - - - - - Bluesky is open. - - - Never lose access to your followers and data. - - - - - - - - Bluesky is flexible. - - - - Choose the algorithms that power your experience with custom - feeds. - - - - - - - + ) : null} + @@ -507,9 +527,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" /> @@ -593,7 +611,7 @@ const styles = StyleSheet.create({ }, bottomBar: { flexDirection: 'row', - paddingVertical: 10, + paddingVertical: 4, paddingLeft: 15, paddingRight: 20, alignItems: 'center', diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 0c1b87d04d..7dc17fd4a7 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -1,21 +1,22 @@ import React from 'react' import {LayoutAnimation, Pressable, StyleSheet, View} from 'react-native' import {Image} from 'expo-image' -import {useLingui} from '@lingui/react' -import {msg} from '@lingui/macro' import { AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyFeedPost, } from '@atproto/api' -import {ComposerOptsPostRef} from 'state/shell/composer' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import {usePalette} from 'lib/hooks/usePalette' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Text} from 'view/com/util/text/Text' +import {ComposerOptsPostRef} from 'state/shell/composer' import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed' +import {Text} from 'view/com/util/text/Text' +import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const pal = usePalette('default') @@ -83,9 +84,9 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { accessibilityHint={_( msg`Expand or collapse the full post you are replying to`, )}> - @@ -216,6 +217,7 @@ function ComposerReplyToImages({ const styles = StyleSheet.create({ replyToLayout: { flexDirection: 'row', + alignItems: 'flex-start', borderTopWidth: 1, paddingTop: 16, paddingBottom: 16, diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx index 02dd1bbd75..321e29b30a 100644 --- a/src/view/com/composer/ExternalEmbed.tsx +++ b/src/view/com/composer/ExternalEmbed.tsx @@ -1,70 +1,82 @@ import React from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' +import {StyleProp, TouchableOpacity, View, ViewStyle} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {AutoSizedImage} from '../util/images/AutoSizedImage' -import {Text} from '../util/text/Text' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {ExternalEmbedDraft} from 'lib/api/index' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {ExternalEmbedDraft} from 'lib/api/index' +import {s} from 'lib/styles' +import {Gif} from 'state/queries/tenor' +import {ExternalLinkEmbed} from 'view/com/util/post-embeds/ExternalLinkEmbed' +import {atoms as a, useTheme} from '#/alf' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' export const ExternalEmbed = ({ link, onRemove, + gif, }: { link?: ExternalEmbedDraft onRemove: () => void + gif?: Gif }) => { - const pal = usePalette('default') - const palError = usePalette('error') + const t = useTheme() const {_} = useLingui() - if (!link) { - return - } + + const linkInfo = React.useMemo( + () => + link && { + title: link.meta?.title ?? link.uri, + uri: link.uri, + description: link.meta?.description ?? '', + thumb: link.localThumb?.path, + }, + [link], + ) + + if (!link) return null + + const loadingStyle: ViewStyle | undefined = gif + ? { + aspectRatio: + gif.media_formats.gif.dims[0] / gif.media_formats.gif.dims[1], + width: '100%', + } + : undefined + return ( - + {link.isLoading ? ( - - + + + + ) : link.meta?.error ? ( + + + {link.uri} + + + {link.meta?.error} + + + ) : linkInfo ? ( + + - ) : link.localThumb ? ( - - ) : undefined} - - {!!link.meta?.title && ( - - {link.meta.title} - - )} - - {link.uri} - - {!!link.meta?.description && ( - - {link.meta.description} - - )} - {link.meta?.error ? ( - - {link.meta.error} - - ) : null} - + ) : null} + children: React.ReactNode +}) { + const t = useTheme() + return ( + + {children} + + ) +} diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx index 4353704d57..8f9152e34d 100644 --- a/src/view/com/composer/photos/OpenCameraBtn.tsx +++ b/src/view/com/composer/photos/OpenCameraBtn.tsx @@ -1,32 +1,31 @@ import React, {useCallback} from 'react' -import {TouchableOpacity, StyleSheet} from 'react-native' import * as MediaLibrary from 'expo-media-library' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {openCamera} from 'lib/media/picker' -import {useCameraPermission} from 'lib/hooks/usePermissions' -import {HITSLOP_10, POST_IMG_MAX} from 'lib/constants' -import {GalleryModel} from 'state/models/media/gallery' -import {isMobileWeb, isNative} from 'platform/detection' -import {logger} from '#/logger' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {POST_IMG_MAX} from '#/lib/constants' +import {useCameraPermission} from '#/lib/hooks/usePermissions' +import {openCamera} from '#/lib/media/picker' +import {logger} from '#/logger' +import {isMobileWeb, isNative} from '#/platform/detection' +import {GalleryModel} from '#/state/models/media/gallery' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera' type Props = { gallery: GalleryModel + disabled?: boolean } -export function OpenCameraBtn({gallery}: Props) { - const pal = usePalette('default') +export function OpenCameraBtn({gallery, disabled}: Props) { const {track} = useAnalytics() const {_} = useLingui() const {requestCameraAccessIfNeeded} = useCameraPermission() const [mediaPermissionRes, requestMediaPermission] = MediaLibrary.usePermissions() + const t = useTheme() const onPressTakePicture = useCallback(async () => { track('Composer:CameraOpened') @@ -68,25 +67,17 @@ export function OpenCameraBtn({gallery}: Props) { } return ( - - - + label={_(msg`Camera`)} + accessibilityHint={_(msg`Opens camera on device`)} + style={a.p_sm} + variant="ghost" + shape="round" + color="primary" + disabled={disabled}> + + ) } - -const styles = StyleSheet.create({ - button: { - paddingHorizontal: 15, - }, -}) diff --git a/src/view/com/composer/photos/SelectGifBtn.tsx b/src/view/com/composer/photos/SelectGifBtn.tsx new file mode 100644 index 0000000000..60cef9a192 --- /dev/null +++ b/src/view/com/composer/photos/SelectGifBtn.tsx @@ -0,0 +1,53 @@ +import React, {useCallback} from 'react' +import {Keyboard} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logEvent} from '#/lib/statsig/statsig' +import {Gif} from '#/state/queries/tenor' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {GifSelectDialog} from '#/components/dialogs/GifSelect' +import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif' + +type Props = { + onClose: () => void + onSelectGif: (gif: Gif) => void + disabled?: boolean +} + +export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) { + const {_} = useLingui() + const control = useDialogControl() + const t = useTheme() + + const onPressSelectGif = useCallback(async () => { + logEvent('composer:gif:open', {}) + Keyboard.dismiss() + control.open() + }, [control]) + + return ( + <> + + + + + ) +} diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx index f7fa9502d6..747653fc8d 100644 --- a/src/view/com/composer/photos/SelectPhotoBtn.tsx +++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx @@ -1,27 +1,26 @@ +/* eslint-disable react-native-a11y/has-valid-accessibility-ignores-invert-colors */ import React, {useCallback} from 'react' -import {TouchableOpacity, StyleSheet} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions' -import {GalleryModel} from 'state/models/media/gallery' -import {HITSLOP_10} from 'lib/constants' -import {isNative} from 'platform/detection' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {usePhotoLibraryPermission} from '#/lib/hooks/usePermissions' +import {isNative} from '#/platform/detection' +import {GalleryModel} from '#/state/models/media/gallery' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {Image_Stroke2_Corner0_Rounded as Image} from '#/components/icons/Image' type Props = { gallery: GalleryModel + disabled?: boolean } -export function SelectPhotoBtn({gallery}: Props) { - const pal = usePalette('default') +export function SelectPhotoBtn({gallery, disabled}: Props) { const {track} = useAnalytics() const {_} = useLingui() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() + const t = useTheme() const onPressSelectPhotos = useCallback(async () => { track('Composer:GalleryOpened') @@ -34,25 +33,17 @@ export function SelectPhotoBtn({gallery}: Props) { }, [track, requestPhotoAccessIfNeeded, gallery]) return ( - - - + label={_(msg`Gallery`)} + accessibilityHint={_(msg`Opens device photo gallery`)} + style={a.p_sm} + variant="ghost" + shape="round" + color="primary" + disabled={disabled}> + + ) } - -const styles = StyleSheet.create({ - button: { - paddingHorizontal: 15, - }, -}) diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 20be585c25..cb16e3c666 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -1,10 +1,10 @@ import React, { + ComponentProps, forwardRef, useCallback, - useRef, useMemo, + useRef, useState, - ComponentProps, } from 'react' import { NativeSyntheticEvent, @@ -13,22 +13,26 @@ import { TextInputSelectionChangeEventData, View, } from 'react-native' +import {AppBskyRichtextFacet, RichText} from '@atproto/api' import PasteInput, { PastedFile, PasteInputRef, } from '@mattermost/react-native-paste-input' -import {AppBskyRichtextFacet, RichText} from '@atproto/api' -import isEqual from 'lodash.isequal' -import {Autocomplete} from './mobile/Autocomplete' -import {Text} from 'view/com/util/text/Text' + +import {POST_IMG_MAX} from 'lib/constants' +import {usePalette} from 'lib/hooks/usePalette' +import {downloadAndResize} from 'lib/media/manip' +import {isUriImage} from 'lib/media/util' import {cleanError} from 'lib/strings/errors' import {getMentionAt, insertMentionAt} from 'lib/strings/mention-manip' -import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' -import {isUriImage} from 'lib/media/util' -import {downloadAndResize} from 'lib/media/manip' -import {POST_IMG_MAX} from 'lib/constants' import {isIOS} from 'platform/detection' +import { + LinkFacetMatch, + suggestLinkCardUri, +} from 'view/com/composer/text-input/text-input-util' +import {Text} from 'view/com/util/text/Text' +import {Autocomplete} from './mobile/Autocomplete' export interface TextInputRef { focus: () => void @@ -39,11 +43,10 @@ export interface TextInputRef { interface TextInputProps extends ComponentProps { richtext: RichText placeholder: string - suggestedLinks: Set setRichText: (v: RichText | ((v: RichText) => RichText)) => void onPhotoPasted: (uri: string) => void onPressPublish: (richtext: RichText) => Promise - onSuggestedLinksChanged: (uris: Set) => void + onNewLink: (uri: string) => void onError: (err: string) => void } @@ -56,10 +59,9 @@ export const TextInput = forwardRef(function TextInputImpl( { richtext, placeholder, - suggestedLinks, setRichText, onPhotoPasted, - onSuggestedLinksChanged, + onNewLink, onError, ...props }: TextInputProps, @@ -70,6 +72,7 @@ export const TextInput = forwardRef(function TextInputImpl( const textInputSelection = useRef({start: 0, end: 0}) const theme = useTheme() const [autocompletePrefix, setAutocompletePrefix] = useState('') + const prevLength = React.useRef(richtext.length) React.useImperativeHandle(ref, () => ({ focus: () => textInput.current?.focus(), @@ -79,6 +82,8 @@ export const TextInput = forwardRef(function TextInputImpl( getCursorPosition: () => undefined, // Not implemented on native })) + const pastSuggestedUris = useRef(new Set()) + const prevDetectedUris = useRef(new Map()) const onChangeText = useCallback( (newText: string) => { /* @@ -92,6 +97,8 @@ export const TextInput = forwardRef(function TextInputImpl( * @see https://github.com/bluesky-social/social-app/issues/929 */ setTimeout(async () => { + const mayBePaste = newText.length > prevLength.current + 1 + const newRt = new RichText({text: newText}) newRt.detectFacetsWithoutResolution() setRichText(newRt) @@ -106,8 +113,7 @@ export const TextInput = forwardRef(function TextInputImpl( setAutocompletePrefix('') } - const set: Set = new Set() - + const nextDetectedUris = new Map() if (newRt.facets) { for (const facet of newRt.facets) { for (const feature of facet.features) { @@ -126,26 +132,26 @@ export const TextInput = forwardRef(function TextInputImpl( onPhotoPasted(res.path) } } else { - set.add(feature.uri) + nextDetectedUris.set(feature.uri, {facet, rt: newRt}) } } } } } - - if (!isEqual(set, suggestedLinks)) { - onSuggestedLinksChanged(set) + const suggestedUri = suggestLinkCardUri( + mayBePaste, + nextDetectedUris, + prevDetectedUris.current, + pastSuggestedUris.current, + ) + prevDetectedUris.current = nextDetectedUris + if (suggestedUri) { + onNewLink(suggestedUri) } + prevLength.current = newText.length }, 1) }, - [ - setRichText, - autocompletePrefix, - setAutocompletePrefix, - suggestedLinks, - onSuggestedLinksChanged, - onPhotoPasted, - ], + [setRichText, autocompletePrefix, onPhotoPasted, onNewLink], ) const onPaste = useCallback( diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index c62d11201f..7f8dc2ed5e 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -1,28 +1,32 @@ -import React from 'react' +import React, {useRef} from 'react' import {StyleSheet, View} from 'react-native' -import {RichText, AppBskyRichtextFacet} from '@atproto/api' -import EventEmitter from 'eventemitter3' -import {useEditor, EditorContent, JSONContent} from '@tiptap/react' +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' +import {AppBskyRichtextFacet, RichText} from '@atproto/api' +import {Trans} from '@lingui/macro' import {Document} from '@tiptap/extension-document' -import History from '@tiptap/extension-history' import Hardbreak from '@tiptap/extension-hard-break' +import History from '@tiptap/extension-history' import {Mention} from '@tiptap/extension-mention' import {Paragraph} from '@tiptap/extension-paragraph' import {Placeholder} from '@tiptap/extension-placeholder' import {Text as TiptapText} from '@tiptap/extension-text' -import isEqual from 'lodash.isequal' -import {createSuggestion} from './web/Autocomplete' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' -import {isUriImage, blobToDataUri} from 'lib/media/util' -import {Emoji} from './web/EmojiPicker.web' -import {LinkDecorator} from './web/LinkDecorator' import {generateJSON} from '@tiptap/html' -import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' +import {EditorContent, JSONContent, useEditor} from '@tiptap/react' +import EventEmitter from 'eventemitter3' + import {usePalette} from '#/lib/hooks/usePalette' +import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' +import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {blobToDataUri, isUriImage} from 'lib/media/util' +import { + LinkFacetMatch, + suggestLinkCardUri, +} from 'view/com/composer/text-input/text-input-util' import {Portal} from '#/components/Portal' import {Text} from '../../util/text/Text' -import {Trans} from '@lingui/macro' -import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' +import {createSuggestion} from './web/Autocomplete' +import {Emoji} from './web/EmojiPicker.web' +import {LinkDecorator} from './web/LinkDecorator' import {TagDecorator} from './web/TagDecorator' export interface TextInputRef { @@ -38,7 +42,7 @@ interface TextInputProps { setRichText: (v: RichText | ((v: RichText) => RichText)) => void onPhotoPasted: (uri: string) => void onPressPublish: (richtext: RichText) => Promise - onSuggestedLinksChanged: (uris: Set) => void + onNewLink: (uri: string) => void onError: (err: string) => void } @@ -48,17 +52,15 @@ export const TextInput = React.forwardRef(function TextInputImpl( { richtext, placeholder, - suggestedLinks, setRichText, onPhotoPasted, onPressPublish, - onSuggestedLinksChanged, + onNewLink, }: // onError, TODO TextInputProps, ref, ) { const autocomplete = useActorAutocompleteFn() - const pal = usePalette('default') const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark') @@ -139,6 +141,8 @@ export const TextInput = React.forwardRef(function TextInputImpl( } }, [setIsDropping]) + const pastSuggestedUris = useRef(new Set()) + const prevDetectedUris = useRef(new Map()) const editor = useEditor( { extensions, @@ -180,25 +184,33 @@ export const TextInput = React.forwardRef(function TextInputImpl( }, onUpdate({editor: editorProp}) { const json = editorProp.getJSON() + const newText = editorJsonToText(json) + const isPaste = window.event?.type === 'paste' - const newRt = new RichText({text: editorJsonToText(json).trimEnd()}) + const newRt = new RichText({text: newText}) newRt.detectFacetsWithoutResolution() setRichText(newRt) - const set: Set = new Set() - + const nextDetectedUris = new Map() if (newRt.facets) { for (const facet of newRt.facets) { for (const feature of facet.features) { if (AppBskyRichtextFacet.isLink(feature)) { - set.add(feature.uri) + nextDetectedUris.set(feature.uri, {facet, rt: newRt}) } } } } - if (!isEqual(set, suggestedLinks)) { - onSuggestedLinksChanged(set) + const suggestedUri = suggestLinkCardUri( + isPaste, + nextDetectedUris, + prevDetectedUris.current, + pastSuggestedUris.current, + ) + prevDetectedUris.current = nextDetectedUris + if (suggestedUri) { + onNewLink(suggestedUri) } }, }, @@ -256,15 +268,29 @@ export const TextInput = React.forwardRef(function TextInputImpl( ) }) -function editorJsonToText(json: JSONContent): string { +function editorJsonToText( + json: JSONContent, + isLastDocumentChild: boolean = false, +): string { let text = '' - if (json.type === 'doc' || json.type === 'paragraph') { + if (json.type === 'doc') { if (json.content?.length) { - for (const node of json.content) { + for (let i = 0; i < json.content.length; i++) { + const node = json.content[i] + const isLastNode = i === json.content.length - 1 + text += editorJsonToText(node, isLastNode) + } + } + } else if (json.type === 'paragraph') { + if (json.content?.length) { + for (let i = 0; i < json.content.length; i++) { + const node = json.content[i] text += editorJsonToText(node) } } - text += '\n' + if (!isLastDocumentChild) { + text += '\n' + } } else if (json.type === 'hardBreak') { text += '\n' } else if (json.type === 'text') { diff --git a/src/view/com/composer/text-input/text-input-util.ts b/src/view/com/composer/text-input/text-input-util.ts new file mode 100644 index 0000000000..cbe8ef6af7 --- /dev/null +++ b/src/view/com/composer/text-input/text-input-util.ts @@ -0,0 +1,92 @@ +import {AppBskyRichtextFacet, RichText} from '@atproto/api' + +export type LinkFacetMatch = { + rt: RichText + facet: AppBskyRichtextFacet.Main +} + +export function suggestLinkCardUri( + mayBePaste: boolean, + nextDetectedUris: Map, + prevDetectedUris: Map, + pastSuggestedUris: Set, +): string | undefined { + const suggestedUris = new Set() + for (const [uri, nextMatch] of nextDetectedUris) { + if (!isValidUrlAndDomain(uri)) { + continue + } + if (pastSuggestedUris.has(uri)) { + // Don't suggest already added or already dismissed link cards. + continue + } + if (mayBePaste) { + // Immediately add the pasted link without waiting to type more. + suggestedUris.add(uri) + continue + } + const prevMatch = prevDetectedUris.get(uri) + if (!prevMatch) { + // If the same exact link wasn't already detected during the last keystroke, + // it means you're probably still typing it. Disregard until it stabilizes. + continue + } + const prevTextAfterUri = prevMatch.rt.unicodeText.slice( + prevMatch.facet.index.byteEnd, + ) + const nextTextAfterUri = nextMatch.rt.unicodeText.slice( + nextMatch.facet.index.byteEnd, + ) + if (prevTextAfterUri === nextTextAfterUri) { + // The text you're editing is before the link, e.g. + // "abc google.com" -> "abcd google.com". + // This is a good time to add the link. + suggestedUris.add(uri) + continue + } + if (/^\s/m.test(nextTextAfterUri)) { + // The link is followed by a space, e.g. + // "google.com" -> "google.com " or + // "google.com." -> "google.com ". + // This is a clear indicator we can linkify it. + suggestedUris.add(uri) + continue + } + if ( + /^[)]?[.,:;!?)](\s|$)/m.test(prevTextAfterUri) && + /^[)]?[.,:;!?)]\s/m.test(nextTextAfterUri) + ) { + // The link was *already* being followed by punctuation, + // and now it's followed both by punctuation and a space. + // This means you're typing after punctuation, e.g. + // "google.com." -> "google.com. " or + // "google.com.foo" -> "google.com. foo". + // This means you're not typing the link anymore, so we can linkify it. + suggestedUris.add(uri) + continue + } + } + for (const uri of pastSuggestedUris) { + if (!nextDetectedUris.has(uri)) { + // If a link is no longer detected, it's eligible for suggestions next time. + pastSuggestedUris.delete(uri) + } + } + + let suggestedUri: string | undefined + if (suggestedUris.size > 0) { + suggestedUri = Array.from(suggestedUris)[0] + pastSuggestedUris.add(suggestedUri) + } + + return suggestedUri +} + +// https://stackoverflow.com/questions/8667070/javascript-regular-expression-to-validate-url +// question credit Muhammad Imran Tariq https://stackoverflow.com/users/420613/muhammad-imran-tariq +// answer credit Christian David https://stackoverflow.com/users/967956/christian-david +function isValidUrlAndDomain(value: string) { + return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( + value, + ) +} diff --git a/src/view/com/composer/text-input/web/LinkDecorator.ts b/src/view/com/composer/text-input/web/LinkDecorator.ts index e36ac80e42..60f2d40855 100644 --- a/src/view/com/composer/text-input/web/LinkDecorator.ts +++ b/src/view/com/composer/text-input/web/LinkDecorator.ts @@ -14,11 +14,11 @@ * the facet-set. */ -import {Mark} from '@tiptap/core' -import {Plugin, PluginKey} from '@tiptap/pm/state' -import {Node as ProsemirrorNode} from '@tiptap/pm/model' -import {Decoration, DecorationSet} from '@tiptap/pm/view' import {URL_REGEX} from '@atproto/api' +import {Mark} from '@tiptap/core' +import {Node as ProsemirrorNode} from '@tiptap/pm/model' +import {Plugin, PluginKey} from '@tiptap/pm/state' +import {Decoration, DecorationSet} from '@tiptap/pm/view' import {isValidDomain} from 'lib/strings/url-helpers' @@ -91,7 +91,7 @@ function iterateUris(str: string, cb: (from: number, to: number) => void) { uri = `https://${uri}` } let from = str.indexOf(match[2], match.index) - let to = from + match[2].length + 1 + let to = from + match[2].length // strip ending puncuation if (/[.,;!?]$/.test(uri)) { uri = uri.slice(0, -1) diff --git a/src/view/com/composer/useExternalLinkFetch.e2e.ts b/src/view/com/composer/useExternalLinkFetch.e2e.ts index ccf619db37..65ecb866e7 100644 --- a/src/view/com/composer/useExternalLinkFetch.e2e.ts +++ b/src/view/com/composer/useExternalLinkFetch.e2e.ts @@ -1,12 +1,14 @@ -import {useState, useEffect} from 'react' +import {useEffect, useState} from 'react' + +import {useAgent} from '#/state/session' import * as apilib from 'lib/api/index' import {getLinkMeta} from 'lib/link-meta/link-meta' import {ComposerOpts} from 'state/shell/composer' -import {getAgent} from '#/state/session' export function useExternalLinkFetch({}: { setQuote: (opts: ComposerOpts['quote']) => void }) { + const {getAgent} = useAgent() const [extLink, setExtLink] = useState( undefined, ) @@ -39,7 +41,7 @@ export function useExternalLinkFetch({}: { }) } return cleanup - }, [extLink]) + }, [extLink, getAgent]) return {extLink, setExtLink} } diff --git a/src/view/com/composer/useExternalLinkFetch.ts b/src/view/com/composer/useExternalLinkFetch.ts index 54773d565c..d51dec42b1 100644 --- a/src/view/com/composer/useExternalLinkFetch.ts +++ b/src/view/com/composer/useExternalLinkFetch.ts @@ -1,24 +1,25 @@ -import {useState, useEffect} from 'react' -import {ImageModel} from 'state/models/media/image' +import {useEffect, useState} from 'react' + +import {logger} from '#/logger' +import {useFetchDid} from '#/state/queries/handle' +import {useGetPost} from '#/state/queries/post' +import {useAgent} from '#/state/session' import * as apilib from 'lib/api/index' -import {getLinkMeta} from 'lib/link-meta/link-meta' +import {POST_IMG_MAX} from 'lib/constants' import { - getPostAsQuote, getFeedAsEmbed, getListAsEmbed, + getPostAsQuote, } from 'lib/link-meta/bsky' +import {getLinkMeta} from 'lib/link-meta/link-meta' import {downloadAndResize} from 'lib/media/manip' import { - isBskyPostUrl, isBskyCustomFeedUrl, isBskyListUrl, + isBskyPostUrl, } from 'lib/strings/url-helpers' +import {ImageModel} from 'state/models/media/image' import {ComposerOpts} from 'state/shell/composer' -import {POST_IMG_MAX} from 'lib/constants' -import {logger} from '#/logger' -import {getAgent} from '#/state/session' -import {useGetPost} from '#/state/queries/post' -import {useFetchDid} from '#/state/queries/handle' export function useExternalLinkFetch({ setQuote, @@ -30,6 +31,7 @@ export function useExternalLinkFetch({ ) const getPost = useGetPost() const fetchDid = useFetchDid() + const {getAgent} = useAgent() useEffect(() => { let aborted = false @@ -135,7 +137,7 @@ export function useExternalLinkFetch({ }) } return cleanup - }, [extLink, setQuote, getPost, fetchDid]) + }, [extLink, setQuote, getPost, fetchDid, getAgent]) return {extLink, setExtLink} } diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 2d0736b096..4ebf64da9a 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -1,28 +1,29 @@ import React from 'react' -import {useNavigation} from '@react-navigation/native' -import {useAnalytics} from 'lib/analytics/analytics' -import {useQueryClient} from '@tanstack/react-query' -import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' -import {MainScrollProvider} from '../util/MainScrollProvider' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useSetMinimalShellMode} from '#/state/shell' -import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' -import {ComposeIcon2} from 'lib/icons' -import {s} from 'lib/styles' -import {View, useWindowDimensions} from 'react-native' -import {ListMethods} from '../util/List' -import {Feed} from '../posts/Feed' -import {FAB} from '../util/fab/FAB' -import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn' +import {useWindowDimensions, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useSession} from '#/state/session' -import {useComposerControls} from '#/state/shell/composer' -import {listenSoftReset} from '#/state/events' -import {truncateAndInvalidate} from '#/state/queries/util' -import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers' +import {useNavigation} from '@react-navigation/native' +import {useQueryClient} from '@tanstack/react-query' + +import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers' +import {logEvent, useGate} from '#/lib/statsig/statsig' import {isNative} from '#/platform/detection' -import {logEvent} from '#/lib/statsig/statsig' +import {listenSoftReset} from '#/state/events' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' +import {truncateAndInvalidate} from '#/state/queries/util' +import {useSession} from '#/state/session' +import {useSetMinimalShellMode} from '#/state/shell' +import {useComposerControls} from '#/state/shell/composer' +import {useAnalytics} from 'lib/analytics/analytics' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {ComposeIcon2} from 'lib/icons' +import {s} from 'lib/styles' +import {Feed} from '../posts/Feed' +import {FAB} from '../util/fab/FAB' +import {ListMethods} from '../util/List' +import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn' +import {MainScrollProvider} from '../util/MainScrollProvider' const POLL_FREQ = 60e3 // 60sec @@ -52,6 +53,7 @@ export function FeedPage({ const headerOffset = useHeaderOffset() const scrollElRef = React.useRef(null) const [hasNew, setHasNew] = React.useState(false) + const gate = useGate() const scrollToTop = React.useCallback(() => { scrollElRef.current?.scrollToOffset({ @@ -71,6 +73,7 @@ export function FeedPage({ setHasNew(false) logEvent('feed:refresh', { feedType: feed.split('|')[0], + feedUrl: feed, reason: 'soft-reset', }) } @@ -96,10 +99,17 @@ export function FeedPage({ setHasNew(false) logEvent('feed:refresh', { feedType: feed.split('|')[0], + feedUrl: feed, reason: 'load-latest', }) }, [scrollToTop, feed, queryClient, setHasNew]) + const isDiscoverFeed = + feed === + 'feedgen|at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' + const adjustedHasNew = + hasNew && !(isDiscoverFeed && gate('disable_poll_on_discover_v2')) + return ( @@ -118,11 +128,11 @@ export function FeedPage({ headerOffset={headerOffset} /> - {(isScrolledDown || hasNew) && ( + {(isScrolledDown || adjustedHasNew) && ( )} diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx index e9cf9e5359..a006b11c06 100644 --- a/src/view/com/feeds/ProfileFeedgens.tsx +++ b/src/view/com/feeds/ProfileFeedgens.tsx @@ -1,22 +1,29 @@ import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import { + findNodeHandle, + StyleProp, + StyleSheet, + View, + ViewStyle, +} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' -import {List, ListRef} from '../util/List' -import {FeedSourceCardLoaded} from './FeedSourceCard' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {Text} from '../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' + import {cleanError} from '#/lib/strings/errors' import {useTheme} from '#/lib/ThemeContext' -import {usePreferencesQuery} from '#/state/queries/preferences' -import {hydrateFeedGenerator} from '#/state/queries/feed' -import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {logger} from '#/logger' import {isNative} from '#/platform/detection' -import {useLingui} from '@lingui/react' +import {hydrateFeedGenerator} from '#/state/queries/feed' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' +import {usePalette} from 'lib/hooks/usePalette' +import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {List, ListRef} from '../util/List' +import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' +import {Text} from '../util/text/Text' +import {FeedSourceCardLoaded} from './FeedSourceCard' const LOADING = {_reactKey: '__loading__'} const EMPTY = {_reactKey: '__empty__'} @@ -34,13 +41,14 @@ interface ProfileFeedgensProps { enabled?: boolean style?: StyleProp testID?: string + setScrollViewTag: (tag: number | null) => void } export const ProfileFeedgens = React.forwardRef< SectionRef, ProfileFeedgensProps >(function ProfileFeedgensImpl( - {did, scrollElRef, headerOffset, enabled, style, testID}, + {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, ref, ) { const pal = usePalette('default') @@ -169,6 +177,13 @@ export const ProfileFeedgens = React.forwardRef< [error, refetch, onPressRetryLoadMore, pal, preferences, _], ) + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + return ( - - - - - - - - - + {hasSession && ( + + + + + + + + + + )} {tabBarAnchor} { diff --git a/src/view/com/home/HomeHeaderLayoutMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx index d7b7231c60..78fa9af865 100644 --- a/src/view/com/home/HomeHeaderLayoutMobile.tsx +++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx @@ -1,23 +1,24 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {usePalette} from 'lib/hooks/usePalette' -import {Link} from '../util/Link' +import Animated from 'react-native-reanimated' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' -import {HITSLOP_10} from 'lib/constants' -import Animated from 'react-native-reanimated' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' + +import {useSession} from '#/state/session' import {useSetDrawerOpen} from '#/state/shell/drawer-open' import {useShellLayout} from '#/state/shell/shell-layout' +import {HITSLOP_10} from 'lib/constants' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' import {isWeb} from 'platform/detection' import {Logo} from '#/view/icons/Logo' - -import {IS_DEV} from '#/env' import {atoms} from '#/alf' -import {Link as Link2} from '#/components/Link' import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette' +import {Link as Link2} from '#/components/Link' +import {IS_DEV} from '#/env' +import {Link} from '../util/Link' export function HomeHeaderLayoutMobile({ children, @@ -30,6 +31,7 @@ export function HomeHeaderLayoutMobile({ const setDrawerOpen = useSetDrawerOpen() const {headerHeight} = useShellLayout() const {headerMinimalShellTransform} = useMinimalShellMode() + const {hasSession} = useSession() const onPressAvi = React.useCallback(() => { setDrawerOpen(true) @@ -76,18 +78,20 @@ export function HomeHeaderLayoutMobile({ )} - - - + {hasSession && ( + + + + )} {children} diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx index a47b25bed4..003d1c60e7 100644 --- a/src/view/com/lists/ProfileLists.tsx +++ b/src/view/com/lists/ProfileLists.tsx @@ -1,21 +1,28 @@ import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import { + findNodeHandle, + StyleProp, + StyleSheet, + View, + ViewStyle, +} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' -import {List, ListRef} from '../util/List' -import {ListCard} from './ListCard' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {Text} from '../util/text/Text' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePalette} from 'lib/hooks/usePalette' -import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists' -import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' + import {cleanError} from '#/lib/strings/errors' import {useTheme} from '#/lib/ThemeContext' -import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {logger} from '#/logger' import {isNative} from '#/platform/detection' -import {useLingui} from '@lingui/react' +import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' +import {useAnalytics} from 'lib/analytics/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {List, ListRef} from '../util/List' +import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' +import {Text} from '../util/text/Text' +import {ListCard} from './ListCard' const LOADING = {_reactKey: '__loading__'} const EMPTY = {_reactKey: '__empty__'} @@ -33,11 +40,12 @@ interface ProfileListsProps { enabled?: boolean style?: StyleProp testID?: string + setScrollViewTag: (tag: number | null) => void } export const ProfileLists = React.forwardRef( function ProfileListsImpl( - {did, scrollElRef, headerOffset, enabled, style, testID}, + {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, ref, ) { const pal = usePalette('default') @@ -171,6 +179,13 @@ export const ProfileLists = React.forwardRef( [error, refetch, onPressRetryLoadMore, pal, _], ) + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + return ( (Stages.InputEmail) diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx index 125da44be7..ae43d1e328 100644 --- a/src/view/com/modals/ChangeHandle.tsx +++ b/src/view/com/modals/ChangeHandle.tsx @@ -16,8 +16,8 @@ import {useModalControls} from '#/state/modals' import {useFetchDid, useUpdateHandleMutation} from '#/state/queries/handle' import {useServiceQuery} from '#/state/queries/service' import { - getAgent, SessionAccount, + useAgent, useSession, useSessionApi, } from '#/state/session' @@ -40,6 +40,7 @@ export type Props = {onChanged: () => void} export function Component(props: Props) { const {currentAccount} = useSession() + const {getAgent} = useAgent() const { isLoading, data: serviceInfo, diff --git a/src/view/com/modals/ChangePassword.tsx b/src/view/com/modals/ChangePassword.tsx index 4badc88aaa..3ce7306b9d 100644 --- a/src/view/com/modals/ChangePassword.tsx +++ b/src/view/com/modals/ChangePassword.tsx @@ -6,24 +6,25 @@ import { TouchableOpacity, View, } from 'react-native' -import {ScrollView} from './util' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {TextInput} from './util' -import {Text} from '../util/text/Text' -import {Button} from '../util/forms/Button' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {s, colors} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import * as EmailValidator from 'email-validator' + +import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' +import {useAgent, useSession} from '#/state/session' import {usePalette} from 'lib/hooks/usePalette' -import {isAndroid, isWeb} from 'platform/detection' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {cleanError, isNetworkError} from 'lib/strings/errors' import {checkAndFormatResetCode} from 'lib/strings/password' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {useSession, getAgent} from '#/state/session' -import * as EmailValidator from 'email-validator' -import {logger} from '#/logger' +import {colors, s} from 'lib/styles' +import {isAndroid, isWeb} from 'platform/detection' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Button} from '../util/forms/Button' +import {Text} from '../util/text/Text' +import {ScrollView} from './util' +import {TextInput} from './util' enum Stages { RequestCode, @@ -36,6 +37,7 @@ export const snapPoints = isAndroid ? ['90%'] : ['45%'] export function Component() { const pal = usePalette('default') const {currentAccount} = useSession() + const {getAgent} = useAgent() const {_} = useLingui() const [stage, setStage] = useState(Stages.RequestCode) const [isProcessing, setIsProcessing] = useState(false) diff --git a/src/view/com/modals/CreateOrEditList.tsx b/src/view/com/modals/CreateOrEditList.tsx index f5f4f56db0..2dff636afc 100644 --- a/src/view/com/modals/CreateOrEditList.tsx +++ b/src/view/com/modals/CreateOrEditList.tsx @@ -25,7 +25,7 @@ import { useListCreateMutation, useListMetadataMutation, } from '#/state/queries/list' -import {getAgent} from '#/state/session' +import {useAgent} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' @@ -62,6 +62,7 @@ export function Component({ const {_} = useLingui() const listCreateMutation = useListCreateMutation() const listMetadataMutation = useListMetadataMutation() + const {getAgent} = useAgent() const activePurpose = useMemo(() => { if (list?.purpose) { @@ -228,6 +229,7 @@ export function Component({ listMetadataMutation, listCreateMutation, _, + getAgent, ]) return ( diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index 4c4fb20f18..5e68daef9a 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -11,7 +11,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useModalControls} from '#/state/modals' -import {getAgent, useSession, useSessionApi} from '#/state/session' +import {useAgent, useSession, useSessionApi} from '#/state/session' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {cleanError} from 'lib/strings/errors' @@ -30,6 +30,7 @@ export function Component({}: {}) { const pal = usePalette('default') const theme = useTheme() const {currentAccount} = useSession() + const {getAgent} = useAgent() const {clearCurrentAccount, removeAccount} = useSessionApi() const {_} = useLingui() const {closeModal} = useModalControls() diff --git a/src/view/com/modals/EmbedConsent.tsx b/src/view/com/modals/EmbedConsent.tsx deleted file mode 100644 index 9419447288..0000000000 --- a/src/view/com/modals/EmbedConsent.tsx +++ /dev/null @@ -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 ( - - - External Media - - - - - This content is hosted by {externalEmbedLabels[source]}. Do you want - to enable external media? - - - - - - 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. - - - - - - - Enable External Media - - - - - - - - Enable {externalEmbedLabels[source]} only - - - - - - - - No thanks - - - - - ) -} - -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, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 85ffccf12b..6524813015 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -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 = - } else if (activeModal?.name === 'embed-consent') { - snapPoints = EmbedConsentModal.snapPoints - element = } else if (activeModal?.name === 'in-app-browser-consent') { snapPoints = InAppBrowserConsentModal.snapPoints element = diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 7e5d548ace..f95c748111 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -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 = } else if (modal.name === 'link-warning') { element = - } else if (modal.name === 'embed-consent') { - element = } else { return null } diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx index d3086d3831..d6a3006cc9 100644 --- a/src/view/com/modals/VerifyEmail.tsx +++ b/src/view/com/modals/VerifyEmail.tsx @@ -6,23 +6,24 @@ import { StyleSheet, View, } from 'react-native' -import {Svg, Circle, Path} from 'react-native-svg' -import {ScrollView, TextInput} from './util' +import {Circle, Path, Svg} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from '../util/text/Text' -import {Button} from '../util/forms/Button' -import {ErrorMessage} from '../util/error/ErrorMessage' -import * as Toast from '../util/Toast' -import {s, colors} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' +import {useAgent, useSession, useSessionApi} from '#/state/session' import {usePalette} from 'lib/hooks/usePalette' -import {isWeb} from 'platform/detection' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {cleanError} from 'lib/strings/errors' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {useSession, useSessionApi, getAgent} from '#/state/session' -import {logger} from '#/logger' +import {colors, s} from 'lib/styles' +import {isWeb} from 'platform/detection' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Button} from '../util/forms/Button' +import {Text} from '../util/text/Text' +import * as Toast from '../util/Toast' +import {ScrollView, TextInput} from './util' export const snapPoints = ['90%'] @@ -32,8 +33,15 @@ enum Stages { ConfirmCode, } -export function Component({showReminder}: {showReminder?: boolean}) { +export function Component({ + showReminder, + onSuccess, +}: { + showReminder?: boolean + onSuccess?: () => void +}) { const pal = usePalette('default') + const {getAgent} = useAgent() const {currentAccount} = useSession() const {updateCurrentAccount} = useSessionApi() const {_} = useLingui() @@ -77,6 +85,7 @@ export function Component({showReminder}: {showReminder?: boolean}) { updateCurrentAccount({emailConfirmed: true}) Toast.show(_(msg`Email verified`)) closeModal() + onSuccess?.() } catch (e) { setError(cleanError(String(e))) } finally { diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 78b1677c3d..94844cb1a7 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -1,20 +1,20 @@ -import React, {memo, useMemo, useState, useEffect} from 'react' +import React, {memo, useEffect, useMemo, useState} from 'react' import { Animated, - TouchableOpacity, Pressable, StyleSheet, + TouchableOpacity, View, } from 'react-native' import { + AppBskyActorDefs, AppBskyEmbedImages, + AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, AppBskyFeedPost, - ModerationOpts, - ModerationDecision, moderateProfile, - AppBskyEmbedRecordWithMedia, - AppBskyActorDefs, + ModerationDecision, + ModerationOpts, } from '@atproto/api' import {AtUri} from '@atproto/api' import { @@ -22,41 +22,41 @@ import { FontAwesomeIconStyle, Props, } from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' + import {FeedNotification} from '#/state/queries/notifications/feed' -import {s, colors} from 'lib/styles' -import {niceDate} from 'lib/strings/time' +import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' +import {usePalette} from 'lib/hooks/usePalette' +import {HeartIconSolid} from 'lib/icons' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {pluralize} from 'lib/strings/helpers' -import {HeartIconSolid} from 'lib/icons' -import {Text} from '../util/text/Text' -import {UserAvatar, PreviewableUserAvatar} from '../util/UserAvatar' -import {UserPreviewLink} from '../util/UserPreviewLink' -import {ImageHorzList} from '../util/images/ImageHorzList' -import {Post} from '../post/Post' -import {Link, TextLink} from '../util/Link' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' -import {formatCount} from '../util/numeric/format' -import {makeProfileLink} from 'lib/routes/links' -import {TimeElapsed} from '../util/TimeElapsed' +import {niceDate} from 'lib/strings/time' +import {colors, s} from 'lib/styles' import {isWeb} from 'platform/detection' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {precacheProfile} from 'state/queries/profile' +import {Link as NewLink} from '#/components/Link' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {FeedSourceCard} from '../feeds/FeedSourceCard' +import {Post} from '../post/Post' +import {ImageHorzList} from '../util/images/ImageHorzList' +import {Link, TextLink} from '../util/Link' +import {formatCount} from '../util/numeric/format' +import {Text} from '../util/text/Text' +import {TimeElapsed} from '../util/TimeElapsed' +import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar' const MAX_AUTHORS = 5 const EXPANDED_AUTHOR_EL_HEIGHT = 35 interface Author { + profile: AppBskyActorDefs.ProfileViewBasic href: string - did: string - handle: string - displayName?: string - avatar?: string moderation: ModerationDecision - associated?: AppBskyActorDefs.ProfileAssociated } let FeedItem = ({ @@ -66,6 +66,7 @@ let FeedItem = ({ item: FeedNotification moderationOpts: ModerationOpts }): React.ReactNode => { + const queryClient = useQueryClient() const pal = usePalette('default') const {_} = useLingui() const [isAuthorsExpanded, setAuthorsExpanded] = useState(false) @@ -93,28 +94,22 @@ let FeedItem = ({ setAuthorsExpanded(currentlyExpanded => !currentlyExpanded) } + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, item.notification.author) + }, [queryClient, item.notification.author]) + const authors: Author[] = useMemo(() => { return [ { + profile: item.notification.author, href: makeProfileLink(item.notification.author), - did: item.notification.author.did, - handle: item.notification.author.handle, - displayName: item.notification.author.displayName, - avatar: item.notification.author.avatar, moderation: moderateProfile(item.notification.author, moderationOpts), - associated: item.notification.author.associated, }, - ...(item.additional?.map(({author}) => { - return { - href: makeProfileLink(author), - did: author.did, - handle: author.handle, - displayName: author.displayName, - avatar: author.avatar, - moderation: moderateProfile(author, moderationOpts), - associated: author.associated, - } - }) || []), + ...(item.additional?.map(({author}) => ({ + profile: author, + href: makeProfileLink(author), + moderation: moderateProfile(author, moderationOpts), + })) || []), ] }, [item, moderationOpts]) @@ -199,7 +194,8 @@ let FeedItem = ({ accessible={ (item.type === 'post-like' && authors.length === 1) || item.type === 'repost' - }> + } + onBeforePress={onBeforePress}> {/* TODO: Prevent conditional rendering and move toward composable notifications for clearer accessibility labeling */} @@ -229,7 +225,7 @@ let FeedItem = ({ style={[pal.text, s.bold]} href={authors[0].href} text={sanitizeDisplayName( - authors[0].displayName || authors[0].handle, + authors[0].profile.displayName || authors[0].profile.handle, )} disableMismatchWarning /> @@ -337,11 +333,9 @@ function CondensedAuthorsList({ ) @@ -356,11 +350,11 @@ function CondensedAuthorsList({ {authors.slice(0, MAX_AUTHORS).map(author => ( - ))} @@ -386,6 +380,7 @@ function ExpandedAuthorsList({ visible: boolean authors: Author[] }) { + const {_} = useLingui() const pal = usePalette('default') const heightInterp = useAnimatedValue(visible ? 1 : 0) const targetHeight = @@ -409,18 +404,23 @@ function ExpandedAuthorsList({ visible ? s.mb10 : undefined, ]}> {authors.map(author => ( - - + + + - {sanitizeDisplayName(author.displayName || author.handle)} + {sanitizeDisplayName( + author.profile.displayName || author.profile.handle, + )}   - {sanitizeHandle(author.handle)} + {sanitizeHandle(author.profile.handle)} - + ))} ) diff --git a/src/view/com/pager/Pager.tsx b/src/view/com/pager/Pager.tsx index 06ec2e4503..26070fb880 100644 --- a/src/view/com/pager/Pager.tsx +++ b/src/view/com/pager/Pager.tsx @@ -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>( const pagerView = React.useRef(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>( // -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>( const onTabBarSelect = React.useCallback( (index: number) => { pagerView.current?.setPage(index) - onPageSelecting?.(index) + onPageSelecting?.(index, 'tabbar-click') }, [pagerView, onPageSelecting], ) diff --git a/src/view/com/pager/Pager.web.tsx b/src/view/com/pager/Pager.web.tsx index 42982ef7f8..abba12b2cc 100644 --- a/src/view/com/pager/Pager.web.tsx +++ b/src/view/com/pager/Pager.web.tsx @@ -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: , - onSelect: onTabBarSelect, + onSelect: e => onTabBarSelect(e, 'tabbar-click'), })} {React.Children.map(children, (child, i) => ( diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx index aa110682a2..2d604d104e 100644 --- a/src/view/com/pager/PagerWithHeader.tsx +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -1,26 +1,28 @@ import * as React from 'react' import { LayoutChangeEvent, + NativeScrollEvent, ScrollView, StyleSheet, View, - NativeScrollEvent, } from 'react-native' import Animated, { - useAnimatedStyle, - useSharedValue, + AnimatedRef, runOnJS, runOnUI, scrollTo, - useAnimatedRef, - AnimatedRef, SharedValue, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, } from 'react-native-reanimated' -import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' -import {TabBar} from './TabBar' + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' -import {ListMethods} from '../util/List' import {ScrollProvider} from '#/lib/ScrollContext' +import {isIOS} from 'platform/detection' +import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' +import {ListMethods} from '../util/List' +import {TabBar} from './TabBar' export interface PagerWithHeaderChildParams { headerHeight: number @@ -236,9 +238,12 @@ let PagerTabBar = ({ const headerRef = React.useRef(null) return ( - + {renderHeader?.()} { // It wouldn't be enough to place `onLayout` on the parent node because diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx index 45c3771f50..1f70f41c4a 100644 --- a/src/view/com/post-thread/PostThreadFollowBtn.tsx +++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx @@ -1,24 +1,25 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {useNavigation} from '@react-navigation/native' import {AppBskyActorDefs} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useNavigation} from '@react-navigation/native' +import {useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' -import {Text} from 'view/com/util/text/Text' -import * as Toast from 'view/com/util/Toast' -import {s} from 'lib/styles' +import {track} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {s} from 'lib/styles' import {Shadow, useProfileShadow} from 'state/cache/profile-shadow' -import {track} from 'lib/analytics/analytics' import { useProfileFollowMutationQueue, useProfileQuery, } from 'state/queries/profile' import {useRequireAuth} from 'state/session' +import {Text} from 'view/com/util/text/Text' +import * as Toast from 'view/com/util/Toast' export function PostThreadFollowBtn({did}: {did: string}) { const {data: profile, isLoading} = useProfileQuery({did}) @@ -47,8 +48,10 @@ function PostThreadFollowBtnLoaded({ 'PostThreadItem', ) const requireAuth = useRequireAuth() + const gate = useGate() const isFollowing = !!profile.viewer?.following + const isFollowedBy = !!profile.viewer?.followedBy const [wasFollowing, setWasFollowing] = React.useState(isFollowing) // This prevents the button from disappearing as soon as we follow. @@ -136,7 +139,15 @@ function PostThreadFollowBtnLoaded({ type="button" style={[!isFollowing ? palInverted.text : pal.text, s.bold]} numberOfLines={1}> - {!isFollowing ? Follow : Following} + {!isFollowing ? ( + isFollowedBy && gate('show_follow_back_label_v2') ? ( + Follow Back + ) : ( + Follow + ) + ) : ( + Following + )} diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 6555bdf73c..564e37e7a6 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -1,50 +1,51 @@ import React, {memo, useMemo} from 'react' import {StyleSheet, View} from 'react-native' import { - AtUri, AppBskyFeedDefs, AppBskyFeedPost, - RichText as RichTextAPI, + AtUri, ModerationDecision, + RichText as RichTextAPI, } from '@atproto/api' -import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' -import {Link, TextLink} from '../util/Link' -import {RichText} from '#/components/RichText' -import {Text} from '../util/text/Text' -import {PreviewableUserAvatar} from '../util/UserAvatar' -import {s} from 'lib/styles' -import {niceDate} from 'lib/strings/time' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' +import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' +import {useLanguagePrefs} from '#/state/preferences' +import {useOpenLink} from '#/state/preferences/in-app-browser' +import {ThreadPost} from '#/state/queries/post-thread' +import {useModerationOpts} from '#/state/queries/preferences' +import {useComposerControls} from '#/state/shell/composer' +import {MAX_POST_LINES} from 'lib/constants' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {countLines, pluralize} from 'lib/strings/helpers' -import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' -import {PostMeta} from '../util/PostMeta' -import {PostEmbeds} from '../util/post-embeds' -import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {PostHider} from '../../../components/moderation/PostHider' -import {ContentHider} from '../../../components/moderation/ContentHider' -import {PostAlerts} from '../../../components/moderation/PostAlerts' -import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {usePalette} from 'lib/hooks/usePalette' -import {formatCount} from '../util/numeric/format' -import {makeProfileLink} from 'lib/routes/links' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {MAX_POST_LINES} from 'lib/constants' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useLanguagePrefs} from '#/state/preferences' -import {useComposerControls} from '#/state/shell/composer' -import {useModerationOpts} from '#/state/queries/preferences' -import {useOpenLink} from '#/state/preferences/in-app-browser' -import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' -import {ThreadPost} from '#/state/queries/post-thread' +import {niceDate} from 'lib/strings/time' +import {s} from 'lib/styles' +import {isWeb} from 'platform/detection' import {useSession} from 'state/session' -import {WhoCanReply} from '../threadgate/WhoCanReply' -import {LoadingPlaceholder} from '../util/LoadingPlaceholder' +import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' import {atoms as a} from '#/alf' +import {RichText} from '#/components/RichText' +import {ContentHider} from '../../../components/moderation/ContentHider' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {PostHider} from '../../../components/moderation/PostHider' +import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' +import {WhoCanReply} from '../threadgate/WhoCanReply' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Link, TextLink} from '../util/Link' +import {formatCount} from '../util/numeric/format' +import {PostCtrls} from '../util/post-ctrls/PostCtrls' +import {PostEmbeds} from '../util/post-embeds' +import {PostMeta} from '../util/PostMeta' +import {Text} from '../util/text/Text' +import {PreviewableUserAvatar} from '../util/UserAvatar' export function PostThreadItem({ post, @@ -248,9 +249,7 @@ let PostThreadItemLoaded = ({ @@ -325,12 +324,6 @@ let PostThreadItemLoaded = ({ {post.repostCount !== 0 || post.likeCount !== 0 ? ( // Show this section unless we're *sure* it has no engagement. - {post.repostCount == null && post.likeCount == null && ( - // If we're still loading and not sure, assume this post has engagement. - // This lets us avoid a layout shift for the common case (embedded post with likes/reposts). - // TODO: embeds should include metrics to avoid us having to guess. - - )} {post.repostCount != null && post.repostCount !== 0 ? ( + } + profile={post.author}> @@ -484,7 +476,12 @@ let PostThreadItemLoaded = ({ avatarSize={28} displayNameType="md-bold" displayNameStyle={isThreadedChild && s.ml2} - style={isThreadedChild && s.mb2} + style={ + isThreadedChild && { + alignItems: 'center', + paddingBottom: isWeb ? 5 : 2, + } + } /> }) { + const queryClient = useQueryClient() const pal = usePalette('default') const {_} = useLingui() const {openComposer} = useComposerControls() @@ -129,16 +134,21 @@ function PostInner({ setLimitLines(false) }, [setLimitLines]) + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, post.author) + }, [queryClient, post.author]) + return ( - + {showReplyLine && } @@ -165,12 +175,14 @@ function PostInner({ numberOfLines={1}> Reply to{' '} - + + + diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 8afcce94f2..fb67d35c5c 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -8,32 +8,33 @@ import { View, ViewStyle, } from 'react-native' -import {useQueryClient} from '@tanstack/react-query' -import {List, ListRef} from '../util/List' -import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' -import {FeedErrorMessage} from './FeedErrorMessage' -import {FeedSlice} from './FeedSlice' -import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' -import {useAnalytics} from 'lib/analytics/analytics' -import {useTheme} from 'lib/ThemeContext' -import {logger} from '#/logger' -import { - RQKEY, - FeedDescriptor, - FeedParams, - usePostFeedQuery, - pollLatest, -} from '#/state/queries/post-feed' -import {isWeb} from '#/platform/detection' -import {listenPostCreated} from '#/state/events' -import {useSession} from '#/state/session' -import {STALE} from '#/state/queries' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' +import {useQueryClient} from '@tanstack/react-query' + import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' -import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' import {logEvent} from '#/lib/statsig/statsig' +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {listenPostCreated} from '#/state/events' +import {STALE} from '#/state/queries' +import { + FeedDescriptor, + FeedParams, + pollLatest, + RQKEY, + usePostFeedQuery, +} from '#/state/queries/post-feed' +import {useSession} from '#/state/session' +import {useAnalytics} from 'lib/analytics/analytics' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {useTheme} from 'lib/ThemeContext' +import {List, ListRef} from '../util/List' +import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' +import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' +import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' +import {FeedErrorMessage} from './FeedErrorMessage' +import {FeedSlice} from './FeedSlice' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -217,6 +218,7 @@ let Feed = ({ track('Feed:onRefresh') logEvent('feed:refresh', { feedType: feedType, + feedUrl: feed, reason: 'pull-to-refresh', }) setIsPTRing(true) @@ -227,13 +229,14 @@ let Feed = ({ logger.error('Failed to refresh posts feed', {message: err}) } setIsPTRing(false) - }, [refetch, track, setIsPTRing, onHasNew, feedType]) + }, [refetch, track, setIsPTRing, onHasNew, feed, feedType]) const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return logEvent('feed:endReached', { feedType: feedType, + feedUrl: feed, itemCount: feedItems.length, }) track('Feed:onEndReached') @@ -248,6 +251,7 @@ let Feed = ({ isError, fetchNextPage, track, + feed, feedType, feedItems.length, ]) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 0fbcc4a13c..605dffde9d 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -11,31 +11,35 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {ReasonFeedSource, isReasonFeedSource} from 'lib/api/feed/types' -import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link' -import {Text} from '../util/text/Text' -import {UserInfoText} from '../util/UserInfoText' -import {PostMeta} from '../util/PostMeta' -import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {PostEmbeds} from '../util/post-embeds' -import {ContentHider} from '#/components/moderation/ContentHider' -import {PostAlerts} from '../../../components/moderation/PostAlerts' -import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' -import {RichText} from '#/components/RichText' -import {PreviewableUserAvatar} from '../util/UserAvatar' -import {s} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' + +import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' +import {useComposerControls} from '#/state/shell/composer' +import {isReasonFeedSource, ReasonFeedSource} from 'lib/api/feed/types' +import {MAX_POST_LINES} from 'lib/constants' import {usePalette} from 'lib/hooks/usePalette' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' -import {useComposerControls} from '#/state/shell/composer' -import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' -import {FeedNameText} from '../util/FeedInfoText' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {s} from 'lib/styles' +import {precacheProfile} from 'state/queries/profile' import {atoms as a} from '#/alf' +import {ContentHider} from '#/components/moderation/ContentHider' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import {RichText} from '#/components/RichText' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {FeedNameText} from '../util/FeedInfoText' +import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link' +import {PostCtrls} from '../util/post-ctrls/PostCtrls' +import {PostEmbeds} from '../util/post-embeds' +import {PostMeta} from '../util/PostMeta' +import {Text} from '../util/text/Text' +import {PreviewableUserAvatar} from '../util/UserAvatar' +import {UserInfoText} from '../util/UserInfoText' export function FeedItem({ post, @@ -104,6 +108,7 @@ let FeedItemInner = ({ isThreadLastChild?: boolean isThreadParent?: boolean }): React.ReactNode => { + const queryClient = useQueryClient() const {openComposer} = useComposerControls() const pal = usePalette('default') const {_} = useLingui() @@ -133,6 +138,10 @@ let FeedItemInner = ({ }) }, [post, record, openComposer, moderation]) + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, post.author) + }, [queryClient, post.author]) + const outerStyles = [ styles.outer, { @@ -151,7 +160,8 @@ let FeedItemInner = ({ style={outerStyles} href={href} noFeedback - accessible={false}> + accessible={false} + onBeforePress={onBeforePress}> {isThreadChild && ( @@ -213,17 +223,20 @@ let FeedItemInner = ({ numberOfLines={1}> Reposted by{' '} - + + + @@ -235,9 +248,7 @@ let FeedItemInner = ({ @@ -279,12 +290,14 @@ let FeedItemInner = ({ numberOfLines={1}> Reply to{' '} - + + + diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 235139fff0..90ab9b7385 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { AppBskyActorDefs, @@ -6,22 +6,25 @@ import { ModerationCause, ModerationDecision, } from '@atproto/api' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {UserAvatar} from '../util/UserAvatar' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {FollowButton} from './FollowButton' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {getModerationCauseKey, isJustAMute} from 'lib/moderation' +import {Trans} from '@lingui/macro' +import {useQueryClient} from '@tanstack/react-query' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/queries/preferences' -import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' -import {Trans} from '@lingui/macro' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {usePalette} from 'lib/hooks/usePalette' +import {getModerationCauseKey, isJustAMute} from 'lib/moderation' +import {makeProfileLink} from 'lib/routes/links' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {s} from 'lib/styles' +import {precacheProfile} from 'state/queries/profile' +import {Link} from '../util/Link' +import {Text} from '../util/text/Text' +import {PreviewableUserAvatar} from '../util/UserAvatar' +import {FollowButton} from './FollowButton' export function ProfileCard({ testID, @@ -46,10 +49,17 @@ export function ProfileCard({ onPress?: () => void style?: StyleProp }) { + const queryClient = useQueryClient() const pal = usePalette('default') const profile = useProfileShadow(profileUnshadowed) const moderationOpts = useModerationOpts() const isLabeler = profile?.associated?.labeler + + const onBeforePress = React.useCallback(() => { + onPress?.() + precacheProfile(queryClient, profile) + }, [onPress, profile, queryClient]) + if (!moderationOpts) { return null } @@ -71,14 +81,14 @@ export function ProfileCard({ ]} href={makeProfileLink(profile)} title={profile.handle} - onBeforePress={onPress} asAnchor + onBeforePress={onBeforePress} anchorNoUnderline> - @@ -221,9 +231,9 @@ function FollowersList({ {followersWithMods.slice(0, 3).map(({f, mod}) => ( - diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index 3602cdb9a8..4c9d164f75 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -1,28 +1,28 @@ import React from 'react' -import {View, StyleSheet, Pressable, ScrollView} from 'react-native' +import {Pressable, ScrollView, StyleSheet, View} from 'react-native' import {AppBskyActorDefs, moderateProfile} from '@atproto/api' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' -import * as Toast from '../util/Toast' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useModerationOpts} from '#/state/queries/preferences' +import {useProfileFollowMutationQueue} from '#/state/queries/profile' +import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' +import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' -import {Text} from 'view/com/util/text/Text' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Button} from 'view/com/util/forms/Button' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {Link} from 'view/com/util/Link' -import {useAnalytics} from 'lib/analytics/analytics' import {isWeb} from 'platform/detection' -import {useModerationOpts} from '#/state/queries/preferences' -import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' -import {useProfileShadow} from '#/state/cache/profile-shadow' -import {useProfileFollowMutationQueue} from '#/state/queries/profile' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' +import {Button} from 'view/com/util/forms/Button' +import {Link} from 'view/com/util/Link' +import {Text} from 'view/com/util/text/Text' +import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' +import * as Toast from '../util/Toast' const OUTER_PADDING = 10 const INNER_PADDING = 14 @@ -218,8 +218,9 @@ function SuggestedFollow({ backgroundColor: pal.view.backgroundColor, }, ]}> - diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx index 22fdd606e4..dccd2bbc9b 100644 --- a/src/view/com/util/ErrorBoundary.tsx +++ b/src/view/com/util/ErrorBoundary.tsx @@ -1,12 +1,14 @@ import React, {Component, ErrorInfo, ReactNode} from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' import {ErrorScreen} from './error/ErrorScreen' import {CenteredView} from './Views' -import {msg} from '@lingui/macro' -import {logger} from '#/logger' -import {useLingui} from '@lingui/react' interface Props { children?: ReactNode + renderError?: (error: any) => ReactNode } interface State { @@ -30,6 +32,10 @@ export class ErrorBoundary extends Component { public render() { if (this.state.hasError) { + if (this.props.renderError) { + return this.props.renderError(this.state.error) + } + return ( diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index b6c512b09e..78d995ee82 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -2,34 +2,35 @@ import React, {ComponentProps, memo, useMemo} from 'react' import { GestureResponderEvent, Platform, + Pressable, StyleProp, - TextStyle, TextProps, + TextStyle, + TouchableOpacity, View, ViewStyle, - Pressable, - TouchableOpacity, } from 'react-native' -import {useLinkProps, StackActions} from '@react-navigation/native' -import {Text} from './text/Text' -import {TypographyVariant} from 'lib/ThemeContext' -import {router} from '../../../routes' +import {sanitizeUrl} from '@braintree/sanitize-url' +import {StackActions, useLinkProps} from '@react-navigation/native' + +import {useModalControls} from '#/state/modals' +import {useOpenLink} from '#/state/preferences/in-app-browser' +import { + DebouncedNavigationProp, + useNavigationDeduped, +} from 'lib/hooks/useNavigationDeduped' import { convertBskyAppUrlIfNeeded, isExternalUrl, linkRequiresWarning, } from 'lib/strings/url-helpers' +import {TypographyVariant} from 'lib/ThemeContext' import {isAndroid, isWeb} from 'platform/detection' -import {sanitizeUrl} from '@braintree/sanitize-url' -import {PressableWithHover} from './PressableWithHover' -import {useModalControls} from '#/state/modals' -import {useOpenLink} from '#/state/preferences/in-app-browser' import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper' -import { - DebouncedNavigationProp, - useNavigationDeduped, -} from 'lib/hooks/useNavigationDeduped' import {useTheme} from '#/alf' +import {router} from '../../../routes' +import {PressableWithHover} from './PressableWithHover' +import {Text} from './text/Text' type Event = | React.MouseEvent @@ -147,8 +148,10 @@ export const TextLink = memo(function TextLink({ dataSet, title, onPress, + onBeforePress, disableMismatchWarning, navigationAction, + anchorNoUnderline, ...orgProps }: { testID?: string @@ -162,6 +165,8 @@ export const TextLink = memo(function TextLink({ title?: string disableMismatchWarning?: boolean navigationAction?: 'push' | 'replace' | 'navigate' + anchorNoUnderline?: boolean + onBeforePress?: () => void } & TextProps) { const {...props} = useLinkProps({to: sanitizeUrl(href)}) const navigation = useNavigationDeduped() @@ -172,6 +177,11 @@ export const TextLink = memo(function TextLink({ console.error('Unable to detect mismatching label') } + if (anchorNoUnderline) { + dataSet = dataSet ?? {} + dataSet.noUnderline = 1 + } + props.onPress = React.useCallback( (e?: Event) => { const requiresWarning = @@ -194,6 +204,7 @@ export const TextLink = memo(function TextLink({ // Let the browser handle opening in new tab etc. return } + onBeforePress?.() if (onPress) { e?.preventDefault?.() // @ts-ignore function signature differs by platform -prf @@ -218,6 +229,7 @@ export const TextLink = memo(function TextLink({ disableMismatchWarning, navigationAction, openLink, + onBeforePress, ], ) const hrefAttrs = useMemo(() => { @@ -266,7 +278,9 @@ interface TextLinkOnWebOnlyProps extends TextProps { title?: string navigationAction?: 'push' | 'replace' | 'navigate' disableMismatchWarning?: boolean + onBeforePress?: () => void onPointerEnter?: () => void + anchorNoUnderline?: boolean } export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ testID, @@ -278,6 +292,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ lineHeight, navigationAction, disableMismatchWarning, + onBeforePress, ...props }: TextLinkOnWebOnlyProps) { if (isWeb) { @@ -293,6 +308,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ title={props.title} navigationAction={navigationAction} disableMismatchWarning={disableMismatchWarning} + onBeforePress={onBeforePress} {...props} /> ) diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index d30a9d805b..5729a43a52 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -1,11 +1,14 @@ import React, {memo} from 'react' import {FlatListProps, RefreshControl} from 'react-native' -import {FlatList_INTERNAL} from './Views' -import {addStyle} from 'lib/styles' -import {useScrollHandlers} from '#/lib/ScrollContext' import {runOnJS, useSharedValue} from 'react-native-reanimated' + import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {usePalette} from '#/lib/hooks/usePalette' +import {useScrollHandlers} from '#/lib/ScrollContext' +import {useGate} from 'lib/statsig/statsig' +import {addStyle} from 'lib/styles' +import {isWeb} from 'platform/detection' +import {FlatList_INTERNAL} from './Views' export type ListMethods = FlatList_INTERNAL export type ListProps = Omit< @@ -37,6 +40,7 @@ function ListImpl( const isScrolledDown = useSharedValue(false) const contextScrollHandlers = useScrollHandlers() const pal = usePalette('default') + const gate = useGate() function handleScrolledDownChange(didScrollDown: boolean) { onScrolledDownChange?.(didScrollDown) @@ -93,6 +97,9 @@ function ListImpl( scrollEventThrottle={1} style={style} ref={ref} + showsVerticalScrollIndicator={ + isWeb || !gate('hide_vertical_scroll_indicators') + } /> ) } diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 529fc54e01..db16ff066d 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,18 +1,20 @@ -import React, {memo} from 'react' +import React, {memo, useCallback} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import {Text} from './text/Text' -import {TextLinkOnWebOnly} from './Link' -import {niceDate} from 'lib/strings/time' +import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' +import {useQueryClient} from '@tanstack/react-query' + +import {precacheProfile, usePrefetchProfileQuery} from '#/state/queries/profile' import {usePalette} from 'lib/hooks/usePalette' -import {TypographyVariant} from 'lib/ThemeContext' -import {UserAvatar} from './UserAvatar' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' +import {niceDate} from 'lib/strings/time' +import {TypographyVariant} from 'lib/ThemeContext' import {isAndroid, isWeb} from 'platform/detection' +import {TextLinkOnWebOnly} from './Link' +import {Text} from './text/Text' import {TimeElapsed} from './TimeElapsed' -import {makeProfileLink} from 'lib/routes/links' -import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' -import {usePrefetchProfileQuery} from '#/state/queries/profile' +import {PreviewableUserAvatar} from './UserAvatar' interface PostMetaOpts { author: AppBskyActorDefs.ProfileViewBasic @@ -34,23 +36,34 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { const handle = opts.author.handle const prefetchProfileQuery = usePrefetchProfileQuery() + const profileLink = makeProfileLink(opts.author) + const onPointerEnter = isWeb + ? () => prefetchProfileQuery(opts.author.did) + : undefined + + const queryClient = useQueryClient() + const onBeforePress = useCallback(() => { + precacheProfile(queryClient, opts.author) + }, [queryClient, opts.author]) + return ( {opts.showAvatar && ( - )} - + { displayName, opts.moderation?.ui('displayName'), )} -   - - {sanitizeHandle(handle, '@')} - } - href={makeProfileLink(opts.author)} - onPointerEnter={ - isWeb ? () => prefetchProfileQuery(opts.author.did) : undefined - } + href={profileLink} + onBeforePress={onBeforePress} + onPointerEnter={onPointerEnter} /> - + + {!isAndroid && ( { title={niceDate(opts.timestamp)} accessibilityHint="" href={opts.postHref} + onBeforePress={onBeforePress} /> )} @@ -107,7 +122,7 @@ export {PostMeta} const styles = StyleSheet.create({ container: { flexDirection: 'row', - alignItems: 'center', + alignItems: 'flex-end', paddingBottom: 2, gap: 4, zIndex: 1, diff --git a/src/view/com/util/TimeElapsed.tsx b/src/view/com/util/TimeElapsed.tsx index aa3a092235..6ea41b82bc 100644 --- a/src/view/com/util/TimeElapsed.tsx +++ b/src/view/com/util/TimeElapsed.tsx @@ -1,6 +1,7 @@ import React from 'react' -import {ago} from 'lib/strings/time' + import {useTickEveryMinute} from '#/state/shell' +import {ago} from 'lib/strings/time' // FIXME(dan): Figure out why the false positives @@ -12,7 +13,7 @@ export function TimeElapsed({ children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element }) { const tick = useTickEveryMinute() - const [timeElapsed, setTimeAgo] = React.useState(ago(timestamp)) + const [timeElapsed, setTimeAgo] = React.useState(() => ago(timestamp)) React.useEffect(() => { setTimeAgo(ago(timestamp)) diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 4beedbd5b4..118e2ce2be 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -1,30 +1,34 @@ import React, {memo, useMemo} from 'react' import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' -import Svg, {Circle, Rect, Path} from 'react-native-svg' import {Image as RNImage} from 'react-native-image-crop-picker' -import {useLingui} from '@lingui/react' -import {msg, Trans} from '@lingui/macro' +import Svg, {Circle, Path, Rect} from 'react-native-svg' +import {AppBskyActorDefs, ModerationUI} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {ModerationUI} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' -import {HighPriorityImage} from 'view/com/util/images/Image' -import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' -import { - usePhotoLibraryPermission, - useCameraPermission, -} from 'lib/hooks/usePermissions' -import {colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' -import {isWeb, isAndroid, isNative} from 'platform/detection' -import {UserPreviewLink} from './UserPreviewLink' -import * as Menu from '#/components/Menu' import { - Camera_Stroke2_Corner0_Rounded as Camera, + useCameraPermission, + usePhotoLibraryPermission, +} from 'lib/hooks/usePermissions' +import {makeProfileLink} from 'lib/routes/links' +import {colors} from 'lib/styles' +import {isAndroid, isNative, isWeb} from 'platform/detection' +import {precacheProfile} from 'state/queries/profile' +import {HighPriorityImage} from 'view/com/util/images/Image' +import {tokens, useTheme} from '#/alf' +import { Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, + Camera_Stroke2_Corner0_Rounded as Camera, } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' -import {useTheme, tokens} from '#/alf' +import {Link} from '#/components/Link' +import * as Menu from '#/components/Menu' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' @@ -45,8 +49,7 @@ interface EditableUserAvatarProps extends BaseUserAvatarProps { interface PreviewableUserAvatarProps extends BaseUserAvatarProps { moderation?: ModerationUI - did: string - handle: string + profile: AppBskyActorDefs.ProfileViewBasic } const BLUR_AMOUNT = isWeb ? 5 : 100 @@ -369,13 +372,30 @@ let EditableUserAvatar = ({ EditableUserAvatar = memo(EditableUserAvatar) export {EditableUserAvatar} -let PreviewableUserAvatar = ( - props: PreviewableUserAvatarProps, -): React.ReactNode => { +let PreviewableUserAvatar = ({ + moderation, + profile, + ...rest +}: PreviewableUserAvatarProps): React.ReactNode => { + const {_} = useLingui() + const queryClient = useQueryClient() + + const onPress = React.useCallback(() => { + precacheProfile(queryClient, profile) + }, [profile, queryClient]) + return ( - - - + + + + + ) } PreviewableUserAvatar = memo(PreviewableUserAvatar) diff --git a/src/view/com/util/UserPreviewLink.tsx b/src/view/com/util/UserPreviewLink.tsx deleted file mode 100644 index a2c46afc01..0000000000 --- a/src/view/com/util/UserPreviewLink.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import {StyleProp, ViewStyle} from 'react-native' -import {Link} from './Link' -import {isWeb} from 'platform/detection' -import {makeProfileLink} from 'lib/routes/links' -import {usePrefetchProfileQuery} from '#/state/queries/profile' - -interface UserPreviewLinkProps { - did: string - handle: string - style?: StyleProp -} -export function UserPreviewLink( - props: React.PropsWithChildren, -) { - const prefetchProfileQuery = usePrefetchProfileQuery() - return ( - { - if (isWeb) { - prefetchProfileQuery(props.did) - } - }} - href={makeProfileLink(props)} - title={props.handle} - asAnchor - style={props.style}> - {props.children} - - ) -} diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 872e10eef0..63a2b3de39 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,19 +1,20 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' -import {CenteredView} from './Views' -import {Text} from './text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useAnalytics} from 'lib/analytics/analytics' -import {NavigationProp} from 'lib/routes/types' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import Animated from 'react-native-reanimated' -import {useSetDrawerOpen} from '#/state/shell' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {useSetDrawerOpen} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' import {useTheme} from '#/alf' +import {Text} from './text/Text' +import {CenteredView} from './Views' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} @@ -62,6 +63,7 @@ export function ViewHeader({ return ( @@ -136,14 +138,17 @@ export function ViewHeader({ function DesktopWebHeader({ title, + subtitle, renderButton, showBorder = true, }: { title: string + subtitle?: string renderButton?: () => JSX.Element showBorder?: boolean }) { const pal = usePalette('default') + const t = useTheme() return ( - - - {title} - + + + + {title} + + + {renderButton?.()} - {renderButton?.()} + {subtitle ? ( + + + + {subtitle} + + + + ) : null} ) } @@ -236,6 +258,9 @@ const styles = StyleSheet.create({ subtitle: { fontSize: 13, }, + subtitleDesktop: { + fontSize: 15, + }, backBtn: { width: 30, height: 30, diff --git a/src/view/com/util/Views.jsx b/src/view/com/util/Views.jsx index 7d6120583f..75f2b50814 100644 --- a/src/view/com/util/Views.jsx +++ b/src/view/com/util/Views.jsx @@ -2,8 +2,19 @@ import React from 'react' import {View} from 'react-native' import Animated from 'react-native-reanimated' +import {useGate} from 'lib/statsig/statsig' + export const FlatList_INTERNAL = Animated.FlatList -export const ScrollView = Animated.ScrollView export function CenteredView(props) { return } + +export function ScrollView(props) { + const gate = useGate() + return ( + + ) +} diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 959e0f692e..32520182e9 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -28,12 +28,14 @@ import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' -import {atoms as a, useTheme as useAlf} from '#/alf' +import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {EmbedDialog} from '#/components/dialogs/Embed' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' +import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' @@ -55,6 +57,7 @@ let PostDropdownBtn = ({ richText, style, hitSlop, + timestamp, }: { testID: string postAuthor: AppBskyActorDefs.ProfileViewBasic @@ -64,10 +67,12 @@ let PostDropdownBtn = ({ richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] + timestamp: string }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() const alf = useAlf() + const {gtMobile} = useBreakpoints() const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl const langPrefs = useLanguagePrefs() @@ -83,6 +88,7 @@ let PostDropdownBtn = ({ const deletePromptControl = useDialogControl() const hidePromptControl = useDialogControl() const loggedOutWarningPromptControl = useDialogControl() + const embedPostControl = useDialogControl() const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) @@ -166,7 +172,7 @@ let PostDropdownBtn = ({ hidePost({uri: postUri}) }, [postUri, hidePost]) - const shouldShowLoggedOutWarning = React.useMemo(() => { + const hideInPWI = React.useMemo(() => { return !!postAuthor.labels?.find( label => label.val === '!no-unauthenticated', ) @@ -177,6 +183,8 @@ let PostDropdownBtn = ({ shareUrl(url) }, [href]) + const canEmbed = isWeb && gtMobile && !hideInPWI + return ( @@ -207,27 +215,31 @@ let PostDropdownBtn = ({ - - {_(msg`Translate`)} - - + {(!hideInPWI || hasSession) && ( + <> + + {_(msg`Translate`)} + + - - {_(msg`Copy post text`)} - - + + {_(msg`Copy post text`)} + + + + )} { - if (shouldShowLoggedOutWarning) { + if (hideInPWI) { loggedOutWarningPromptControl.open() } else { onSharePost() @@ -238,6 +250,16 @@ let PostDropdownBtn = ({ + + {canEmbed && ( + + {_(msg`Embed post`)} + + + )} {hasSession && ( @@ -283,29 +305,33 @@ let PostDropdownBtn = ({ )} - + {hasSession && ( + <> + - - {!isAuthor && ( - reportDialogControl.open()}> - {_(msg`Report post`)} - - - )} + + {!isAuthor && ( + reportDialogControl.open()}> + {_(msg`Report post`)} + + + )} - {isAuthor && ( - - {_(msg`Delete post`)} - - - )} - + {isAuthor && ( + + {_(msg`Delete post`)} + + + )} + + + )} @@ -346,6 +372,17 @@ let PostDropdownBtn = ({ onConfirm={onSharePost} confirmButtonCta={_(msg`Share anyway`)} /> + + {canEmbed && ( + + )} ) } diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 58874cd551..cb50ee6dc3 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -16,7 +16,6 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' -import {Haptics} from '#/lib/haptics' import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' @@ -32,6 +31,7 @@ import { } from '#/state/queries/post' import {useRequireAuth} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' +import {useHaptics} from 'lib/haptics' import {useDialogControl} from '#/components/Dialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' import * as Prompt from '#/components/Prompt' @@ -67,6 +67,7 @@ let PostCtrls = ({ ) const requireAuth = useRequireAuth() const loggedOutWarningPromptControl = useDialogControl() + const playHaptic = useHaptics() const shouldShowLoggedOutWarning = React.useMemo(() => { return !!post.author.labels?.find( @@ -84,7 +85,7 @@ let PostCtrls = ({ const onPressToggleLike = React.useCallback(async () => { try { if (!post.viewer?.like) { - Haptics.default() + playHaptic() await queueLike() } else { await queueUnlike() @@ -94,13 +95,13 @@ let PostCtrls = ({ throw e } } - }, [post.viewer?.like, queueLike, queueUnlike]) + }, [playHaptic, post.viewer?.like, queueLike, queueUnlike]) const onRepost = useCallback(async () => { closeModal() try { if (!post.viewer?.repost) { - Haptics.default() + playHaptic() await queueRepost() } else { await queueUnrepost() @@ -110,7 +111,7 @@ let PostCtrls = ({ throw e } } - }, [post.viewer?.repost, queueRepost, queueUnrepost, closeModal]) + }, [closeModal, post.viewer?.repost, playHaptic, queueRepost, queueUnrepost]) const onQuote = useCallback(() => { closeModal() @@ -123,15 +124,16 @@ let PostCtrls = ({ indexedAt: post.indexedAt, }, }) - Haptics.default() + playHaptic() }, [ + closeModal, + openComposer, post.uri, post.cid, post.author, post.indexedAt, record.text, - openComposer, - closeModal, + playHaptic, ]) const onShare = useCallback(() => { @@ -262,6 +264,7 @@ let PostCtrls = ({ richText={richText} style={styles.btnPad} hitSlop={big ? HITSLOP_20 : HITSLOP_10} + timestamp={post.indexedAt} /> diff --git a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx index f06c8b794d..b2720752ca 100644 --- a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx @@ -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 ( - - {(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay - - - {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active - - ) : ( - // Activity indicator while gif loads - - )} - - - )} - + - + + + {(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay + + + {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active + + ) : ( + // Activity indicator while gif loads + + )} + + + )} + + + ) } diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx index aaa98a41f6..1fe75c44ea 100644 --- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx @@ -1,20 +1,28 @@ -import React from 'react' +import React, {useCallback} from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' import {Image} from 'expo-image' -import {Text} from '../text/Text' -import {StyleSheet, View} from 'react-native' +import {AppBskyEmbedExternal} from '@atproto/api' + import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {AppBskyEmbedExternal} from '@atproto/api' -import {toNiceDomain} from 'lib/strings/url-helpers' +import {shareUrl} from 'lib/sharing' import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' -import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed' -import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed' +import {toNiceDomain} from 'lib/strings/url-helpers' +import {isNative} from 'platform/detection' import {useExternalEmbedsPrefs} from 'state/preferences' +import {Link} from 'view/com/util/Link' +import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed' +import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed' +import {GifEmbed} from 'view/com/util/post-embeds/GifEmbed' +import {atoms as a, useTheme} from '#/alf' +import {Text} from '../text/Text' export const ExternalLinkEmbed = ({ link, + style, }: { link: AppBskyEmbedExternal.ViewExternal + style?: StyleProp }) => { const pal = usePalette('default') const {isMobile} = useWebMediaQueries() @@ -28,62 +36,95 @@ export const ExternalLinkEmbed = ({ } }, [link.uri, externalEmbedPrefs]) + if (embedPlayerParams?.source === 'tenor') { + return + } + return ( - - {link.thumb && !embedPlayerParams ? ( - - ) : undefined} - {(embedPlayerParams?.isGif && ( - - )) || - (embedPlayerParams && ( - - ))} - - - {toNiceDomain(link.uri)} - - {!embedPlayerParams?.isGif && ( - - {link.title || link.uri} - - )} - {link.description && !embedPlayerParams?.hideDetails ? ( - - {link.description} - + + + {link.thumb && !embedPlayerParams ? ( + ) : undefined} - + {embedPlayerParams?.isGif ? ( + + ) : embedPlayerParams ? ( + + ) : undefined} + + + {toNiceDomain(link.uri)} + + + {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && ( + + {link.title || link.uri} + + )} + {link.description ? ( + + {link.description} + + ) : undefined} + + ) } -const styles = StyleSheet.create({ - container: { - flexDirection: 'column', - borderRadius: 6, - overflow: 'hidden', - }, - info: { - width: '100%', - bottom: 0, - paddingTop: 8, - paddingBottom: 10, - }, - extUri: { - marginTop: 2, - }, - extDescription: { - marginTop: 4, - }, -}) +function LinkWrapper({ + link, + style, + children, +}: { + link: AppBskyEmbedExternal.ViewExternal + style?: StyleProp + children: React.ReactNode +}) { + const t = useTheme() + + const onShareExternal = useCallback(() => { + if (link.uri && isNative) { + shareUrl(link.uri) + } + }, [link.uri]) + + return ( + + {children} + + ) +} diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx index cf2db5b333..9fdede877d 100644 --- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx @@ -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 ( - + + { - setPlayerActive(true) - }, - }) + consentDialogControl.open() return } setPlayerActive(true) }, - [externalEmbedsPrefs, openModal, params.source], + [externalEmbedsPrefs, consentDialogControl, params.source], ) + const onAcceptConsent = React.useCallback(() => { + setPlayerActive(true) + }, []) + return ( - - {link.thumb && (!isPlayerActive || isLoading) && ( - - )} - + - - + + + {link.thumb && (!isPlayerActive || isLoading) && ( + + )} + + + + ) } @@ -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', diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx new file mode 100644 index 0000000000..5d21ce0642 --- /dev/null +++ b/src/view/com/util/post-embeds/GifEmbed.tsx @@ -0,0 +1,141 @@ +import React from 'react' +import {Pressable, View} from 'react-native' +import {AppBskyEmbedExternal} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {EmbedPlayerParams} from 'lib/strings/embed-player' +import {useAutoplayDisabled} from 'state/preferences' +import {atoms as a, useTheme} from '#/alf' +import {Loader} from '#/components/Loader' +import {GifView} from '../../../../../modules/expo-bluesky-gif-view' +import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' + +function PlaybackControls({ + onPress, + isPlaying, + isLoaded, +}: { + onPress: () => void + isPlaying: boolean + isLoaded: boolean +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + {!isLoaded ? ( + + + + + + ) : !isPlaying ? ( + + + + ) : undefined} + + ) +} + +export function GifEmbed({ + params, + link, +}: { + params: EmbedPlayerParams + link: AppBskyEmbedExternal.ViewExternal +}) { + const {_} = useLingui() + const autoplayDisabled = useAutoplayDisabled() + + const playerRef = React.useRef(null) + + const [playerState, setPlayerState] = React.useState<{ + isPlaying: boolean + isLoaded: boolean + }>({ + isPlaying: !autoplayDisabled, + isLoaded: false, + }) + + const onPlayerStateChange = React.useCallback( + (e: GifViewStateChangeEvent) => { + setPlayerState(e.nativeEvent) + }, + [], + ) + + const onPress = React.useCallback(() => { + playerRef.current?.toggleAsync() + }, []) + + return ( + + + + + + + ) +} diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 2b1c3e6179..e0178f34b4 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -1,31 +1,44 @@ import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { - AppBskyFeedDefs, - AppBskyEmbedRecord, - AppBskyFeedPost, - AppBskyEmbedImages, - AppBskyEmbedRecordWithMedia, + StyleProp, + StyleSheet, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native' +import { AppBskyEmbedExternal, - RichText as RichTextAPI, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyFeedDefs, + AppBskyFeedPost, moderatePost, ModerationDecision, + RichText as RichTextAPI, } from '@atproto/api' import {AtUri} from '@atproto/api' -import {PostMeta} from '../PostMeta' -import {Link} from '../Link' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {ComposerOptsQuote} from 'state/shell/composer' -import {PostEmbeds} from '.' -import {PostAlerts} from '../../../../components/moderation/PostAlerts' -import {makeProfileLink} from 'lib/routes/links' -import {InfoCircleIcon} from 'lib/icons' -import {Trans} from '@lingui/macro' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' + +import {HITSLOP_20} from '#/lib/constants' +import {s} from '#/lib/styles' import {useModerationOpts} from '#/state/queries/preferences' -import {ContentHider} from '../../../../components/moderation/ContentHider' -import {RichText} from '#/components/RichText' +import {usePalette} from 'lib/hooks/usePalette' +import {InfoCircleIcon} from 'lib/icons' +import {makeProfileLink} from 'lib/routes/links' +import {precacheProfile} from 'state/queries/profile' +import {ComposerOptsQuote} from 'state/shell/composer' import {atoms as a} from '#/alf' +import {RichText} from '#/components/RichText' +import {ContentHider} from '../../../../components/moderation/ContentHider' +import {PostAlerts} from '../../../../components/moderation/PostAlerts' +import {Link} from '../Link' +import {PostMeta} from '../PostMeta' +import {Text} from '../text/Text' +import {PostEmbeds} from '.' export function MaybeQuoteEmbed({ embed, @@ -107,6 +120,7 @@ export function QuoteEmbed({ moderation?: ModerationDecision style?: StyleProp }) { + const queryClient = useQueryClient() const pal = usePalette('default') const itemUrip = new AtUri(quote.uri) const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey) @@ -134,13 +148,18 @@ export function QuoteEmbed({ } }, [quote.embeds]) + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, quote.author) + }, [queryClient, quote.author]) + return ( + title={itemTitle} + onBeforePress={onBeforePress}> void}) { + const {_} = useLingui() + return ( + + + + ) +} + function viewRecordToPostView( viewRecord: AppBskyEmbedRecord.ViewRecord, ): AppBskyFeedDefs.PostView { diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 47091fbb07..7ea5b55cfe 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -1,34 +1,32 @@ -import React, {useCallback} from 'react' +import React from 'react' import { - StyleSheet, + InteractionManager, StyleProp, + StyleSheet, + Text, View, ViewStyle, - Text, - InteractionManager, } from 'react-native' import {Image} from 'expo-image' import { - AppBskyEmbedImages, AppBskyEmbedExternal, + AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, AppBskyGraphDefs, ModerationDecision, } from '@atproto/api' -import {Link} from '../Link' -import {ImageLayoutGrid} from '../images/ImageLayoutGrid' -import {useLightboxControls, ImagesLightbox} from '#/state/lightbox' + +import {ImagesLightbox, useLightboxControls} from '#/state/lightbox' import {usePalette} from 'lib/hooks/usePalette' -import {ExternalLinkEmbed} from './ExternalLinkEmbed' -import {MaybeQuoteEmbed} from './QuoteEmbed' -import {AutoSizedImage} from '../images/AutoSizedImage' -import {ListEmbed} from './ListEmbed' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {ContentHider} from '../../../../components/moderation/ContentHider' -import {isNative} from '#/platform/detection' -import {shareUrl} from '#/lib/sharing' +import {AutoSizedImage} from '../images/AutoSizedImage' +import {ImageLayoutGrid} from '../images/ImageLayoutGrid' +import {ExternalLinkEmbed} from './ExternalLinkEmbed' +import {ListEmbed} from './ListEmbed' +import {MaybeQuoteEmbed} from './QuoteEmbed' type Embed = | AppBskyEmbedRecord.View @@ -49,16 +47,6 @@ export function PostEmbeds({ const pal = usePalette('default') const {openLightbox} = useLightboxControls() - const externalUri = AppBskyEmbedExternal.isView(embed) - ? embed.external.uri - : null - - const onShareExternal = useCallback(() => { - if (externalUri && isNative) { - shareUrl(externalUri) - } - }, [externalUri]) - // quote post with media // = if (AppBskyEmbedRecordWithMedia.isView(embed)) { @@ -161,18 +149,9 @@ export function PostEmbeds({ // = if (AppBskyEmbedExternal.isView(embed)) { const link = embed.external - return ( - - - + ) } @@ -187,11 +166,6 @@ const styles = StyleSheet.create({ singleImage: { borderRadius: 8, }, - extOuter: { - borderWidth: 1, - borderRadius: 8, - marginTop: 4, - }, altContainer: { backgroundColor: 'rgba(0, 0, 0, 0.75)', borderRadius: 6, diff --git a/src/view/icons/index.tsx b/src/view/icons/index.tsx index ede1e63355..b9af6a519c 100644 --- a/src/view/icons/index.tsx +++ b/src/view/icons/index.tsx @@ -1,63 +1,72 @@ import {library} from '@fortawesome/fontawesome-svg-core' - import {faAddressCard} from '@fortawesome/free-regular-svg-icons' +import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' +import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faBookmark' +import {faCalendar as farCalendar} from '@fortawesome/free-regular-svg-icons/faCalendar' +import {faCircle} from '@fortawesome/free-regular-svg-icons/faCircle' +import {faCircleCheck as farCircleCheck} from '@fortawesome/free-regular-svg-icons/faCircleCheck' +import {faCirclePlay} from '@fortawesome/free-regular-svg-icons/faCirclePlay' +import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' +import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' +import {faComment} from '@fortawesome/free-regular-svg-icons/faComment' +import {faComments} from '@fortawesome/free-regular-svg-icons/faComments' +import {faCompass} from '@fortawesome/free-regular-svg-icons/faCompass' +import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' +import {faFaceSmile} from '@fortawesome/free-regular-svg-icons/faFaceSmile' +import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk' +import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand' +import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart' +import {faImage as farImage} from '@fortawesome/free-regular-svg-icons/faImage' +import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' +import {faPaste} from '@fortawesome/free-regular-svg-icons/faPaste' +import {faSquare} from '@fortawesome/free-regular-svg-icons/faSquare' +import {faSquareCheck} from '@fortawesome/free-regular-svg-icons/faSquareCheck' +import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus' +import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' +import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' +import {faFlask} from '@fortawesome/free-solid-svg-icons' +import {faUniversalAccess} from '@fortawesome/free-solid-svg-icons' import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faAngleUp} from '@fortawesome/free-solid-svg-icons/faAngleUp' +import {faArrowDown} from '@fortawesome/free-solid-svg-icons/faArrowDown' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' -import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' -import {faArrowDown} from '@fortawesome/free-solid-svg-icons/faArrowDown' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowRightFromBracket' +import {faArrowRotateLeft} from '@fortawesome/free-solid-svg-icons/faArrowRotateLeft' +import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate' +import {faArrowTrendUp} from '@fortawesome/free-solid-svg-icons/faArrowTrendUp' +import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' -import {faArrowRotateLeft} from '@fortawesome/free-solid-svg-icons/faArrowRotateLeft' -import {faArrowTrendUp} from '@fortawesome/free-solid-svg-icons/faArrowTrendUp' -import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate' import {faAt} from '@fortawesome/free-solid-svg-icons/faAt' -import {faBars} from '@fortawesome/free-solid-svg-icons/faBars' import {faBan} from '@fortawesome/free-solid-svg-icons/faBan' +import {faBars} from '@fortawesome/free-solid-svg-icons/faBars' import {faBell} from '@fortawesome/free-solid-svg-icons/faBell' -import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' import {faBookmark} from '@fortawesome/free-solid-svg-icons/faBookmark' -import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faBookmark' -import {faCalendar as farCalendar} from '@fortawesome/free-regular-svg-icons/faCalendar' import {faCamera} from '@fortawesome/free-solid-svg-icons/faCamera' import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck' +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight' -import {faCircle} from '@fortawesome/free-regular-svg-icons/faCircle' -import {faCircleCheck as farCircleCheck} from '@fortawesome/free-regular-svg-icons/faCircleCheck' import {faCircleCheck} from '@fortawesome/free-solid-svg-icons/faCircleCheck' import {faCircleDot} from '@fortawesome/free-solid-svg-icons/faCircleDot' import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation' -import {faCirclePlay} from '@fortawesome/free-regular-svg-icons/faCirclePlay' -import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' import {faClone} from '@fortawesome/free-solid-svg-icons/faClone' -import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' -import {faComment} from '@fortawesome/free-regular-svg-icons/faComment' import {faCommentSlash} from '@fortawesome/free-solid-svg-icons/faCommentSlash' -import {faComments} from '@fortawesome/free-regular-svg-icons/faComments' -import {faCompass} from '@fortawesome/free-regular-svg-icons/faCompass' import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload' import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis' import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope' import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation' import {faEye} from '@fortawesome/free-solid-svg-icons/faEye' -import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' -import {faFaceSmile} from '@fortawesome/free-regular-svg-icons/faFaceSmile' +import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter' import {faFire} from '@fortawesome/free-solid-svg-icons/faFire' -import {faFlask} from '@fortawesome/free-solid-svg-icons' -import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk' import {faGear} from '@fortawesome/free-solid-svg-icons/faGear' import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe' import {faHand} from '@fortawesome/free-solid-svg-icons/faHand' -import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand' import {faHashtag} from '@fortawesome/free-solid-svg-icons/faHashtag' -import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart' import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart' import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse' -import {faImage as farImage} from '@fortawesome/free-regular-svg-icons/faImage' import {faImage} from '@fortawesome/free-solid-svg-icons/faImage' import {faInfo} from '@fortawesome/free-solid-svg-icons/faInfo' import {faLanguage} from '@fortawesome/free-solid-svg-icons/faLanguage' @@ -66,10 +75,8 @@ import {faList} from '@fortawesome/free-solid-svg-icons/faList' import {faListUl} from '@fortawesome/free-solid-svg-icons/faListUl' import {faLock} from '@fortawesome/free-solid-svg-icons/faLock' import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' -import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' import {faPause} from '@fortawesome/free-solid-svg-icons/faPause' -import {faPaste} from '@fortawesome/free-regular-svg-icons/faPaste' import {faPen} from '@fortawesome/free-solid-svg-icons/faPen' import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib' import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare' @@ -87,23 +94,16 @@ import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSq import {faShield} from '@fortawesome/free-solid-svg-icons/faShield' import {faSignal} from '@fortawesome/free-solid-svg-icons/faSignal' import {faSliders} from '@fortawesome/free-solid-svg-icons/faSliders' -import {faSquare} from '@fortawesome/free-regular-svg-icons/faSquare' -import {faSquareCheck} from '@fortawesome/free-regular-svg-icons/faSquareCheck' -import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus' import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack' import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket' -import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' -import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' -import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' import {faUserCheck} from '@fortawesome/free-solid-svg-icons/faUserCheck' -import {faUserSlash} from '@fortawesome/free-solid-svg-icons/faUserSlash' import {faUserPlus} from '@fortawesome/free-solid-svg-icons/faUserPlus' -import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' +import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' +import {faUserSlash} from '@fortawesome/free-solid-svg-icons/faUserSlash' import {faUsersSlash} from '@fortawesome/free-solid-svg-icons/faUsersSlash' +import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' import {faX} from '@fortawesome/free-solid-svg-icons/faX' import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark' -import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' -import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter' library.add( faAddressCard, @@ -196,6 +196,7 @@ library.add( faSquare, faSquareCheck, faSquarePlus, + faUniversalAccess, faUser, faUsers, faUserCheck, diff --git a/src/view/screens/AccessibilitySettings.tsx b/src/view/screens/AccessibilitySettings.tsx new file mode 100644 index 0000000000..ac0d985f10 --- /dev/null +++ b/src/view/screens/AccessibilitySettings.tsx @@ -0,0 +1,132 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' + +import {isNative} from '#/platform/detection' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {s} from 'lib/styles' +import { + useAutoplayDisabled, + useHapticsDisabled, + useRequireAltTextEnabled, + useSetAutoplayDisabled, + useSetHapticsDisabled, + useSetRequireAltTextEnabled, +} from 'state/preferences' +import {ToggleButton} from 'view/com/util/forms/ToggleButton' +import {SimpleViewHeader} from '../com/util/SimpleViewHeader' +import {Text} from '../com/util/text/Text' +import {ScrollView} from '../com/util/Views' + +type Props = NativeStackScreenProps< + CommonNavigatorParams, + 'AccessibilitySettings' +> +export function AccessibilitySettingsScreen({}: Props) { + const pal = usePalette('default') + const setMinimalShellMode = useSetMinimalShellMode() + const {screen} = useAnalytics() + const {isMobile} = useWebMediaQueries() + const {_} = useLingui() + + const requireAltTextEnabled = useRequireAltTextEnabled() + const setRequireAltTextEnabled = useSetRequireAltTextEnabled() + const autoplayDisabled = useAutoplayDisabled() + const setAutoplayDisabled = useSetAutoplayDisabled() + const hapticsDisabled = useHapticsDisabled() + const setHapticsDisabled = useSetHapticsDisabled() + + useFocusEffect( + React.useCallback(() => { + screen('PreferencesExternalEmbeds') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) + + return ( + + + + + Accessibility Settings + + + + + + Alt text + + + setRequireAltTextEnabled(!requireAltTextEnabled)} + /> + + + Media + + + setAutoplayDisabled(!autoplayDisabled)} + /> + + {isNative && ( + <> + + Haptics + + + setHapticsDisabled(!hapticsDisabled)} + /> + + + )} + + + ) +} + +const styles = StyleSheet.create({ + heading: { + paddingHorizontal: 18, + paddingTop: 14, + paddingBottom: 6, + }, + toggleCard: { + paddingVertical: 8, + paddingHorizontal: 6, + marginBottom: 1, + }, +}) diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 2e3bf08db5..e64ab08df2 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -1,52 +1,53 @@ import React from 'react' import { ActivityIndicator, - StyleSheet, - View, type FlatList, Pressable, + StyleSheet, + View, } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {FAB} from 'view/com/util/fab/FAB' -import {Link} from 'view/com/util/Link' -import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ComposeIcon2, CogIcon, MagnifyingGlassIcon2} from 'lib/icons' -import {s} from 'lib/styles' -import {atoms as a, useTheme} from '#/alf' -import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput' -import {UserAvatar} from 'view/com/util/UserAvatar' -import { - LoadingPlaceholder, - FeedFeedLoadingPlaceholder, -} from 'view/com/util/LoadingPlaceholder' -import {ErrorMessage} from 'view/com/util/error/ErrorMessage' -import debounce from 'lodash.debounce' -import {Text} from 'view/com/util/text/Text' -import {List} from 'view/com/util/List' -import {useFocusEffect} from '@react-navigation/native' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {Trans, msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useSetMinimalShellMode} from '#/state/shell' -import {usePreferencesQuery} from '#/state/queries/preferences' +import {useFocusEffect} from '@react-navigation/native' +import debounce from 'lodash.debounce' + +import {isNative, isWeb} from '#/platform/detection' import { + getAvatarTypeFromUri, useFeedSourceInfoQuery, useGetPopularFeedsQuery, useSearchPopularFeedsMutation, - getAvatarTypeFromUri, } from '#/state/queries/feed' -import {cleanError} from 'lib/strings/errors' -import {useComposerControls} from '#/state/shell/composer' +import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' -import {isNative, isWeb} from '#/platform/detection' +import {useSetMinimalShellMode} from '#/state/shell' +import {useComposerControls} from '#/state/shell/composer' import {HITSLOP_10} from 'lib/constants' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CogIcon, ComposeIcon2, MagnifyingGlassIcon2} from 'lib/icons' +import {FeedsTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {cleanError} from 'lib/strings/errors' +import {s} from 'lib/styles' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import {ErrorMessage} from 'view/com/util/error/ErrorMessage' +import {FAB} from 'view/com/util/fab/FAB' +import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput' +import {Link} from 'view/com/util/Link' +import {List} from 'view/com/util/List' +import { + FeedFeedLoadingPlaceholder, + LoadingPlaceholder, +} from 'view/com/util/LoadingPlaceholder' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {atoms as a, useTheme} from '#/alf' import {IconCircle} from '#/components/IconCircle' -import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass' +import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' type Props = NativeStackScreenProps @@ -100,6 +101,22 @@ type FlatlistSlice = key: string } +// HACK +// the protocol doesn't yet tell us which feeds are personalized +// this list is used to filter out feed recommendations from logged out users +// for the ones we know need it +// -prf +const KNOWN_AUTHED_ONLY_FEEDS = [ + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed + 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz + 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why +] + export function FeedsScreen(_props: Props) { const pal = usePalette('default') const {openComposer} = useComposerControls() @@ -299,7 +316,15 @@ export function FeedsScreen(_props: Props) { for (const page of popularFeeds.pages || []) { slices = slices.concat( page.feeds - .filter(feed => !preferences?.feeds?.saved.includes(feed.uri)) + .filter(feed => { + if ( + !hasSession && + KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) + ) { + return false + } + return !preferences?.feeds?.saved.includes(feed.uri) + }) .map(feed => ({ key: `popularFeed:${feed.uri}`, type: 'popularFeed', diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 99ac8c44af..3eaa1b8757 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,23 +1,32 @@ import React from 'react' -import {View, ActivityIndicator, StyleSheet} from 'react-native' +import {ActivityIndicator, AppState, StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' -import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types' + +import {PROD_DEFAULT_FEED} from '#/lib/constants' +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {useSetTitle} from '#/lib/hooks/useSetTitle' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +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' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' +import {useSession} from '#/state/session' +import { + useMinimalShellMode, + useSetDrawerSwipeDisabled, + useSetMinimalShellMode, +} from '#/state/shell' +import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' +import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' +import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {FeedPage} from 'view/com/feeds/FeedPage' +import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' +import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed' -import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {HomeHeader} from '../com/home/HomeHeader' -import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager' -import {FeedPage} from 'view/com/feeds/FeedPage' -import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA' -import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell' -import {usePreferencesQuery} from '#/state/queries/preferences' -import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed' -import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' -import {emitSoftReset} from '#/state/events' -import {useSession} from '#/state/session' -import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' -import {useSetTitle} from '#/lib/hooks/useSetTitle' type Props = NativeStackScreenProps export function HomeScreen(props: Props) { @@ -48,6 +57,8 @@ function HomeScreenReady({ preferences: UsePreferencesQueryResponse pinnedFeedInfos: FeedSourceInfo[] }) { + useOTAUpdates() + const allFeeds = React.useMemo(() => { const feeds: FeedDescriptor[] = [] feeds.push('home') @@ -77,7 +88,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]) @@ -94,6 +105,37 @@ function HomeScreenReady({ }, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]), ) + useFocusEffect( + useNonReactiveCallback(() => { + logEvent('home:feedDisplayed', { + index: selectedIndex, + feedType: selectedFeed.split('|')[0], + feedUrl: selectedFeed, + reason: 'focus', + }) + }), + ) + + const gate = useGate() + const mode = useMinimalShellMode() + const {isMobile} = useWebMediaQueries() + React.useEffect(() => { + const listener = AppState.addEventListener('change', nextAppState => { + if (nextAppState === 'active') { + if ( + isMobile && + mode.value === 1 && + gate('disable_min_shell_on_foregrounding_v2') + ) { + setMinimalShellMode(false) + } + } + }) + return () => { + listener.remove() + } + }, [setMinimalShellMode, mode, isMobile, gate]) + const onPageSelected = React.useCallback( (index: number) => { setMinimalShellMode(false) @@ -105,6 +147,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() }, []) @@ -157,6 +212,7 @@ function HomeScreenReady({ ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} + onPageSelecting={onPageSelecting} onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}> @@ -187,7 +243,12 @@ function HomeScreenReady({ onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}> - + ) } diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index eb3b270488..b7ce8cdd00 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -7,23 +7,26 @@ import { View, } from 'react-native' import {AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {Text} from '../com/util/text/Text' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' -import {useAnalytics} from 'lib/analytics/analytics' -import {useFocusEffect} from '@react-navigation/native' -import {ViewHeader} from '../com/util/ViewHeader' +import {useGate} from 'lib/statsig/statsig' +import {isWeb} from 'platform/detection' +import {ProfileCard} from 'view/com/profile/ProfileCard' import {CenteredView} from 'view/com/util/Views' import {ErrorScreen} from '../com/util/error/ErrorScreen' -import {ProfileCard} from 'view/com/profile/ProfileCard' -import {logger} from '#/logger' -import {useSetMinimalShellMode} from '#/state/shell' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts' -import {cleanError} from '#/lib/strings/errors' +import {Text} from '../com/util/text/Text' +import {ViewHeader} from '../com/util/ViewHeader' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -35,6 +38,8 @@ export function ModerationBlockedAccounts({}: Props) { const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop} = useWebMediaQueries() const {screen} = useAnalytics() + const gate = useGate() + const [isPTRing, setIsPTRing] = React.useState(false) const { data, @@ -163,6 +168,9 @@ export function ModerationBlockedAccounts({}: Props) { )} // @ts-ignore our .web version only -prf desktopFixedHeight + showsVerticalScrollIndicator={ + isWeb || !gate('hide_vertical_scroll_indicators') + } /> )} diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 911ace7782..4d7ca62946 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -7,23 +7,26 @@ import { View, } from 'react-native' import {AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {Text} from '../com/util/text/Text' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' -import {useAnalytics} from 'lib/analytics/analytics' -import {useFocusEffect} from '@react-navigation/native' -import {ViewHeader} from '../com/util/ViewHeader' +import {useGate} from 'lib/statsig/statsig' +import {isWeb} from 'platform/detection' +import {ProfileCard} from 'view/com/profile/ProfileCard' import {CenteredView} from 'view/com/util/Views' import {ErrorScreen} from '../com/util/error/ErrorScreen' -import {ProfileCard} from 'view/com/profile/ProfileCard' -import {logger} from '#/logger' -import {useSetMinimalShellMode} from '#/state/shell' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts' -import {cleanError} from '#/lib/strings/errors' +import {Text} from '../com/util/text/Text' +import {ViewHeader} from '../com/util/ViewHeader' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -35,6 +38,8 @@ export function ModerationMutedAccounts({}: Props) { const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop} = useWebMediaQueries() const {screen} = useAnalytics() + const gate = useGate() + const [isPTRing, setIsPTRing] = React.useState(false) const { data, @@ -162,6 +167,9 @@ export function ModerationMutedAccounts({}: Props) { )} // @ts-ignore our .web version only -prf desktopFixedHeight + showsVerticalScrollIndicator={ + isWeb || !gate('hide_vertical_scroll_indicators') + } /> )} diff --git a/src/view/screens/PreferencesExternalEmbeds.tsx b/src/view/screens/PreferencesExternalEmbeds.tsx index 1e8cedf7e2..5eec7e5077 100644 --- a/src/view/screens/PreferencesExternalEmbeds.tsx +++ b/src/view/screens/PreferencesExternalEmbeds.tsx @@ -1,25 +1,26 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import {Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {s} from 'lib/styles' -import {Text} from '../com/util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' + import { EmbedPlayerSource, externalEmbedLabels, } from '#/lib/strings/embed-player' import {useSetMinimalShellMode} from '#/state/shell' -import {Trans} from '@lingui/macro' -import {ScrollView} from '../com/util/Views' +import {useAnalytics} from 'lib/analytics/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {s} from 'lib/styles' import { useExternalEmbedsPrefs, useSetExternalEmbedPref, } from 'state/preferences' import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {SimpleViewHeader} from '../com/util/SimpleViewHeader' +import {Text} from '../com/util/text/Text' +import {ScrollView} from '../com/util/Views' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -74,13 +75,16 @@ export function PreferencesExternalEmbeds({}: Props) { Enable media players for - {Object.entries(externalEmbedLabels).map(([key, label]) => ( - - ))} + {Object.entries(externalEmbedLabels) + // TODO: Remove special case when we disable the old integration. + .filter(([key]) => key !== 'tenor') + .map(([key, label]) => ( + + ))} ) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 6073b95716..eb99798232 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react' +import React, {useEffect, useMemo} from 'react' import {StyleSheet} from 'react-native' import { AppBskyActorDefs, @@ -11,16 +11,15 @@ import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {logEvent, useGate} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' -import {isInvalidHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' -import {listenSoftReset} from '#/state/events' import {useLabelerInfoQuery} from '#/state/queries/labeler' import {resetProfilePostsQueries} from '#/state/queries/post-feed' import {useModerationOpts} from '#/state/queries/preferences' import {useProfileQuery} from '#/state/queries/profile' import {useResolveDidQuery} from '#/state/queries/resolve-uri' -import {getAgent, useSession} from '#/state/session' +import {useAgent, useSession} from '#/state/session' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' import {useAnalytics} from 'lib/analytics/analytics' @@ -28,12 +27,15 @@ import {useSetTitle} from 'lib/hooks/useSetTitle' import {ComposeIcon2} from 'lib/icons' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {combinedDisplayName} from 'lib/strings/display-names' +import {isInvalidHandle} from 'lib/strings/handles' import {colors, s} from 'lib/styles' +import {listenSoftReset} from 'state/events' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' import {ScreenHider} from '#/components/moderation/ScreenHider' +import {ExpoScrollForwarderView} from '../../../modules/expo-scroll-forwarder' import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens' import {ProfileLists} from '../com/lists/ProfileLists' import {ErrorScreen} from '../com/util/error/ErrorScreen' @@ -152,6 +154,9 @@ function ProfileScreenLoaded({ const [currentPage, setCurrentPage] = React.useState(0) const {_} = useLingui() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() + + const [scrollViewTag, setScrollViewTag] = React.useState(null) + const postsSectionRef = React.useRef(null) const repliesSectionRef = React.useRef(null) const mediaSectionRef = React.useRef(null) @@ -178,8 +183,7 @@ function ProfileScreenLoaded({ const showRepliesTab = hasSession const showMediaTab = !hasLabeler const showLikesTab = isMe - const showFeedsTab = - hasSession && (isMe || (profile.associated?.feedgens || 0) > 0) + const showFeedsTab = isMe || (profile.associated?.feedgens || 0) > 0 const showListsTab = hasSession && (isMe || (profile.associated?.lists || 0) > 0) @@ -297,12 +301,9 @@ function ProfileScreenLoaded({ openComposer({mention}) }, [openComposer, currentAccount, track, profile]) - const onPageSelected = React.useCallback( - (i: number) => { - setCurrentPage(i) - }, - [setCurrentPage], - ) + const onPageSelected = React.useCallback((i: number) => { + setCurrentPage(i) + }, []) const onCurrentPageSelected = React.useCallback( (index: number) => { @@ -316,20 +317,23 @@ function ProfileScreenLoaded({ const renderHeader = React.useCallback(() => { return ( - + + + ) }, [ + scrollViewTag, profile, labelerInfo, - descriptionRT, hasDescription, + descriptionRT, moderationOpts, hideBackButton, showPlaceholder, @@ -349,7 +353,7 @@ function ProfileScreenLoaded({ onCurrentPageSelected={onCurrentPageSelected} renderHeader={renderHeader}> {showFiltersTab - ? ({headerHeight, scrollElRef}) => ( + ? ({headerHeight, isFocused, scrollElRef}) => ( ) : null} @@ -369,6 +375,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -381,6 +388,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -393,6 +401,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -405,6 +414,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -417,6 +427,7 @@ function ProfileScreenLoaded({ isFocused={isFocused} scrollElRef={scrollElRef as ListRef} ignoreFilterFor={profile.did} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -428,6 +439,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -439,6 +451,7 @@ function ProfileScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerOffset={headerHeight} enabled={isFocused} + setScrollViewTag={setScrollViewTag} /> ) : null} @@ -453,11 +466,13 @@ function ProfileScreenLoaded({ accessibilityHint="" /> )} + ) } function useRichText(text: string): [RichTextAPI, boolean] { + const {getAgent} = useAgent() const [prevText, setPrevText] = React.useState(text) const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text})) const [resolvedRT, setResolvedRT] = React.useState(null) @@ -481,7 +496,7 @@ function useRichText(text: string): [RichTextAPI, boolean] { return () => { ignore = true } - }, [text]) + }, [text, getAgent]) const isResolving = resolvedRT === null return [resolvedRT ?? rawRT, isResolving] } @@ -510,3 +525,77 @@ const styles = StyleSheet.create({ textAlign: 'center', }, }) + +const shouldExposeToGate2 = Math.random() < 0.2 + +// --- Temporary: we're testing our Statsig setup --- +let TestGates = React.memo(function TestGates() { + const gate = useGate() + + useEffect(() => { + logEvent('test:all:always', {}) + if (Math.random() < 0.2) { + logEvent('test:all:sometimes', {}) + } + if (Math.random() < 0.1) { + logEvent('test:all:boosted_by_gate1', { + reason: 'base', + }) + } + if (Math.random() < 0.1) { + logEvent('test:all:boosted_by_gate2', { + reason: 'base', + }) + } + if (Math.random() < 0.1) { + logEvent('test:all:boosted_by_both', { + reason: 'base', + }) + } + }, []) + + return [ + gate('test_gate_1') ? : null, + shouldExposeToGate2 && gate('test_gate_2') ? : null, + ] +}) + +function TestGate1() { + useEffect(() => { + logEvent('test:gate1:always', {}) + if (Math.random() < 0.2) { + logEvent('test:gate1:sometimes', {}) + } + if (Math.random() < 0.5) { + logEvent('test:all:boosted_by_gate1', { + reason: 'gate1', + }) + } + if (Math.random() < 0.5) { + logEvent('test:all:boosted_by_both', { + reason: 'gate1', + }) + } + }, []) + return null +} + +function TestGate2() { + useEffect(() => { + logEvent('test:gate2:always', {}) + if (Math.random() < 0.2) { + logEvent('test:gate2:sometimes', {}) + } + if (Math.random() < 0.5) { + logEvent('test:all:boosted_by_gate2', { + reason: 'gate2', + }) + } + if (Math.random() < 0.5) { + logEvent('test:all:boosted_by_both', { + reason: 'gate2', + }) + } + }, []) + return null +} diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 4560e14ebc..814c1e8558 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -27,7 +27,7 @@ import {truncateAndInvalidate} from '#/state/queries/util' import {useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {useAnalytics} from 'lib/analytics/analytics' -import {Haptics} from 'lib/haptics' +import {useHaptics} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {ComposeIcon2} from 'lib/icons' @@ -159,6 +159,7 @@ export function ProfileFeedScreenInner({ const reportDialogControl = useReportDialogControl() const {openComposer} = useComposerControls() const {track} = useAnalytics() + const playHaptic = useHaptics() const feedSectionRef = React.useRef(null) const isScreenFocused = useIsFocused() @@ -201,7 +202,7 @@ export function ProfileFeedScreenInner({ const onToggleSaved = React.useCallback(async () => { try { - Haptics.default() + playHaptic() if (isSaved) { await removeFeed({uri: feedInfo.uri}) @@ -221,18 +222,19 @@ export function ProfileFeedScreenInner({ logger.error('Failed up update feeds', {message: err}) } }, [ - feedInfo, + playHaptic, isSaved, - saveFeed, removeFeed, - resetSaveFeed, + feedInfo, resetRemoveFeed, _, + saveFeed, + resetSaveFeed, ]) const onTogglePinned = React.useCallback(async () => { try { - Haptics.default() + playHaptic() if (isPinned) { await unpinFeed({uri: feedInfo.uri}) @@ -245,7 +247,16 @@ export function ProfileFeedScreenInner({ Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed, _]) + }, [ + playHaptic, + isPinned, + unpinFeed, + feedInfo, + resetUnpinFeed, + pinFeed, + resetPinFeed, + _, + ]) const onPressShare = React.useCallback(() => { const url = toShareUrl(feedInfo.route.href) @@ -517,6 +528,7 @@ function AboutSection({ const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri) const {hasSession} = useSession() const {track} = useAnalytics() + const playHaptic = useHaptics() const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation() const {mutateAsync: unlikeFeed, isPending: isUnlikePending} = useUnlikeMutation() @@ -527,7 +539,7 @@ function AboutSection({ const onToggleLiked = React.useCallback(async () => { try { - Haptics.default() + playHaptic() if (isLiked && likeUri) { await unlikeFeed({uri: likeUri}) @@ -546,7 +558,7 @@ function AboutSection({ ) logger.error('Failed up toggle like', {message: err}) } - }, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _]) + }, [playHaptic, isLiked, likeUri, unlikeFeed, track, likeFeed, feedInfo, _]) return ( diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index 58b89f2399..1d93a9fd7d 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -1,69 +1,70 @@ import React, {useCallback, useMemo} from 'react' import {Pressable, StyleSheet, View} from 'react-native' -import {useFocusEffect, useIsFocused} from '@react-navigation/native' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {useNavigation} from '@react-navigation/native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AppBskyGraphDefs, AtUri, RichText as RichTextAPI} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect, useIsFocused} from '@react-navigation/native' +import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' -import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' -import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' -import {Feed} from 'view/com/posts/Feed' -import {Text} from 'view/com/util/text/Text' -import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown' -import {CenteredView} from 'view/com/util/Views' -import {EmptyState} from 'view/com/util/EmptyState' -import {LoadingScreen} from 'view/com/util/LoadingScreen' -import {RichText} from '#/components/RichText' -import {Button} from 'view/com/util/forms/Button' -import {TextLink} from 'view/com/util/Link' -import {ListRef} from 'view/com/util/List' -import * as Toast from 'view/com/util/Toast' -import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' -import {FAB} from 'view/com/util/fab/FAB' -import {Haptics} from 'lib/haptics' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {isNative, isWeb} from '#/platform/detection' +import {listenSoftReset} from '#/state/events' +import {useModalControls} from '#/state/modals' +import { + useListBlockMutation, + useListDeleteMutation, + useListMuteMutation, + useListQuery, +} from '#/state/queries/list' import {FeedDescriptor} from '#/state/queries/post-feed' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import { + usePinFeedMutation, + usePreferencesQuery, + useSetSaveFeedsMutation, + useUnpinFeedMutation, +} from '#/state/queries/preferences' +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {truncateAndInvalidate} from '#/state/queries/util' +import {useSession} from '#/state/session' +import {useSetMinimalShellMode} from '#/state/shell' +import {useComposerControls} from '#/state/shell/composer' +import {useHaptics} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' -import {NavigationProp} from 'lib/routes/types' -import {toShareUrl} from 'lib/strings/url-helpers' -import {shareUrl} from 'lib/sharing' -import {s} from 'lib/styles' -import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink, makeListLink} from 'lib/routes/links' import {ComposeIcon2} from 'lib/icons' +import {makeListLink, makeProfileLink} from 'lib/routes/links' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {NavigationProp} from 'lib/routes/types' +import {shareUrl} from 'lib/sharing' +import {sanitizeHandle} from 'lib/strings/handles' +import {toShareUrl} from 'lib/strings/url-helpers' +import {s} from 'lib/styles' import {ListMembers} from '#/view/com/lists/ListMembers' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSetMinimalShellMode} from '#/state/shell' -import {useModalControls} from '#/state/modals' -import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' -import {useResolveUriQuery} from '#/state/queries/resolve-uri' -import { - useListQuery, - useListMuteMutation, - useListBlockMutation, - useListDeleteMutation, -} from '#/state/queries/list' -import {cleanError} from '#/lib/strings/errors' -import {useSession} from '#/state/session' -import {useComposerControls} from '#/state/shell/composer' -import {isNative, isWeb} from '#/platform/detection' -import {truncateAndInvalidate} from '#/state/queries/util' -import { - usePreferencesQuery, - usePinFeedMutation, - useUnpinFeedMutation, - useSetSaveFeedsMutation, -} from '#/state/queries/preferences' -import {logger} from '#/logger' -import {useAnalytics} from '#/lib/analytics/analytics' -import {listenSoftReset} from '#/state/events' +import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' +import {Feed} from 'view/com/posts/Feed' +import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' +import {EmptyState} from 'view/com/util/EmptyState' +import {FAB} from 'view/com/util/fab/FAB' +import {Button} from 'view/com/util/forms/Button' +import {DropdownItem, NativeDropdown} from 'view/com/util/forms/NativeDropdown' +import {TextLink} from 'view/com/util/Link' +import {ListRef} from 'view/com/util/List' +import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' +import {LoadingScreen} from 'view/com/util/LoadingScreen' +import {Text} from 'view/com/util/text/Text' +import * as Toast from 'view/com/util/Toast' +import {CenteredView} from 'view/com/util/Views' import {atoms as a, useTheme} from '#/alf' -import * as Prompt from '#/components/Prompt' import {useDialogControl} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' +import {RichText} from '#/components/RichText' const SECTION_TITLES_CURATE = ['Posts', 'About'] const SECTION_TITLES_MOD = ['About'] @@ -254,6 +255,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const {data: preferences} = usePreferencesQuery() const {mutate: setSavedFeeds} = useSetSaveFeedsMutation() const {track} = useAnalytics() + const playHaptic = useHaptics() const deleteListPromptControl = useDialogControl() const subscribeMutePromptControl = useDialogControl() @@ -263,7 +265,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const isSaved = preferences?.feeds?.saved?.includes(list.uri) const onTogglePinned = React.useCallback(async () => { - Haptics.default() + playHaptic() try { if (isPinned) { @@ -275,7 +277,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [list.uri, isPinned, pinFeed, unpinFeed, _]) + }, [playHaptic, isPinned, unpinFeed, list.uri, pinFeed, _]) const onSubscribeMute = useCallback(async () => { try { diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 251c706384..0003dbd5d9 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -1,31 +1,32 @@ import React from 'react' -import {StyleSheet, View, ActivityIndicator, Pressable} from 'react-native' +import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' + import {track} from '#/lib/analytics/analytics' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePalette} from 'lib/hooks/usePalette' -import {CommonNavigatorParams} from 'lib/routes/types' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {ScrollView, CenteredView} from 'view/com/util/Views' -import {Text} from 'view/com/util/text/Text' -import {s, colors} from 'lib/styles' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import * as Toast from 'view/com/util/Toast' -import {Haptics} from 'lib/haptics' -import {TextLink} from 'view/com/util/Link' import {logger} from '#/logger' -import {useSetMinimalShellMode} from '#/state/shell' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' import { - usePreferencesQuery, usePinFeedMutation, - useUnpinFeedMutation, + usePreferencesQuery, useSetSaveFeedsMutation, + useUnpinFeedMutation, } from '#/state/queries/preferences' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {useHaptics} from 'lib/haptics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams} from 'lib/routes/types' +import {colors, s} from 'lib/styles' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import {TextLink} from 'view/com/util/Link' +import {Text} from 'view/com/util/text/Text' +import * as Toast from 'view/com/util/Toast' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {CenteredView, ScrollView} from 'view/com/util/Views' const HITSLOP_TOP = { top: 20, @@ -189,13 +190,14 @@ function ListItem({ }) { const pal = usePalette('default') const {_} = useLingui() + const playHaptic = useHaptics() const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() const {isPending: isUnpinPending, mutateAsync: unpinFeed} = useUnpinFeedMutation() const isPending = isPinPending || isUnpinPending const onTogglePinned = React.useCallback(async () => { - Haptics.default() + playHaptic() try { resetSaveFeedsMutationState() @@ -209,7 +211,15 @@ function ListItem({ Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState, _]) + }, [ + playHaptic, + resetSaveFeedsMutationState, + isPinned, + unpinFeed, + feedUri, + pinFeed, + _, + ]) const onPressUp = React.useCallback(async () => { if (!isPinned) return diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index c0f4cf1950..ee9e694333 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -27,14 +27,15 @@ import {s} from '#/lib/styles' import {logger} from '#/logger' import {isNative, isWeb} from '#/platform/detection' import {listenSoftReset} from '#/state/events' -import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' +import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useActorSearch} from '#/state/queries/actor-search' import {useModerationOpts} from '#/state/queries/preferences' import {useSearchPostsQuery} from '#/state/queries/search-posts' -import {useGetSuggestedFollowersByActor} from '#/state/queries/suggested-follows' +import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' import {useSession} from '#/state/session' import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' +import {useNonReactiveCallback} from 'lib/hooks/useNonReactiveCallback' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import { NativeStackScreenProps, @@ -117,49 +118,47 @@ function EmptyState({message, error}: {message: string; error?: string}) { ) } +function useSuggestedFollows(): [ + AppBskyActorDefs.ProfileViewBasic[], + () => void, +] { + const { + data: suggestions, + hasNextPage, + isFetchingNextPage, + isError, + fetchNextPage, + } = useSuggestedFollowsQuery() + + const onEndReached = React.useCallback(async () => { + if (isFetchingNextPage || !hasNextPage || isError) return + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more suggested follows', {message: err}) + } + }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) + + const items: AppBskyActorDefs.ProfileViewBasic[] = [] + if (suggestions) { + // Currently the responses contain duplicate items. + // Needs to be fixed on backend, but let's dedupe to be safe. + let seen = new Set() + for (const page of suggestions.pages) { + for (const actor of page.actors) { + if (!seen.has(actor.did)) { + seen.add(actor.did) + items.push(actor) + } + } + } + } + return [items, onEndReached] +} + function SearchScreenSuggestedFollows() { const pal = usePalette('default') - const {currentAccount} = useSession() - const [suggestions, setSuggestions] = React.useState< - AppBskyActorDefs.ProfileViewBasic[] - >([]) - const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor() - - React.useEffect(() => { - async function getSuggestions() { - const friends = await getSuggestedFollowsByActor( - currentAccount!.did, - ).then(friendsRes => friendsRes.suggestions) - - if (!friends) return // :( - - const friendsOfFriends = new Map< - string, - AppBskyActorDefs.ProfileViewBasic - >() - - await Promise.all( - friends.slice(0, 4).map(friend => - getSuggestedFollowsByActor(friend.did).then(foafsRes => { - for (const user of foafsRes.suggestions) { - if (user.associated?.labeler) continue - friendsOfFriends.set(user.did, user) - } - }), - ), - ) - - setSuggestions(Array.from(friendsOfFriends.values())) - } - - try { - getSuggestions() - } catch (e) { - logger.error(`SearchScreenSuggestedFollows: failed to get suggestions`, { - message: e, - }) - } - }, [currentAccount, setSuggestions, getSuggestedFollowsByActor]) + const [suggestions, onEndReached] = useSuggestedFollows() return suggestions.length ? ( item.did} // @ts-ignore web only -prf desktopFixedHeight - contentContainerStyle={{paddingBottom: 1200}} + contentContainerStyle={{paddingBottom: 200}} keyboardShouldPersistTaps="handled" keyboardDismissMode="on-drag" + onEndReached={onEndReached} + onEndReachedThreshold={2} /> ) : ( @@ -191,7 +192,15 @@ type SearchResultSlice = key: string } -function SearchScreenPostResults({query}: {query: string}) { +function SearchScreenPostResults({ + query, + sort, + active, +}: { + query: string + sort?: 'top' | 'latest' + active: boolean +}) { const {_} = useLingui() const {currentAccount} = useSession() const [isPTR, setIsPTR] = React.useState(false) @@ -209,7 +218,7 @@ function SearchScreenPostResults({query}: {query: string}) { fetchNextPage, isFetchingNextPage, hasNextPage, - } = useSearchPostsQuery({query: augmentedQuery}) + } = useSearchPostsQuery({query: augmentedQuery, sort, enabled: active}) const onPullToRefresh = React.useCallback(async () => { setIsPTR(true) @@ -290,9 +299,19 @@ function SearchScreenPostResults({query}: {query: string}) { ) } -function SearchScreenUserResults({query}: {query: string}) { +function SearchScreenUserResults({ + query, + active, +}: { + query: string + active: boolean +}) { const {_} = useLingui() - const {data: results, isFetched} = useActorSearch(query) + + const {data: results, isFetched} = useActorSearch({ + query: query, + enabled: active, + }) return isFetched && results ? ( <> @@ -316,71 +335,55 @@ function SearchScreenUserResults({query}: {query: string}) { ) } -const SECTIONS_LOGGEDOUT = ['Users'] -const SECTIONS_LOGGEDIN = ['Posts', 'Users'] -export function SearchScreenInner({ - query, - primarySearch, -}: { - query?: string - primarySearch?: boolean -}) { +export function SearchScreenInner({query}: {query?: string}) { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const {hasSession} = useSession() const {isDesktop} = useWebMediaQueries() + const [activeTab, setActiveTab] = React.useState(0) + const {_} = useLingui() const onPageSelected = React.useCallback( (index: number) => { setMinimalShellMode(false) setDrawerSwipeDisabled(index > 0) + setActiveTab(index) }, [setDrawerSwipeDisabled, setMinimalShellMode], ) - if (hasSession) { - return query ? ( - ( - - - - )} - initialPage={0}> - - - - - - - - ) : ( - - - - Suggested Follows - - - - - - ) - } + const sections = React.useMemo(() => { + if (!query) return [] + return [ + { + title: _(msg`Top`), + component: ( + + ), + }, + { + title: _(msg`Latest`), + component: ( + + ), + }, + { + title: _(msg`People`), + component: ( + + ), + }, + ] + }, [_, query, activeTab]) return query ? ( - + section.title)} {...props} /> )} initialPage={0}> - - - + {sections.map((section, i) => ( + {section.component} + ))} + ) : hasSession ? ( + + + + Suggested Follows + + + + + ) : ( - {isDesktop && !primarySearch ? ( - Find users with the search tool on the right - ) : ( - Find users on Bluesky - )} + Find posts and users on Bluesky @@ -459,43 +479,25 @@ export function SearchScreen( const {track} = useAnalytics() const setDrawerOpen = useSetDrawerOpen() const moderationOpts = useModerationOpts() - const search = useActorAutocompleteFn() const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop, isTabletOrMobile} = useWebMediaQueries() - const searchDebounceTimeout = React.useRef( - undefined, - ) - const [isFetching, setIsFetching] = React.useState(false) - const [query, setQuery] = React.useState(props.route?.params?.q || '') - const [searchResults, setSearchResults] = React.useState< - AppBskyActorDefs.ProfileViewBasic[] - >([]) - const [inputIsFocused, setInputIsFocused] = React.useState(false) - const [showAutocompleteResults, setShowAutocompleteResults] = - React.useState(false) + // Query terms + const queryParam = props.route?.params?.q ?? '' + const [searchText, setSearchText] = React.useState(queryParam) + const {data: autocompleteData, isFetching: isAutocompleteFetching} = + useActorAutocompleteQuery(searchText, true) + + const [showAutocomplete, setShowAutocomplete] = React.useState(false) const [searchHistory, setSearchHistory] = React.useState([]) - /** - * The Search screen's `q` param - */ - const queryParam = props.route?.params?.q - - /** - * If `true`, this means we received new instructions from the router. This - * is handled in a effect, and used to update the value of `query` locally - * within this screen. - */ - const routeParamsMismatch = queryParam && queryParam !== query - - React.useEffect(() => { - if (queryParam && routeParamsMismatch) { - // reset immediately and let local state take over - navigation.setParams({q: ''}) - // update query for next search - setQuery(queryParam) - } - }, [queryParam, routeParamsMismatch, navigation]) + useFocusEffect( + useNonReactiveCallback(() => { + if (isWeb) { + setSearchText(queryParam) + } + }), + ) React.useEffect(() => { const loadSearchHistory = async () => { @@ -517,60 +519,45 @@ export function SearchScreen( setDrawerOpen(true) }, [track, setDrawerOpen]) - const onPressCancelSearch = React.useCallback(() => { - scrollToTopWeb() - textInput.current?.blur() - setQuery('') - setShowAutocompleteResults(false) - if (searchDebounceTimeout.current) - clearTimeout(searchDebounceTimeout.current) - }, [textInput]) - const onPressClearQuery = React.useCallback(() => { scrollToTopWeb() - setQuery('') - setShowAutocompleteResults(false) - }, [setQuery]) + setSearchText('') + textInput.current?.focus() + }, []) - const onChangeText = React.useCallback( - async (text: string) => { - scrollToTopWeb() + const onPressCancelSearch = React.useCallback(() => { + scrollToTopWeb() - setQuery(text) - - if (text.length > 0) { - setIsFetching(true) - setShowAutocompleteResults(true) - - if (searchDebounceTimeout.current) { - clearTimeout(searchDebounceTimeout.current) - } - - searchDebounceTimeout.current = setTimeout(async () => { - const results = await search({query: text, limit: 30}) - - if (results) { - setSearchResults(results) - setIsFetching(false) - } - }, 300) + if (showAutocomplete) { + textInput.current?.blur() + setShowAutocomplete(false) + setSearchText(queryParam) + } else { + // If we just `setParams` and set `q` to an empty string, the URL still displays `q=`, which isn't pretty. + // However, `.replace()` on native has a "push" animation that we don't want. So we need to handle these + // differently. + if (isWeb) { + navigation.replace('Search', {}) } else { - if (searchDebounceTimeout.current) { - clearTimeout(searchDebounceTimeout.current) - } - setSearchResults([]) - setIsFetching(false) - setShowAutocompleteResults(false) + setSearchText('') + navigation.setParams({q: ''}) } - }, - [setQuery, search, setSearchResults], - ) + } + }, [showAutocomplete, navigation, queryParam]) + + const onChangeText = React.useCallback(async (text: string) => { + scrollToTopWeb() + setSearchText(text) + }, []) const updateSearchHistory = React.useCallback( async (newQuery: string) => { newQuery = newQuery.trim() - if (newQuery && !searchHistory.includes(newQuery)) { - let newHistory = [newQuery, ...searchHistory] + if (newQuery) { + let newHistory = [ + newQuery, + ...searchHistory.filter(q => q !== newQuery), + ] if (newHistory.length > 5) { newHistory = newHistory.slice(0, 5) @@ -590,11 +577,30 @@ export function SearchScreen( [searchHistory, setSearchHistory], ) + const navigateToItem = React.useCallback( + (item: string) => { + scrollToTopWeb() + setShowAutocomplete(false) + updateSearchHistory(item) + + if (isWeb) { + navigation.push('Search', {q: item}) + } else { + textInput.current?.blur() + navigation.setParams({q: item}) + } + }, + [updateSearchHistory, navigation], + ) + const onSubmit = React.useCallback(() => { - scrollToTopWeb() - setShowAutocompleteResults(false) - updateSearchHistory(query) - }, [query, setShowAutocompleteResults, updateSearchHistory]) + navigateToItem(searchText) + }, [navigateToItem, searchText]) + + const handleHistoryItemClick = (item: string) => { + setSearchText(item) + navigateToItem(item) + } const onSoftReset = React.useCallback(() => { scrollToTopWeb() @@ -602,9 +608,9 @@ export function SearchScreen( }, [onPressCancelSearch]) const queryMaybeHandle = React.useMemo(() => { - const match = MATCH_HANDLE.exec(query) + const match = MATCH_HANDLE.exec(queryParam) return match && match[1] - }, [query]) + }, [queryParam]) useFocusEffect( React.useCallback(() => { @@ -613,11 +619,6 @@ export function SearchScreen( }, [onSoftReset, setMinimalShellMode]), ) - const handleHistoryItemClick = (item: React.SetStateAction) => { - setQuery(item) - onSubmit() - } - const handleRemoveHistoryItem = (itemToRemove: string) => { const updatedHistory = searchHistory.filter(item => item !== itemToRemove) setSearchHistory(updatedHistory) @@ -669,17 +670,21 @@ export function SearchScreen( ref={textInput} placeholder={_(msg`Search`)} placeholderTextColor={pal.colors.textLight} - selectTextOnFocus + selectTextOnFocus={isNative} returnKeyType="search" - value={query} + value={searchText} style={[pal.text, styles.headerSearchInput]} keyboardAppearance={theme.colorScheme} - onFocus={() => setInputIsFocused(true)} - onBlur={() => { - // HACK - // give 100ms to not stop click handlers in the search history - // -prf - setTimeout(() => setInputIsFocused(false), 100) + onFocus={() => { + if (isWeb) { + // Prevent a jump on iPad by ensuring that + // the initial focused render has no result list. + requestAnimationFrame(() => { + setShowAutocomplete(true) + }) + } else { + setShowAutocomplete(true) + } }} onChangeText={onChangeText} onSubmitEditing={onSubmit} @@ -691,7 +696,7 @@ export function SearchScreen( autoComplete="off" autoCapitalize="none" /> - {query ? ( + {showAutocomplete ? ( - {query || inputIsFocused ? ( + {(queryParam || showAutocomplete) && ( - ) : undefined} + )} - {showAutocompleteResults ? ( + {showAutocomplete && searchText.length > 0 ? ( <> - {isFetching || !moderationOpts ? ( + {(isAutocompleteFetching && !autocompleteData?.length) || + !moderationOpts ? ( ) : ( @@ -751,11 +757,18 @@ export function SearchScreen( /> ) : null} - {searchResults.map(item => ( + {autocompleteData?.map(item => ( { + if (isWeb) { + setShowAutocomplete(false) + } else { + textInput.current?.blur() + } + }} /> ))} @@ -763,7 +776,7 @@ export function SearchScreen( )} - ) : !query && inputIsFocused ? ( + ) : !queryParam && showAutocomplete ? ( - ) : routeParamsMismatch ? ( - ) : ( - + )} ) diff --git a/src/view/screens/Settings/DisableEmail2FADialog.tsx b/src/view/screens/Settings/DisableEmail2FADialog.tsx new file mode 100644 index 0000000000..83b133f656 --- /dev/null +++ b/src/view/screens/Settings/DisableEmail2FADialog.tsx @@ -0,0 +1,197 @@ +import React, {useState} from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {cleanError} from '#/lib/strings/errors' +import {isNative} from '#/platform/detection' +import {useAgent, useSession, useSessionApi} from '#/state/session' +import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' +import * as Toast from '#/view/com/util/Toast' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' +import {Loader} from '#/components/Loader' +import {P, Text} from '#/components/Typography' + +enum Stages { + Email, + ConfirmCode, +} + +export function DisableEmail2FADialog({ + control, +}: { + control: Dialog.DialogOuterProps['control'] +}) { + const {_} = useLingui() + const t = useTheme() + const {gtMobile} = useBreakpoints() + const {currentAccount} = useSession() + const {updateCurrentAccount} = useSessionApi() + const {getAgent} = useAgent() + + const [stage, setStage] = useState(Stages.Email) + const [confirmationCode, setConfirmationCode] = useState('') + const [isProcessing, setIsProcessing] = useState(false) + const [error, setError] = useState('') + + const onSendEmail = async () => { + setError('') + setIsProcessing(true) + try { + await getAgent().com.atproto.server.requestEmailUpdate() + setStage(Stages.ConfirmCode) + } catch (e) { + setError(cleanError(String(e))) + } finally { + setIsProcessing(false) + } + } + + const onConfirmDisable = async () => { + setError('') + setIsProcessing(true) + try { + if (currentAccount?.email) { + await getAgent().com.atproto.server.updateEmail({ + email: currentAccount!.email, + token: confirmationCode.trim(), + emailAuthFactor: false, + }) + updateCurrentAccount({emailAuthFactor: false}) + Toast.show(_(msg`Email 2FA disabled`)) + } + control.close() + } catch (e) { + const errMsg = String(e) + if (errMsg.includes('Token is invalid')) { + setError(_(msg`Invalid 2FA confirmation code.`)) + } else { + setError(cleanError(errMsg)) + } + } finally { + setIsProcessing(false) + } + } + + return ( + + + + + + + Disable Email 2FA + +

+ {stage === Stages.ConfirmCode ? ( + + An email has been sent to{' '} + {currentAccount?.email || '(no email)'}. It includes a + confirmation code which you can enter below. + + ) : ( + + To disable the email 2FA method, please verify your access to + the email address. + + )} +

+ + {error ? : undefined} + + {stage === Stages.Email ? ( + + + + + ) : stage === Stages.ConfirmCode ? ( + + + + Confirmation code + + + + + + + + + + + + ) : undefined} + + {!gtMobile && isNative && } + +
+
+ ) +} diff --git a/src/view/screens/Settings/Email2FAToggle.tsx b/src/view/screens/Settings/Email2FAToggle.tsx new file mode 100644 index 0000000000..87a56ba5eb --- /dev/null +++ b/src/view/screens/Settings/Email2FAToggle.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useModalControls} from '#/state/modals' +import {useAgent, useSession, useSessionApi} from '#/state/session' +import {ToggleButton} from 'view/com/util/forms/ToggleButton' +import {useDialogControl} from '#/components/Dialog' +import {DisableEmail2FADialog} from './DisableEmail2FADialog' + +export function Email2FAToggle() { + const {_} = useLingui() + const {currentAccount} = useSession() + const {updateCurrentAccount} = useSessionApi() + const {openModal} = useModalControls() + const disableDialogCtrl = useDialogControl() + const {getAgent} = useAgent() + + const enableEmailAuthFactor = React.useCallback(async () => { + if (currentAccount?.email) { + await getAgent().com.atproto.server.updateEmail({ + email: currentAccount.email, + emailAuthFactor: true, + }) + updateCurrentAccount({ + emailAuthFactor: true, + }) + } + }, [currentAccount, updateCurrentAccount, getAgent]) + + const onToggle = React.useCallback(() => { + if (!currentAccount) { + return + } + if (currentAccount.emailAuthFactor) { + disableDialogCtrl.open() + } else { + if (!currentAccount.emailConfirmed) { + openModal({ + name: 'verify-email', + onSuccess: enableEmailAuthFactor, + }) + return + } + enableEmailAuthFactor() + } + }, [currentAccount, enableEmailAuthFactor, openModal, disableDialogCtrl]) + + return ( + <> + + + + ) +} diff --git a/src/view/screens/Settings/ExportCarDialog.tsx b/src/view/screens/Settings/ExportCarDialog.tsx index e901fb0905..1b8d430b2a 100644 --- a/src/view/screens/Settings/ExportCarDialog.tsx +++ b/src/view/screens/Settings/ExportCarDialog.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {getAgent, useSession} from '#/state/session' +import {useAgent, useSession} from '#/state/session' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -19,6 +19,7 @@ export function ExportCarDialog({ const t = useTheme() const {gtMobile} = useBreakpoints() const {currentAccount} = useSession() + const {getAgent} = useAgent() const downloadUrl = React.useMemo(() => { const agent = getAgent() @@ -30,7 +31,7 @@ export function ExportCarDialog({ url.pathname = '/xrpc/com.atproto.sync.getRepo' url.searchParams.set('did', agent.session.did) return url.toString() - }, [currentAccount]) + }, [currentAccount, getAgent]) return ( diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index 830a73ff26..6b5390c293 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -23,12 +23,7 @@ import {useQueryClient} from '@tanstack/react-query' import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {clearLegacyStorage} from '#/state/persisted/legacy' -// TODO import {useInviteCodesQuery} from '#/state/queries/invites' import {clear as clearStorage} from '#/state/persisted/store' -import { - useRequireAltTextEnabled, - useSetRequireAltTextEnabled, -} from '#/state/preferences' import { useInAppBrowser, useSetInAppBrowser, @@ -68,6 +63,8 @@ import {UserAvatar} from 'view/com/util/UserAvatar' import {ScrollView} from 'view/com/util/Views' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' +import {navigate, resetToTab} from '#/Navigation' +import {Email2FAToggle} from './Email2FAToggle' import {ExportCarDialog} from './ExportCarDialog' function SettingsAccountCard({account}: {account: SessionAccount}) { @@ -101,7 +98,14 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { { - logout('Settings') + if (isNative) { + logout('Settings') + resetToTab('HomeTab') + } else { + navigate('Home').then(() => { + logout('Settings') + }) + } }} accessibilityRole="button" accessibilityLabel={_(msg`Sign out`)} @@ -151,8 +155,6 @@ export function SettingsScreen({}: Props) { const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() - const requireAltTextEnabled = useRequireAltTextEnabled() - const setRequireAltTextEnabled = useSetRequireAltTextEnabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() const onboardingDispatch = useOnboardingDispatch() @@ -162,9 +164,6 @@ export function SettingsScreen({}: Props) { const {openModal} = useModalControls() const {isSwitchingAccounts, accounts, currentAccount} = useSession() const {mutate: clearPreferences} = useClearPreferencesMutation() - // TODO - // const {data: invites} = useInviteCodesQuery() - // const invitesAvailable = invites?.available?.length ?? 0 const {setShowLoggedOut} = useLoggedOutViewControls() const closeAllActiveElements = useCloseAllActiveElements() const exportCarControl = useDialogControl() @@ -220,13 +219,6 @@ export function SettingsScreen({}: Props) { exportCarControl.open() }, [exportCarControl]) - /* TODO - const onPressInviteCodes = React.useCallback(() => { - track('Settings:InvitecodesButtonClicked') - openModal({name: 'invite-codes'}) - }, [track, openModal]) - */ - const onPressLanguageSettings = React.useCallback(() => { navigation.navigate('LanguageSettings') }, [navigation]) @@ -279,6 +271,10 @@ export function SettingsScreen({}: Props) { navigation.navigate('SavedFeeds') }, [navigation]) + const onPressAccessibilitySettings = React.useCallback(() => { + navigation.navigate('AccessibilitySettings') + }, [navigation]) + const onPressStatusPage = React.useCallback(() => { Linking.openURL(STATUS_PAGE_URL) }, []) @@ -315,7 +311,7 @@ export function SettingsScreen({}: Props) {
- {/* TODO ( - <> - - Invite a Friend - - - - 0 ? primaryBg : pal.btn, - ]}> - 0 - ? primaryText - : pal.text) as FontAwesomeIconStyle - } - /> - - 0 ? pal.link : pal.text}> - {invites?.disabled ? ( - - Your invite codes are hidden when logged in using an App - Password - - ) : invitesAvailable === 1 ? ( - {invitesAvailable} invite code available - ) : ( - {invitesAvailable} invite codes available - )} - - - - - - )*/} - - - Accessibility - - - setRequireAltTextEnabled(!requireAltTextEnabled)} - /> - - - - Appearance @@ -541,6 +470,72 @@ export function SettingsScreen({}: Props) { Basics + + + + + + Accessibility + + + + + + + + Languages + + + navigation.navigate('Moderation') + } + accessibilityRole="button" + accessibilityLabel={_(msg`Moderation settings`)} + accessibilityHint={_(msg`Opens moderation settings`)}> + + + + + Moderation + + My Saved Feeds - - - - - - Languages - - - navigation.navigate('Moderation') - } - accessibilityRole="button" - accessibilityLabel={_(msg`Moderation settings`)} - accessibilityHint={_(msg`Opens moderation settings`)}> - - - - - Moderation - - @@ -739,6 +691,13 @@ export function SettingsScreen({}: Props) { )} + + Two-factor authentication + + + + + Account diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx index cae8ec3144..b532b0dd16 100644 --- a/src/view/screens/Storybook/Buttons.tsx +++ b/src/view/screens/Storybook/Buttons.tsx @@ -9,7 +9,7 @@ import { ButtonText, ButtonVariant, } from '#/components/Button' -import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight' +import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {H1} from '#/components/Typography' diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx index 4722784cae..6d166d4b64 100644 --- a/src/view/screens/Storybook/Dialogs.tsx +++ b/src/view/screens/Storybook/Dialogs.tsx @@ -6,13 +6,51 @@ 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() + const unmountTestDialog = Dialog.useDialogControl() + const [shouldRenderUnmountTest, setShouldRenderUnmountTest] = + React.useState(false) + const unmountTestInterval = React.useRef() + + const onUnmountTestStartPressWithClose = () => { + setShouldRenderUnmountTest(true) + + setTimeout(() => { + unmountTestDialog.open() + }, 1000) + + setTimeout(() => { + unmountTestDialog.close() + }, 4950) + + setInterval(() => { + setShouldRenderUnmountTest(prev => !prev) + }, 5000) + } + + const onUnmountTestStartPressWithoutClose = () => { + setShouldRenderUnmountTest(true) + + setTimeout(() => { + unmountTestDialog.open() + }, 1000) + + setInterval(() => { + setShouldRenderUnmountTest(prev => !prev) + }, 5000) + } + + const onUnmountTestEndPress = () => { + setShouldRenderUnmountTest(false) + clearInterval(unmountTestInterval.current) + } return ( @@ -60,6 +98,42 @@ export function Dialogs() { Open prompt + + + + + + + + This is a prompt @@ -122,6 +196,142 @@ export function Dialogs() { + + + + + + + + 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. + + + + + + + + + + + + + + + + + + {shouldRenderUnmountTest && ( + + + + +

Unmount Test Dialog

+

Will unmount in about 5 seconds

+
+
+ )}
) } diff --git a/src/view/screens/Storybook/Icons.tsx b/src/view/screens/Storybook/Icons.tsx index 9d7dc0aa8a..bff1fdc9b7 100644 --- a/src/view/screens/Storybook/Icons.tsx +++ b/src/view/screens/Storybook/Icons.tsx @@ -2,11 +2,11 @@ import React from 'react' import {View} from 'react-native' import {atoms as a, useTheme} from '#/alf' -import {H1} from '#/components/Typography' -import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' -import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight' +import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow' import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays' +import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Loader} from '#/components/Loader' +import {H1} from '#/components/Typography' export function Icons() { const t = useTheme() diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 1bf5647f66..8145fa4087 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -9,49 +9,48 @@ import { View, ViewStyle, } from 'react-native' -import {useNavigation, StackActions} from '@react-navigation/native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {s, colors} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {StackActions, useNavigation} from '@react-navigation/native' + +import {emitSoftReset} from '#/state/events' +import {useUnreadNotifications} from '#/state/queries/notifications/unread' +import {useProfileQuery} from '#/state/queries/profile' +import {SessionAccount, useSession} from '#/state/session' +import {useSetDrawerOpen} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' +import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' +import {usePalette} from 'lib/hooks/usePalette' import { - HomeIcon, - HomeIconSolid, BellIcon, BellIconSolid, - UserIcon, CogIcon, + HashtagIcon, + HomeIcon, + HomeIconSolid, + ListIcon, MagnifyingGlassIcon2, MagnifyingGlassIcon2Solid, + UserIcon, UserIconSolid, - HashtagIcon, - ListIcon, - HandIcon, } from 'lib/icons' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Text} from 'view/com/util/text/Text' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {pluralize} from 'lib/strings/helpers' import {getTabState, TabState} from 'lib/routes/helpers' import {NavigationProp} from 'lib/routes/types' -import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' +import {pluralize} from 'lib/strings/helpers' +import {colors, s} from 'lib/styles' +import {useTheme} from 'lib/ThemeContext' import {isWeb} from 'platform/detection' -import {formatCountShortOnly} from 'view/com/util/numeric/format' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSetDrawerOpen} from '#/state/shell' -import {useSession, SessionAccount} from '#/state/session' -import {useProfileQuery} from '#/state/queries/profile' -import {useUnreadNotifications} from '#/state/queries/notifications/unread' -import {emitSoftReset} from '#/state/events' import {NavSignupCard} from '#/view/shell/NavSignupCard' -import {TextLink} from '../com/util/Link' - +import {formatCountShortOnly} from 'view/com/util/numeric/format' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' import {useTheme as useAlfTheme} from '#/alf' +import {TextLink} from '../com/util/Link' let DrawerProfileCard = ({ account, @@ -177,12 +176,6 @@ let DrawerContent = ({}: {}): React.ReactNode => { setDrawerOpen(false) }, [navigation, track, setDrawerOpen]) - const onPressModeration = React.useCallback(() => { - track('Menu:ItemClicked', {url: 'Moderation'}) - navigation.navigate('Moderation') - setDrawerOpen(false) - }, [navigation, track, setDrawerOpen]) - const onPressSettings = React.useCallback(() => { track('Menu:ItemClicked', {url: 'Settings'}) navigation.navigate('Settings') @@ -224,7 +217,9 @@ let DrawerContent = ({}: {}): React.ReactNode => { />
) : ( - + + + )} {hasSession ? ( @@ -238,7 +233,6 @@ let DrawerContent = ({}: {}): React.ReactNode => { /> - { ) : ( - + <> + + + + )} @@ -501,25 +499,6 @@ let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => { } ListsMenuItem = React.memo(ListsMenuItem) -let ModerationMenuItem = ({ - onPress, -}: { - onPress: () => void -}): React.ReactNode => { - const {_} = useLingui() - const pal = usePalette('default') - return ( - } - label={_(msg`Moderation`)} - accessibilityLabel={_(msg`Moderation`)} - accessibilityHint="" - onPress={onPress} - /> - ) -} -ModerationMenuItem = React.memo(ModerationMenuItem) - let ProfileMenuItem = ({ isActive, onPress, diff --git a/src/view/shell/NavSignupCard.tsx b/src/view/shell/NavSignupCard.tsx index 83d1414984..12bfa7ea05 100644 --- a/src/view/shell/NavSignupCard.tsx +++ b/src/view/shell/NavSignupCard.tsx @@ -3,13 +3,16 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {Text} from '#/view/com/util/text/Text' -import {Button} from '#/view/com/util/forms/Button' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' +import {Button} from '#/view/com/util/forms/Button' +import {Text} from '#/view/com/util/text/Text' import {Logo} from '#/view/icons/Logo' +import {atoms as a} from '#/alf' +import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' +import {Link} from '#/components/Link' let NavSignupCard = ({}: {}): React.ReactNode => { const {_} = useLingui() @@ -35,7 +38,9 @@ let NavSignupCard = ({}: {}): React.ReactNode => { paddingTop: 6, marginBottom: 24, }}> - + + + @@ -43,7 +48,13 @@ let NavSignupCard = ({}: {}): React.ReactNode => { - + + + + + ) } diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index f41631a969..33f713322a 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -8,7 +8,7 @@ import {BottomTabBarProps} from '@react-navigation/bottom-tabs' import {StackActions} from '@react-navigation/native' import {useAnalytics} from '#/lib/analytics/analytics' -import {Haptics} from '#/lib/haptics' +import {useHaptics} from '#/lib/haptics' import {useDedupe} from '#/lib/hooks/useDedupe' import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode' import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' @@ -24,6 +24,7 @@ import { } from '#/lib/icons' import {clamp} from '#/lib/numbers' import {getTabState, TabState} from '#/lib/routes/helpers' +import {useGate} from '#/lib/statsig/statsig' import {s} from '#/lib/styles' import {emitSoftReset} from '#/state/events' import {useUnreadNotifications} from '#/state/queries/notifications/unread' @@ -39,9 +40,17 @@ import {Logo} from '#/view/icons/Logo' import {Logotype} from '#/view/icons/Logotype' import {useDialogControl} from '#/components/Dialog' import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' +import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' +import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope' import {styles} from './BottomBarStyles' -type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' +type TabOptions = + | 'Home' + | 'Search' + | 'Notifications' + | 'MyProfile' + | 'Feeds' + | 'Messages' export function BottomBar({navigation}: BottomTabBarProps) { const {hasSession, currentAccount} = useSession() @@ -50,8 +59,14 @@ export function BottomBar({navigation}: BottomTabBarProps) { const safeAreaInsets = useSafeAreaInsets() const {track} = useAnalytics() const {footerHeight} = useShellLayout() - const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} = - useNavigationTabState() + const { + isAtHome, + isAtSearch, + isAtFeeds, + isAtNotifications, + isAtMyProfile, + isAtMessages, + } = useNavigationTabState() const numUnreadNotifications = useUnreadNotifications() const {footerMinimalShellTransform} = useMinimalShellMode() const {data: profile} = useProfileQuery({did: currentAccount?.did}) @@ -59,6 +74,8 @@ export function BottomBar({navigation}: BottomTabBarProps) { const closeAllActiveElements = useCloseAllActiveElements() const dedupe = useDedupe() const accountSwitchControl = useDialogControl() + const playHaptic = useHaptics() + const gate = useGate() const showSignIn = React.useCallback(() => { closeAllActiveElements() @@ -103,10 +120,14 @@ export function BottomBar({navigation}: BottomTabBarProps) { onPressTab('MyProfile') }, [onPressTab]) + const onPressMessages = React.useCallback(() => { + onPressTab('Messages') + }, [onPressTab]) + const onLongPressProfile = React.useCallback(() => { - Haptics.default() + playHaptic() accountSwitchControl.open() - }, [accountSwitchControl]) + }, [accountSwitchControl, playHaptic]) return ( <> @@ -219,6 +240,28 @@ export function BottomBar({navigation}: BottomTabBarProps) { : `${numUnreadNotifications} unread` } /> + {gate('dms') && ( + + ) : ( + + ) + } + onPress={onPressMessages} + accessibilityRole="tab" + accessibilityLabel={_(msg`Messages`)} + accessibilityHint="" + /> + )} + {icon} {notificationCount ? ( {notificationCount} ) : undefined} - {icon} ) } diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx index f226406f5d..f76df5bd88 100644 --- a/src/view/shell/bottom-bar/BottomBarStyles.tsx +++ b/src/view/shell/bottom-bar/BottomBarStyles.tsx @@ -1,4 +1,5 @@ import {StyleSheet} from 'react-native' + import {colors} from 'lib/styles' export const styles = StyleSheet.create({ @@ -65,6 +66,9 @@ export const styles = StyleSheet.create({ profileIcon: { top: -4, }, + messagesIcon: { + top: 2, + }, onProfile: { borderWidth: 1, borderRadius: 100, diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx index b330c4b808..8b316faa5e 100644 --- a/src/view/shell/bottom-bar/BottomBarWeb.tsx +++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx @@ -1,37 +1,41 @@ import React from 'react' -import {usePalette} from 'lib/hooks/usePalette' -import {useNavigationState} from '@react-navigation/native' +import {View} from 'react-native' import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {getCurrentRoute, isTab} from 'lib/routes/helpers' -import {styles} from './BottomBarStyles' -import {clamp} from 'lib/numbers' +import {useNavigationState} from '@react-navigation/native' + +import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode' +import {usePalette} from '#/lib/hooks/usePalette' import { BellIcon, BellIconSolid, + HashtagIcon, HomeIcon, HomeIconSolid, MagnifyingGlassIcon2, MagnifyingGlassIcon2Solid, - HashtagIcon, UserIcon, UserIconSolid, -} from 'lib/icons' -import {Link} from 'view/com/util/Link' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' -import {makeProfileLink} from 'lib/routes/links' -import {CommonNavigatorParams} from 'lib/routes/types' +} from '#/lib/icons' +import {clamp} from '#/lib/numbers' +import {getCurrentRoute, isTab} from '#/lib/routes/helpers' +import {makeProfileLink} from '#/lib/routes/links' +import {CommonNavigatorParams} from '#/lib/routes/types' +import {useGate} from '#/lib/statsig/statsig' +import {s} from '#/lib/styles' import {useSession} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {Button} from '#/view/com/util/forms/Button' import {Text} from '#/view/com/util/text/Text' -import {s} from 'lib/styles' import {Logo} from '#/view/icons/Logo' import {Logotype} from '#/view/icons/Logotype' +import {Link} from 'view/com/util/Link' +import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' +import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope' +import {styles} from './BottomBarStyles' export function BottomBarWeb() { const {_} = useLingui() @@ -41,6 +45,7 @@ export function BottomBarWeb() { const {footerMinimalShellTransform} = useMinimalShellMode() const {requestSwitchToAccount} = useLoggedOutViewControls() const closeAllActiveElements = useCloseAllActiveElements() + const gate = useGate() const showSignIn = React.useCallback(() => { closeAllActiveElements() @@ -117,6 +122,19 @@ export function BottomBarWeb() { ) }} + {gate('dms') && ( + + {({isActive}) => { + const Icon = isActive ? EnvelopeFilled : Envelope + return ( + + ) + }} + + )} setShowLoggedOut(false)} /> } if (onboardingState.isActive) { - if (NEW_ONBOARDING_ENABLED) { - return - } else { - return - } + return } const newDescriptors: typeof descriptors = {} for (let key in descriptors) { diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 097ca2fbfb..91d20e089e 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,52 +1,55 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {PressableWithHover} from 'view/com/util/PressableWithHover' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import { useLinkProps, useNavigation, useNavigationState, } from '@react-navigation/native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {Text} from 'view/com/util/text/Text' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Link} from 'view/com/util/Link' -import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' + +import {useGate} from '#/lib/statsig/statsig' +import {isInvalidHandle} from '#/lib/strings/handles' +import {emitSoftReset} from '#/state/events' +import {useFetchHandle} from '#/state/queries/handle' +import {useUnreadNotifications} from '#/state/queries/notifications/unread' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {useComposerControls} from '#/state/shell/composer' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {s, colors} from 'lib/styles' import { - HomeIcon, - HomeIconSolid, - MagnifyingGlassIcon2, - MagnifyingGlassIcon2Solid, BellIcon, BellIconSolid, - UserIcon, - UserIconSolid, CogIcon, CogIconSolid, ComposeIcon2, - ListIcon, HashtagIcon, - HandIcon, + HomeIcon, + HomeIconSolid, + ListIcon, + MagnifyingGlassIcon2, + MagnifyingGlassIcon2Solid, + UserIcon, + UserIconSolid, } from 'lib/icons' -import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers' -import {NavigationProp, CommonNavigatorParams} from 'lib/routes/types' -import {router} from '../../../routes' +import {getCurrentRoute, isStateAtTabRoot, isTab} from 'lib/routes/helpers' import {makeProfileLink} from 'lib/routes/links' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' -import {useProfileQuery} from '#/state/queries/profile' -import {useSession} from '#/state/session' -import {useUnreadNotifications} from '#/state/queries/notifications/unread' -import {useComposerControls} from '#/state/shell/composer' -import {useFetchHandle} from '#/state/queries/handle' -import {emitSoftReset} from '#/state/events' +import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' +import {colors, s} from 'lib/styles' import {NavSignupCard} from '#/view/shell/NavSignupCard' -import {isInvalidHandle} from '#/lib/strings/handles' +import {Link} from 'view/com/util/Link' +import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' +import {PressableWithHover} from 'view/com/util/PressableWithHover' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' +import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeFilled} from '#/components/icons/Envelope' +import {router} from '../../../routes' function ProfileCard() { const {currentAccount} = useSession() @@ -272,6 +275,7 @@ export function DesktopLeftNav() { const {_} = useLingui() const {isDesktop, isTablet} = useWebMediaQueries() const numUnread = useUnreadNotifications() + const gate = useGate() if (!hasSession && !isDesktop) { return null @@ -327,24 +331,6 @@ export function DesktopLeftNav() { } label={_(msg`Search`)} /> - - } - iconFilled={ - - } - label={_(msg`Feeds`)} - /> + {gate('dms') && ( + } + iconFilled={ + + } + label={_(msg`Messages`)} + /> + )} + + } + iconFilled={ + + } + label={_(msg`Feeds`)} + /> - - } - iconFilled={ - - } - label={_(msg`Moderation`)} - /> void }) { const pal = usePalette('default') + const queryClient = useQueryClient() + + const onPress = React.useCallback(() => { + precacheProfile(queryClient, profile) + onPressInner() + }, [queryClient, profile, onPressInner]) return ( + anchorNoUnderline + onBeforePress={onPress}> () - const searchDebounceTimeout = React.useRef( - undefined, - ) const [isActive, setIsActive] = React.useState(false) - const [isFetching, setIsFetching] = React.useState(false) const [query, setQuery] = React.useState('') - const [searchResults, setSearchResults] = React.useState< - AppBskyActorDefs.ProfileViewBasic[] - >([]) + const {data: autocompleteData, isFetching} = useActorAutocompleteQuery( + query, + true, + ) const moderationOpts = useModerationOpts() - const search = useActorAutocompleteFn() - const onChangeText = React.useCallback( - async (text: string) => { - setQuery(text) - - if (text.length > 0) { - setIsFetching(true) - setIsActive(true) - - if (searchDebounceTimeout.current) - clearTimeout(searchDebounceTimeout.current) - - searchDebounceTimeout.current = setTimeout(async () => { - const results = await search({query: text}) - - if (results) { - setSearchResults(results) - setIsFetching(false) - } - }, 300) - } else { - if (searchDebounceTimeout.current) - clearTimeout(searchDebounceTimeout.current) - setSearchResults([]) - setIsFetching(false) - setIsActive(false) - } - }, - [setQuery, search, setSearchResults], - ) + const onChangeText = React.useCallback((text: string) => { + setQuery(text) + setIsActive(text.length > 0) + }, []) const onPressCancelSearch = React.useCallback(() => { setQuery('') setIsActive(false) - if (searchDebounceTimeout.current) - clearTimeout(searchDebounceTimeout.current) }, [setQuery]) + const onSubmit = React.useCallback(() => { setIsActive(false) if (!query.length) return - setSearchResults([]) - if (searchDebounceTimeout.current) - clearTimeout(searchDebounceTimeout.current) navigation.dispatch(StackActions.push('Search', {q: query})) - }, [query, navigation, setSearchResults]) + }, [query, navigation]) + + const onSearchProfileCardPress = React.useCallback(() => { + setQuery('') + setIsActive(false) + }, []) const queryMaybeHandle = React.useMemo(() => { const match = MATCH_HANDLE.exec(query) @@ -246,7 +229,7 @@ export function DesktopSearch() { {query !== '' && isActive && moderationOpts && ( - {isFetching ? ( + {isFetching && !autocompleteData?.length ? ( @@ -255,7 +238,11 @@ export function DesktopSearch() { 0 + ? {borderBottomWidth: 1} + : undefined + } /> {queryMaybeHandle ? ( @@ -265,11 +252,12 @@ export function DesktopSearch() { /> ) : null} - {searchResults.map(item => ( + {autocompleteData?.map(item => ( ))} diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index f29183095a..f13a8d7dfe 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -1,37 +1,40 @@ import React from 'react' -import {StatusBar} from 'expo-status-bar' import { + BackHandler, DimensionValue, StyleSheet, useWindowDimensions, View, - BackHandler, } from 'react-native' -import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Drawer} from 'react-native-drawer-layout' +import Animated from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import * as NavigationBar from 'expo-navigation-bar' +import {StatusBar} from 'expo-status-bar' import {useNavigationState} from '@react-navigation/native' -import {ModalsContainer} from 'view/com/modals/Modal' -import {Lightbox} from 'view/com/lightbox/Lightbox' -import {ErrorBoundary} from 'view/com/util/ErrorBoundary' -import {DrawerContent} from './Drawer' -import {Composer} from './Composer' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {RoutesContainer, TabsNavigator} from '../../Navigation' -import {isStateAtTabRoot} from 'lib/routes/helpers' + +import {useAgent, useSession} from '#/state/session' import { useIsDrawerOpen, - useSetDrawerOpen, useIsDrawerSwipeDisabled, + useSetDrawerOpen, } from '#/state/shell' -import {isAndroid} from 'platform/detection' -import {useSession} from '#/state/session' import {useCloseAnyActiveElement} from '#/state/util' +import {usePalette} from 'lib/hooks/usePalette' import * as notifications from 'lib/notifications/notifications' -import {Outlet as PortalOutlet} from '#/components/Portal' -import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {isStateAtTabRoot} from 'lib/routes/helpers' +import {useTheme} from 'lib/ThemeContext' +import {isAndroid} from 'platform/detection' import {useDialogStateContext} from 'state/dialogs' -import Animated from 'react-native-reanimated' +import {Lightbox} from 'view/com/lightbox/Lightbox' +import {ModalsContainer} from 'view/com/modals/Modal' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' +import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {SigninDialog} from '#/components/dialogs/Signin' +import {Outlet as PortalOutlet} from '#/components/Portal' +import {RoutesContainer, TabsNavigator} from '../../Navigation' +import {Composer} from './Composer' +import {DrawerContent} from './Drawer' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -54,6 +57,7 @@ function ShellInner() { ) const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) const {hasSession, currentAccount} = useSession() + const {getAgent} = useAgent() const closeAnyActiveElement = useCloseAnyActiveElement() const {importantForAccessibility} = useDialogStateContext() // start undefined @@ -75,11 +79,14 @@ function ShellInner() { // only runs when did changes if (currentAccount && currentAccountDid.current !== currentAccount.did) { currentAccountDid.current = currentAccount.did - notifications.requestPermissionsAndRegisterToken(currentAccount) - const unsub = notifications.registerTokenChangeHandler(currentAccount) + notifications.requestPermissionsAndRegisterToken(getAgent, currentAccount) + const unsub = notifications.registerTokenChangeHandler( + getAgent, + currentAccount, + ) return unsub } - }, [currentAccount]) + }, [currentAccount, getAgent]) return ( <> @@ -101,6 +108,7 @@ function ShellInner() { + @@ -110,6 +118,15 @@ function ShellInner() { export const Shell: React.FC = function ShellImpl() { const pal = usePalette('default') const theme = useTheme() + React.useEffect(() => { + if (isAndroid) { + NavigationBar.setBackgroundColorAsync(theme.palette.default.background) + NavigationBar.setBorderColorAsync(theme.palette.default.background) + NavigationBar.setButtonStyleAsync( + theme.colorScheme === 'dark' ? 'light' : 'dark', + ) + } + }, [theme]) return ( diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 02993ac462..9dab23671f 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -1,24 +1,25 @@ import React, {useEffect} from 'react' -import {View, StyleSheet, TouchableOpacity} from 'react-native' -import {useNavigation} from '@react-navigation/native' +import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' -import {ErrorBoundary} from '../com/util/ErrorBoundary' -import {Lightbox} from '../com/lightbox/Lightbox' -import {ModalsContainer} from '../com/modals/Modal' -import {Composer} from './Composer.web' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' -import {s, colors} from 'lib/styles' -import {RoutesContainer, FlatNavigator} from '../../Navigation' -import {DrawerContent} from './Drawer' -import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' +import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' -import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' -import {Outlet as PortalOutlet} from '#/components/Portal' +import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {NavigationProp} from 'lib/routes/types' +import {colors, s} from 'lib/styles' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {SigninDialog} from '#/components/dialogs/Signin' +import {Outlet as PortalOutlet} from '#/components/Portal' +import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' +import {FlatNavigator, RoutesContainer} from '../../Navigation' +import {Lightbox} from '../com/lightbox/Lightbox' +import {ModalsContainer} from '../com/modals/Modal' +import {ErrorBoundary} from '../com/util/ErrorBoundary' +import {Composer} from './Composer.web' +import {DrawerContent} from './Drawer' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -45,19 +46,26 @@ function ShellInner() { + {!isDesktop && isDrawerOpen && ( - setDrawerOpen(false)} - style={styles.drawerMask} + { + // Only close if press happens outside of the drawer + if (ev.target === ev.currentTarget) { + setDrawerOpen(false) + } + }} accessibilityLabel={_(msg`Close navigation footer`)} accessibilityHint={_(msg`Closes bottom navigation bar`)}> - - + + + + - + )} ) diff --git a/web/index.html b/web/index.html index 06d00dec97..b059e69e90 100644 --- a/web/index.html +++ b/web/index.html @@ -239,6 +239,16 @@ inset:0; animation: rotate 500ms linear infinite; } + + @keyframes avatarHoverFadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes avatarHoverFadeOut { + from { opacity: 1; } + to { opacity: 0; } + } diff --git a/yarn.lock b/yarn.lock index 3f2725b60c..fb322057b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,10 +34,10 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@atproto/api@^0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.2.tgz#5df6d4f60dea0395c84fdebd9e81a7e853edf130" - integrity sha512-UVzCiDZH2j0wrr/O8nb1edD5cYLVqB5iujueXUCbHS3rAwIxgmyLtA3Hzm2QYsGPo/+xsIg1fNvpq9rNT6KWUA== +"@atproto/api@^0.12.3": + version "0.12.3" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.3.tgz#5b7b1c7d4210ee9315961504900c8409395cbb17" + integrity sha512-y/kGpIEo+mKGQ7VOphpqCAigTI0LZRmDThNChTfSzDKm9TzEobwiw0zUID0Yw6ot1iLLFx3nKURmuZAYlEuobw== dependencies: "@atproto/common-web" "^0.3.0" "@atproto/lexicon" "^0.4.0" @@ -46,28 +46,26 @@ 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/api@^0.12.5": + version "0.12.5" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.5.tgz#3ed70990b27c468d9663ca71306039cab663ca96" + integrity sha512-xqdl/KrAK2kW6hN8+eSmKTWHgMNaPnDAEvZzo08Xbk/5jdRzjoEPS+p7k/wQ+ZefwOHL3QUbVPO4hMfmVxzO/Q== dependencies: - "@atproto/common-web" "^0.2.3" - "@atproto/lexicon" "^0.3.1" - "@atproto/syntax" "^0.1.5" - "@atproto/xrpc" "^0.4.1" + "@atproto/common-web" "^0.3.0" + "@atproto/lexicon" "^0.4.0" + "@atproto/syntax" "^0.3.0" + "@atproto/xrpc" "^0.5.0" 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== +"@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" "^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 +75,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.45": + version "0.0.45" + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.45.tgz#c3083d8038fe8c5ff921d9bcb0b5a043cc840827" + integrity sha512-osWeigdYzQH2vZki+eszCR8ta9zdUB4om79aFmnE+zvxw7HFduwAAbcHf6kmmiLCfaOWvCsYb1wS2i3IC66TAg== 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.3" + "@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 +157,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 +180,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.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.5.tgz#cd13313dbc52131731d039a1d22808ee8193505d" + integrity sha512-dqRNihzX1xIHbWPHmfYsliUUXyZn5FFhCeButrGie5soQmHA4okQJTB1XWDly3mdHLjUM90g+5zjRSAKoui77Q== 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.3" + "@atproto/bsky" "^0.0.45" + "@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.7" + "@atproto/pds" "^0.4.14" + "@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 +234,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.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.7.tgz#248d88e1acfe56936651754975472d03d047d689" + integrity sha512-vvaV0MFynOzZJcL8m8mEW21o1FFIkP+wHTXEC9LJrL3h03+PMaby8Ujmif6WX5eikhfxvr9xsU/Jxbi/iValuQ== 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.3" + "@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.14": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.14.tgz#5b55ef307323bda712f2ddaba5c1fff7740ed91b" + integrity sha512-rqVcvtw5oMuuJIpWZbSSTSx19+JaZyUcg9OEjdlUmyEpToRN88zTEQySEksymrrLQkW/LPRyWGd7WthbGEuEfQ== 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.3" + "@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 +285,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 +296,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 +303,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 +340,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" @@ -1261,6 +1212,21 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" @@ -1364,6 +1330,17 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -1376,6 +1353,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + "@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" @@ -1403,6 +1385,15 @@ "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -1454,6 +1445,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + "@babel/helper-wrap-function@^7.22.9": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz#d845e043880ed0b8c18bd194a12005cb16d2f614" @@ -1562,6 +1558,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-default-from" "^7.22.5" +"@babel/plugin-proposal-logical-assignment-operators@^7.18.0": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" + integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" @@ -1633,7 +1637,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.0.0", "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== @@ -1675,7 +1679,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.22.5": +"@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz#163b820b9e7696ce134df3ee716d9c0c98035859" integrity sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ== @@ -1710,7 +1714,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.7.2": +"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.7.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== @@ -1724,6 +1728,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1745,7 +1756,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== @@ -1787,6 +1798,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -1821,7 +1839,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.5" -"@babel/plugin-transform-block-scoped-functions@^7.0.0", "@babel/plugin-transform-block-scoped-functions@^7.22.5": +"@babel/plugin-transform-block-scoped-functions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== @@ -1937,7 +1955,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-flow" "^7.22.5" -"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.22.5": +"@babel/plugin-transform-for-of@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== @@ -1976,7 +1994,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.0.0", "@babel/plugin-transform-member-expression-literals@^7.22.5": +"@babel/plugin-transform-member-expression-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== @@ -2000,6 +2018,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + "@babel/plugin-transform-modules-systemjs@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" @@ -2078,7 +2105,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.5" -"@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.22.5": +"@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== @@ -2145,7 +2172,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.0.0", "@babel/plugin-transform-property-literals@^7.22.5": +"@babel/plugin-transform-property-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== @@ -2281,7 +2308,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-template-literals@^7.0.0", "@babel/plugin-transform-template-literals@^7.22.5": +"@babel/plugin-transform-template-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== @@ -2305,6 +2332,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" +"@babel/plugin-transform-typescript@^7.24.1": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz#03e0492537a4b953e491f53f2bc88245574ebd15" + integrity sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript" "^7.24.1" + "@babel/plugin-transform-unicode-escapes@^7.22.10": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" @@ -2475,6 +2512,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.22.5" "@babel/plugin-transform-typescript" "^7.22.5" +"@babel/preset-typescript@^7.23.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" + integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-syntax-jsx" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-typescript" "^7.24.1" + "@babel/register@^7.13.16": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.5.tgz#e4d8d0f615ea3233a27b5c6ada6750ee59559939" @@ -2995,28 +3043,28 @@ mv "~2" safe-json-stringify "~1" -"@expo/cli@0.17.8": - version "0.17.8" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.17.8.tgz#4abe0d8c604b73a6e1d0a10f34e993cbf1cbad42" - integrity sha512-yfkoghCltbGPDbRI71Qu3puInjXx4wO82+uhW82qbWLvosfIN7ep5Gr0Lq54liJpvlUG6M0IXM1GiGqcCyP12w== +"@expo/cli@0.18.6": + version "0.18.6" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.18.6.tgz#6033605b49dc0cb90c8c94fad1e1f64b82496e46" + integrity sha512-qKNyvkU+Lvp/H9mbavgAPRwjIeIWsOPB5fXARUF9WKLBXlGo4zlES06OvFECxHtCMmtNCyPT96x1jciGlyCf4A== dependencies: "@babel/runtime" "^7.20.0" "@expo/code-signing-certificates" "0.0.5" - "@expo/config" "~8.5.0" - "@expo/config-plugins" "~7.8.0" + "@expo/config" "~9.0.0-beta.0" + "@expo/config-plugins" "~8.0.0-beta.0" "@expo/devcert" "^1.0.0" - "@expo/env" "~0.2.2" - "@expo/image-utils" "^0.4.0" - "@expo/json-file" "^8.2.37" - "@expo/metro-config" "~0.17.0" + "@expo/env" "~0.3.0" + "@expo/image-utils" "^0.5.0" + "@expo/json-file" "^8.3.0" + "@expo/metro-config" "~0.18.0" "@expo/osascript" "^2.0.31" - "@expo/package-manager" "^1.1.1" + "@expo/package-manager" "^1.5.0" "@expo/plist" "^0.1.0" - "@expo/prebuild-config" "6.7.4" + "@expo/prebuild-config" "7.0.1" "@expo/rudder-sdk-node" "1.1.1" - "@expo/spawn-async" "1.5.0" + "@expo/spawn-async" "^1.7.2" "@expo/xcpretty" "^4.3.0" - "@react-native/dev-middleware" "^0.73.6" + "@react-native/dev-middleware" "~0.74.75" "@urql/core" "2.3.6" "@urql/exchange-retry" "0.3.0" accepts "^1.3.8" @@ -3029,6 +3077,7 @@ connect "^3.7.0" debug "^4.3.4" env-editor "^0.4.1" + fast-glob "^3.3.2" find-yarn-workspace-root "~2.0.0" form-data "^3.0.1" freeport-async "2.0.0" @@ -3046,7 +3095,6 @@ lodash.debounce "^4.0.8" md5hex "^1.0.0" minimatch "^3.0.4" - minipass "3.3.6" node-fetch "^2.6.7" node-forge "^1.3.1" npm-package-arg "^7.0.0" @@ -3062,7 +3110,7 @@ resolve "^1.22.2" resolve-from "^5.0.0" resolve.exports "^2.0.2" - semver "^7.5.3" + semver "^7.6.0" send "^0.18.0" slugify "^1.3.4" source-map-support "~0.5.21" @@ -3107,24 +3155,22 @@ xcode "^3.0.1" xml2js "0.6.0" -"@expo/config-plugins@7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.4.tgz#533b5d536c1dc8b5544d64878b51bda28f2e1a1f" - integrity sha512-hv03HYxb/5kX8Gxv/BTI8TLc9L06WzqAfHRRXdbar4zkLcP2oTzvsLEF4/L/TIpD3rsnYa0KU42d0gWRxzPCJg== +"@expo/config-plugins@8.0.3", "@expo/config-plugins@~8.0.0-beta.0": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-8.0.3.tgz#6b63b8f33c0f266de12257df928176599f85edaa" + integrity sha512-XZRpVGdBKS8p8JrGp9J7wX6oCGq3zwhrVMDXTXsGfZZTVJo/uX6NkXtnl/C3TlxG4Kv5kOuYBekSsnw+i3plRA== dependencies: - "@expo/config-types" "^50.0.0-alpha.1" - "@expo/fingerprint" "^0.6.0" + "@expo/config-types" "^51.0.0-unreleased" "@expo/json-file" "~8.3.0" "@expo/plist" "^0.1.0" "@expo/sdk-runtime-versions" "^1.0.0" - "@react-native/normalize-color" "^2.0.0" chalk "^4.1.2" debug "^4.3.1" find-up "~5.0.0" getenv "^1.0.0" glob "7.1.6" resolve-from "^5.0.0" - semver "^7.5.3" + semver "^7.5.4" slash "^3.0.0" slugify "^1.6.6" xcode "^3.0.1" @@ -3151,53 +3197,35 @@ xcode "^3.0.1" xml2js "0.4.23" -"@expo/config-plugins@~7.8.2": - version "7.8.2" - resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.2.tgz#c00ce93c4d6c2cb9e345ed9cd56ceeea05ab8ddb" - integrity sha512-XM2eXA5EvcpmXFCui48+bVy8GTskYSjPf2yC+LliYv8PDcedu7+pdgmbnvH4eZCyHfTMO8/UiF+w8e5WgOEj5A== - dependencies: - "@expo/config-types" "^50.0.0-alpha.1" - "@expo/fingerprint" "^0.6.0" - "@expo/json-file" "~8.3.0" - "@expo/plist" "^0.1.0" - "@expo/sdk-runtime-versions" "^1.0.0" - "@react-native/normalize-color" "^2.0.0" - chalk "^4.1.2" - debug "^4.3.1" - find-up "~5.0.0" - getenv "^1.0.0" - glob "7.1.6" - resolve-from "^5.0.0" - semver "^7.5.3" - slash "^3.0.0" - slugify "^1.6.6" - xcode "^3.0.1" - xml2js "0.6.0" - "@expo/config-types@^47.0.0": version "47.0.0" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-47.0.0.tgz#99eeabe0bba7a776e0f252b78beb0c574692c38d" integrity sha512-r0pWfuhkv7KIcXMUiNACJmJKKwlTBGMw9VZHNdppS8/0Nve8HZMTkNRFQzTHW1uH3pBj8jEXpyw/2vSWDHex9g== -"@expo/config-types@^50.0.0", "@expo/config-types@^50.0.0-alpha.1": +"@expo/config-types@^50.0.0-alpha.1": version "50.0.0" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-50.0.0.tgz#b534d3ec997ec60f8af24f6ad56244c8afc71a0b" integrity sha512-0kkhIwXRT6EdFDwn+zTg9R2MZIAEYGn1MVkyRohAd+C9cXOb5RA8WLQi7vuxKF9m1SMtNAUrf0pO+ENK0+/KSw== -"@expo/config@8.5.4": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.5.4.tgz#bb5eb06caa36e4e35dc8c7647fae63e147b830ca" - integrity sha512-ggOLJPHGzJSJHVBC1LzwXwR6qUn8Mw7hkc5zEKRIdhFRuIQ6s2FE4eOvP87LrNfDF7eZGa6tJQYsiHSmZKG+8Q== +"@expo/config-types@^51.0.0-unreleased": + version "51.0.0" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-51.0.0.tgz#f5df238cd1237d7e4d9cc8217cdef3383c2a00cf" + integrity sha512-acn03/u8mQvBhdTQtA7CNhevMltUhbSrpI01FYBJwpVntufkU++ncQujWKlgY/OwIajcfygk1AY4xcNZ5ImkRA== + +"@expo/config@9.0.1", "@expo/config@~9.0.0-beta.0": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-9.0.1.tgz#e7b79de5af29d5ab2a98a62c3cda31f03bd75827" + integrity sha512-0tjaXBstTbXmD4z+UMFBkh2SZFwilizSQhW6DlaTMnPG5ezuw93zSFEWAuEC3YzkpVtNQTmYzxAYjxwh6seOGg== dependencies: "@babel/code-frame" "~7.10.4" - "@expo/config-plugins" "~7.8.2" - "@expo/config-types" "^50.0.0" - "@expo/json-file" "^8.2.37" + "@expo/config-plugins" "~8.0.0-beta.0" + "@expo/config-types" "^51.0.0-unreleased" + "@expo/json-file" "^8.3.0" getenv "^1.0.0" glob "7.1.6" require-from-string "^2.0.2" resolve-from "^5.0.0" - semver "7.5.3" + semver "^7.6.0" slugify "^1.3.4" sucrase "3.34.0" @@ -3254,26 +3282,15 @@ tmp "^0.0.33" tslib "^2.4.0" -"@expo/env@~0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.2.0.tgz#59964a52b92e0582cc8dd0e87bcd8967d019c809" - integrity sha512-NgcU8oxWCL0xEoOmFc6bHmKq0/sYma7AUARgas8X9wbXz4tfH9SmKrRntDdez33Yw2tarBsQ14MjZD2iJLj7xA== +"@expo/env@~0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.3.0.tgz#a66064e5656e0e48197525f47f3398034fdf579e" + integrity sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q== dependencies: chalk "^4.0.0" debug "^4.3.4" - dotenv "~16.0.3" - dotenv-expand "~10.0.0" - getenv "^1.0.0" - -"@expo/env@~0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.2.2.tgz#49f589f32e9bae279a6509d7a02218c0f4e32a60" - integrity sha512-m9nGuaSpzdvMzevQ1H60FWgf4PG5s4J0dfKUzdAGnDu7sMUerY/yUeDaA4+OBo3vBwGVQ+UHcQS9vPSMBNaPcg== - dependencies: - chalk "^4.0.0" - debug "^4.3.4" - dotenv "~16.0.3" - dotenv-expand "~10.0.0" + dotenv "~16.4.5" + dotenv-expand "~11.0.6" getenv "^1.0.0" "@expo/fingerprint@^0.6.0": @@ -3328,6 +3345,22 @@ semver "7.3.2" tempy "0.3.0" +"@expo/image-utils@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.5.1.tgz#06fade141facebcd8431355923d30f3839309942" + integrity sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A== + dependencies: + "@expo/spawn-async" "^1.7.2" + chalk "^4.0.0" + fs-extra "9.0.0" + getenv "^1.0.0" + jimp-compact "0.16.1" + node-fetch "^2.6.0" + parse-png "^2.1.0" + resolve-from "^5.0.0" + semver "^7.6.0" + tempy "0.3.0" + "@expo/json-file@8.2.36": version "8.2.36" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.36.tgz#62a505cb7f30a34d097386476794680a3f7385ff" @@ -3346,6 +3379,15 @@ json5 "^2.2.2" write-file-atomic "^2.3.0" +"@expo/json-file@^8.3.0": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.3.1.tgz#4344a30a0dfb46e8e86a10c5b5b9b3bd1bf6afd9" + integrity sha512-QIMMaqPvm8EGflp041h27OG8DDgh3RxzkEjEEvHJ9AUImgeieMCGrpDsnGOcPI4TR6MpJpLNAk5rZK4szhEwIQ== + dependencies: + "@babel/code-frame" "~7.10.4" + json5 "^2.2.2" + write-file-atomic "^2.3.0" + "@expo/json-file@~8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.3.0.tgz#fc84af77b532a4e9bfb5beafd0e3b7f692b6bd7e" @@ -3355,20 +3397,19 @@ json5 "^2.2.2" write-file-atomic "^2.3.0" -"@expo/metro-config@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.17.6.tgz#f1f4ef056aa357c1dba3841de465f5d319f17216" - integrity sha512-WaC1C+sLX/Wa7irwUigLhng3ckmXIEQefZczB8DfYmleV6uhfWWo2kz/HijFBpV7FKs2cW6u8J/aBQpFkxlcqg== +"@expo/metro-config@0.18.3", "@expo/metro-config@~0.18.0": + version "0.18.3" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.18.3.tgz#fa198b9bf806df44fd7684f1df9af2535c107aa8" + integrity sha512-E4iW+VT/xHPPv+t68dViOsW7egtGIr+sRElcym0iGpC4goLz9WBux/xGzWgxvgvvHEWa21uSZQPM0jWla0OZXg== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.5" "@babel/parser" "^7.20.0" "@babel/types" "^7.20.0" - "@expo/config" "~8.5.0" - "@expo/env" "~0.2.2" + "@expo/config" "~9.0.0-beta.0" + "@expo/env" "~0.3.0" "@expo/json-file" "~8.3.0" "@expo/spawn-async" "^1.7.2" - babel-preset-fbjs "^3.4.0" chalk "^4.1.0" debug "^4.3.2" find-yarn-workspace-root "~2.0.0" @@ -3379,33 +3420,6 @@ lightningcss "~1.19.0" postcss "~8.4.32" resolve-from "^5.0.0" - sucrase "3.34.0" - -"@expo/metro-config@~0.17.0": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.17.1.tgz#8e1cd7b9f63ea84cc18696807cf23560d010e5d8" - integrity sha512-ZOE0Jx0YTZyPpsGiiE09orGEFgZ5sMrOOFSgOe8zrns925g/uCuEbowyNq38IfQt//3xSl5mW3z0l4rxgi7hHQ== - dependencies: - "@babel/core" "^7.20.0" - "@babel/generator" "^7.20.5" - "@babel/parser" "^7.20.0" - "@babel/types" "^7.20.0" - "@expo/config" "~8.5.0" - "@expo/env" "~0.2.0" - "@expo/json-file" "~8.3.0" - "@expo/spawn-async" "^1.7.2" - babel-preset-fbjs "^3.4.0" - chalk "^4.1.0" - debug "^4.3.2" - find-yarn-workspace-root "~2.0.0" - fs-extra "^9.1.0" - getenv "^1.0.0" - glob "^7.2.3" - jsc-safe-url "^0.2.4" - lightningcss "~1.19.0" - postcss "~8.4.21" - resolve-from "^5.0.0" - sucrase "^3.20.0" "@expo/osascript@^2.0.31": version "2.0.33" @@ -3415,13 +3429,13 @@ "@expo/spawn-async" "^1.5.0" exec-async "^2.2.0" -"@expo/package-manager@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.1.2.tgz#e58c9bed4cbb829ebf2cbb80b8542600a6609bd1" - integrity sha512-JI9XzrxB0QVXysyuJ996FPCJGDCYRkbUvgG4QmMTTMFA1T+mv8YzazC3T9C1pHQUAAveVCre1+Pqv0nZXN24Xg== +"@expo/package-manager@^1.5.0": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.5.2.tgz#6015963669977a188bbbac930aa0dc103162ee73" + integrity sha512-IuA9XtGBilce0q8cyxtWINqbzMB1Fia0Yrug/O53HNuRSwQguV/iqjV68bsa4z8mYerePhcFgtvISWLAlNEbUA== dependencies: - "@expo/json-file" "^8.2.37" - "@expo/spawn-async" "^1.5.0" + "@expo/json-file" "^8.3.0" + "@expo/spawn-async" "^1.7.2" ansi-regex "^5.0.0" chalk "^4.0.0" find-up "^5.0.0" @@ -3429,6 +3443,7 @@ js-yaml "^3.13.1" micromatch "^4.0.2" npm-package-arg "^7.0.0" + ora "^3.4.0" split "^1.0.1" sudo-prompt "9.1.1" @@ -3482,6 +3497,23 @@ semver "7.5.3" xml2js "0.6.0" +"@expo/prebuild-config@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-7.0.1.tgz#22c3e876ef69ca475d0b30e1f89874cbe02016a0" + integrity sha512-5q1rzbINPCZzOO1uFR6dhr7NwGOO36gUs1cZO9N7n5McuejwHId0r6TIBNJlHg7vRUJDaeL9C9OTg68N7b809A== + dependencies: + "@expo/config" "~9.0.0-beta.0" + "@expo/config-plugins" "~8.0.0-beta.0" + "@expo/config-types" "^51.0.0-unreleased" + "@expo/image-utils" "^0.5.0" + "@expo/json-file" "^8.3.0" + "@react-native/normalize-colors" "~0.74.81" + debug "^4.3.1" + fs-extra "^9.0.0" + resolve-from "^5.0.0" + semver "^7.6.0" + xml2js "0.6.0" + "@expo/rudder-sdk-node@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz#6aa575f346833eb6290282118766d4919c808c6a" @@ -3572,6 +3604,13 @@ resolved "https://registry.yarnpkg.com/@flatten-js/interval-tree/-/interval-tree-1.1.2.tgz#fcc891da48bc230392884be01c26fe8c625702e8" integrity sha512-OwLoV9E/XM6b7bes2rSFnGNjyRy7vcoIHFTnmBR2WAaZTf0Fe4EX4GdA65vU1KgFAasti7iRSg2dZfYd1Zt00Q== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + "@floating-ui/core@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" @@ -3587,6 +3626,14 @@ "@floating-ui/core" "^1.4.1" "@floating-ui/utils" "^0.1.1" +"@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.1.tgz#7972a4fc488a8c746cded3cfe603b6057c308a91" @@ -3594,11 +3641,23 @@ dependencies: "@floating-ui/dom" "^1.3.0" +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + "@floating-ui/utils@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83" integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@fortawesome/fontawesome-common-types@6.4.2": version "6.4.2" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5" @@ -3708,6 +3767,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" @@ -4470,6 +4541,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" @@ -4836,10 +4912,10 @@ dependencies: "@babel/runtime" "^7.13.10" -"@react-native-async-storage/async-storage@1.21.0": - version "1.21.0" - resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz#d7e370028e228ab84637016ceeb495878b7a44c8" - integrity sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag== +"@react-native-async-storage/async-storage@1.23.1": + version "1.23.1" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883" + integrity sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA== dependencies: merge-options "^3.0.4" @@ -4857,50 +4933,51 @@ dependencies: merge-options "^3.0.4" -"@react-native-community/cli-clean@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-12.3.0.tgz#667b32daa58b4d11d5b5ab9eb0a2e216d500c90b" - integrity sha512-iAgLCOWYRGh9ukr+eVQnhkV/OqN3V2EGd/in33Ggn/Mj4uO6+oUncXFwB+yjlyaUNz6FfjudhIz09yYGSF+9sg== +"@react-native-community/cli-clean@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-13.6.4.tgz#53c07c6f2834a971dc40eab290edcf8ccc5d1e00" + integrity sha512-nS1BJ+2Z+aLmqePxB4AYgJ+C/bgQt02xAgSYtCUv+lneRBGhL2tHRrK8/Iolp0y+yQoUtHHf4txYi90zGXLVfw== dependencies: - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" execa "^5.0.0" + fast-glob "^3.3.2" -"@react-native-community/cli-config@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-12.3.0.tgz#255b4e5391878937a25888f452f50a968d053e3e" - integrity sha512-BrTn5ndFD9uOxO8kxBQ32EpbtOvAsQExGPI7SokdI4Zlve70FziLtTq91LTlTUgMq1InVZn/jJb3VIDk6BTInQ== +"@react-native-community/cli-config@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-13.6.4.tgz#3004c7bca55cb384b3a99c38c1a48dad24533237" + integrity sha512-GGK415WoTx1R9FXtfb/cTnan9JIWwSm+a5UCuFd6+suzS0oIt1Md1vCzjNh6W1CK3b43rZC2e+3ZU7Ljd7YtyQ== dependencies: - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" cosmiconfig "^5.1.0" deepmerge "^4.3.0" - glob "^7.1.3" + fast-glob "^3.3.2" joi "^17.2.1" -"@react-native-community/cli-debugger-ui@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz#75bbb2082a369b3559e0dffa8bfeebf2a9107e3e" - integrity sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ== +"@react-native-community/cli-debugger-ui@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.4.tgz#3881b9cfe14e66b3ee827a84f19ca9d0283fd764" + integrity sha512-9Gs31s6tA1kuEo69ay9qLgM3x2gsN/RI994DCUKnFSW+qSusQJyyrmfllR2mGU3Wl1W09/nYpIg87W9JPf5y4A== dependencies: serve-static "^1.13.1" -"@react-native-community/cli-doctor@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-12.3.0.tgz#420eb4e80d482f16d431c4df33fbc203862508af" - integrity sha512-BPCwNNesoQMkKsxB08Ayy6URgGQ8Kndv6mMhIvJSNdST3J1+x3ehBHXzG9B9Vfi+DrTKRb8lmEl/b/7VkDlPkA== +"@react-native-community/cli-doctor@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-13.6.4.tgz#07e5c2f163807e61ce0ba12901903e591177e3d3" + integrity sha512-lWOXCISH/cHtLvO0cWTr+IPSzA54FewVOw7MoCMEvWusH+1n7c3hXTAve78mLozGQ7iuUufkHFWwKf3dzOkflQ== dependencies: - "@react-native-community/cli-config" "12.3.0" - "@react-native-community/cli-platform-android" "12.3.0" - "@react-native-community/cli-platform-ios" "12.3.0" - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-config" "13.6.4" + "@react-native-community/cli-platform-android" "13.6.4" + "@react-native-community/cli-platform-apple" "13.6.4" + "@react-native-community/cli-platform-ios" "13.6.4" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" command-exists "^1.2.8" deepmerge "^4.3.0" envinfo "^7.10.0" execa "^5.0.0" hermes-profile-transformer "^0.0.6" - ip "^1.1.5" node-stream-zip "^1.9.1" ora "^5.4.1" semver "^7.5.2" @@ -4908,53 +4985,54 @@ wcwidth "^1.0.1" yaml "^2.2.1" -"@react-native-community/cli-hermes@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-12.3.0.tgz#c302acbfb07e1f4e73e76e3150c32f0e4f54e9ed" - integrity sha512-G6FxpeZBO4AimKZwtWR3dpXRqTvsmEqlIkkxgwthdzn3LbVjDVIXKpVYU9PkR5cnT+KuAUxO0WwthrJ6Nmrrlg== +"@react-native-community/cli-hermes@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-13.6.4.tgz#6d3e9b5c251461e9bb35b04110544db8a4f5968f" + integrity sha512-VIAufA/2wTccbMYBT9o+mQs9baOEpTxCiIdWeVdkPWKzIwtKsLpDZJlUqj4r4rI66mwjFyQ60PhwSzEJ2ApFeQ== dependencies: - "@react-native-community/cli-platform-android" "12.3.0" - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-platform-android" "13.6.4" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" hermes-profile-transformer "^0.0.6" - ip "^1.1.5" -"@react-native-community/cli-platform-android@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.0.tgz#eafa5fb12ebc25f716aea18cd55039c19fbedca6" - integrity sha512-VU1NZw63+GLU2TnyQ919bEMThpHQ/oMFju9MCfrd3pyPJz4Sn+vc3NfnTDUVA5Z5yfLijFOkHIHr4vo/C9bjnw== +"@react-native-community/cli-platform-android@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.4.tgz#78ab4c840f4f1f5252ad2fcc5a55f7681ec458cb" + integrity sha512-WhknYwIobKKCqaGCN3BzZEQHTbaZTDiGvcXzevvN867ldfaGdtbH0DVqNunbPoV1RNzeV9qKoQHFdWBkg83tpg== dependencies: - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" execa "^5.0.0" + fast-glob "^3.3.2" fast-xml-parser "^4.2.4" - glob "^7.1.3" logkitty "^0.7.1" -"@react-native-community/cli-platform-ios@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.0.tgz#42a9185bb51f35a7eb9c5818b2f0072846945ef5" - integrity sha512-H95Sgt3wT7L8V75V0syFJDtv4YgqK5zbu69ko4yrXGv8dv2EBi6qZP0VMmkqXDamoPm9/U7tDTdbcf26ctnLfg== +"@react-native-community/cli-platform-apple@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.4.tgz#4912eaf519800a957745192718822b94655c8119" + integrity sha512-TLBiotdIz0veLbmvNQIdUv9fkBx7m34ANGYqr5nH7TFxdmey+Z+omoBqG/HGpvyR7d0AY+kZzzV4k+HkYHM/aQ== dependencies: - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-tools" "13.6.4" chalk "^4.1.2" execa "^5.0.0" + fast-glob "^3.3.2" fast-xml-parser "^4.0.12" - glob "^7.1.3" ora "^5.4.1" -"@react-native-community/cli-plugin-metro@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.0.tgz#b4ea8da691d294aee98ccfcd1162bcd958cae834" - integrity sha512-tYNHIYnNmxrBcsqbE2dAnLMzlKI3Cp1p1xUgTrNaOMsGPDN1epzNfa34n6Nps3iwKElSL7Js91CzYNqgTalucA== - -"@react-native-community/cli-server-api@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz#0460472d44c121d1db8a98ad1df811200c074fb3" - integrity sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw== +"@react-native-community/cli-platform-ios@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.4.tgz#96ec915c6df23b2b7b7e0d8cb3db7368e448d620" + integrity sha512-8Dlva8RY+MY5nhWAj6V7voG3+JOEzDTJmD0FHqL+4p0srvr9v7IEVcxfw5lKBDIUNd0OMAHNevGA+cyz1J60jg== dependencies: - "@react-native-community/cli-debugger-ui" "12.3.0" - "@react-native-community/cli-tools" "12.3.0" + "@react-native-community/cli-platform-apple" "13.6.4" + +"@react-native-community/cli-server-api@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-13.6.4.tgz#6bcec7ae387fc3aeb3e78f62561a91962e6fadf7" + integrity sha512-D2qSuYCFwrrUJUM0SDc9l3lEhU02yjf+9Peri/xhspzAhALnsf6Z/H7BCjddMV42g9/eY33LqiGyN5chr83a+g== + dependencies: + "@react-native-community/cli-debugger-ui" "13.6.4" + "@react-native-community/cli-tools" "13.6.4" compression "^1.7.1" connect "^3.6.5" errorhandler "^1.5.1" @@ -4963,13 +5041,14 @@ serve-static "^1.13.1" ws "^7.5.1" -"@react-native-community/cli-tools@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz#d459a116e1a95034d3c9a6385069c9e2049fb2a6" - integrity sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q== +"@react-native-community/cli-tools@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-13.6.4.tgz#ab396604b6dcf215790807fe89656e779b11f0ec" + integrity sha512-N4oHLLbeTdg8opqJozjClmuTfazo1Mt+oxU7mr7m45VCsFgBqTF70Uwad289TM/3l44PP679NRMAHVYqpIRYtQ== dependencies: appdirsjs "^1.2.4" chalk "^4.1.2" + execa "^5.0.0" find-up "^5.0.0" mime "^2.4.1" node-fetch "^2.6.0" @@ -4979,27 +5058,26 @@ shell-quote "^1.7.3" sudo-prompt "^9.0.0" -"@react-native-community/cli-types@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-12.3.0.tgz#2d21a1f93aefbdb34a04311d68097aef0388704f" - integrity sha512-MgOkmrXH4zsGxhte4YqKL7d+N8ZNEd3w1wo56MZlhu5WabwCJh87wYpU5T8vyfujFLYOFuFK5jjlcbs8F4/WDw== +"@react-native-community/cli-types@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-13.6.4.tgz#e499a3691ee597aa4b93196ff182a4782fae7afb" + integrity sha512-NxGCNs4eYtVC8x0wj0jJ/MZLRy8C+B9l8lY8kShuAcvWTv5JXRqmXjg8uK1aA+xikPh0maq4cc/zLw1roroY/A== dependencies: joi "^17.2.1" -"@react-native-community/cli@12.3.0": - version "12.3.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-12.3.0.tgz#c89aacc3973943bf24002255d7d0859b511d88a1" - integrity sha512-XeQohi2E+S2+MMSz97QcEZ/bWpi8sfKiQg35XuYeJkc32Til2g0b97jRpn0/+fV0BInHoG1CQYWwHA7opMsrHg== +"@react-native-community/cli@13.6.4": + version "13.6.4" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-13.6.4.tgz#dabe2749470a34533e18aada51d97c94b3568307" + integrity sha512-V7rt2N5JY7M4dJFgdNfR164r3hZdR/Z7V54dv85TFQHRbdwF4QrkG+GeagAU54qrkK/OU8OH3AF2+mKuiNWpGA== dependencies: - "@react-native-community/cli-clean" "12.3.0" - "@react-native-community/cli-config" "12.3.0" - "@react-native-community/cli-debugger-ui" "12.3.0" - "@react-native-community/cli-doctor" "12.3.0" - "@react-native-community/cli-hermes" "12.3.0" - "@react-native-community/cli-plugin-metro" "12.3.0" - "@react-native-community/cli-server-api" "12.3.0" - "@react-native-community/cli-tools" "12.3.0" - "@react-native-community/cli-types" "12.3.0" + "@react-native-community/cli-clean" "13.6.4" + "@react-native-community/cli-config" "13.6.4" + "@react-native-community/cli-debugger-ui" "13.6.4" + "@react-native-community/cli-doctor" "13.6.4" + "@react-native-community/cli-hermes" "13.6.4" + "@react-native-community/cli-server-api" "13.6.4" + "@react-native-community/cli-tools" "13.6.4" + "@react-native-community/cli-types" "13.6.4" chalk "^4.1.2" commander "^9.4.1" deepmerge "^4.3.0" @@ -5054,11 +5132,21 @@ resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-1.16.8.tgz#2126ca54d4a5a3e9ea5e3f39ad1e6643f8e4b3d4" integrity sha512-pacdQDX6V6EmjF+HoiIh6u++qx4mTK0WnhgUHRc01B+Qt5eoeUwseBqmqfTSXTx/aHDEd6PiIw7UGvKgFoqgFQ== -"@react-native/assets-registry@0.73.1", "@react-native/assets-registry@~0.73.1": +"@react-native/assets-registry@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.74.80.tgz#03f03f4ca0e1fd3773e0ff81adc7fee11ce05263" + integrity sha512-ttVNY7Am+0OmgYABABzCB+P0a5LlwAHoNK+nRfyFgwyy8gsJ89yr1QNCWusLYDOaHg2RIqNgAcUquOyTS8qDXg== + +"@react-native/assets-registry@~0.73.1": version "0.73.1" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.73.1.tgz#e2a6b73b16c183a270f338dc69c36039b3946e85" integrity sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg== +"@react-native/assets-registry@~0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.74.81.tgz#76b17f8f79b366ec4f18a0f4e99b7cd466aa5aa7" + integrity sha512-ms+D6pJ6l30epm53pwnAislW79LEUHJxWfe1Cu0LWyTTBlg1OFoqXfB3eIbpe4WyH3nrlkQAh0yyk4huT2mCvw== + "@react-native/babel-plugin-codegen@*": version "0.74.0" resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.0.tgz#01ba90840e23c6d1fbf739f75cce1d0f5be97bfa" @@ -5066,22 +5154,30 @@ dependencies: "@react-native/codegen" "*" -"@react-native/babel-plugin-codegen@0.73.2": - version "0.73.2" - resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.2.tgz#447656cde437b71dc3ef0af3f8a5b215653d5d07" - integrity sha512-PadyFZWVaWXIBP7Q5dgEL7eAd7tnsgsLjoHJB1hIRZZuVUg1Zqe3nULwC7RFAqOtr5Qx7KXChkFFcKQ3WnZzGw== +"@react-native/babel-plugin-codegen@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.80.tgz#fb7dc91cb13a39659ea01a5521b91a6efe7180e7" + integrity sha512-uk3Z1osmMihwQlhzGWAvr7+krsK7sT1OkyclR8j1nIyVllhDeJbRmGgFFf+U5huADWOr7T0ZZpQi6fRJ0O/dGg== dependencies: - "@react-native/codegen" "0.73.2" + "@react-native/codegen" "0.74.80" -"@react-native/babel-preset@0.73.19": - version "0.73.19" - resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.73.19.tgz#a6c0587651804f8f01d6f3b7729f1d4a2d469691" - integrity sha512-ujon01uMOREZecIltQxPDmJ6xlVqAUFGI/JCSpeVYdxyXBoBH5dBb0ihj7h6LKH1q1jsnO9z4MxfddtypKkIbg== +"@react-native/babel-plugin-codegen@0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.81.tgz#80484fb9029038694a92193ae2653529e44aab64" + integrity sha512-Bj6g5/xkLMBAdC6665TbD3uCKCQSmLQpGv3gyqya/ydZpv3dDmDXfkGmO4fqTwEMunzu09Sk55st2ipmuXAaAg== + dependencies: + "@react-native/codegen" "0.74.81" + +"@react-native/babel-preset@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.74.80.tgz#4ab3565dc664baa139f12946da9b2f0b861e387a" + integrity sha512-p2n2CGs1DJL+PZTszHuBA1JttosawgYrw4SV2yiR4ChlJUaUaaEUvgcI4Ex5sBZidnNgwgOtVzGLTEgTw8vwEg== dependencies: "@babel/core" "^7.20.0" "@babel/plugin-proposal-async-generator-functions" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.18.0" "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.0" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" "@babel/plugin-proposal-numeric-separator" "^7.0.0" "@babel/plugin-proposal-object-rest-spread" "^7.20.0" @@ -5117,7 +5213,7 @@ "@babel/plugin-transform-typescript" "^7.5.0" "@babel/plugin-transform-unicode-regex" "^7.0.0" "@babel/template" "^7.0.0" - "@react-native/babel-plugin-codegen" "0.73.2" + "@react-native/babel-plugin-codegen" "0.74.80" babel-plugin-transform-flow-enums "^0.0.2" react-refresh "^0.14.0" @@ -5169,7 +5265,56 @@ babel-plugin-transform-flow-enums "^0.0.2" react-refresh "^0.14.0" -"@react-native/codegen@*", "@react-native/codegen@0.73.2": +"@react-native/babel-preset@~0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.74.81.tgz#80d0b96eef35d671f97eaf223c4d770170d7f23f" + integrity sha512-H80B3Y3lBBVC4x9tceTEQq/04lx01gW6ajWCcVbd7sHvGEAxfMFEZUmVZr0451Cafn02wVnDJ8psto1F+0w5lw== + dependencies: + "@babel/core" "^7.20.0" + "@babel/plugin-proposal-async-generator-functions" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.18.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" + "@babel/plugin-proposal-numeric-separator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.20.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.18.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.20.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.20.0" + "@babel/plugin-transform-flow-strip-types" "^7.20.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.5.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + "@react-native/babel-plugin-codegen" "0.74.81" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.14.0" + +"@react-native/codegen@*": version "0.73.2" resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.73.2.tgz#58af4e4c3098f0e6338e88ec64412c014dd51519" integrity sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ== @@ -5182,78 +5327,116 @@ mkdirp "^0.5.1" nullthrows "^1.1.1" -"@react-native/community-cli-plugin@0.73.12": - version "0.73.12" - resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.12.tgz#3a72a8cbae839a0382d1a194a7067d4ffa0da04c" - integrity sha512-xWU06OkC1cX++Duh/cD/Wv+oZ0oSY3yqbtxAqQA2H3Q+MQltNNJM6MqIHt1VOZSabRf/LVlR1JL6U9TXJirkaw== +"@react-native/codegen@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.74.80.tgz#2e144b5904e19156d488a3bbc64c647b13e72908" + integrity sha512-zeIjOCso4iyE02ulWVYAbSgF0cCqUDdrnrZu1D86Y91EyQh/OaZFuM2QbVrQH2lavbr/g9CBpMAVH7/IVsBB5g== dependencies: - "@react-native-community/cli-server-api" "12.3.0" - "@react-native-community/cli-tools" "12.3.0" - "@react-native/dev-middleware" "0.73.7" - "@react-native/metro-babel-transformer" "0.73.13" + "@babel/parser" "^7.20.0" + glob "^7.1.1" + hermes-parser "0.19.1" + invariant "^2.2.4" + jscodeshift "^0.14.0" + mkdirp "^0.5.1" + nullthrows "^1.1.1" + +"@react-native/codegen@0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.74.81.tgz#1025ffd41f2b4710fd700c9e8e85210b9651a7c4" + integrity sha512-hhXo4ccv2lYWaJrZDsdbRTZ5SzSOdyZ0MY6YXwf3xEFLuSunbUMu17Rz5LXemKXlpVx4KEgJ/TDc2pPVaRPZgA== + dependencies: + "@babel/parser" "^7.20.0" + glob "^7.1.1" + hermes-parser "0.19.1" + invariant "^2.2.4" + jscodeshift "^0.14.0" + mkdirp "^0.5.1" + nullthrows "^1.1.1" + +"@react-native/community-cli-plugin@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.80.tgz#10df3ad1976e9fd81bb7d69ab170437ead27779a" + integrity sha512-JKmv08DkWgkrSr4P0jaEjOUke/Bpn5J4U4sMpTEnJXGQPNYbY1Y++7kZuTPDr3JPD4R22MLc6O0DZrAFDyR1mw== + dependencies: + "@react-native-community/cli-server-api" "13.6.4" + "@react-native-community/cli-tools" "13.6.4" + "@react-native/dev-middleware" "0.74.80" + "@react-native/metro-babel-transformer" "0.74.80" chalk "^4.0.0" execa "^5.1.1" metro "^0.80.3" metro-config "^0.80.3" metro-core "^0.80.3" node-fetch "^2.2.0" + querystring "^0.2.1" readline "^1.3.0" -"@react-native/debugger-frontend@0.73.3", "@react-native/debugger-frontend@^0.73.3": - version "0.73.3" - resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz#033757614d2ada994c68a1deae78c1dd2ad33c2b" - integrity sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw== +"@react-native/debugger-frontend@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.74.80.tgz#8439dc88b7ac761c80bd1dea22605ceb27a9b20f" + integrity sha512-uomR3w8aLyQPKZSOA6FRoxqY4+wRSJzmYEH//4C6qTdRl5qocRAhQ6NKG2kmmr+Kz/Iqcuh/lvU5C5RPfKNo6Q== -"@react-native/dev-middleware@0.73.7": - version "0.73.7" - resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz#61d2bf08973d9a537fa3f2a42deeb13530d721ae" - integrity sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg== +"@react-native/debugger-frontend@0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.74.81.tgz#17cefe2b3ff485071bd30d819995867fd145da27" + integrity sha512-HCYF1/88AfixG75558HkNh9wcvGweRaSZGBA71KoZj03umXM8XJy0/ZpacGOml2Fwiqpil72gi6uU+rypcc/vw== + +"@react-native/dev-middleware@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.74.80.tgz#72e045ddf629cddaf8eef5af1d8c97731ba8a214" + integrity sha512-5T0BmJxiTicUQ4wN+7PovdtNF+GYpRYpswLuf7InhgIGgqt5WXWCNSeciAZlRdFLJjuovYbNXb8335E88FJGnQ== dependencies: "@isaacs/ttlcache" "^1.4.1" - "@react-native/debugger-frontend" "0.73.3" + "@react-native/debugger-frontend" "0.74.80" + "@rnx-kit/chromium-edge-launcher" "^1.0.0" chrome-launcher "^0.15.2" - chromium-edge-launcher "^1.0.0" connect "^3.6.5" debug "^2.2.0" node-fetch "^2.2.0" + nullthrows "^1.1.1" open "^7.0.3" + selfsigned "^2.4.1" serve-static "^1.13.1" temp-dir "^2.0.0" + ws "^6.2.2" -"@react-native/dev-middleware@^0.73.6": - version "0.73.6" - resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.73.6.tgz#19ee210fddc3abb8eeb3da5f98711719ad032323" - integrity sha512-9SD7gIso+hO1Jy1Y/Glbd+JWQwyH7Xjnwebtkxdm5TMB51LQPjaGtMcwEigbIZyAtvoaDGmhWmudwbKpDlS+gA== +"@react-native/dev-middleware@~0.74.75": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.74.81.tgz#120ab62982a48cba90c7724d099ddaa50184c200" + integrity sha512-x2IpvUJN1LJE0WmPsSfQIbQaa9xwH+2VDFOUrzuO9cbQap8rNfZpcvVNbrZgrlKbgS4LXbbsj6VSL8b6SnMKMA== dependencies: "@isaacs/ttlcache" "^1.4.1" - "@react-native/debugger-frontend" "^0.73.3" + "@react-native/debugger-frontend" "0.74.81" + "@rnx-kit/chromium-edge-launcher" "^1.0.0" chrome-launcher "^0.15.2" - chromium-edge-launcher "^1.0.0" connect "^3.6.5" debug "^2.2.0" node-fetch "^2.2.0" + nullthrows "^1.1.1" open "^7.0.3" + selfsigned "^2.4.1" serve-static "^1.13.1" temp-dir "^2.0.0" + ws "^6.2.2" -"@react-native/gradle-plugin@0.73.4": - version "0.73.4" - resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz#aa55784a8c2b471aa89934db38c090d331baf23b" - integrity sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg== +"@react-native/gradle-plugin@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.74.80.tgz#3ec5c760a8f7bb2d6c8a711b651ce40eac0f4984" + integrity sha512-dNUm89UkON7FAjcgKj4MeLIHwu8Z+cwfjMblUAgDfBifnUr1GpCAoC5SFIUsKzIqWGMciDbJIHrdjMPYQ++7dQ== -"@react-native/js-polyfills@0.73.1": - version "0.73.1" - resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz#730b0a7aaab947ae6f8e5aa9d995e788977191ed" - integrity sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g== +"@react-native/js-polyfills@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.74.80.tgz#62740e9a8b1e1a952a6333575302952286c533f7" + integrity sha512-BztuMz1Cisz0s+shchrlmbptaXkMGhmDw/lcHbm8eoeCMy1FvqBDbX8GHU9DhLOEez5xtOu/iCLfT2cxlzM8Yw== -"@react-native/metro-babel-transformer@0.73.13": - version "0.73.13" - resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.13.tgz#81cb6dd8d5140c57f5595183fd6857feb8b7f5d7" - integrity sha512-k9AQifogQfgUXPlqQSoMtX2KUhniw4XvJl+nZ4hphCH7qiMDAwuP8OmkJbz5E/N+Ro9OFuLE7ax4GlwxaTsAWg== +"@react-native/metro-babel-transformer@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.80.tgz#4ab09924778ea6d3840fde8354f1d9c77d7a9f54" + integrity sha512-6wPVs97xio6eDaMSxuF3+qtoaYHYulbnoNTk2naVn+N4d/z3hObJrKiHgIE/Ws6vFvBi8xZgPgIjBEBaXxPw5g== dependencies: "@babel/core" "^7.20.0" - "@react-native/babel-preset" "0.73.19" - hermes-parser "0.15.0" + "@react-native/babel-preset" "0.74.80" + hermes-parser "0.19.1" nullthrows "^1.1.1" "@react-native/normalize-color@^2.0.0", "@react-native/normalize-color@^2.1.0": @@ -5261,20 +5444,25 @@ resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91" integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA== -"@react-native/normalize-colors@0.73.2", "@react-native/normalize-colors@^0.73.0": - version "0.73.2" - resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz#cc8e48fbae2bbfff53e12f209369e8d2e4cf34ec" - integrity sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w== +"@react-native/normalize-colors@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.74.80.tgz#5fa872848f4f16d33a5608bb533bf4bf2ff48acb" + integrity sha512-nU44K8hV4uQ+VM2XWfJkgwTm7uu+vWGkkynW9iK7KenTKi/F2I8e1pS8mASlli/hW8Pftzsc+/F9zlJrgd/+Kg== + +"@react-native/normalize-colors@~0.74.81": + version "0.74.81" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.74.81.tgz#0b7c440b6e126f79036cbe74a88791aba72b9fcf" + integrity sha512-g3YvkLO7UsSWiDfYAU+gLhRHtEpUyz732lZB+N8IlLXc5MnfXHC8GKneDGY3Mh52I3gBrs20o37D5viQX9E1CA== "@react-native/typescript-config@^0.74.0": version "0.74.0" resolved "https://registry.yarnpkg.com/@react-native/typescript-config/-/typescript-config-0.74.0.tgz#cb2cb58e4e424593c4ff5859e50d24dd54b14a63" integrity sha512-Nt7AkbuLXIfoWmUrlTp06UTUj6LrMhwJhf/ReEHVpiaVJRjuqfjmwelvW/6dGSJjPFtYvziC+iaLLeyv2oBV7w== -"@react-native/virtualized-lists@0.73.4": - version "0.73.4" - resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz#640e594775806f63685435b5d9c3d05c378ccd8c" - integrity sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog== +"@react-native/virtualized-lists@0.74.80": + version "0.74.80" + resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.74.80.tgz#dace37f77d64a59c5b4a2cfe61dd19f5ccc44dd2" + integrity sha512-hXfTwE27cqS6JkSTUc5b/c4WYxb45J0ScbsRLJ0BeDM7wnOK0/SedBH8B+Gx2nQ8xF5/kfJKSlN54XZhR/y0uA== dependencies: invariant "^2.2.4" nullthrows "^1.1.1" @@ -5370,6 +5558,18 @@ dependencies: type-fest "^2.19.0" +"@rnx-kit/chromium-edge-launcher@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz#c0df8ea00a902c7a417cd9655aab06de398b939c" + integrity sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg== + dependencies: + "@types/node" "^18.0.0" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -7701,11 +7901,25 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + "@types/node@*": version "20.5.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30" integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== +"@types/node@^18.0.0": + version "18.19.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.31.tgz#b7d4a00f7cb826b60a543cebdbda5d189aaecdcd" + integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA== + dependencies: + undici-types "~5.26.4" + "@types/node@^18.16.2": version "18.17.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.6.tgz#0296e9a30b22d2a8fcaa48d3c45afe51474ca55b" @@ -8734,6 +8948,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" @@ -8894,10 +9117,10 @@ babel-plugin-react-native-web@^0.18.12, babel-plugin-react-native-web@~0.18.10: resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.18.12.tgz#3e9764484492ea612a16b40135b07c2d05b7969d" integrity sha512-4djr9G6fMdwQoD6LQ7hOKAm39+y12flWgovAqS1k5O8f42YQ3A1FFMyV5kKfetZuGhZO5BmNmOdRRZQ1TixtDw== -babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: - version "7.0.0-beta.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" - integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ== +babel-plugin-react-native-web@~0.19.10: + version "0.19.11" + resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.11.tgz#32316f51de0053bba6815f6522bf7b17c483bf17" + integrity sha512-0sHf8GgDhsRZxGwlwHHdfL3U8wImFaLw4haEa60U9M3EiO3bg6u3BJ+1vXhwgrevqSq76rMb5j1HJs+dNvMj5g== babel-plugin-transform-flow-enums@^0.0.2: version "0.0.2" @@ -8949,54 +9172,21 @@ babel-preset-expo@^10.0.0: babel-plugin-react-native-web "~0.18.10" react-refresh "0.14.0" -babel-preset-expo@~10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-10.0.1.tgz#a0e7ad0119f46e58cb3f0738c3ca0c6e97b69c11" - integrity sha512-uWIGmLfbP3dS5+8nesxaW6mQs41d4iP7X82ZwRdisB/wAhKQmuJM9Y1jQe4006uNYkw6Phf2TT03ykLVro7KuQ== +babel-preset-expo@~11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-11.0.2.tgz#5c8bbda89519e83a3bb2f3360dbf7757f49b938e" + integrity sha512-Vbe/es+J7roUkOxoyTvqc1eQnyR7bKlF7csflSOttcQuLo0O0bSZW+3rGA2rXkYtc2MBw/3Bax0Yk4acc/JUaQ== dependencies: "@babel/plugin-proposal-decorators" "^7.12.9" "@babel/plugin-transform-export-namespace-from" "^7.22.11" "@babel/plugin-transform-object-rest-spread" "^7.12.13" "@babel/plugin-transform-parameters" "^7.22.15" - "@babel/preset-env" "^7.20.0" "@babel/preset-react" "^7.22.15" - "@react-native/babel-preset" "^0.73.18" - babel-plugin-react-native-web "~0.18.10" + "@babel/preset-typescript" "^7.23.0" + "@react-native/babel-preset" "~0.74.81" + babel-plugin-react-native-web "~0.19.10" react-refresh "0.14.0" -babel-preset-fbjs@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" - integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== - dependencies: - "@babel/plugin-proposal-class-properties" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.0.0" - "@babel/plugin-syntax-class-properties" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-block-scoped-functions" "^7.0.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.0.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/plugin-transform-for-of" "^7.0.0" - "@babel/plugin-transform-function-name" "^7.0.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-member-expression-literals" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-object-super" "^7.0.0" - "@babel/plugin-transform-parameters" "^7.0.0" - "@babel/plugin-transform-property-literals" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-template-literals" "^7.0.0" - babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" - babel-preset-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" @@ -9067,13 +9257,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" @@ -9121,11 +9311,6 @@ bluebird@^3.5.4, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -blueimp-md5@^2.10.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" - integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== - bn.js@^4.0.0, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -9490,7 +9675,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== @@ -9609,18 +9794,6 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -chromium-edge-launcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz#0443083074715a13c669530b35df7bfea33b1509" - integrity sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA== - dependencies: - "@types/node" "*" - escape-string-regexp "^4.0.0" - is-wsl "^2.2.0" - lighthouse-logger "^1.0.0" - mkdirp "^1.0.4" - rimraf "^3.0.2" - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -10120,7 +10293,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== @@ -10586,15 +10759,6 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -deprecated-react-native-prop-types@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz#02a12f090da7bd9e8c3ac53c31cf786a1315d302" - integrity sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ== - dependencies: - "@react-native/normalize-colors" "^0.73.0" - invariant "^2.2.4" - prop-types "^15.8.1" - dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -10852,10 +11016,12 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-expand@~10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" - integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== +dotenv-expand@~11.0.6: + version "11.0.6" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.6.tgz#f2c840fd924d7c77a94eff98f153331d876882d3" + integrity sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g== + dependencies: + dotenv "^16.4.4" dotenv@^10.0.0: version "10.0.0" @@ -10867,10 +11033,10 @@ dotenv@^16.0.3, dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== -dotenv@~16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +dotenv@^16.4.4, dotenv@~16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== dset@^3.1.1, dset@^3.1.2: version "3.1.2" @@ -11740,15 +11906,13 @@ expo-application@~5.8.0: resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.8.0.tgz#b82cb98a08f91d61f047f6578e883e0deb9661f2" integrity sha512-nNQ/ayC4P1ue0ZQSmUlG/K2ZHTPwHyYGsb0QtEmCFUCitsjPKIx4coNvAreZMuELvY7pD1zKr+pdtN/ULnljBA== -expo-asset@~9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-9.0.2.tgz#e8a6b6da356d5fc97955599d2fa49af78c7f0bfd" - integrity sha512-PzYKME1MgUOoUvwtdzhAyXkjXOXGiSYqGKG/MsXwWr0Ef5wlBaBm2DCO9V6KYbng5tBPFu6hTjoRNil1tBOSow== +expo-asset@~10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-10.0.3.tgz#4a493af75a05fcd524ecdf357d5f906245e4adc9" + integrity sha512-J7qJRwj39md+NoLJtZyimVx348MRRQq7KCfMbEZqOA9RQOzhtX2+IY9K3EeF/1sCn1QsWaVJx4e7xFAwVshzUA== dependencies: - "@react-native/assets-registry" "~0.73.1" - blueimp-md5 "^2.10.0" - expo-constants "~15.4.0" - expo-file-system "~16.0.0" + "@react-native/assets-registry" "~0.74.81" + expo-constants "~16.0.0" invariant "^2.2.4" md5-file "^3.2.3" @@ -11801,6 +11965,13 @@ expo-constants@~15.4.5: dependencies: "@expo/config" "~8.5.0" +expo-constants@~16.0.0: + version "16.0.1" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-16.0.1.tgz#1285e29c85513c6e88e118289e2baab72596d3f7" + integrity sha512-s6aTHtglp926EsugWtxN7KnpSsE9FCEjb7CgEjQQ78Gpu4btj4wB+IXot2tlqNwqv+x7xFe5veoPGfJDGF/kVg== + dependencies: + "@expo/config" "~9.0.0-beta.0" + expo-dev-client@~3.3.8: version "3.3.11" resolved "https://registry.yarnpkg.com/expo-dev-client/-/expo-dev-client-3.3.11.tgz#f2541ccbcfc2ba32bcea47293bc9beae4e10db60" @@ -11860,15 +12031,20 @@ expo-file-system@^16.0.8, expo-file-system@~16.0.8: resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.8.tgz#13c79a8e06e42a8e76e9297df6920597a011d989" integrity sha512-yDbVT0TUKd7ewQjaY5THum2VRFx2n/biskGhkUmLh3ai21xjIVtaeIzHXyv9ir537eVgt4ReqDNWi7jcXjdUcA== -expo-file-system@~16.0.0: - version "16.0.1" - resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.1.tgz#326b7c2f6e53e1a0eaafc9769578aafb3f9c9f43" - integrity sha512-/U6ufN2wRPgg4m2a9sqbL3dThqQsysT022qulEXWnUTmNaqnzYSk9ihjDWqoqjXLi9slQLsyok5t6CNzhM7HPw== +expo-file-system@^16.0.9: + version "16.0.9" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.9.tgz#cbd6c4b228b60a6b6c71fd1b91fe57299fb24da7" + integrity sha512-3gRPvKVv7/Y7AdD9eHMIdfg5YbUn2zbwKofjsloTI5sEC57SLUFJtbLvUCz9Pk63DaSQ7WIE1JM0EASyvuPbuw== -expo-font@~11.10.3: - version "11.10.3" - resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-11.10.3.tgz#a3115ebda8e09bd7cb8052619a4bbe606f0c17f4" - integrity sha512-q1Td2zUvmLbCA9GV4OG4nLPw5gJuNY1VrPycsnemN1m8XWTzzs8nyECQQqrcBhgulCgcKZZJJ6U0kC2iuSoQHQ== +expo-file-system@~17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-17.0.1.tgz#b9f8af8c1c06ec71d96fd7a0d2567fa9e1c88f15" + integrity sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw== + +expo-font@~12.0.4: + version "12.0.4" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.4.tgz#c3d9b7fc152286c1e2beae80f97b94ca4622db34" + integrity sha512-VtOQB7MEeFMVwo46/9/ntqzrgraTE7gAsnfi2NukFcCpDmyAU3G1R7m287LUXltE46SmGkMgAvM6+fflXFjaJA== dependencies: fontfaceobserver "^2.1.0" @@ -11908,10 +12084,10 @@ expo-json-utils@~0.12.0: resolved "https://registry.yarnpkg.com/expo-json-utils/-/expo-json-utils-0.12.0.tgz#15ad797e9518a6a47eae9b95599e6373e641f8f2" integrity sha512-xsUsPUZcXZWoT4RY3FhEPYGYvr2iThMNNU5drdmkC/vmkePvqy5kK4aIqlIKzQboXxj7k1dXoNSSLg5mKy8uKg== -expo-keep-awake@~12.8.2: - version "12.8.2" - resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz#6cfdf8ad02b5fa130f99d4a1eb98e459d5b4332e" - integrity sha512-uiQdGbSX24Pt8nGbnmBtrKq6xL/Tm3+DuDRGBk/3ZE/HlizzNosGRIufIMJ/4B4FRw4dw8KU81h2RLuTjbay6g== +expo-keep-awake@~13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-13.0.1.tgz#6c0a0d1fe388fe5a63e2089ade66798eba23f089" + integrity sha512-Kqv8Bf1f5Jp7YMUgTTyKR9GatgHJuAcC8vVWDEkgVhB3O7L3pgBy5MMSMUhkTmRRV6L8TZe/rDmjiBoVS/soFA== expo-linear-gradient@^12.7.2: version "12.7.2" @@ -11946,25 +12122,32 @@ expo-media-library@~15.9.1: resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-15.9.1.tgz#1eaf5a0c8f51669f6f86d385a8fa411226042216" integrity sha512-Y29uKFJ3qWwNejIrjoCppXp3OgIFs/RYHWXkF9xey6evpNrUlHoP1WHG2jYAMSrss6aIRVt3tO7EtYUCZxz50Q== -expo-modules-autolinking@1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.10.3.tgz#19f349884a90f3f27ec9d64e8f2fa6be609558c5" - integrity sha512-pn4n2Dl4iRh/zUeiChjRIe1C7EqOw1qhccr85viQV7W6l5vgRpY0osE51ij5LKg/kJmGRcJfs12+PwbdTplbKw== +expo-modules-autolinking@1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.11.1.tgz#4a867f727d9dfde07de8dde14b333a3cbf82ce3c" + integrity sha512-2dy3lTz76adOl7QUvbreMCrXyzUiF8lygI7iFJLjgIQIVH+43KnFWE5zBumpPbkiaq0f0uaFpN9U0RGQbnKiMw== dependencies: - "@expo/config" "~8.5.0" chalk "^4.1.0" commander "^7.2.0" fast-glob "^3.2.5" find-up "^5.0.0" fs-extra "^9.1.0" -expo-modules-core@1.11.12: - version "1.11.12" - resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.11.12.tgz#d5c7b3ed7ab57d4fb6885a0d8e10287dcf1ffe5f" - integrity sha512-/e8g4kis0pFLer7C0PLyx98AfmztIM6gU9jLkYnB1pU9JAfQf904XEi3bmszO7uoteBQwSL6FLp1m3TePKhDaA== +expo-modules-core@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.3.tgz#7c6731052f8a42536b3f21e4b11c177cfd1754a7" + integrity sha512-dJ3R41XKWaHoSzfq4SW9RzjfZckFSVNADJmRLla58vmwgFseiZ90V8YSUFPCiJJOShggrvtlqTbupcBhUOM+QQ== dependencies: invariant "^2.2.4" +expo-navigation-bar@~2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/expo-navigation-bar/-/expo-navigation-bar-2.8.1.tgz#c4152f878d9fb6ca74c90b80e934af76c29b5377" + integrity sha512-aT5G+7SUsXDVPsRwp8fF940ycka1ABb4g3QKvTZN3YP6kMWvsiYEmRqMIJVy0zUr/i6bxBG1ZergkXimWrFt3w== + dependencies: + "@react-native/normalize-color" "^2.0.0" + debug "^4.3.2" + expo-notifications@~0.27.6: version "0.27.6" resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.27.6.tgz#ef7c95504034ac8b5fa360e13f5b037c5bf7e80d" @@ -12056,24 +12239,24 @@ expo-web-browser@~12.8.2: compare-urls "^2.0.0" url "^0.11.0" -expo@^50.0.8: - version "50.0.14" - resolved "https://registry.yarnpkg.com/expo/-/expo-50.0.14.tgz#ddcae86aa0ba8d1be3da9ad1bdda23fa539dc97d" - integrity sha512-yLPdxCMVAbmeEIpzzyAuJ79wvr6ToDDtQmuLDMAgWtjqP8x3CGddXxUe07PpKEQgzwJabdHvCLP5Bv94wMFIjQ== +expo@^51.0.0-preview.7: + version "51.0.0-preview.7" + resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.0-preview.7.tgz#a30fd86a9fe1f67c03e996447039cd22979bb6e2" + integrity sha512-PHQje+/tnVgzXRgNCxFzGO3IiwvPZdySxLfMD0gBHpOpxgNnStg8L0pvsVRQ5NQDr3QQUpOBQhro5R/Wy7wQkA== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "0.17.8" - "@expo/config" "8.5.4" - "@expo/config-plugins" "7.8.4" - "@expo/metro-config" "0.17.6" + "@expo/cli" "0.18.6" + "@expo/config" "9.0.1" + "@expo/config-plugins" "8.0.3" + "@expo/metro-config" "0.18.3" "@expo/vector-icons" "^14.0.0" - babel-preset-expo "~10.0.1" - expo-asset "~9.0.2" - expo-file-system "~16.0.8" - expo-font "~11.10.3" - expo-keep-awake "~12.8.2" - expo-modules-autolinking "1.10.3" - expo-modules-core "1.11.12" + babel-preset-expo "~11.0.2" + expo-asset "~10.0.3" + expo-file-system "~17.0.1" + expo-font "~12.0.4" + expo-keep-awake "~13.0.1" + expo-modules-autolinking "1.11.1" + expo-modules-core "1.12.3" fbemitter "^3.0.0" whatwg-url-without-unicode "8.0.0-3" @@ -12159,6 +12342,17 @@ fast-glob@^3.2.12, fast-glob@^3.2.5, fast-glob@^3.2.7, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -12481,6 +12675,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" @@ -12493,6 +12692,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" @@ -12699,10 +12906,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" @@ -12765,6 +12972,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" @@ -12986,22 +13204,15 @@ he@1.2.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hermes-estree@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.15.0.tgz#e32f6210ab18c7b705bdcb375f7700f2db15d6ba" - integrity sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ== - hermes-estree@0.18.2: version "0.18.2" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.18.2.tgz#fd450fa1659cf074ceaa2ddeeb21674f3b2342f3" integrity sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ== -hermes-parser@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.15.0.tgz#f611a297c2a2dbbfbce8af8543242254f604c382" - integrity sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q== - dependencies: - hermes-estree "0.15.0" +hermes-estree@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.19.1.tgz#d5924f5fac2bf0532547ae9f506d6db8f3c96392" + integrity sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g== hermes-parser@0.18.2: version "0.18.2" @@ -13010,6 +13221,13 @@ hermes-parser@0.18.2: dependencies: hermes-estree "0.18.2" +hermes-parser@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.19.1.tgz#1044348097165b7c93dc198a80b04ed5130d6b1a" + integrity sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A== + dependencies: + hermes-estree "0.19.1" + hermes-profile-transformer@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz#bd0f5ecceda80dd0ddaae443469ab26fb38fc27b" @@ -13438,11 +13656,6 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== -ip@^1.1.5: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" - integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== - ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -13934,6 +14147,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" @@ -14249,13 +14471,13 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" -jest-expo@^50.0.1: - version "50.0.1" - resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-50.0.1.tgz#4b180b2a06a2cad49c5c74cf8cbbd4387bb30621" - integrity sha512-osvA63UDLJ/v7MG9UHjU7WJ0oZ0Krq9UhXxm2s6rdOlnt85ARocyMU57RC0T0yzPN47C9Ref45sEeOIxoV4Mzg== +jest-expo@^51.0.1: + version "51.0.1" + resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-51.0.1.tgz#d47a9ec67fb3245c9849f686253e36bc16d981c9" + integrity sha512-dnLditgU4RXeQnz5XmyMm1EoOn8k1i7F1DSxA3edjIqK5lydzjfeUHwef7Bs0ToOMIIqm5Bs37i7tsHYpMNLCw== dependencies: - "@expo/config" "~8.5.0" - "@expo/json-file" "^8.2.37" + "@expo/config" "~9.0.0-beta.0" + "@expo/json-file" "^8.3.0" "@jest/create-cache-key-function" "^29.2.1" babel-jest "^29.2.1" find-up "^5.0.0" @@ -15667,6 +15889,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" @@ -15920,16 +16147,17 @@ metro-minify-terser@0.80.4: dependencies: terser "^5.15.0" -metro-react-native-babel-preset@^0.73.7: - version "0.73.10" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz#304b24bb391537d2c987732cc0a9774be227d3f6" - integrity sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ== +metro-react-native-babel-preset@^0.74.1: + version "0.74.1" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.74.1.tgz#81c1c30f13f543c5848049e46e1afbc81df87de6" + integrity sha512-DjsG9nqm5C7cjB2SlgbcNJOn9y5MBUd3bRlCfnoj8CxAeGTGkS+yXd183lHR3C1bhmQNjuUE0abzzpE1CFh6JQ== dependencies: "@babel/core" "^7.20.0" "@babel/plugin-proposal-async-generator-functions" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-export-default-from" "^7.0.0" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-proposal-numeric-separator" "^7.0.0" "@babel/plugin-proposal-object-rest-spread" "^7.0.0" "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" "@babel/plugin-proposal-optional-chaining" "^7.0.0" @@ -15958,7 +16186,6 @@ metro-react-native-babel-preset@^0.73.7: "@babel/plugin-transform-shorthand-properties" "^7.0.0" "@babel/plugin-transform-spread" "^7.0.0" "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-template-literals" "^7.0.0" "@babel/plugin-transform-typescript" "^7.5.0" "@babel/plugin-transform-unicode-regex" "^7.0.0" "@babel/template" "^7.0.0" @@ -16174,6 +16401,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" @@ -16200,7 +16434,7 @@ minipass-pipeline@^1.2.2: dependencies: minipass "^3.0.0" -minipass@3.3.6, minipass@^3.0.0, minipass@^3.1.1: +minipass@^3.0.0, minipass@^3.1.1: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -16212,6 +16446,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" @@ -16308,11 +16547,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" @@ -16811,7 +17045,7 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" -ora@3.4.0: +ora@3.4.0, ora@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== @@ -17105,6 +17339,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" @@ -17904,15 +18146,6 @@ postcss@^8.3.5, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.4: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@~8.4.21: - version "8.4.29" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" - integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - postcss@~8.4.32: version "8.4.38" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" @@ -17949,7 +18182,7 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== -prebuild-install@^7.1.0, prebuild-install@^7.1.1: +prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== @@ -18369,6 +18602,11 @@ query-string@^7.1.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +querystring@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -18496,10 +18734,10 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-devtools-core@^4.27.7: - version "4.28.5" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.28.5.tgz#c8442b91f068cdf0c899c543907f7f27d79c2508" - integrity sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA== +react-devtools-core@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-5.1.0.tgz#3396494ac94b21602cac4fd657d600e0b52f4a0b" + integrity sha512-NRtLBqYVLrIY+lOa2oTpFiAhI7Hru0AUXI0tP9neCyaPPAzlZyeH0i+VZ0shIyRTJbpvyqbD/uCsewA2hpfZHw== dependencies: shell-quote "^1.6.1" ws "^7" @@ -18701,27 +18939,27 @@ react-native-webview@13.6.4: escape-string-regexp "2.0.0" invariant "2.2.4" -react-native@0.73.2: - version "0.73.2" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.2.tgz#74ee163c8189660d41d1da6560411da7ce41a608" - integrity sha512-7zj9tcUYpJUBdOdXY6cM8RcXYWkyql4kMyGZflW99E5EuFPoC7Ti+ZQSl7LP9ZPzGD0vMfslwyDW0I4tPWUCFw== +react-native@0.74.0-rc.9: + version "0.74.0-rc.9" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.74.0-rc.9.tgz#899f0a5bb55cdf889c34b1dc2e5e4453c784d8cd" + integrity sha512-yQ3DvwwdvtzxJnp42Ewz0XCrho3rwLDLsZkIZq/YdQS6g+AqyxU11VjFZEYE0xdpw/eE4XQZ+GAXo7sXaTbHrQ== dependencies: "@jest/create-cache-key-function" "^29.6.3" - "@react-native-community/cli" "12.3.0" - "@react-native-community/cli-platform-android" "12.3.0" - "@react-native-community/cli-platform-ios" "12.3.0" - "@react-native/assets-registry" "0.73.1" - "@react-native/codegen" "0.73.2" - "@react-native/community-cli-plugin" "0.73.12" - "@react-native/gradle-plugin" "0.73.4" - "@react-native/js-polyfills" "0.73.1" - "@react-native/normalize-colors" "0.73.2" - "@react-native/virtualized-lists" "0.73.4" + "@react-native-community/cli" "13.6.4" + "@react-native-community/cli-platform-android" "13.6.4" + "@react-native-community/cli-platform-ios" "13.6.4" + "@react-native/assets-registry" "0.74.80" + "@react-native/codegen" "0.74.80" + "@react-native/community-cli-plugin" "0.74.80" + "@react-native/gradle-plugin" "0.74.80" + "@react-native/js-polyfills" "0.74.80" + "@react-native/normalize-colors" "0.74.80" + "@react-native/virtualized-lists" "0.74.80" abort-controller "^3.0.0" anser "^1.4.9" ansi-regex "^5.0.0" base64-js "^1.5.1" - deprecated-react-native-prop-types "^5.0.0" + chalk "^4.0.0" event-target-shim "^5.0.1" flow-enums-runtime "^0.0.6" invariant "^2.2.4" @@ -18734,7 +18972,7 @@ react-native@0.73.2: nullthrows "^1.1.1" pretty-format "^26.5.2" promise "^8.3.0" - react-devtools-core "^4.27.7" + react-devtools-core "^5.0.0" react-refresh "^0.14.0" react-shallow-renderer "^16.15.0" regenerator-runtime "^0.13.2" @@ -19510,6 +19748,14 @@ selfsigned@^2.1.1: dependencies: node-forge "^1" +selfsigned@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -19544,6 +19790,13 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + semver@~7.3.2: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -19745,6 +19998,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" @@ -20143,7 +20401,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== @@ -20152,7 +20410,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== @@ -20252,6 +20510,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" @@ -20259,13 +20524,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" @@ -20338,6 +20596,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" @@ -21050,6 +21313,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.28.2: version "5.28.2" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.2.tgz#fea200eac65fc7ecaff80a023d1a0543423b4c91" @@ -21945,19 +22213,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" @@ -22227,11 +22495,6 @@ zeego@^1.6.2: "@radix-ui/react-dropdown-menu" "^2.0.1" sf-symbols-typescript "^1.0.0" -zod@3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== - zod@^3.14.2, zod@^3.20.2, zod@^3.21.4: version "3.22.2" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"