diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md index 8734fc0c36..0fecce2ee8 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.md +++ b/.github/ISSUE_TEMPLATE/01_bug-report.md @@ -22,7 +22,10 @@ First, in order to avoid duplicate Issues, please search to see if the problem y ## ๐คฌ Actual Behavior -<!--- Tell us what happens instead of the expected behavior --> +<!-- +Tell us what happens instead of the expected behavior. +Please include errors from the developer console and/or server log files if you have access to them. +--> ## ๐ Steps to Reproduce diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..98f1d2e383 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,12 @@ +'โ๏ธServer': +- packages/backend/**/* + +'๐ฅ๏ธClient': +- packages/client/**/* + +'๐งชTest': +- cypress/**/* +- packages/backend/test/**/* + +'โผ๏ธ wrong locales': +- any: ['locales/*.yml', '!locales/ja-JP.yml'] diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..fa4a58c3a9 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,16 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + branches-ignore: + - 'l10n_develop' + +jobs: + triage: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index da2c73a656..4e42fa9314 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,25 +1,39 @@ -name: Lint - -on: - push: - branches: - - master - - develop - pull_request: - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - cache-dependency-path: | - packages/backend/yarn.lock - packages/client/yarn.lock - - run: yarn install - - run: yarn lint +name: Lint + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + backend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + cache-dependency-path: | + packages/backend/yarn.lock + - run: yarn install + - run: yarn --cwd ./packages/backend lint + + client: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + cache-dependency-path: | + packages/client/yarn.lock + - run: yarn install + - run: yarn --cwd ./packages/client lint diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml new file mode 100644 index 0000000000..87af3a6ba6 --- /dev/null +++ b/.github/workflows/ok-to-test.yml @@ -0,0 +1,36 @@ +# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event +name: Ok To Test + +on: + issue_comment: + types: [created] + +jobs: + ok-to-test: + runs-on: ubuntu-latest + # Only run for PRs, not issue comments + if: ${{ github.event.issue.pull_request }} + steps: + # Generate a GitHub App installation access token from an App ID and private key + # To create a new GitHub App: + # https://developer.github.com/apps/building-github-apps/creating-a-github-app/ + # See app.yml for an example app manifest + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.DEPLOYBOT_APP_ID }} + private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }} + + - name: Slash Command Dispatch + uses: peter-evans/slash-command-dispatch@v1 + env: + TOKEN: ${{ steps.generate_token.outputs.token }} + with: + token: ${{ env.TOKEN }} # GitHub App installation access token + # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work + reaction-token: ${{ secrets.GITHUB_TOKEN }} + issue-type: pull-request + commands: deploy + named-args: true + permission: write diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml new file mode 100644 index 0000000000..fd43bce9e6 --- /dev/null +++ b/.github/workflows/pr-preview-deploy.yml @@ -0,0 +1,95 @@ +# Run secret-dependent integration tests only after /deploy approval +on: + pull_request: + types: [opened, reopened, synchronize] + repository_dispatch: + types: [deploy-command] + +name: Deploy preview environment + +jobs: + # Repo owner has commented /deploy on a (fork-based) pull request + deploy-preview-environment: + runs-on: ubuntu-latest + if: + github.event_name == 'repository_dispatch' && + github.event.client_payload.slash_command.sha != '' && + contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha) + steps: + - uses: actions/github-script@v5 + id: check-id + env: + number: ${{ github.event.client_payload.pull_request.number }} + job: ${{ github.job }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + const { data: pull } = await github.rest.pulls.get({ + ...context.repo, + pull_number: process.env.number + }); + const ref = pull.head.sha; + + const { data: checks } = await github.rest.checks.listForRef({ + ...context.repo, + ref + }); + + const check = checks.check_runs.filter(c => c.name === process.env.job); + + return check[0].id; + + - uses: actions/github-script@v5 + env: + check_id: ${{ steps.check-id.outputs.result }} + details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.checks.update({ + ...context.repo, + check_run_id: process.env.check_id, + status: 'in_progress', + details_url: process.env.details_url + }); + + # Check out merge commit + - name: Fork based /deploy checkout + uses: actions/checkout@v2 + with: + ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge' + + # <insert integration tests needing secrets> + - name: Context + uses: okteto/context@latest + with: + token: ${{ secrets.OKTETO_TOKEN }} + + - name: Deploy preview environment + uses: ikuradon/deploy-preview@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo + timeout: 15m + + # Update check run called "integration-fork" + - uses: actions/github-script@v5 + id: update-check-run + if: ${{ always() }} + env: + # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run + conclusion: ${{ job.status }} + check_id: ${{ steps.check-id.outputs.result }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: result } = await github.rest.checks.update({ + ...context.repo, + check_run_id: process.env.check_id, + status: 'completed', + conclusion: process.env.conclusion + }); + + return result; diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml new file mode 100644 index 0000000000..c14c3db5c5 --- /dev/null +++ b/.github/workflows/pr-preview-destroy.yml @@ -0,0 +1,21 @@ +# file: .github/workflows/preview-closed.yaml +on: + pull_request: + types: + - closed + +name: Destroy preview environment + +jobs: + destroy-preview-environment: + runs-on: ubuntu-latest + steps: + - name: Context + uses: okteto/context@latest + with: + token: ${{ secrets.OKTETO_TOKEN }} + + - name: Destroy preview environment + uses: okteto/destroy-preview@latest + with: + name: pr-${{ github.event.number }}-syuilo diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d57d85c874..c32c82e2a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [18.x] services: postgres: @@ -57,7 +57,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16.x] + node-version: [18.x] browser: [chrome] services: @@ -103,7 +103,7 @@ jobs: - name: ALSA Env run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc - name: Cypress run - uses: cypress-io/github-action@v2 + uses: cypress-io/github-action@v4 with: install: false start: npm run start:test diff --git a/.node-version b/.node-version index bf79505bb8..7fd023741b 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v16.14.0 +v16.15.0 diff --git a/.okteto/okteto-pipeline.yml b/.okteto/okteto-pipeline.yml new file mode 100644 index 0000000000..e2996fbbc9 --- /dev/null +++ b/.okteto/okteto-pipeline.yml @@ -0,0 +1,6 @@ +build: + misskey: + args: + - NODE_ENV=development +deploy: + - helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9adb0d0697..42264548ea 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ "editorconfig.editorconfig", "eg2.vscode-npm-script", "dbaeumer.vscode-eslint", - "johnsoncodehk.volar", + "Vue.volar", + "Vue.vscode-typescript-vue-plugin" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index dff67b40dc..eb16a1675d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## 12.x.x (unreleased) ### Improvements -- ### Bugfixes - @@ -10,11 +9,59 @@ You should also include the user name that made the change. --> +## 12.111.0 (2022/06/11) +### Improvements +- Supports Unicode Emoji 14.0 @mei23 +- ใใใทใฅ้็ฅใ่คๆฐใขใซใฆใณใๅฏพๅฟใซ #7667 @tamaina +- ใใใทใฅ้็ฅใซใฏใชใใฏใactionใ่จญๅฎ #7667 @tamaina +- ใใฉใคใใซ็ปๅใใกใคใซใใขใใใญใผใใใใจใใชใชใธใใซ็ปๅใ็ ดๆฃใใฆwebpublicใฎใฟไฟๆใใใชใใทใงใณ @tamaina +- Server: always remove completed tasks of job queue @Johann150 +- Client: ใขใใฟใผใฎ่จญๅฎใง็ปๅใใฏใญใใใงใใใใใซ @syuilo +- Client: make emoji stand out more on reaction button @Johann150 +- Client: display URL of QR code for TOTP registration @tamaina +- Client: render quote renote CWs as MFM @pixeldesu +- API: notifications/readใฏ้ ๅใงใๅใไปใใใใใซ #7667 @tamaina +- API: ใฆใผใถใผๆค็ดขใงใใฏใจใชใusernameใฎๆกไปถใๆบใใๅ ดๅใฏusernameใLIKEๆค็ดขใใใใใซ @tamaina +- MFM: Allow speed changes in all animated MFMs @Johann150 +- The theme color is now better validated. @Johann150 + Your own theme color may be unset if it was in an invalid format. + Admins should check their instance settings if in doubt. +- Perform port diagnosis at startup only when Listen fails @mei23 +- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23 + Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address. + +### Bugfixes +- Server: keep file order of note attachement @Johann150 +- Server: fix caching @Johann150 +- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150 +- Server: fix internal in-memory caching @Johann150 +- Server: use correct order of attachments on notes @Johann150 +- Server: prevent crash when processing certain PNGs @syuilo +- Server: Fix unable to generate video thumbnails @mei23 +- Server: Fix `Cannot find module` issue @mei23 +- Federation: Add rel attribute to host-meta @mei23 +- Federation: add id for activitypub follows @Johann150 +- Federation: use `source` instead of `_misskey_content` @Johann150 +- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150 +- Federation: correctly render empty note text @Johann150 +- Federation: Fix quote renotes containing no text being federated correctly @Johann150 +- Federation: remove duplicate br tag/newline @Johann150 +- Federation: add missing authorization checks @Johann150 +- Client: fix profile picture height in mentions @tamaina +- Client: fix abuse reports page to be able to show all reports @Johann150 +- Client: fix settings page @tamaina +- Client: fix profile tabs @futchitwo +- Client: fix popout URL @futchitwo +- Client: correctly handle MiAuth URLs with query string @sn0w +- Client: ใใผใ่ฉณ็ดฐใใผใธใฎๆฐใใใใผใใ่กจ็คบใใๆฉ่ฝใฎๅไฝใๆญฃใใใชใใใใซไฟฎๆญฃใใ @xianonn +- MFM: more animated functions support `speed` parameter @futchitwo +- MFM: limit large MFM @Johann150 + ## 12.110.1 (2022/04/23) ### Bugfixes - Fix GOP rendering @syuilo -- Improve performance of antenna, clip, and list @xianon +- Improve performance of antenna, clip, and list @xianonn ## 12.110.0 (2022/04/11) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a696bc5ceb..a37df3bdee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,11 @@ # Contribution guide We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project. -**โน๏ธ Important:** This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.** -Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\ -The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. -It will also allow the reader to use the translation tool of their preference if necessary. +> **Note** +> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.** +> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\ +> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. +> It will also allow the reader to use the translation tool of their preference if necessary. ## Roadmap See [ROADMAP.md](./ROADMAP.md) @@ -16,6 +17,9 @@ Before creating an issue, please check the following: - Issues should only be used to feature requests, suggestions, and bug tracking. - Please ask questions or troubleshooting in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3). +> **Warning** +> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged. + ## Before implementation When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented. @@ -62,6 +66,30 @@ Be willing to comment on the good points and not just the things you want fixed - Are there any omissions or gaps? - Does it check for anomalies? +## Deploy +The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment. +``` +/deploy sha=<commit hash> +``` +An actual domain will be assigned so you can test the federation. + +## Merge +For now, basically only @syuilo has the authority to merge PRs into develop because he is most familiar with the codebase. +However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor. + +## Release +### Release Instructions +1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json)) +2. Create a release PR. + - Into `master` from `develop` branch. + - The title must be in the format `Release: x.y.z`. + - `x.y.z` is the new version you are trying to release. +3. Deploy and perform a simple QA check. Also verify that the tests passed. +4. Merge it. +5. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases) + - The target branch must be `master` + - The tag name must be the version + ## Localization (l10n) Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management. You can improve our translations with your Crowdin account. diff --git a/Dockerfile b/Dockerfile index e4959756e8..33d5faad12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM node:16.14.0-alpine3.15 AS base +FROM node:18.0.0-alpine3.15 AS base -ENV NODE_ENV=production +ARG NODE_ENV=production WORKDIR /misskey @@ -11,16 +11,16 @@ FROM base AS builder COPY . ./ RUN apk add --no-cache $BUILD_DEPS && \ - git submodule update --init && \ - yarn install && \ - yarn build && \ - rm -rf .git + git submodule update --init && \ + yarn install && \ + yarn build && \ + rm -rf .git FROM base AS runner RUN apk add --no-cache \ - ffmpeg \ - tini + ffmpeg \ + tini ENTRYPOINT ["/sbin/tini", "--"] @@ -31,5 +31,6 @@ COPY --from=builder /misskey/packages/backend/built ./packages/backend/built COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node_modules COPY . ./ +ENV NODE_ENV=production CMD ["npm", "run", "migrateandstart"] diff --git a/README.md b/README.md index c7bc9ef219..c273270644 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,29 @@ -[](https://join.misskey.page/) - <div align="center"> - -**๐ A forever evolving, interplanetary microblogging platform. ๐** - -**Misskey** is a distributed microblogging platform with advanced features such as Reactions and a highly customizable UI. - -[Learn more](https://misskey-hub.net/) - +<a href="https://misskey-hub.net"> + <img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="400"/> +</a> + +**๐ **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! ๐** + --- -[โจ Find an instance](https://misskey-hub.net/instances.html) -โข -[๐ฆ Create your own instance](https://misskey-hub.net/docs/install.html) -โข -[๐ ๏ธ Contribute](./CONTRIBUTING.md) -โข -[๐ Join the community](https://discord.gg/Wp8gVStHW3) +<a href="https://misskey-hub.net/instances.html"> + <img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a> +<a href="https://misskey-hub.net/docs/install.html"> + <img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a> + +<a href="./CONTRIBUTING.md"> + <img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a> + +<a href="https://discord.gg/Wp8gVStHW3"> + <img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a> + +<a href="https://www.patreon.com/syuilo"> + <img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a> + --- -<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a> - </div> <div> @@ -30,139 +32,26 @@ ## โจ Features - **ActivityPub support**\ - It is possible to interact with other software. +Not on Misskey? No problem! Not only can Misskey instances talk to each other, but you can make friends with people on other networks like Mastodon and Pixelfed! - **Reactions**\ - You can add "reactions" to each post, making it easy for you to express your feelings. +You can add emoji reactions to any post! No longer are you bound by a like button, show everyone exactly how you feel with the tap of a button. - **Drive**\ - An interface to manage uploaded files such as images, videos, sounds, etc. - You can also organize your favorite content into folders, making it easy to share again. +With Misskey's built in drive, you get cloud storage right in your social media, where you can upload any files, make folders, and find media from posts you've made! - **Rich Web UI**\ - Misskey has a rich WebUI by default. - It is highly customizable by flexibly changing the layout and installing various widgets and themes. - Furthermore, plug-ins can be created using AiScript, a original programming language. -- and more... + Misskey has a rich and easy to use Web UI! + It is highly customizable, from changing the layout and adding widgets to making custom themes. + Furthermore, plugins can be created using AiScript, an original programming language. +- And much more... </div> <div style="clear: both;"></div> +## Documentation + +Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it. + ## Sponsors <div align="center"> <a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a> </div> - -## Backers -<!-- PATREON_START --> -<table><tr> -<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td> -<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td> -<td><img src="https://c8.patreon.com/2/200/27648259" alt="ใฟใชใใพ " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td> -</tr><tr> -<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td> -<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td> -<td><a href="https://www.patreon.com/weepjp">weepjp </a></td> -<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td> -<td><a href="https://www.patreon.com/user?u=27648259">ใฟใชใใพ </a></td> -<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td> -</tr></table> -<table><tr> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/4.jpg?token-time=2145916800&token-hash=BslMqDjTjz8KYANLvxL87agHTugHa0dMPUzT-hwR6Vk%3D" alt="Nesakko" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/776209" alt="Demogrognard" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " width="100"></td> -<td><img src="https://c8.patreon.com/2/200/16869916" alt="่ฆๅฝใใชใฟ " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/36813045/29876ea679d443bcbba3c3f16edab8c2/2.jpeg?token-time=2145916800&token-hash=YCKWnIhrV9rjUCV9KqtJnEqjy_uGYF3WMXftjUdpi7o%3D" alt="Wataru Manji (manji0)" width="100"></td> -</tr><tr> -<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td> -<td><a href="https://www.patreon.com/user?u=776209">Demogrognard</a></td> -<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td> -<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td> -<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td> -<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td> -<td><a href="https://www.patreon.com/osapon">osapon </a></td> -<td><a href="https://www.patreon.com/user?u=16869916">่ฆๅฝใใชใฟ </a></td> -<td><a href="https://www.patreon.com/user?u=36813045">Wataru Manji (manji0)</a></td> -</tr></table> -<table><tr> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61 " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/38837364/9421361c54c645ac8f5fc442a40c32e9/1.png?token-time=2145916800&token-hash=TUZB48Nem3BeUPLBH6s3P6WyKBnQOy0xKaDSTBBUNzA%3D" alt="xianon" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td> -</tr><tr> -<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td> -<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td> -<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td> -<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td> -<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td> -<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> -<td><a href="https://www.patreon.com/user?u=38837364">xianon</a></td> -<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td> -</tr></table> -<table><tr> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26144593/9514b10a5c1b42a3af58621aee213d1d/1.png?token-time=2145916800&token-hash=v1PYRsjzu4c_mndN4Hvi_dlispZJsuGRCQeNS82pUSM%3D" alt="EBISUME" width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td> -</tr><tr> -<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td> -<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td> -<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </a></td> -<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td> -<td><a href="https://www.patreon.com/takimura">takimura </a></td> -<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td> -<td><a href="https://www.patreon.com/user?u=9109588">nafuchoco </a></td> -<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> -<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> -<td><a href="https://www.patreon.com/user?u=26144593">EBISUME</a></td> -<td><a href="https://www.patreon.com/noellabo">noellabo </a></td> -</tr></table> -<table><tr> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/14661394" alt="Chandler " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td> -<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " width="100"></td> -<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td> -</tr><tr> -<td><a href="https://www.patreon.com/Corset">CG </a></td> -<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td> -<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td> -<td><a href="https://www.patreon.com/user?u=14661394">Chandler </a></td> -<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> -<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td> -<td><a href="https://www.patreon.com/efertone">Efertone </a></td> -<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> -</tr></table> - -**Last updated:** Sun, 26 Jul 2020 07:00:10 UTC -<!-- PATREON_END --> - -[backer-url]: #backers -[backer-badge]: https://opencollective.com/misskey/backers/badge.svg -[backers-image]: https://opencollective.com/misskey/backers.svg -[sponsor-url]: #sponsors -[sponsor-badge]: https://opencollective.com/misskey/sponsors/badge.svg -[sponsors-image]: https://opencollective.com/misskey/sponsors.svg -[support-url]: https://opencollective.com/misskey#support - -[syuilo-link]: https://syuilo.com -[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70 diff --git a/assets/title_float.svg b/assets/title_float.svg new file mode 100644 index 0000000000..43205ac1c4 --- /dev/null +++ b/assets/title_float.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + id="svg10" + version="1.1" + viewBox="0 0 162.642 54.261" + height="205.08" + width="614.71"> + <metadata + id="metadata16"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <style> + #g8 { + animation-name: floating; + animation-duration: 3s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + } + + @keyframes floating { + 0% { transform: translate(0, 0px); } + 50% { transform: translate(0, -5px); } + 100% { transform: translate(0, 0px); } + } + </style> + <linearGradient id="myGradient" gradientTransform="rotate(90)"> + <stop offset="5%" stop-color="#A1CA03" /> + <stop offset="95%" stop-color="#91BA03" /> + </linearGradient> + <defs + id="defs14" /> + <g + id="g8" + fill="url('#myGradient')" + word-spacing="0" + letter-spacing="0" + font-family="OTADESIGN Rounded" + font-weight="400"> + <g + id="g4" + style="line-height:476.69509888px;-inkscape-font-specification:'OTADESIGN Rounded'"> + <path + id="path2" + font-size="141.034" + aria-label="Mi" + d="m 27.595,34.59 c -1.676,0.006 -3.115,-1.004 -3.793,-2.179 -0.363,-0.513 -1.08,-0.696 -1.09,0 v 3.214 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.031,1.371 -3.35,1.371 -1.29,0 -2.407,-0.457 -3.349,-1.372 -0.914,-0.941 -1.372,-2.058 -1.372,-3.349 V 17.95 c 0,-0.995 0.283,-1.896 0.848,-2.703 0.591,-0.834 1.345,-1.413 2.26,-1.735 0.516591,-0.189385 1.062793,-0.285215 1.613,-0.283 1.453,0 2.664,0.565 3.632,1.695 l 4.832,5.608 c 0.108,0.08 0.424,0.697 1.18,0.697 0.758,0 1.115,-0.617 1.222,-0.698 l 4.791,-5.607 c 0.996,-1.13 2.22,-1.695 3.673,-1.695 0.538,0 1.076,0.094 1.614,0.283 0.914,0.322 1.654,0.9 2.22,1.735 0.591,0.807 0.887,1.708 0.887,2.703 v 17.675 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.032,1.371 -3.35,1.371 -1.291,0 -2.407,-0.457 -3.35,-1.372 -0.914,-0.941 -1.371,-2.058 -1.371,-3.349 v -3.214 c -0.08,-0.877 -0.855,-0.324 -1.13,0 -0.726,1.345 -2.118,2.173 -3.793,2.18 z M 47.806,21.38 c -1.13,0 -2.098333,-0.39 -2.905,-1.17 -0.78,-0.806667 -1.17,-1.775 -1.17,-2.905 0,-1.13 0.39,-2.085 1.17,-2.865 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 1.13,0 2.098667,0.403333 2.906,1.21 0.806667,0.78 1.21,1.735 1.21,2.865 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.807333,0.78 -1.776,1.17 -2.906,1.17 z m 0.04,0.808 c 1.13,0 2.085333,0.403333 2.866,1.21 0.806667,0.806667 1.21,1.775333 1.21,2.906 v 9.967 c 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.78,0.78 -1.735333,1.17 -2.866,1.17 -1.129333,0 -2.097667,-0.39 -2.905,-1.17 -0.806667,-0.806667 -1.21,-1.775 -1.21,-2.905 v -9.967 c 0,-1.13 0.403333,-2.098667 1.21,-2.906 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 z" + style="font-size:141.03399658px;-inkscape-font-specification:'OTADESIGN Rounded'" /> + </g> + <path + id="path6" + d="M60.925 27.24q.968.243 2.42.525 2.42.403 3.792 1.29 2.582 1.695 2.582 5.083 0 2.743-1.815 4.478-2.098 2.017-5.85 2.017-2.742 0-6.13-.767-1.09-.242-1.776-1.089-.645-.847-.645-1.896 0-1.29.887-2.178.928-.928 2.179-.928.363 0 .685.081 1.17.242 4.478.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.372-.242-2.542-.524-1.33-.282-1.896-.484-1.129-.323-1.895-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.783.121 5.567.726 1.048.242 1.734 1.09.686.846.686 1.936 0 1.25-.928 2.178-.887.887-2.178.887-.323 0-.645-.08-1.17-.242-4.518-.565-.404-.04-.767 0-.323.04-.323.242.04.242.323.323zm17.555 0q.968.243 2.42.525 2.42.403 3.792 1.29 2.581 1.695 2.581 5.083 0 2.743-1.815 4.478-2.098 2.017-5.849 2.017-2.743 0-6.131-.767-1.09-.242-1.775-1.089-.646-.847-.646-1.896 0-1.29.888-2.178.927-.928 2.178-.928.363 0 .686.081 1.17.242 4.477.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.371-.242-2.541-.524-1.331-.282-1.896-.484-1.13-.323-1.896-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.784.121 5.567.726 1.049.242 1.735 1.09.685.846.685 1.936 0 1.25-.927 2.178-.888.887-2.179.887-.322 0-.645-.08-1.17-.242-4.518-.565-.403-.04-.767 0-.322.04-.322.242.04.242.322.323zm26.075 3.335q.12.08 2.864 2.783 1.25 1.21 1.25 2.945 0 1.613-1.17 2.864-1.17 1.21-2.904 1.21-1.654 0-2.864-1.17l-4.034-3.913q-.161-.12-.323-.12-.322 0-.322 1.21 0 1.694-1.21 2.904-1.21 1.17-2.905 1.17-1.694 0-2.904-1.17-1.17-1.21-1.17-2.905V17.586q0-1.694 1.17-2.864 1.21-1.21 2.904-1.21t2.904 1.21q1.21 1.17 1.21 2.864v6.293q0 .403.283.524.242.121.524-.08.162-.081 4.841-3.188 1.049-.645 2.259-.645 2.219 0 3.429 1.815.645 1.05.645 2.26 0 2.218-1.815 3.428l-2.541 1.614v.04l-.081.04q-.565.363-.04.888zm15.599 10.058q-4.195 0-7.18-2.945-2.945-2.985-2.945-7.18 0-4.155 2.945-7.1 2.985-2.985 7.18-2.985 4.155 0 6.979 2.784.928.927.928 2.259 0 1.33-.928 2.259l-4.68 4.639q-1.008 1.008-2.016 1.008-1.453 0-2.26-.807-.806-.807-.806-2.138 0-1.29.928-2.218l.806-.847q.162-.121.081-.243-.12-.08-.323-.04-.806.202-1.371.807-1.13 1.09-1.13 2.622 0 1.573 1.09 2.703 1.13 1.089 2.702 1.089 1.533 0 2.622-1.13.928-.927 2.26-.927 1.33 0 2.258.927.928.928.928 2.26 0 1.33-.928 2.258-2.985 2.945-7.14 2.945zm29.259-15.786v5.607q0 .564-.08 1.21v7.382q0 4.518-2.744 7.22-2.702 2.703-7.301 2.703-2.662 0-4.8-1.008-2.138-.968-2.138-3.348 0-.807.363-1.533.968-2.179 3.348-2.179.565 0 1.573.323 1.009.323 1.654.323 1.694 0 2.219-.726.201-.283.08-.444-.161-.242-.564-.161-.686.12-1.493.12-4.074 0-6.979-2.904-2.904-2.904-2.904-6.978v-5.607q0-1.695 1.17-2.864 1.21-1.21 2.904-1.21t2.905 1.21q1.21 1.17 1.21 2.864v5.607q0 .685.484 1.21.524.484 1.21.484.726 0 1.21-.484.484-.525.484-1.21v-5.607q0-1.695 1.21-2.864 1.21-1.21 2.905-1.21 1.694 0 2.864 1.21 1.21 1.17 1.21 2.864z" + style="line-height:136.34428406px;-inkscape-font-specification:'OTADESIGN Rounded'" /> + </g> +</svg> diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000000..8f31cf7fb4 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: misskey +version: 0.0.0 diff --git a/chart/files/default.yml b/chart/files/default.yml new file mode 100644 index 0000000000..a9ef22f424 --- /dev/null +++ b/chart/files/default.yml @@ -0,0 +1,165 @@ +#โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ +# Misskey configuration +#โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +# โโโโโโโ +#โโโโ URL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +# Final accessible URL seen by a user. +# url: https://example.tld/ + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# URL SETTINGS AFTER THAT! + +# โโโโโโโโโโโโโโโโโโโโโโโโโ +#โโโโ Port and TLS settings โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +# +# Misskey supports two deployment options for public. +# + +# Option 1: With Reverse Proxy +# +# +----- https://example.tld/ ------------+ +# +------+ |+-------------+ +----------------+| +# | User | ---> || Proxy (443) | ---> | Misskey (3000) || +# +------+ |+-------------+ +----------------+| +# +---------------------------------------+ +# +# You need to setup reverse proxy. (eg. nginx) +# You do not define 'https' section. + +# Option 2: Standalone +# +# +- https://example.tld/ -+ +# +------+ | +---------------+ | +# | User | ---> | | Misskey (443) | | +# +------+ | +---------------+ | +# +------------------------+ +# +# You need to run Misskey as root. +# You need to set Certificate in 'https' section. + +# To use option 1, uncomment below line. +port: 3000 # A port that your Misskey server should listen. + +# To use option 2, uncomment below lines. +#port: 443 + +#https: +# # path for certification +# key: /etc/letsencrypt/live/example.tld/privkey.pem +# cert: /etc/letsencrypt/live/example.tld/fullchain.pem + +# โโโโโโโโโโโโโโโโโโโโโโโโโโโโ +#โโโโ PostgreSQL configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +db: + host: localhost + port: 5432 + + # Database name + db: misskey + + # Auth + user: example-misskey-user + pass: example-misskey-pass + + # Whether disable Caching queries + #disableCache: true + + # Extra Connection options + #extra: + # ssl: true + +# โโโโโโโโโโโโโโโโโโโโโโโ +#โโโโ Redis configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +redis: + host: localhost + port: 6379 + #pass: example-pass + #prefix: example-prefix + #db: 1 + +# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ +#โโโโ Elasticsearch configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +#elasticsearch: +# host: localhost +# port: 9200 +# ssl: false +# user: +# pass: + +# โโโโโโโโโโโโโโโโโ +#โโโโ ID generation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +# You can select the ID generation method. +# You don't usually need to change this setting, but you can +# change it according to your preferences. + +# Available methods: +# aid ... Short, Millisecond accuracy +# meid ... Similar to ObjectID, Millisecond accuracy +# ulid ... Millisecond accuracy +# objectid ... This is left for backward compatibility + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# ID SETTINGS AFTER THAT! + +id: "aid" +# โโโโโโโโโโโโโโโโโโโโโโโ +#โโโโ Other configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ + +# Whether disable HSTS +#disableHsts: true + +# Number of worker processes +#clusterLimit: 1 + +# Job concurrency per worker +# deliverJobConcurrency: 128 +# inboxJobConcurrency: 16 + +# Job rate limiter +# deliverJobPerSec: 128 +# inboxJobPerSec: 16 + +# Job attempts +# deliverJobMaxAttempts: 12 +# inboxJobMaxAttempts: 8 + +# IP address family used for outgoing request (ipv4, ipv6 or dual) +#outgoingAddressFamily: ipv4 + +# Syslog option +#syslog: +# host: localhost +# port: 514 + +# Proxy for HTTP/HTTPS +#proxy: http://127.0.0.1:3128 + +#proxyBypassHosts: [ +# 'example.com', +# '192.0.2.8' +#] + +# Proxy for SMTP/SMTPS +#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT +#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 +#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5 + +# Media Proxy +#mediaProxy: https://example.com/proxy + +# Sign to ActivityPub GET request (default: false) +#signToActivityPubGet: true + +#allowedPrivateNetworks: [ +# '127.0.0.1/32' +#] + +# Upload or download file size limits (bytes) +#maxFileSize: 262144000 diff --git a/chart/templates/ConfigMap.yml b/chart/templates/ConfigMap.yml new file mode 100644 index 0000000000..37c25e0864 --- /dev/null +++ b/chart/templates/ConfigMap.yml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "misskey.fullname" . }}-configuration +data: + default.yml: |- + {{ .Files.Get "files/default.yml"|nindent 4 }} + url: {{ .Values.url }} diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml new file mode 100644 index 0000000000..d16aece915 --- /dev/null +++ b/chart/templates/Deployment.yml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "misskey.fullname" . }} + labels: + {{- include "misskey.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "misskey.selectorLabels" . | nindent 6 }} + replicas: 1 + template: + metadata: + labels: + {{- include "misskey.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: misskey + image: {{ .Values.image }} + env: + - name: NODE_ENV + value: {{ .Values.environment }} + volumeMounts: + - name: {{ include "misskey.fullname" . }}-configuration + mountPath: /misskey/.config + readOnly: true + ports: + - containerPort: 3000 + - name: postgres + image: postgres:14-alpine + env: + - name: POSTGRES_USER + value: "example-misskey-user" + - name: POSTGRES_PASSWORD + value: "example-misskey-pass" + - name: POSTGRES_DB + value: "misskey" + ports: + - containerPort: 5432 + - name: redis + image: redis:alpine + ports: + - containerPort: 6379 + volumes: + - name: {{ include "misskey.fullname" . }}-configuration + configMap: + name: {{ include "misskey.fullname" . }}-configuration diff --git a/chart/templates/Service.yml b/chart/templates/Service.yml new file mode 100644 index 0000000000..3209581298 --- /dev/null +++ b/chart/templates/Service.yml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "misskey.fullname" . }} + annotations: + dev.okteto.com/auto-ingress: "true" +spec: + type: ClusterIP + ports: + - port: 3000 + protocol: TCP + name: http + selector: + {{- include "misskey.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000000..a5a2499f3f --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "misskey.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "misskey.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "misskey.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "misskey.labels" -}} +helm.sh/chart: {{ include "misskey.chart" . }} +{{ include "misskey.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "misskey.selectorLabels" -}} +app.kubernetes.io/name: {{ include "misskey.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "misskey.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "misskey.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/chart/values.yml b/chart/values.yml new file mode 100644 index 0000000000..a7031538a9 --- /dev/null +++ b/chart/values.yml @@ -0,0 +1,3 @@ +url: https://example.tld/ +image: okteto.dev/misskey +environment: production diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000000..e390c41a54 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.js')(on, config) + }, + baseUrl: 'http://localhost:61812', + }, +}) diff --git a/cypress.json b/cypress.json deleted file mode 100644 index e858e480b0..0000000000 --- a/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "baseUrl": "http://localhost:61812" -} diff --git a/cypress/integration/basic.js b/cypress/e2e/basic.cy.js similarity index 75% rename from cypress/integration/basic.js rename to cypress/e2e/basic.cy.js index 7d27b649f4..eb5195c4b2 100644 --- a/cypress/integration/basic.js +++ b/cypress/e2e/basic.cy.js @@ -1,8 +1,6 @@ describe('Before setup instance', () => { beforeEach(() => { - cy.request('POST', '/api/reset-db').as('reset'); - cy.get('@reset').its('status').should('equal', 204); - cy.reload(true); + cy.resetState(); }); afterEach(() => { @@ -32,15 +30,10 @@ describe('Before setup instance', () => { describe('After setup instance', () => { beforeEach(() => { - cy.request('POST', '/api/reset-db').as('reset'); - cy.get('@reset').its('status').should('equal', 204); - cy.reload(true); + cy.resetState(); // ใคใณในใฟใณในๅๆใปใใใขใใ - cy.request('POST', '/api/admin/accounts/create', { - username: 'admin', - password: 'pass', - }).its('body').as('admin'); + cy.registerUser('admin', 'pass', true); }); afterEach(() => { @@ -70,21 +63,13 @@ describe('After setup instance', () => { describe('After user signup', () => { beforeEach(() => { - cy.request('POST', '/api/reset-db').as('reset'); - cy.get('@reset').its('status').should('equal', 204); - cy.reload(true); + cy.resetState(); // ใคใณในใฟใณในๅๆใปใใใขใใ - cy.request('POST', '/api/admin/accounts/create', { - username: 'admin', - password: 'pass', - }).its('body').as('admin'); + cy.registerUser('admin', 'pass', true); // ใฆใผใถใผไฝๆ - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); + cy.registerUser('alice', 'alice1234'); }); afterEach(() => { @@ -129,31 +114,15 @@ describe('After user signup', () => { describe('After user singed in', () => { beforeEach(() => { - cy.request('POST', '/api/reset-db').as('reset'); - cy.get('@reset').its('status').should('equal', 204); - cy.reload(true); + cy.resetState(); // ใคใณในใฟใณในๅๆใปใใใขใใ - cy.request('POST', '/api/admin/accounts/create', { - username: 'admin', - password: 'pass', - }).its('body').as('admin'); + cy.registerUser('admin', 'pass', true); // ใฆใผใถใผไฝๆ - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); + cy.registerUser('alice', 'alice1234'); - cy.visit('/'); - - cy.intercept('POST', '/api/signin').as('signin'); - - cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type('alice'); - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - - cy.wait('@signin').as('signedIn'); + cy.login('alice', 'alice1234'); }); afterEach(() => { @@ -163,12 +132,10 @@ describe('After user singed in', () => { }); it('successfully loads', () => { - cy.visit('/'); + cy.get('[data-cy-open-post-form]').should('be.visible'); }); it('note', () => { - cy.visit('/'); - cy.get('[data-cy-open-post-form]').click(); cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); cy.get('[data-cy-open-post-form-submit]').click(); diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js new file mode 100644 index 0000000000..56ad95ee94 --- /dev/null +++ b/cypress/e2e/widgets.cy.js @@ -0,0 +1,65 @@ +describe('After user signed in', () => { + beforeEach(() => { + cy.resetState(); + cy.viewport('macbook-16'); + + // ใคใณในใฟใณในๅๆใปใใใขใใ + cy.registerUser('admin', 'pass', true); + + // ใฆใผใถใผไฝๆ + cy.registerUser('alice', 'alice1234'); + + cy.login('alice', 'alice1234'); + }); + + afterEach(() => { + // ใในใ็ตไบ็ดๅใซใใผใธ้ท็งปใใใใใชใในใใฑใผใน(ไพใใฐใขใซใฆใณใไฝๆ)ใ ใจใใใถใCypressใฎใใฐใงใใฉใฆใถใฎๅ ๅฎนใๆฌกใฎใในใใฑใผในใซๅผใ็ถใใใฆใใพใ(ไพใใฐใขใซใฆใณใใไฝๆใ็ตใใฃใๆฎต้ใใใในใใๅงใพใ)ใ + // waitใๅ ฅใใใใจใงใใใ้ฒๆญขใงใใ + cy.wait(1000); + }); + + it('widget edit toggle is visible', () => { + cy.get('.mk-widget-edit').should('be.visible'); + }); + + it('widget select should be visible in edit mode', () => { + cy.get('.mk-widget-edit').click(); + cy.get('.mk-widget-select').should('be.visible'); + }); + + it('first widget should be removed', () => { + cy.get('.mk-widget-edit').click(); + cy.get('.customize-container:first-child .remove._button').click(); + cy.get('.customize-container').should('have.length', 2); + }); + + function buildWidgetTest(widgetName) { + it(`${widgetName} widget should get added`, () => { + cy.get('.mk-widget-edit').click(); + cy.get('.mk-widget-select select').select(widgetName, { force: true }); + cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true }); + cy.get('.mk-widget-add').click({ force: true }); + cy.get(`.mkw-${widgetName}`).should('exist'); + }); + } + + buildWidgetTest('memo'); + buildWidgetTest('notifications'); + buildWidgetTest('timeline'); + buildWidgetTest('calendar'); + buildWidgetTest('rss'); + buildWidgetTest('trends'); + buildWidgetTest('clock'); + buildWidgetTest('activity'); + buildWidgetTest('photos'); + buildWidgetTest('digitalClock'); + buildWidgetTest('federation'); + buildWidgetTest('postForm'); + buildWidgetTest('slideshow'); + buildWidgetTest('serverMetric'); + buildWidgetTest('onlineUsers'); + buildWidgetTest('jobQueue'); + buildWidgetTest('button'); + buildWidgetTest('aiscript'); + buildWidgetTest('aichan'); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 119ab03f7c..95bfcf6855 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,3 +23,33 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +Cypress.Commands.add('resetState', () => { + cy.window(win => { + win.indexedDB.deleteDatabase('keyval-store'); + }); + cy.request('POST', '/api/reset-db').as('reset'); + cy.get('@reset').its('status').should('equal', 204); + cy.reload(true); +}); + +Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => { + const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup'; + + cy.request('POST', route, { + username: username, + password: password, + }).its('body').as(username); +}); + +Cypress.Commands.add('login', (username, password) => { + cy.visit('/'); + + cy.intercept('POST', '/api/signin').as('signin'); + + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type(username); + cy.get('[data-cy-signin-password] input').type(`${password}{enter}`); + + cy.wait('@signin').as('signedIn'); +}); diff --git a/cypress/support/index.js b/cypress/support/e2e.js similarity index 100% rename from cypress/support/index.js rename to cypress/support/e2e.js diff --git a/docker-compose.yml b/docker-compose.yml index e1d51668a7..0bf17a5557 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - redis # - es ports: - - "127.0.0.1:3000:3000" + - "3000:3000" networks: - internal_network - external_network diff --git a/gulpfile.js b/gulpfile.js index b7aa4e328e..90f8ebaabe 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -37,7 +37,6 @@ gulp.task('copy:client:locales', cb => { gulp.task('build:backend:script', () => { return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js']) - .pipe(replace('VERSION', JSON.stringify(meta.version))) .pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) .pipe(terser({ toplevel: true diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index f3f8b45777..3bd8f1e506 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -141,7 +141,7 @@ flagAsBotDescription: "ูุนูู ูุฐุง ุงูุฎูุงุฑ ุฅุฐุง ูุงู ูุฐุง ุงูุญ flagAsCat: "ุนููู ูุฐุง ุงูุญุณุงุจ ูุญุณุงุจ ูุท" flagAsCatDescription: "ูุนูู ูุฐุง ุงูุฎูุงุฑ ููุถุน ุนูุงู ุฉ ุนูู ุงูุญุณุงุจ ูุชูุถูุญ ุฃูู ุญุณุงุจ ูุท." flagShowTimelineReplies: "ุฃุธูุฑ ุงูุชุนูููุงุช ูู ุงูุฎูุท ุงูุฒู ูู" -flagShowTimelineRepliesDescription: "ูุธูุฑ ุงูุฑุฏูุฏ ูู ุงูุฎุท ุงูุฒู ูู" +flagShowTimelineRepliesDescription: "ูุธูุฑ ุงูุฑุฏูุฏ ูู ุงูุฎูุท ุงูุฒู ูู" autoAcceptFollowed: "ุงูุจู ุทูุจุงุช ุงูู ุชุงุจุนุฉ ุชููุงุฆูุง ู ู ุงูุญุณุงุจุงุช ุงูู ุชุงุจูุนุฉ" addAccount: "ุฃุถู ุญุณุงุจุงู" loginFailed: "ูุดู ุงููููุฌ" @@ -312,12 +312,12 @@ dayX: "{day}" monthX: "{month}" yearX: "{year}" pages: "ุงูุตูุญุงุช" -integration: "ุฏู ุฌ" +integration: "ุงูุชูุงู ู" connectService: "ุงุชุตู" disconnectService: "ุงูุทุน ุงูุงุชุตุงู" enableLocalTimeline: "ุชูุนูู ุงูุฎูุท ุงูู ุญูู" enableGlobalTimeline: "ุชูุนูู ุงูุฎูุท ุงูุฒู ูู ุงูุดุงู ู" -disablingTimelinesInfo: "ุณูุชู ูู ุงูู ุฏูุฑูู ูุงูู ุดุฑููู ู ู ุงููุตูู ุฅูู ูู ุงูุฎุทูุท ุงูุฒู ููุฉ ุญุชู ูุฅู ูู ุชูุนูู." +disablingTimelinesInfo: "ุณูุชู ูู ุงูู ุฏูุฑูู ูุงูู ุดุฑููู ู ู ุงููุตูู ุฅูู ูู ุงูุฎููุท ุงูุฒู ููุฉ ุญุชู ูุฅู ูู ุชูุนูู." registration: "ุฅูุดุงุก ุญุณุงุจ" enableRegistration: "ุชูุนูู ุฅูุดุงุก ุงูุญุณุงุจุงุช ุงูุฌุฏูุฏุฉ" invite: "ุฏุนูุฉ" @@ -532,6 +532,7 @@ poll: "ุงุณุชุทูุงุน ุฑุฃู" useCw: "ุฅุฎูุงุก ุงูู ุญุชูู" enablePlayer: "ุงูุชุญ ู ุดุบู ุงูููุฏูู" disablePlayer: "ุฃุบูู ู ุดุบู ุงูููุฏูู" +expandTweet: "ูุณูุน ุงูุชุบุฑูุฏุฉ" themeEditor: "ู ุตู ู ุงูููุงูุจ" description: "ุงููุตู" describeFile: "ุฃุถู ุชุนููููุง ุชูุถูุญููุง" @@ -635,6 +636,7 @@ yes: "ูุนู " no: "ูุง" driveFilesCount: "ุนุฏุฏ ุงูู ููุงุช ูู ูุฑุต ุงูุชุฎุฒูู" driveUsage: "ุงูู ุณุชุบู ู ู ูุฑุต ุงูุชุฎุฒูู" +noCrawle: "ุงุฑูุถ ููุฑุณุฉ ุฒุงุญู ุงูููุจ" noCrawleDescription: "ูุทูุจ ู ู ู ุญุฑูุงุช ุงูุจุญุซ ุฃููุง ููููุฑุณูุง ู ููู ุงูุดุฎุตู ูู ูุงุญุธุงุช ูุตูุญุงุชู ูู ุง ุดุงุจู." alwaysMarkSensitive: "ุนููู ุงูุชุฑุงุถููุง ุฌู ูุน ู ูุงุญุธุงุชู ูุฐุงุช ู ุญุชูู ุญุณุงุณ" loadRawImages: "ุญู ูู ุงูุตูุฑ ุงูุฃุตููุฉ ุจุฏููุง ู ู ุงูู ุตุบุฑุงุช" @@ -878,9 +880,11 @@ _mfm: center: "ูุณุท" centerDescription: "ูู ุฑูุฒ ุงูู ุญุชูู ูู ุงูููุณูุท." quote: "ุงูุชุจุณ" + quoteDescription: "ูุนุฑุถ ุงูู ุญุชูู ูุงูุชุจุงุณ" emoji: "ุฅูู ูุฌู ู ุฎุตุต" emojiDescription: "ุฅุญุงุทุฉ ุงุณู ุงูุฅูู ูุฌู ุจููุทุชู ุชูุณูุฑ ุณูุณุชุจุฏูู ุจุตูุฑุฉ ุงูุฅูู ูุฌู." search: "ุงูุจุญุซ" + searchDescription: "ูุนุฑุถ ูุตูุง ูู ุตูุฏูู ุงูุจุญุซ" flip: "ุงููุจ" flipDescription: "ูููุจ ุงูู ุญุชูู ุนู ูุฏููุง ุฃู ุฃููููุง" jelly: "ุชุฃุซูุฑ (ููุงู )" @@ -1003,7 +1007,6 @@ _sfx: antenna: "ุงูููุงุฆูุงุช" channel: "ุฅุดุนุงุฑุงุช ุงูููุงุช" _ago: - unknown: "ู ุฌููู" future: "ุงูู ุณุชูุจูู" justNow: "ุงููุญุธุฉ" secondsAgo: "ู ูุฐ {n} ุซูุงูู" @@ -1030,12 +1033,12 @@ _tutorial: step3_3: "ุงู ูุฃ ุงููู ูุฐุฌ ูุงููุฑ ุงูุฒุฑู ุงูู ูุฌูุฏ ูู ุฃุนูู ุงููู ูู ููุฅุฑุณุงู." step3_4: "ููุณ ูุฏูู ู ุง ุชููููุ ุฅุฐุง ุงูุชุจ \"ุจุฏุฃุชู ุงุณุชุฎุฏู ู ูุณูู\"." step4_1: "ูู ูุดุฑุช ู ูุงุญุธุชู ุงูุฃูููุ" - step4_2: "ู ุฑุญู! ูู ููู ุงูุขู ุฑุคูุฉ ู ูุงุญุธุชู ูู ุงูุฎุท ุงูุฒู ูู." - step5_1: "ูุงูุขูุ ููุฌุนู ุงูุฎุท ุงูุฒู ูู ุฃูุซุฑ ุญูููุฉ ูุฐูู ุจู ุชุงุจุนุฉ ุจุนุถ ุงูู ุณุชุฎุฏู ูู." + step4_2: "ู ุฑุญู! ูู ููู ุงูุขู ุฑุคูุฉ ู ูุงุญุธุชู ูู ุงูุฎูุท ุงูุฒู ูู." + step5_1: "ูุงูุขูุ ููุฌุนู ุงูุฎูุท ุงูุฒู ูู ุฃูุซุฑ ุญูููุฉ ูุฐูู ุจู ุชุงุจุนุฉ ุจุนุถ ุงูู ุณุชุฎุฏู ูู." step5_2: "ุชุนุฑุถ ุตูุญุฉ {features} ุงูู ูุงุญุธุงุช ุงูู ุชุฏุงููุฉ ูู ูุฐุง ุงูู ุซูู ููุชูุญ ูู {Explore} ุงูุนุซูุฑ ุนูู ุงูู ุณุชุฎุฏู ูู ุงูุฑุงุฆุฏูู. ุงุนุซุฑ ุนูู ุงูุฃุดุฎุงุต ุงูุฐูู ูุซูุฑูู ุฅูุชู ุงู ู ูุชุงุจุนูู !" step5_3: "ูู ุชุงุจุนุฉ ู ุณุชุฎุฏู ูู ุงุฏุฎู ู ูููู ุงูุดุฎุตู ุจุงูููุฑ ุนูู ุตูุฑุชูู ุงูุดุฎุตูุฉ ุซู ุงุถุบุท ุฒุฑ 'ุชุงุจุน'." step5_4: "ุฅุฐุง ูุงู ูุฏู ุงูู ุณุชุฎุฏู ุฑู ุฒ ููู ุจุฌูุงุฑ ุงุณู ู ุ ูุฌุจ ุนููู ุงูุชุธุงุฑู ูููุจู ุทูุจ ุงูู ุชุงุจุนุฉ ูุฏูููุง." - step6_1: "ุงูุขู ุณุชุชู ูู ู ู ุฑุคูุฉ ู ูุงุญุธุงุช ุงูู ุณุชุฎุฏู ูู ุงูู ุชุงุจูุนูู ูู ุงูุฎุท ุงูุฒู ูู." + step6_1: "ุงูุขู ุณุชุชู ูู ู ู ุฑุคูุฉ ู ูุงุญุธุงุช ุงูู ุณุชุฎุฏู ูู ุงูู ุชุงุจูุนูู ูู ุงูุฎูุท ุงูุฒู ูู." step6_2: "ูู ููู ุงูุชูุงุนู ุจุณุฑุนุฉ ู ุน ุงูู ูุงุญุธุงุช ุนู ุทุฑูู ุฅุถุงูุฉ \"ุชูุงุนู\"." step6_3: "ูุฅุถุงูุฉ ุชูุงุนู ูู ูุงุญุธุฉ ุ ุงููุฑ ููู ุนูุงู ุฉ \"+\" ุฃุณูู ููู ูุงุญุธุฉ ูุงุฎุชุฑ ุงูุฅูู ูุฌู ุงูู ุทููุจ." step7_1: "ู ุจุงุฑู ! ุฃูููุช ุงูุฏูุฑุฉ ุงูุชุนููู ูุฉ ุงูุฃุณุงุณูุฉ ูุงุณุชุฎุฏุงู ู ูุณูู." @@ -1201,8 +1204,13 @@ _charts: _instanceCharts: requests: "ุงูุทูุจุงุช" users: "ุชุจุงูู ุนุฏุฏ ุงูู ุณุชุฎุฏู ูู" + usersTotal: "ุชุจุงูู ุนุฏุฏ ุงูู ุณุชุฎุฏู ูู" notes: "ุชุจุงูู ุนุฏุฏ ุงูู ูุงุญุธุงุช" + notesTotal: "ุชุจุงูู ุนุฏุฏ ุงูู ูุงุญุธุงุช" + ff: "ุชุจุงูู ุนุฏุฏ ุญุณุงุจุงุช ุงูู ุชุงุจูุนุฉ/ุงูู ุชุงุจูุนุฉ" + ffTotal: "ุชุจุงูู ุนุฏุฏ ุญุณุงุจุงุช ุงูู ุชุงุจูุนุฉ/ุงูู ุชุงุจูุนุฉ" files: "ุชุจุงูู ุนุฏุฏ ุงูู ููุงุช" + filesTotal: "ุชุจุงูู ุนุฏุฏ ุงูู ููุงุช" _timelines: home: "ุงูุฑุฆูุณู" local: "ุงูู ุญูู" @@ -1321,6 +1329,7 @@ _pages: random: "ุนุดูุงุฆู" value: "ุงูููู " fn: "ุฏูุงู" + text: "ุฅุฌุฑุงุกุงุช ุนูู ุงููุตูุต" convert: "ุชุญููู" list: "ุงูููุงุฆู " blocks: @@ -1501,6 +1510,10 @@ _notification: followRequestAccepted: "ุทูุจุงุช ุงูู ุชุงุจุนุฉ ุงูู ูุจููุฉ" groupInvited: "ุฏุนูุงุช ุงููุฑูู" app: "ุฅุดุนุงุฑุงุช ุงูุชุทุจููุงุช ุงูู ุฑุชุจุทุฉ" + _actions: + followBack: "ุชุงุจุนู ุจุงูู ุซู" + reply: "ุฑุฏ" + renote: "ุฃุนุฏ ุงููุดุฑ" _deck: alwaysShowMainColumn: "ุฃุธูุฑ ุงูุนู ูุฏ ุงูุฑุฆูุณู ุฏุงุฆู ูุง" columnAlign: "ุญุงุฐู ุงูุฃุนู ุฏุฉ" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index b2ba236fd5..d7753b6dcf 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -831,11 +831,18 @@ themeColor: "เฆฅเฆฟเฆฎเงเฆฐ เฆฐเฆ" size: "เฆเฆเฆพเฆฐ" numberOfColumn: "เฆเฆฒเฆพเฆฎเงเฆฐ เฆธเฆเฆเงเฆฏเฆพ" searchByGoogle: "เฆเงเฆเฆฒ" +instanceDefaultLightTheme: "เฆเฆจเงเฆธเฆเงเฆฏเฆพเฆจเงเฆธเงเฆฐ เฆกเฆฟเฆซเฆฒเงเฆ เฆฒเฆพเฆเฆ เฆฅเฆฟเฆฎ" +instanceDefaultDarkTheme: "เฆเฆจเงเฆธเฆเงเฆฏเฆพเฆจเงเฆธเงเฆฐ เฆกเฆฟเฆซเฆฒเงเฆ เฆกเฆพเฆฐเงเฆ เฆฅเฆฟเฆฎ" +instanceDefaultThemeDescription: "เฆ เฆฌเฆเงเฆเงเฆ เฆซเฆฐเฆฎเงเฆฏเฆพเฆเง เฆฅเฆฟเฆฎ เฆเงเฆก เฆฒเฆฟเฆเงเฆจ" +mutePeriod: "เฆฎเฆฟเฆเฆเงเฆฐ เฆธเฆฎเงเฆเฆพเฆฒ" indefinitely: "เฆ เฆจเฆฟเฆฐเงเฆฆเฆฟเฆทเงเฆ" tenMinutes: "เงงเงฆ เฆฎเฆฟเฆจเฆฟเฆ" oneHour: "เงง เฆเฆฃเงเฆเฆพ" oneDay: "เฆเฆเฆฆเฆฟเฆจ" oneWeek: "เฆเฆ เฆธเฆชเงเฆคเฆพเฆน" +reflectMayTakeTime: "เฆเฆเฆฟเฆฐ เฆเฆพเฆ เฆฆเงเฆเฆพ เฆฏเงเฆคเง เฆเฆฟเฆเงเฆเฆพ เฆธเฆฎเฆฏเฆผ เฆฒเฆพเฆเฆคเง เฆชเฆพเฆฐเงเฅค" +failedToFetchAccountInformation: "เฆ เงเฆฏเฆพเฆเฆพเฆเฆจเงเฆเงเฆฐ เฆคเฆฅเงเฆฏ เฆเฆฆเงเฆงเฆพเฆฐ เฆเฆฐเฆพ เฆฏเฆพเงเฆจเฆฟ" +rateLimitExceeded: "เฆฐเงเฆ เฆฒเฆฟเฆฎเฆฟเฆ เฆเฆพเงเฆฟเงเง เฆเงเฆเง " _emailUnavailable: used: "เฆเฆ เฆเฆฎเงเฆเฆฒ เฆ เฆฟเฆเฆพเฆจเฆพเฆเฆฟ เฆเฆคเงเฆฎเฆงเงเฆฏเง เฆฌเงเฆฏเฆฌเฆนเงเฆค เฆนเงเงเฆเง" format: "เฆเฆ เฆเฆฎเงเฆฒ เฆ เฆฟเฆเฆพเฆจเฆพเฆเฆฟ เฆธเฆ เฆฟเฆเฆญเฆพเฆฌเง เฆฒเฆฟเฆเฆพ เฆนเงเฆจเฆฟ" @@ -1081,7 +1088,6 @@ _sfx: antenna: "เฆ เงเฆฏเฆพเฆจเงเฆเงเฆจเฆพเฆเงเฆฒเฆฟ" channel: "เฆเงเฆฏเฆพเฆจเงเฆฒเงเฆฐ เฆฌเฆฟเฆเงเฆเฆชเงเฆคเฆฟ" _ago: - unknown: "เฆ เฆเฆพเฆจเฆพ" future: "เฆญเฆฌเฆฟเฆทเงเฆฏเง" justNow: "เฆเฆเฆฎเฆพเฆคเงเฆฐ" secondsAgo: "{n} เฆธเงเฆเงเฆจเงเฆก เฆเฆเง" @@ -1125,6 +1131,7 @@ _2fa: registerKey: "เฆธเฆฟเฆเฆฟเฆเฆฐเฆฟเฆเฆฟ เฆเง เฆจเฆฟเฆฌเฆจเงเฆงเฆจ เฆเฆฐเงเฆจ" step1: "เฆชเงเฆฐเฆฅเฆฎเง, เฆเฆชเฆจเฆพเฆฐ เฆกเฆฟเฆญเฆพเฆเฆธเง {a} เฆฌเฆพ {b} เฆเฆฐ เฆฎเฆคเง เฆเฆเฆเฆฟ เฆ เฆฅเงเฆจเฆเฆฟเฆเงเฆถเฆจ เฆ เงเฆฏเฆพเฆช เฆเฆจเฆธเงเฆเฆฒ เฆเฆฐเงเฆจเงท" step2: "เฆเฆฐเฆชเฆฐเง, เฆ เงเฆฏเฆพเฆชเงเฆฐ เฆธเฆพเฆนเฆพเฆฏเงเฆฏเง เฆชเงเฆฐเฆฆเฆฐเงเฆถเฆฟเฆค QR เฆเงเฆกเฆเฆฟ เฆธเงเฆเงเฆฏเฆพเฆจ เฆเฆฐเงเฆจเฅค" + step2Url: "เฆกเงเฆธเงเฆเฆเฆช เฆ เงเฆฏเฆพเฆชเง, เฆจเฆฟเฆฎเงเฆจเฆฒเฆฟเฆเฆฟเฆค URL เฆฒเฆฟเฆเงเฆจ:" step3: "เฆ เงเฆฏเฆพเฆชเง เฆชเงเฆฐเฆฆเฆฐเงเฆถเฆฟเฆค เฆเงเฆเงเฆจเฆเฆฟ เฆฒเฆฟเฆเงเฆจ เฆเฆฌเฆ เฆเฆชเฆจเฆพเฆฐ เฆเฆพเฆ เฆถเงเฆทเฅค" step4: "เฆเฆชเฆจเฆพเฆเง เฆเฆเฆจ เฆฅเงเฆเง เฆฒเฆ เฆเฆจ เฆเฆฐเฆพเฆฐ เฆธเฆฎเฆฏเฆผ, เฆเฆเฆญเฆพเฆฌเง เฆเงเฆเงเฆจ เฆฒเฆฟเฆเฆคเง เฆนเฆฌเงเฅค" securityKeyInfo: "เฆเฆชเฆจเฆฟ เฆเฆเฆเฆฟ เฆนเฆพเฆฐเงเฆกเฆเฆฏเฆผเงเฆฏเฆพเฆฐ เฆธเฆฟเฆเฆฟเฆเฆฐเฆฟเฆเฆฟ เฆเง เฆฌเงเฆฏเฆฌเฆนเฆพเฆฐ เฆเฆฐเง เฆฒเฆ เฆเฆจ เฆเฆฐเฆคเง เฆชเฆพเฆฐเงเฆจ เฆฏเฆพ FIDO2 เฆฌเฆพ เฆกเฆฟเฆญเฆพเฆเฆธเงเฆฐ เฆซเฆฟเฆเงเฆเฆพเฆฐเฆชเงเฆฐเฆฟเฆจเงเฆ เฆธเงเฆจเงเฆธเฆฐ เฆฌเฆพ เฆชเฆฟเฆจ เฆธเฆฎเฆฐเงเฆฅเฆจ เฆเฆฐเงเงท" @@ -1608,6 +1615,8 @@ _notification: youReceivedFollowRequest: "เฆ เฆจเงเฆธเฆฐเฆฃ เฆเฆฐเฆพเฆฐ เฆเฆจเงเฆฏ เฆ เฆจเงเฆฐเงเฆง เฆชเฆพเฆเงเฆพ เฆเงเฆเง" yourFollowRequestAccepted: "เฆเฆชเฆจเฆพเฆฐ เฆ เฆจเงเฆธเฆฐเฆฃ เฆเฆฐเฆพเฆฐ เฆ เฆจเงเฆฐเงเฆง เฆเงเฆนเงเฆค เฆนเงเงเฆเง" youWereInvitedToGroup: "เฆเฆชเฆจเฆฟ เฆเฆเฆเฆฟ เฆเงเฆฐเงเฆชเง เฆเฆฎเฆจเงเฆคเงเฆฐเฆฟเฆค เฆนเงเงเฆเงเฆจ" + pollEnded: "เฆชเงเฆฒเงเฆฐ เฆซเฆฒเฆพเฆซเฆฒ เฆฆเงเฆเฆพ เฆฏเฆพเฆฌเง" + emptyPushNotificationMessage: "เฆเฆชเฆกเงเฆ เฆเฆฐเฆพ เฆชเงเฆถ เฆฌเฆฟเฆเงเฆเฆชเงเฆคเฆฟ" _types: all: "เฆธเฆเฆฒ" follow: "เฆ เฆจเงเฆธเฆฐเฆฃ เฆเฆฐเฆพ เฆนเฆเงเฆเง" @@ -1617,10 +1626,15 @@ _notification: quote: "เฆเฆฆเงเฆงเงเฆคเฆฟ" reaction: "เฆชเงเฆฐเฆคเฆฟเฆเงเฆฐเฆฟเฆฏเฆผเฆพ" pollVote: "เฆชเงเฆฒเง เฆญเงเฆ เฆเฆเง" + pollEnded: "เฆชเงเฆฒ เฆถเงเฆท" receiveFollowRequest: "เฆชเงเฆฐเฆพเฆชเงเฆค เฆ เฆจเงเฆธเฆฐเฆฃเงเฆฐ เฆ เฆจเงเฆฐเงเฆงเฆธเฆฎเงเฆน" followRequestAccepted: "เฆเงเฆนเงเฆค เฆ เฆจเงเฆธเฆฐเฆฃเงเฆฐ เฆ เฆจเงเฆฐเงเฆงเฆธเฆฎเงเฆน" groupInvited: "เฆเงเฆฐเงเฆชเงเฆฐ เฆเฆฎเฆจเงเฆคเงเฆฐเฆจเฆธเฆฎเงเฆน" app: "เฆฒเฆฟเฆเงเฆ เฆเฆฐเฆพ เฆ เงเฆฏเฆพเฆช เฆฅเงเฆเง เฆฌเฆฟเฆเงเฆเฆชเงเฆคเฆฟ" + _actions: + followBack: "เฆซเฆฒเง เฆฌเงเฆฏเฆพเฆ เฆเฆฐเงเฆเง" + reply: "เฆเฆฌเฆพเฆฌ" + renote: "เฆฐเฆฟเฆจเงเฆ" _deck: alwaysShowMainColumn: "เฆธเฆฐเงเฆฌเฆฆเฆพ เฆฎเงเฆเฆจ เฆเฆฒเฆพเฆฎ เฆฆเงเฆเฆพเฆจ" columnAlign: "เฆเฆฒเฆพเฆฎ เฆธเฆพเฆเฆพเฆจ" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 5f74cb6bef..74eab3603b 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1,6 +1,8 @@ --- _lang_: "Catalร " headlineMisskey: "Una xarxa connectada per notes" +introMisskey: "Benvingut! Misskey รฉs un servei de microblogging descentralitzat de codi obert.\nCrea \"notes\" per compartir els teus pensaments amb tots els que t'envolten. ๐ก\nAmb \"reaccions\", tambรฉ pots expressar rร pidament els teus sentiments sobre les notes de tothom. ๐\nExplorem un mรณn nou! ๐" +monthAndDay: "{day}/{month}" search: "Cercar" notifications: "Notificacions" username: "Nom d'usuari" @@ -10,17 +12,175 @@ fetchingAsApObject: "Cercant en el Fediverse..." ok: "OK" gotIt: "Ho he entรจs!" cancel: "Cancelยทlar" +enterUsername: "Introdueix el teu nom d'usuari" +renotedBy: "Resignat per {usuari}" +noNotes: "Cap nota" +noNotifications: "Cap notificaciรณ" +instance: "Instร ncies" +settings: "Preferรจncies" +basicSettings: "Configuraciรณ bร sica" +otherSettings: "Configuraciรณ avanรงada" +openInWindow: "Obrir en una nova finestra" +profile: "Perfil" +timeline: "Lรญnia de temps" +noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia." +login: "Iniciar sessiรณ" +loggingIn: "Identificant-se" +logout: "Tancar la sessiรณ" +signup: "Registrar-se" +uploading: "Pujant..." +save: "Desar" +users: "Usuaris" +addUser: "Afegir un usuari" +favorite: "Afegir a preferits" +favorites: "Favorits" +unfavorite: "Eliminar dels preferits" +favorited: "Afegit als preferits." +alreadyFavorited: "Ja s'ha afegit als preferits." +cantFavorite: "No s'ha pogut afegir als preferits." +pin: "Fixar al perfil" +unpin: "Para de fixar del perfil" +copyContent: "Copiar el contingut" +copyLink: "Copiar l'enllaรง" +delete: "Eliminar" +deleteAndEdit: "Esborrar i editar" +deleteAndEditConfirm: "Estร s segur que vols suprimir aquesta nota i editar-la? Perdrร s totes les reaccions, notes i respostes." +addToList: "Afegir a una llista" +sendMessage: "Enviar un missatge" +copyUsername: "Copiar nom d'usuari" +searchUser: "Cercar usuaris" +reply: "Respondre" +loadMore: "Carregar mรฉs" +showMore: "Veure mรฉs" +youGotNewFollower: "t'ha seguit" +receiveFollowRequest: "Solยทlicitud de seguiment rebuda" +followRequestAccepted: "Solยทlicitud de seguiment acceptada" +mention: "Menciรณ" +mentions: "Mencions" +directNotes: "Notes directes" +importAndExport: "Importar / Exportar" +import: "Importar" +export: "Exportar" +files: "Fitxers" +download: "Baixar" +driveFileDeleteConfirm: "Estร s segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer adjunt tambรฉ se suprimiran." +unfollowConfirm: "Estร s segur que vols deixar de seguir {name}?" +exportRequested: "Has solยทlicitat una exportaciรณ. Aixรฒ pot trigar una estona. S'afegirร a la teva unitat un cop completat." +importRequested: "Has solยทlicitat una importaciรณ. Aixรฒ pot trigar una estona." +lists: "Llistes" +noLists: "No tens cap llista" +note: "Nota" +notes: "Notes" +following: "Seguint" +followers: "Seguidors" +followsYou: "Et segueix" +createList: "Crear llista" +manageLists: "Gestionar les llistes" +error: "Error" +somethingHappened: "S'ha produรฏt un error" +retry: "Torna-ho a intentar" +pageLoadError: "S'ha produรฏt un error en carregar la pร gina" +pageLoadErrorDescription: "Aixรฒ normalment es deu a errors de xarxa o a la memรฒria cau del navegador. Prova d'esborrar la memรฒria cau i torna-ho a provar desprรฉs d'esperar una estona." +serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar." +youShouldUpgradeClient: "Per veure aquesta pร gina, actualitzeu-la per actualitzar el vostre client." +enterListName: "Introdueix un nom per a la llista" +privacy: "Privadesa" +makeFollowManuallyApprove: "Les solยทlicituds de seguiment requereixen aprovaciรณ" +defaultNoteVisibility: "Visibilitat per defecte" +follow: "Seguint" +followRequest: "Enviar la solยทlicitud de seguiment" +followRequests: "Solยทlicituds de seguiment" +unfollow: "Deixar de seguir" +followRequestPending: "Solยทlicituds de seguiment pendents" +enterEmoji: "Introduir un emoji" +renote: "Renotar" +unrenote: "Anulยทlar renota" +renoted: "Renotat." +cantRenote: "Aquesta publicaciรณ no pot ser renotada." +cantReRenote: "Impossible renotar una renota." +quote: "Citar" +pinnedNote: "Nota fixada" +pinned: "Fixar al perfil" +you: "Tu" +clickToShow: "Fes clic per mostrar" +sensitive: "NSFW" +add: "Afegir" +reaction: "Reaccions" +reactionSetting: "Reaccions a mostrar al selector de reaccions" +reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." +rememberNoteVisibility: "Recorda la configuraciรณ de visibilitat de les notes" +attachCancel: "Eliminar el fitxer adjunt" +markAsSensitive: "Marcar com a NSFW" +instances: "Instร ncies" +remove: "Eliminar" +nsfw: "NSFW" +pinnedNotes: "Nota fixada" +userList: "Llistes" smtpUser: "Nom d'usuari" smtpPass: "Contrasenya" +user: "Usuaris" searchByGoogle: "Cercar" +_email: + _follow: + title: "t'ha seguit" _mfm: + mention: "Menciรณ" + quote: "Citar" search: "Cercar" +_theme: + keys: + mention: "Menciรณ" + renote: "Renotar" _sfx: + note: "Notes" notification: "Notificacions" +_2fa: + step2Url: "Tambรฉ pots inserir aquest enllaรง i utilitzes una aplicaciรณ d'escriptori:" _widgets: notifications: "Notificacions" + timeline: "Lรญnia de temps" +_cw: + show: "Carregar mรฉs" +_visibility: + followers: "Seguidors" _profile: username: "Nom d'usuari" +_exportOrImport: + followingList: "Seguint" + userLists: "Llistes" +_pages: + script: + categories: + list: "Llistes" + blocks: + _join: + arg1: "Llistes" + _randomPick: + arg1: "Llistes" + _dailyRandomPick: + arg1: "Llistes" + _seedRandomPick: + arg2: "Llistes" + _pick: + arg1: "Llistes" + _listLen: + arg1: "Llistes" + types: + array: "Llistes" +_notification: + youWereFollowed: "t'ha seguit" + _types: + follow: "Seguint" + mention: "Menciรณ" + renote: "Renotar" + quote: "Citar" + reaction: "Reaccions" + _actions: + reply: "Respondre" + renote: "Renotar" _deck: _columns: notifications: "Notificacions" + tl: "Lรญnia de temps" + list: "Llistes" + mentions: "Mencions" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 2f5e375372..4b20340df1 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -53,6 +53,8 @@ reply: "Odpovฤdฤt" loadMore: "Zobrazit vรญce" showMore: "Zobrazit vรญce" youGotNewFollower: "Mรกte novรฉho nรกsledovnรญka" +receiveFollowRequest: "ลฝรกdost o sledovรกnรญ pลijata" +followRequestAccepted: "ลฝรกdost o sledovรกnรญ pลijata" mention: "Zmรญnฤnรญ" mentions: "Zmรญnฤnรญ" importAndExport: "Import a export" @@ -60,7 +62,9 @@ import: "Importovat" export: "Exportovat" files: "Soubor(ลฏ)" download: "Stรกhnout" +driveFileDeleteConfirm: "Opravdu chcete smazat soubor \"{name}\"? Poznรกmky, ke kterรฝm je tento soubor pลipojen, budou takรฉ smazรกny." unfollowConfirm: "Jste si jisti ลพe uลพ nechcete sledovat {name}?" +exportRequested: "Poลพรกdali jste o export. To mลฏลพe chvรญli trvat. Pลidรกme ho na vรกลก Disk aลพ bude dokonฤen." importRequested: "Poลพรกdali jste o export. To mลฏลพe chvilku trvat." lists: "Seznamy" noLists: "Nemรกte ลพรกdnรฉ seznamy" @@ -75,13 +79,25 @@ error: "Chyba" somethingHappened: "Jejda. Nฤco se nepovedlo." retry: "Opakovat" pageLoadError: "Nepodaลilo se naฤรญst strรกnku" +serverIsDead: "Server neodpovรญdรก. Poฤkejte chvรญli a zkuste to znovu." +youShouldUpgradeClient: "Pro zobrazenรญ tรฉto strรกnky obnovte strรกnku pro aktualizaci klienta." enterListName: "Jmรฉno seznamu" privacy: "Soukromรญ" +makeFollowManuallyApprove: "ลฝรกdosti o sledovรกnรญ vyลพadujรญ potvrzenรญ" +defaultNoteVisibility: "Vรฝchozรญ viditelnost" follow: "Sledovanรญ" +followRequest: "Odeslat ลพรกdost o sledovรกnรญ" +followRequests: "ลฝรกdosti o sledovรกnรญ" unfollow: "Pลestat sledovat" +followRequestPending: "ฤekajรญcรญ ลพรกdosti o sledovรกnรญ" +enterEmoji: "Vloลพte emoji" renote: "Pลeposlat" +unrenote: "Zruลกit pลeposlรกnรญ" +renoted: "Pลeposlรกno" +cantRenote: "Tento pลรญspฤvek nelze pลeposlat." cantReRenote: "Odpovฤฤ nemลฏลพe bรฝt odstranฤna." quote: "Citovat" +pinnedNote: "Pลipnutรก poznรกmka" pinned: "Pลipnout" you: "Vy" clickToShow: "Kliknฤte pro zobrazenรญ" @@ -122,6 +138,8 @@ flagAsBot: "Tento รบฤet je bot" flagAsBotDescription: "Pokud je tento รบฤet kontrolovรกn programem zaลกkrtnฤte tuto moลพnost. To oznaฤรญ tento รบฤet jako bot pro ostatnรญ vรฝvojรกลe a zabrรกnรญ tak nekoneฤnรฝm interakcรญm s ostatnรญmi boty a upravรญ Misskey systรฉm aby se choval k tomuhle รบฤtu jako bot." flagAsCat: "Tenhle รบฤet je koฤka" flagAsCatDescription: "Vyberte tuto moลพnost aby tento รบฤet byl oznaฤen jako koฤka." +flagShowTimelineReplies: "Zobrazovat odpovฤdi na ฤasovรฉ ose" +flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazรญ odpovฤdi uลพivatelลฏ na poznรกmky jinรฝch uลพivatelลฏ na vaลกรญ ฤasovรฉ ose." autoAcceptFollowed: "Automaticky akceptovat nรกsledovรกnรญ od รบฤtลฏ kterรฉ sledujete" addAccount: "Pลidat รบฤet" loginFailed: "Pลihlรกลกenรญ se nezdaลilo." @@ -130,13 +148,16 @@ general: "Obecnฤ" wallpaper: "Obrรกzek na pozadรญ" setWallpaper: "Nastavenรญ obrรกzku na pozadรญ" removeWallpaper: "Odstranit pozadรญ" +searchWith: "Hledat: {q}" youHaveNoLists: "Nemรกte ลพรกdnรฉ seznamy" +followConfirm: "Jste si jisti, ลพe chcete sledovat {name}?" proxyAccount: "Proxy รบฤet" proxyAccountDescription: "Proxy รบฤet je รบฤet, kterรฝ za urฤitรฝch podmรญnek sleduje uลพivatele na dรกlku vaลกรญm jmรฉnem. Napลรญklad kdyลพ uลพivatel zaลadรญ vzdรกlenรฉho uลพivatele do seznamu, pokud nikdo nesleduje uลพivatele na seznamu, aktivita nebude doruฤena instanci, takลพe mรญsto toho bude uลพivatele sledovat รบฤet proxy." host: "Hostitel" selectUser: "Vyberte uลพivatele" recipient: "Pro" annotation: "Komentรกลe" +federation: "Federace" instances: "Instance" registeredAt: "Registrovรกn" latestRequestSentAt: "Poslednรญ poลพadavek poslรกn" @@ -146,6 +167,7 @@ storageUsage: "Vyuลพitรญ รบloลพiลกtฤ" charts: "Grafy" perHour: "za hodinu" perDay: "za den" +stopActivityDelivery: "Pลestat zasรญlat aktivitu" blockThisInstance: "Blokovat tuto instanci" operations: "Operace" software: "Software" @@ -283,6 +305,8 @@ iconUrl: "Favicon URL" bannerUrl: "Baner URL" backgroundImageUrl: "Adresa URL obrรกzku pozadรญ" basicInfo: "Zรกkladnรญ informace" +pinnedUsers: "Pลipnutรญ uลพivatelรฉ" +pinnedNotes: "Pลipnutรก poznรกmka" hcaptcha: "hCaptcha" enableHcaptcha: "Aktivovat hCaptchu" hcaptchaSecretKey: "Tajnรฝ Klรญฤ (Secret Key)" @@ -471,6 +495,7 @@ _widgets: notifications: "Oznรกmenรญ" timeline: "ฤasovรก osa" activity: "Aktivita" + federation: "Federace" jobQueue: "Fronta รบloh" _cw: show: "Zobrazit vรญce" @@ -485,6 +510,8 @@ _exportOrImport: muteList: "Ztlumit" blockingList: "Zablokovat" userLists: "Seznamy" +_charts: + federation: "Federace" _timelines: home: "Domลฏ" _pages: @@ -517,6 +544,9 @@ _notification: renote: "Pลeposlat" quote: "Citovat" reaction: "Reakce" + _actions: + reply: "Odpovฤdฤt" + renote: "Pลeposlat" _deck: _columns: notifications: "Oznรกmenรญ" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 1f558787ab..5dfce28002 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -842,6 +842,9 @@ oneDay: "Einen Tag" oneWeek: "Eine Woche" reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt." failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden" +rateLimitExceeded: "Versuchsanzahl รผberschritten" +cropImage: "Bild zuschneiden" +cropImageAsk: "Mรถchtest du das Bild zuschneiden?" _emailUnavailable: used: "Diese Email-Adresse wird bereits verwendet" format: "Das Format dieser Email-Adresse ist ungรผltig" @@ -1006,7 +1009,7 @@ _instanceMute: heading: "Liste der stummzuschaltenden Instanzen" _theme: explore: "Farbschemata erforschen" - install: "Farbschmata installieren" + install: "Farbschemata installieren" manage: "Farbschemaverwaltung" code: "Farbschemencode" description: "Beschreibung" @@ -1087,7 +1090,6 @@ _sfx: antenna: "Antennen" channel: "Kanalbenachrichtigung" _ago: - unknown: "Unbekannt" future: "Zukunft" justNow: "Gerade eben" secondsAgo: "vor {n} Sekunde(n)" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "Neuen Sicherheitsschlรผssel registrieren" step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerรคt." step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerรคt." + step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:" step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird." step4: "Alle folgenden Anmeldungsversuche werden ab sofort die Eingabe eines solchen Tokens benรถtigen." securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerรคt auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlรผssels einrichten." @@ -1613,8 +1616,9 @@ _notification: youWereFollowed: "ist dir gefolgt" youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten" yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert" - youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen" + youWereInvitedToGroup: "{userName} hat dich in eine Gruppe eingeladen" pollEnded: "Umfrageergebnisse sind verfรผgbar" + emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert" _types: all: "Alle" follow: "Neue Follower" @@ -1629,6 +1633,10 @@ _notification: followRequestAccepted: "Akzeptierte Follow-Anfragen" groupInvited: "Erhaltene Gruppeneinladungen" app: "Benachrichtigungen von Apps" + _actions: + followBack: "folgt dir nun auch" + reply: "Antworten" + renote: "Renote" _deck: alwaysShowMainColumn: "Hauptspalte immer zeigen" columnAlign: "Spaltenausrichtung" diff --git a/locales/en-US.yml b/locales/en-US.yml index 99fe05375b..8bfea26b0f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -842,6 +842,9 @@ oneDay: "One day" oneWeek: "One week" reflectMayTakeTime: "It may take some time for this to be reflected." failedToFetchAccountInformation: "Could not fetch account information" +rateLimitExceeded: "Rate limit exceeded" +cropImage: "Crop image" +cropImageAsk: "Do you want to crop this image?" _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" @@ -1087,7 +1090,6 @@ _sfx: antenna: "Antennas" channel: "Channel notifications" _ago: - unknown: "Unknown" future: "Future" justNow: "Just now" secondsAgo: "{n} second(s) ago" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "Register a security key" step1: "First, install an authentication app (such as {a} or {b}) on your device." step2: "Then, scan the QR code displayed on this screen." + step2Url: "You can also enter this URL if you're using a desktop program:" step3: "Enter the token provided by your app to finish setup." step4: "From now on, any future login attempts will ask for such a login token." securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account." @@ -1613,8 +1616,9 @@ _notification: youWereFollowed: "followed you" youReceivedFollowRequest: "You've received a follow request" yourFollowRequestAccepted: "Your follow request was accepted" - youWereInvitedToGroup: "You've been invited to a group" + youWereInvitedToGroup: "{userName} invited you to a group" pollEnded: "Poll results have become available" + emptyPushNotificationMessage: "Push notifications have been updated" _types: all: "All" follow: "New followers" @@ -1629,6 +1633,10 @@ _notification: followRequestAccepted: "Accepted follow requests" groupInvited: "Group invitations" app: "Notifications from linked apps" + _actions: + followBack: "followed you back" + reply: "Reply" + renote: "Renote" _deck: alwaysShowMainColumn: "Always show main column" columnAlign: "Align columns" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index fd69f62ff5..6c10942b48 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -141,6 +141,8 @@ flagAsBot: "Esta cuenta es un bot" flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opciรณn. Al hacerlo, esta opciรณn servirรก para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustarรก los sistemas internos de Misskey para que trate a esta cuenta como un bot." flagAsCat: "Esta cuenta es un gato" flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opciรณn." +flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografรญa" +flagShowTimelineRepliesDescription: "Cuando se marca, la lรญnea de tiempo muestra respuestas a otras notas ademรกs de las notas del usuario" autoAcceptFollowed: "Aceptar automรกticamente las solicitudes de seguimiento de los usuarios que sigues" addAccount: "Agregar Cuenta" loginFailed: "Error al iniciar sesiรณn." @@ -235,6 +237,8 @@ resetAreYouSure: "ยฟDesea reestablecer?" saved: "Guardado" messaging: "Chat" upload: "Subir" +keepOriginalUploading: "Mantener la imagen original" +keepOriginalUploadingDescription: "Mantener la versiรณn original al cargar imรกgenes. Si estรก desactivado, el navegador generarรก imรกgenes para la publicaciรณn web en el momento de recargar la pรกgina" fromDrive: "Desde el drive" fromUrl: "Desde la URL" uploadFromUrl: "Subir desde una URL" @@ -444,6 +448,7 @@ uiLanguage: "Idioma de visualizaciรณn de la interfaz" groupInvited: "Invitado al grupo" aboutX: "Acerca de {x}" useOsNativeEmojis: "Usa los emojis nativos de la plataforma" +disableDrawer: "No mostrar los menรบs en cajones" youHaveNoGroups: "Sin grupos" joinOrCreateGroup: "Obtenga una invitaciรณn para unirse al grupos o puede crear su propio grupo." noHistory: "No hay datos en el historial" @@ -615,6 +620,10 @@ reportAbuse: "Reportar" reportAbuseOf: "Reportar a {name}" fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta." abuseReported: "Se ha enviado el reporte. Muchas gracias." +reporteeOrigin: "Informar a" +reporterOrigin: "Origen del informe" +forwardReport: "Transferir un informe a una instancia remota" +forwardReportIsAnonymous: "No puede ver su informaciรณn de la instancia remota y aparecerรก como una cuenta anรณnima del sistema" send: "Enviar" abuseMarkAsResolved: "Marcar reporte como resuelto" openInNewTab: "Abrir en una Nueva Pestaรฑa" @@ -676,6 +685,7 @@ center: "Centrar" wide: "Ancho" narrow: "Estrecho" reloadToApplySetting: "Esta configuraciรณn sรณlo se aplicarรก despuรฉs de recargar la pรกgina. ยฟRecargar ahora?" +needReloadToApply: "Se requiere un reinicio para la aplicar los cambios" showTitlebar: "Mostrar la barra de tรญtulo" clearCache: "Limpiar cachรฉ" onlineUsersCount: "{n} usuarios en lรญnea" @@ -706,19 +716,55 @@ capacity: "Capacidad" inUse: "Usado" editCode: "Editar cรณdigo" apply: "Aplicar" +receiveAnnouncementFromInstance: "Recibir notificaciones de la instancia" +emailNotification: "Notificaciones por correo electrรณnico" publish: "Publicar" inChannelSearch: "Buscar en el canal" +useReactionPickerForContextMenu: "Haga clic con el botรณn derecho para abrir el menu de reacciones" +typingUsers: "{users} estรก escribiendo" +jumpToSpecifiedDate: "Saltar a una fecha especรญfica" +showingPastTimeline: "Mostrar lรญneas de tiempo antiguas" +clear: "Limpiar" markAllAsRead: "Marcar todo como leรญdo" goBack: "Deseleccionar" +fullView: "Vista completa" +quitFullView: "quitar vista completa" +addDescription: "Agregar descripciรณn" +userPagePinTip: "Puede mantener sus notas visibles aquรญ seleccionando Pin en el menรบ de notas individuales" +notSpecifiedMentionWarning: "Algunas menciones no estรกn incluidas en el destino" info: "Informaciรณn" +userInfo: "Informaciรณn del usuario" +unknown: "Desconocido" +onlineStatus: "En lรญnea" +hideOnlineStatus: "mostrarse como desconectado" +hideOnlineStatusDescription: "Ocultar su estado en lรญnea puede reducir la eficacia de algunas funciones, como la bรบsqueda" online: "En lรญnea" +active: "Activo" offline: "Sin conexiรณn" +notRecommended: "obsoleto" +botProtection: "Protecciรณn contra bots" +instanceBlocking: "Instancias bloqueadas" +selectAccount: "Elija una cuenta" +switchAccount: "Cambiar de cuenta" +enabled: "Activado" +disabled: "Desactivado" +quickAction: "Acciones rรกpidas" user: "Usuarios" administration: "Administrar" +accounts: "Cuentas" +switch: "Cambiar" +noMaintainerInformationWarning: "No se ha establecido la informaciรณn del administrador" +noBotProtectionWarning: "La protecciรณn contra los bots no estรก configurada" +configure: "Configurar" +postToGallery: "Crear una nueva publicaciรณn en la galerรญa" gallery: "Galerรญa" recentPosts: "Posts recientes" popularPosts: "Mรกs vistos" +shareWithNote: "Compartir con una nota" +ads: "Anuncios" expiration: "Termina el" +memo: "Notas" +priority: "Prioridad" high: "Alta" middle: "Mediano" low: "Baja" @@ -770,22 +816,50 @@ _accountDelete: accountDelete: "Eliminar Cuenta" _ad: back: "Deseleccionar" +_forgotPassword: + contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrรณnico, pรณngase en contacto con el administrador de la instancia para restablecer su contraseรฑa" _gallery: my: "Mi galerรญa" + liked: "Publicaciones que me gustan" + like: "ยกMuy bien!" unlike: "Quitar me gusta" _email: _follow: title: "te ha seguido" + _receiveFollowRequest: + title: "Has recibido una solicitud de seguimiento" +_plugin: + install: "Instalar plugins" + installWarn: "Por favor no instale plugins que no son de confianza" + manage: "Gestionar plugins" _registry: + scope: "Alcance" key: "Clave" keys: "Clave" + domain: "Dominio" + createKey: "Crear una llave" +_aboutMisskey: + about: "Misskey es un software de cรณdigo abierto, desarrollado por syuilo desde el 2014" + contributors: "Principales colaboradores" + allContributors: "Todos los colaboradores" + source: "Cรณdigo fuente" + translation: "Traducir Misskey" + donate: "Donar a Misskey" + morePatrons: "Muchas mรกs personas nos apoyan. Muchas gracias๐ฅฐ" + patrons: "Patrocinadores" +_nsfw: + respect: "Ocultar medios NSFW" + ignore: "No esconder medios NSFW " + force: "Ocultar todos los medios" _mfm: cheatSheet: "Hoja de referencia de MFM" intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquรญ puede ver una lista de sintaxis disponibles en MFM." + dummy: "Misskey expande el mundo de la Fediverso" mention: "Menciones" mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular." hashtag: "Hashtag" url: "URL" + urlDescription: "Se pueden mostrar las URL" link: "Vรญnculo" bold: "Negrita" center: "Centrar" @@ -915,7 +989,6 @@ _sfx: antenna: "Antena receptora" channel: "Notificaciones del canal" _ago: - unknown: "Desconocido" future: "Futuro" justNow: "Reciรฉn ahora" secondsAgo: "Hace {n} segundos" @@ -1432,6 +1505,9 @@ _notification: followRequestAccepted: "El seguimiento fue aceptado" groupInvited: "Invitado al grupo" app: "Notificaciones desde aplicaciones" + _actions: + reply: "Responder" + renote: "Renotar" _deck: alwaysShowMainColumn: "Siempre mostrar la columna principal" columnAlign: "Alinear columnas" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 1fe74fa9ab..7e225c2992 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -804,7 +804,7 @@ manageAccounts: "Gรฉrer les comptes" makeReactionsPublic: "Rendre les rรฉactions publiques" makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos rรฉactions donnรฉes publique." classic: "Classique" -muteThread: "Mettre ce thread en sourdine" +muteThread: "Masquer cette discussion" unmuteThread: "Ne plus masquer le fil" ffVisibility: "Visibilitรฉ des abonnรฉs/abonnements" ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu suis et les personnes qui te suivent." @@ -1075,7 +1075,6 @@ _sfx: antenna: "Rรฉception de lโantenne" channel: "Notifications de canal" _ago: - unknown: "Inconnu" future: "Futur" justNow: "ร lโinstant" secondsAgo: "Il y a {n}s" @@ -1615,6 +1614,9 @@ _notification: followRequestAccepted: "Demande d'abonnement acceptรฉe" groupInvited: "Invitation ร un groupe" app: "Notifications provenant des apps" + _actions: + reply: "Rรฉpondre" + renote: "Renoter" _deck: alwaysShowMainColumn: "Toujours afficher la colonne principale" columnAlign: "Aligner les colonnes" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 11dff184cd..39e2c1f661 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -593,6 +593,7 @@ smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS" testEmail: "Tes pengiriman surel" wordMute: "Bisukan kata" regexpError: "Kesalahan ekspresi reguler" +regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:" instanceMute: "Bisuka instansi" userSaysSomething: "{name} mengatakan sesuatu" makeActive: "Aktifkan" @@ -839,7 +840,11 @@ tenMinutes: "10 Menit" oneHour: "1 Jam" oneDay: "1 Hari" oneWeek: "1 Bulan" +reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan." failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun" +rateLimitExceeded: "Batas sudah terlampaui" +cropImage: "potong gambar" +cropImageAsk: "Ingin memotong gambar?" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." @@ -1085,7 +1090,6 @@ _sfx: antenna: "Penerimaan Antenna" channel: "Pemberitahuan saluran" _ago: - unknown: "Tidak diketahui" future: "Masa depan" justNow: "Baru saja" secondsAgo: "{n} detik lalu" @@ -1129,6 +1133,7 @@ _2fa: registerKey: "Daftarkan kunci keamanan baru" step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu." step2: "Lalu, pindai kode QR yang ada di layar." + step2Url: "Di aplikasi desktop, masukkan URL berikut:" step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan." step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu." securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu." @@ -1613,6 +1618,7 @@ _notification: yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima" youWereInvitedToGroup: "Telah diundang ke grup" pollEnded: "Hasil Kuesioner telah keluar" + emptyPushNotificationMessage: "Pembaruan notifikasi dorong" _types: all: "Semua" follow: "Ikuti" @@ -1627,6 +1633,10 @@ _notification: followRequestAccepted: "Permintaan mengikuti disetujui" groupInvited: "Diundang ke grup" app: "Pemberitahuan dari aplikasi" + _actions: + followBack: "Ikuti Kembali" + reply: "Balas" + renote: "Renote" _deck: alwaysShowMainColumn: "Selalu tampilkan kolom utama" columnAlign: "Luruskan kolom" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 1eaa78b646..8584ed6a8e 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -10,7 +10,7 @@ password: "Password" forgotPassword: "Hai dimenticato la tua password?" fetchingAsApObject: "Recuperando dal Fediverso..." ok: "OK" -gotIt: "Capito!" +gotIt: "Ho capito" cancel: "Annulla" enterUsername: "Inserisci un nome utente" renotedBy: "Rinotato da {user}" @@ -767,6 +767,7 @@ customCss: "CSS personalizzato" global: "Federata" squareAvatars: "Mostra l'immagine del profilo come quadrato" sent: "Inviare" +received: "Ricevuto" searchResult: "Risultati della Ricerca" hashtags: "Hashtag" troubleshooting: "Risoluzione problemi" @@ -804,6 +805,10 @@ welcomeBackWithName: "Bentornato/a, {name}" clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email." searchByGoogle: "Cerca" indefinitely: "Non scade" +tenMinutes: "10 minuti" +oneHour: "1 ora" +oneDay: "1 giorno" +oneWeek: "1 settimana" _emailUnavailable: used: "Email giร in uso" format: "Formato email non valido" @@ -999,7 +1004,6 @@ _sfx: antenna: "Ricezione dell'antenna" channel: "Notifiche di canale" _ago: - unknown: "Sconosciuto" future: "Futuro" justNow: "Ora" secondsAgo: "{n}s fa" @@ -1433,6 +1437,9 @@ _notification: followRequestAccepted: "Richiesta di follow accettata" groupInvited: "Invito a un gruppo" app: "Notifiche da applicazioni" + _actions: + reply: "Rispondi" + renote: "Rinota" _deck: alwaysShowMainColumn: "Mostra sempre la colonna principale" columnAlign: "Allineare colonne" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6326094dd8..43ab7f2d69 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -356,7 +356,7 @@ antennaExcludeKeywords: "้คๅคใญใผใฏใผใ" antennaKeywordsDescription: "ในใใผในใงๅบๅใใจANDๆๅฎใซใชใใๆน่กใงๅบๅใใจORๆๅฎใซใชใใพใ" notifyAntenna: "ๆฐใใใใผใใ้็ฅใใ" withFileAntenna: "ใใกใคใซใๆทปไปใใใใใผใใฎใฟ" -enableServiceworker: "ServiceWorkerใๆๅนใซใใ" +enableServiceworker: "ใใฉใฆใถใธใฎใใใทใฅ้็ฅใๆๅนใซใใ" antennaUsersDescription: "ใฆใผใถใผๅใๆน่กใงๅบๅใฃใฆๆๅฎใใพใ" caseSensitive: "ๅคงๆๅญๅฐๆๅญใๅบๅฅใใ" withReplies: "่ฟไฟกใๅซใ" @@ -425,7 +425,7 @@ quoteQuestion: "ๅผ็จใจใใฆๆทปไปใใพใใ๏ผ" noMessagesYet: "ใพใ ใใฃใใใฏใใใพใใ" newMessageExists: "ๆฐใใใกใใปใผใธใใใใพใ" onlyOneFileCanBeAttached: "ใกใใปใผใธใซๆทปไปใงใใใใกใคใซใฏใฒใจใคใงใ" -signinRequired: "ใญใฐใคใณใใฆใใ ใใ" +signinRequired: "็ถ่กใใๅใซใใตใคใณใขใใใพใใฏใตใคใณใคใณใๅฟ ่ฆใงใ" invitations: "ๆๅพ " invitationCode: "ๆๅพ ใณใผใ" checking: "็ขบ่ชใใฆใใพใ" @@ -842,6 +842,9 @@ oneDay: "1ๆฅ" oneWeek: "1้ฑ้" reflectMayTakeTime: "ๅๆ ใใใใพใงๆ้ใใใใๅ ดๅใใใใพใใ" failedToFetchAccountInformation: "ใขใซใฆใณใๆ ๅ ฑใฎๅๅพใซๅคฑๆใใพใใ" +rateLimitExceeded: "ใฌใผใๅถ้ใ่ถ ใใพใใ" +cropImage: "็ปๅใฎใฏใญใใ" +cropImageAsk: "็ปๅใใฏใญใใใใพใใ๏ผ" _emailUnavailable: used: "ๆขใซไฝฟ็จใใใฆใใพใ" @@ -1110,7 +1113,6 @@ _sfx: channel: "ใใฃใณใใซ้็ฅ" _ago: - unknown: "่ฌ" future: "ๆชๆฅ" justNow: "ใใฃใไป" secondsAgo: "{n}็งๅ" @@ -1157,6 +1159,7 @@ _2fa: registerKey: "ใญใผใ็ป้ฒ" step1: "ใพใใ{a}ใ{b}ใชใฉใฎ่ช่จผใขใใชใใไฝฟใใฎใใใคในใซใคใณในใใผใซใใพใใ" step2: "ๆฌกใซใ่กจ็คบใใใฆใใQRใณใผใใใขใใชใงในใญใฃใณใใพใใ" + step2Url: "ใในใฏใใใใขใใชใงใฏๆฌกใฎURLใๅ ฅๅใใพใ:" step3: "ใขใใชใซ่กจ็คบใใใฆใใใใผใฏใณใๅ ฅๅใใฆๅฎไบใงใใ" step4: "ใใใใใญใฐใคใณใใใจใใใๅใใใใซใใผใฏใณใๅ ฅๅใใพใใ" securityKeyInfo: "FIDO2ใใตใใผใใใใใผใใฆใงใขใปใญใฅใชใใฃใญใผใใใใฏ็ซฏๆซใฎๆ็ด่ช่จผใPINใไฝฟ็จใใฆใญใฐใคใณใใใใใซ่จญๅฎใงใใพใใ" @@ -1668,8 +1671,9 @@ _notification: youWereFollowed: "ใใฉใญใผใใใพใใ" youReceivedFollowRequest: "ใใฉใญใผใชใฏใจในใใๆฅใพใใ" yourFollowRequestAccepted: "ใใฉใญใผใชใฏใจในใใๆฟ่ชใใใพใใ" - youWereInvitedToGroup: "ใฐใซใผใใซๆๅพ ใใใพใใ" + youWereInvitedToGroup: "{userName}ใใใชใใใฐใซใผใใซๆๅพ ใใพใใ" pollEnded: "ใขใณใฑใผใใฎ็ตๆใๅบใพใใ" + emptyPushNotificationMessage: "ใใใทใฅ้็ฅใฎๆดๆฐใใใพใใ" _types: all: "ใในใฆ" @@ -1686,6 +1690,11 @@ _notification: groupInvited: "ใฐใซใผใใซๆๅพ ใใใ" app: "้ฃๆบใขใใชใใใฎ้็ฅ" + _actions: + followBack: "ใใฉใญใผใใใฏ" + reply: "่ฟไฟก" + renote: "Renote" + _deck: alwaysShowMainColumn: "ๅธธใซใกใคใณใซใฉใ ใ่กจ็คบ" columnAlign: "ใซใฉใ ใฎๅฏใ" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 52ecd8c24e..5458152dda 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -799,7 +799,6 @@ _sfx: notification: "้็ฅ" chat: "ใใฃใใ" _ago: - unknown: "ใใใใ" future: "ๆชๆฅ" justNow: "ใใฃใไป" secondsAgo: "{n}็งๅ" @@ -1202,6 +1201,9 @@ _notification: reaction: "ใชใขใฏใทใงใณ" receiveFollowRequest: "ใใฉใญใผ่จฑๅฏใใฆใปใใใฟใใใใง" followRequestAccepted: "ใใฉใญใผใๅ็ใใใใง" + _actions: + reply: "่ฟไบ" + renote: "Renote" _deck: alwaysShowMainColumn: "ใใคใใกใคใณใซใฉใ ใ่กจ็คบ" columnAlign: "ใซใฉใ ใฎๅฏใ" diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index 6a14cbe1ba..77ca824528 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -116,6 +116,8 @@ _notification: _types: follow: "Ig แนญแนญafaแน" mention: "Bder" + _actions: + reply: "Err" _deck: _columns: notifications: "Ilษฃuyen" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index 3111c90dd5..3682277175 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -76,6 +76,8 @@ _profile: username: "เฒฌเฒณเฒเณเฒนเณเฒธเฒฐเณ" _notification: youWereFollowed: "เฒนเฒฟเฒเฒฌเฒพเฒฒเฒฟเฒธเฒฟเฒฆเฒฐเณ" + _actions: + reply: "เฒเฒคเณเฒคเฒฐเฒฟเฒธเณ" _deck: _columns: notifications: "เฒ เฒงเฒฟเฒธเณเฒเฒจเณเฒเฒณเณ" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index e1ad77cbc9..4e7369a5ef 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -592,6 +592,8 @@ smtpSecure: "SMTP ์ฐ๊ฒฐ์ Implicit SSL/TTS ์ฌ์ฉ" smtpSecureInfo: "STARTTLS ์ฌ์ฉ ์์๋ ํด์ ํฉ๋๋ค." testEmail: "์ด๋ฉ์ผ ์ ์ก ํ ์คํธ" wordMute: "๋จ์ด ๋ฎคํธ" +regexpError: "์ ๊ท ํํ์ ์ค๋ฅ" +regexpErrorDescription: "{tab}๋จ์ด ๋ฎคํธ {line}ํ์ ์ ๊ท ํํ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค:" instanceMute: "์ธ์คํด์ค ๋ฎคํธ" userSaysSomething: "{name}๋์ด ๋ฌด์ธ๊ฐ๋ฅผ ๋งํ์ต๋๋ค" makeActive: "ํ์ฑํ" @@ -825,8 +827,21 @@ overridedDeviceKind: "์ฅ์น ์ ํ" smartphone: "์ค๋งํธํฐ" tablet: "ํ๋ธ๋ฆฟ" auto: "์๋" +themeColor: "ํ ๋ง ์ปฌ๋ฌ" +size: "ํฌ๊ธฐ" +numberOfColumn: "ํ ์ค์ ๋ณด์ผ ๋ฆฌ์ก์ ์ ์" searchByGoogle: "๊ฒ์" +instanceDefaultLightTheme: "์ธ์คํด์ค ๊ธฐ๋ณธ ๋ผ์ดํธ ํ ๋ง" +instanceDefaultDarkTheme: "์ธ์คํด์ค ๊ธฐ๋ณธ ๋คํฌ ํ ๋ง" +instanceDefaultThemeDescription: "๊ฐ์ฒด ํ์์ ํ ๋ง ์ฝ๋๋ฅผ ์ ๋ ฅํด ์ฃผ์ธ์." +mutePeriod: "๋ฎคํธํ ๊ธฐ๊ฐ" indefinitely: "๋ฌด๊ธฐํ" +tenMinutes: "10๋ถ" +oneHour: "1์๊ฐ" +oneDay: "1์ผ" +oneWeek: "์ผ์ฃผ์ผ" +reflectMayTakeTime: "๋ฐ์๋๊ธฐ๊น์ง ์๊ฐ์ด ๊ฑธ๋ฆด ์ ์์ต๋๋ค." +failedToFetchAccountInformation: "๊ณ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์ต๋๋ค" _emailUnavailable: used: "์ด ๋ฉ์ผ ์ฃผ์๋ ์ฌ์ฉ์ค์ ๋๋ค" format: "ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค" @@ -1072,7 +1087,6 @@ _sfx: antenna: "์ํ ๋ ์์ " channel: "์ฑ๋ ์๋ฆผ" _ago: - unknown: "์ ์ ์์" future: "๋ฏธ๋" justNow: "๋ฐฉ๊ธ ์ " secondsAgo: "{n}์ด ์ " @@ -1116,6 +1130,7 @@ _2fa: registerKey: "ํค๋ฅผ ๋ฑ๋ก" step1: "๋จผ์ , {a}๋ {b}๋ฑ์ ์ธ์ฆ ์ฑ์ ์ฌ์ฉ ์ค์ธ ๋๋ฐ์ด์ค์ ์ค์นํฉ๋๋ค." step2: "๊ทธ ํ, ํ์๋์ด ์๋ QR์ฝ๋๋ฅผ ์ฑ์ผ๋ก ์ค์บํฉ๋๋ค." + step2Url: "๋ฐ์คํฌํฑ ์ฑ์์๋ ๋ค์ URL์ ์ ๋ ฅํ์ธ์:" step3: "์ฑ์ ํ์๋ ํ ํฐ์ ์ ๋ ฅํ์๋ฉด ์๋ฃ๋ฉ๋๋ค." step4: "๋ค์ ๋ก๊ทธ์ธ๋ถํฐ๋ ํ ํฐ์ ์ ๋ ฅํด์ผ ํฉ๋๋ค." securityKeyInfo: "FIDO2๋ฅผ ์ง์ํ๋ ํ๋์จ์ด ๋ณด์ ํค ํน์ ๋๋ฐ์ด์ค์ ์ง๋ฌธ์ธ์์ด๋ ํ๋ฉด์ ๊ธ PIN์ ์ด์ฉํด์ ๋ก๊ทธ์ธํ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค." @@ -1249,7 +1264,7 @@ _profile: youCanIncludeHashtags: "ํด์ ํ๊ทธ๋ฅผ ํฌํจํ ์ ์์ต๋๋ค." metadata: "์ถ๊ฐ ์ ๋ณด" metadataEdit: "์ถ๊ฐ ์ ๋ณด ํธ์ง" - metadataDescription: "ํ๋กํ์ ์ต๋ 4๊ฐ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ํ์ํ ์ ์์ด์" + metadataDescription: "ํ๋กํ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ํ์ํ ์ ์์ด์" metadataLabel: "๋ผ๋ฒจ" metadataContent: "๋ด์ฉ" changeAvatar: "์๋ฐํ ์ด๋ฏธ์ง ๋ณ๊ฒฝ" @@ -1599,6 +1614,8 @@ _notification: youReceivedFollowRequest: "์๋ก์ด ํ๋ก์ฐ ์์ฒญ์ด ์์ต๋๋ค" yourFollowRequestAccepted: "ํ๋ก์ฐ ์์ฒญ์ด ์๋ฝ๋์์ต๋๋ค" youWereInvitedToGroup: "๊ทธ๋ฃน์ ์ด๋๋์์ต๋๋ค" + pollEnded: "ํฌํ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋์์ต๋๋ค" + emptyPushNotificationMessage: "ํธ์ ์๋ฆผ์ด ๊ฐฑ์ ๋์์ต๋๋ค" _types: all: "์ ๋ถ" follow: "ํ๋ก์" @@ -1608,10 +1625,15 @@ _notification: quote: "์ธ์ฉ" reaction: "๋ฆฌ์ก์ " pollVote: "ํฌํ ์ฐธ์ฌ" + pollEnded: "ํฌํ๊ฐ ์ข ๋ฃ๋จ" receiveFollowRequest: "ํ๋ก์ฐ ์์ฒญ์ ๋ฐ์์ ๋" followRequestAccepted: "ํ๋ก์ฐ ์์ฒญ์ด ์น์ธ๋์์ ๋" groupInvited: "๊ทธ๋ฃน์ ์ด๋๋์์ ๋" app: "์ฐ๋๋ ์ฑ์ ํตํ ์๋ฆผ" + _actions: + followBack: "ํ๋ก์ฐ" + reply: "๋ต๊ธ" + renote: "Renote" _deck: alwaysShowMainColumn: "๋ฉ์ธ ์นผ๋ผ ํญ์ ํ์" columnAlign: "์นผ๋ผ ์ ๋ ฌ" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index f4e4a62182..0ded573948 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -303,6 +303,8 @@ muteThread: "Discussies dempen " unmuteThread: "Dempen van discussie ongedaan maken" hide: "Verbergen" searchByGoogle: "Zoeken" +cropImage: "Afbeelding bijsnijden" +cropImageAsk: "Bijsnijdengevraagd" _email: _follow: title: "volgde jou" @@ -371,6 +373,9 @@ _notification: renote: "Herdelen" quote: "Quote" reaction: "Reacties" + _actions: + reply: "Antwoord" + renote: "Herdelen" _deck: _columns: notifications: "Meldingen" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 78d86dd7e3..fa1dad2173 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -946,7 +946,6 @@ _sfx: chatBg: "Rozmowy (tลo)" channel: "Powiadomienia kanaลu" _ago: - unknown: "Nieznane" future: "W przyszลoลci" justNow: "Przed chwilฤ " secondsAgo: "{n} sek. temu" @@ -1401,6 +1400,9 @@ _notification: followRequestAccepted: "Przyjฤto proลbฤ o moลผliwoลฤ obserwacji" groupInvited: "Zaproszono do grup" app: "Powiadomienia z aplikacji" + _actions: + reply: "Odpowiedz" + renote: "Udostฤpnij" _deck: alwaysShowMainColumn: "Zawsze pokazuj gลรณwnฤ kolumnฤ" columnAlign: "Wyrรณwnaj kolumny" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 104e4ceb7c..0dc15a27bb 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -37,26 +37,117 @@ favorites: "Favoritar" unfavorite: "Remover dos favoritos" favorited: "Adicionado aos favoritos." alreadyFavorited: "Jรก adicionado aos favoritos." +cantFavorite: "Nรฃo foi possรญvel adicionar aos favoritos." +pin: "Afixar no perfil" +unpin: "Desafixar do perfil" +copyContent: "Copiar conteรบdos" +copyLink: "Copiar hiperligaรงรฃo" +delete: "Eliminar" +deleteAndEdit: "Eliminar e editar" +deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editรก-la? Irรกs perder todas as suas reaรงรตes, renotas e respostas." +addToList: "Adicionar a lista" +sendMessage: "Enviar uma mensagem" +copyUsername: "Copiar nome de utilizador" +searchUser: "Pesquisar utilizador" +reply: "Responder" +loadMore: "Carregar mais" showMore: "Ver mais" youGotNewFollower: "Vocรช tem um novo seguidor" +receiveFollowRequest: "Pedido de seguimento recebido" followRequestAccepted: "Pedido de seguir aceito" +mention: "Menรงรฃo" +mentions: "Menรงรตes" +directNotes: "Notas diretas" +importAndExport: "Importar/Exportar" +import: "Importar" +export: "Exportar" +files: "Ficheiros" +download: "Descarregar" +driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serรฃo tambรฉm apagadas." +unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?" +exportRequested: "Pediste uma exportaรงรฃo. Este processo pode demorar algum tempo. Serรก adicionado ร tua Drive apรณs a conclusรฃo do processo." +importRequested: "Pediste uma importaรงรฃo. Este processo pode demorar algum tempo." +lists: "Listas" +noLists: "Nรฃo tens nenhuma lista" note: "Post" notes: "Posts" +following: "Seguindo" +followers: "Seguidores" +followsYou: "Segue-te" +createList: "Criar lista" +manageLists: "Gerir listas" +error: "Erro" +somethingHappened: "Ocorreu um erro" +retry: "Tentar novamente" +pageLoadError: "Ocorreu um erro ao carregar a pรกgina." +pageLoadErrorDescription: "Isto รฉ normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente apรณs algum tempo." +serverIsDead: "O servidor nรฃo estรก respondendo. Por favor espere um pouco e tente novamente." +youShouldUpgradeClient: "Para visualizar essa pรกgina, por favor recarregue-a para atualizar seu cliente." +enterListName: "Insira um nome para a lista" +privacy: "Privacidade" +makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados" +defaultNoteVisibility: "Visibilidade padrรฃo" +follow: "Seguindo" +followRequest: "Mandar pedido de seguimento" +followRequests: "Pedidos de seguimento" +unfollow: "Deixar de seguir" +followRequestPending: "Pedido de seguimento pendente" enterEmoji: "Inserir emoji" renote: "Repostar" renoted: "Repostado" cantRenote: "Nรฃo pode repostar" cantReRenote: "Nรฃo pode repostar este repost" +quote: "Citar" pinnedNote: "Post fixado" +pinned: "Afixar no perfil" +you: "Vocรช" +clickToShow: "Clique para ver" sensitive: "Conteรบdo sensรญvel" +add: "Adicionar" +reaction: "Reaรงรตes" +reactionSetting: "Quais reaรงรตes a mostrar no selecionador de reaรงรตes" +rememberNoteVisibility: "Lembrar das configuraรงรตes de visibilidade de notas" +attachCancel: "Remover anexo" +markAsSensitive: "Marcar como sensรญvel" +unmarkAsSensitive: "Desmarcar como sensรญvel" +enterFileName: "Digite o nome do ficheiro" mute: "Silenciar" unmute: "Dessilenciar" +block: "Bloquear" +unblock: "Desbloquear" +suspend: "Suspender" +unsuspend: "Cancelar suspensรฃo" +blockConfirm: "Tem certeza que gostaria de bloquear essa conta?" +unblockConfirm: "Tem certeza que gostaria de desbloquear essa conta?" +suspendConfirm: "Tem certeza que gostaria de suspender essa conta?" +unsuspendConfirm: "Tem certeza que gostaria de cancelar a suspensรฃo dessa conta?" +selectList: "Escolhe uma lista" +selectAntenna: "Escolhe uma antena" +selectWidget: "Escolhe um widget" +editWidgets: "Editar widgets" +editWidgetsExit: "Pronto" +customEmojis: "Emoji personalizado" +emoji: "Emoji" +emojis: "Emojis" +emojiName: "Nome do Emoji" +emojiUrl: "URL do Emoji" +addEmoji: "Adicionar um Emoji" settingGuide: "Guia de configuraรงรฃo" +flagAsBot: "Marcar conta como robรด" +flagAsCat: "Marcar conta como gato" +flagAsCatDescription: "Ative essa opรงรฃo para marcar essa conta como gato." +flagShowTimelineReplies: "Mostrar respostas na linha de tempo" +general: "Geral" +wallpaper: "Papel de parede" +searchWith: "Buscar: {q}" +youHaveNoLists: "Nรฃo tem nenhuma lista" +followConfirm: "Tem certeza que quer deixar de seguir {name}?" instances: "Instรขncia" registeredAt: "Registrado em" perHour: "por hora" perDay: "por dia" noUsers: "Sem usuรกrios" +remove: "Eliminar" messageRead: "Lida" lightThemes: "Tema claro" darkThemes: "Tema escuro" @@ -64,6 +155,9 @@ addFile: "Adicionar arquivo" nsfw: "Conteรบdo sensรญvel" monthX: "mรชs de {month}" pinnedNotes: "Post fixado" +userList: "Listas" +none: "Nenhum" +output: "Resultado" smtpUser: "Nome de usuรกrio" smtpPass: "Senha" user: "Usuรกrios" @@ -72,9 +166,13 @@ _email: _follow: title: "Vocรช tem um novo seguidor" _mfm: + mention: "Menรงรฃo" + quote: "Citar" + emoji: "Emoji personalizado" search: "Pesquisar" _theme: keys: + mention: "Menรงรฃo" renote: "Repostar" _sfx: note: "Posts" @@ -82,15 +180,238 @@ _sfx: _widgets: notifications: "Notificaรงรตes" timeline: "Timeline" +_cw: + show: "Carregar mais" +_visibility: + followers: "Seguidores" _profile: username: "Nome de usuรกrio" _exportOrImport: + followingList: "Seguindo" muteList: "Silenciar" + blockingList: "Bloquear" + userLists: "Listas" +_pages: + blocks: + _button: + _action: + _pushEvent: + event: "Nome do evento" + message: "Mostrar mensagem quando ativado" + variable: "Variรกvel a mandar" + no-variable: "Nenhum" + callAiScript: "Invocar AiScript" + _callAiScript: + functionName: "Nome da funรงรฃo" + radioButton: "Escolha" + _radioButton: + values: "Lista de escolhas separadas por quebras de texto" + script: + categories: + logical: "Operaรงรฃo lรณgica" + operation: "Cรกlculos" + comparison: "Comparaรงรฃo" + list: "Listas" + blocks: + _strReplace: + arg2: "Texto que irรก ser substituรญdo" + arg3: "Substituir com" + strReverse: "Virar texto" + join: "Sequรชncia de texto" + _join: + arg1: "Listas" + arg2: "Separador" + add: "Somar" + _add: + arg1: "A" + arg2: "B" + subtract: "Subtrair" + _subtract: + arg1: "A" + arg2: "B" + multiply: "Multiplicar" + _multiply: + arg1: "A" + arg2: "B" + divide: "Dividir" + _divide: + arg1: "A" + arg2: "B" + mod: "O resto de" + _mod: + arg1: "A" + arg2: "B" + round: "Arredondar decimal" + _round: + arg1: "Numรฉrico" + eq: "A e B sรฃo iguais" + _eq: + arg1: "A" + arg2: "B" + notEq: "A e B sรฃo diferentes" + _notEq: + arg1: "A" + arg2: "B" + and: "A e B" + _and: + arg1: "A" + arg2: "B" + or: "A OU B" + _or: + arg1: "A" + arg2: "B" + lt: "< A รฉ menor do que B" + _lt: + arg1: "A" + arg2: "B" + gt: "> A รฉ maior do que B" + _gt: + arg1: "A" + arg2: "B" + ltEq: "<= A รฉ maior ou igual a B" + _ltEq: + arg1: "A" + arg2: "B" + gtEq: ">= A รฉ maior ou igual a B" + _gtEq: + arg1: "A" + arg2: "B" + if: "Galho" + _if: + arg1: "Se" + arg2: "Entรฃo" + arg3: "Se nรฃo" + not: "NรO" + _not: + arg1: "NรO" + random: "Aleatรณrio" + _random: + arg1: "Probabilidade" + rannum: "Numeral aleatรณrio" + _rannum: + arg1: "Valor mรญnimo" + arg2: "Valor mรกximo" + randomPick: "Escolher aleatoriamente de uma lista" + _randomPick: + arg1: "Listas" + dailyRandom: "Aleatรณrio (Muda uma vez por dia para cada usuรกrio)" + _dailyRandom: + arg1: "Probabilidade" + dailyRannum: "Numeral aleatรณrio (Muda uma vez por dia para cada usuรกrio)" + _dailyRannum: + arg1: "Valor mรญnimo" + arg2: "Valor mรกximo" + dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuรกrio)" + _dailyRandomPick: + arg1: "Listas" + seedRandom: "Aleatรณrio (com semente)" + _seedRandom: + arg1: "Semente" + arg2: "Probabilidade" + seedRannum: "Nรบmero aleatรณrio (com semente)" + _seedRannum: + arg1: "Semente" + arg2: "Valor mรญnimo" + arg3: "Valor mรกximo" + seedRandomPick: "Escolher aleatoriamente de uma lista (com uma semente)" + _seedRandomPick: + arg1: "Semente" + arg2: "Listas" + DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuรกrio)" + _DRPWPM: + arg1: "Lista de texto" + pick: "Escolhe a partir da lista" + _pick: + arg1: "Listas" + arg2: "Posiรงรฃo" + listLen: "Pegar comprimento da lista" + _listLen: + arg1: "Listas" + number: "Numรฉrico" + stringToNumber: "Texto para numรฉrico" + _stringToNumber: + arg1: "Texto" + numberToString: "Numรฉrico para texto" + _numberToString: + arg1: "Numรฉrico" + splitStrByLine: "Dividir texto por quebras" + _splitStrByLine: + arg1: "Texto" + ref: "Variรกvel" + aiScriptVar: "Variรกvel AiScript" + fn: "Funรงรฃo" + _fn: + slots: "Espaรงos" + slots-info: "Separar cada espaรงo com uma quebra de texto" + arg1: "Resultado" + for: "Repetiรงรฃo 'for'" + _for: + arg1: "Nรบmero de repetiรงรตes" + arg2: "Aรงรฃo" + typeError: "Espaรงo {slot} aceita valores de tipo \"{expect}\", mas o valor dado รฉ do tipo \"{actual}\"!" + thereIsEmptySlot: "O espaรงo {slot} estรก vazio!" + types: + string: "Texto" + number: "Numรฉrico" + array: "Listas" + stringArray: "Lista de texto" + emptySlot: "Espaรงo vazio" + enviromentVariables: "Variรกveis de ambiente" + pageVariables: "Variรกveis de pรกgina" +_relayStatus: + requesting: "Pendente" + accepted: "Aprovado" + rejected: "Recusado" _notification: + fileUploaded: "Carregamento de arquivo efetuado com sucesso" + youGotMention: "{name} te mencionou" + youGotReply: "{name} te respondeu" + youGotQuote: "{name} te citou" + youGotPoll: "{name} votou em sua enquete" + youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo" + youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}" youWereFollowed: "Vocรช tem um novo seguidor" + youReceivedFollowRequest: "Vocรช recebeu um pedido de seguimento" + yourFollowRequestAccepted: "Seu pedido de seguimento foi aceito" + youWereInvitedToGroup: "{userName} te convidou para um grupo" + pollEnded: "Os resultados da enquete agora estรฃo disponรญveis" + emptyPushNotificationMessage: "As notificaรงรตes de alerta foram atualizadas" _types: + all: "Todos" + follow: "Seguindo" + mention: "Menรงรฃo" + reply: "Respostas" + renote: "Repostar" + quote: "Citar" + reaction: "Reaรงรตes" + pollVote: "Votaรงรตes em enquetes" + pollEnded: "Enquetes terminando" + receiveFollowRequest: "Recebeu pedidos de seguimento" + followRequestAccepted: "Aceitou pedidos de seguimento" + groupInvited: "Convites de grupo" + app: "Notificaรงรตes de aplicativos conectados" + _actions: + followBack: "te seguiu de volta" + reply: "Responder" renote: "Repostar" _deck: + alwaysShowMainColumn: "Sempre mostrar a coluna principal" + columnAlign: "Alinhar colunas" + columnMargin: "Margem entre colunas" + columnHeaderHeight: "Altura do cabeรงalho de coluna" + addColumn: "Adicionar coluna" + swapLeft: "Trocar de posiรงรฃo com a coluna ร esquerda" + swapRight: "Trocar de posiรงรฃo com a coluna ร direita" + swapUp: "Trocar de posiรงรฃo com a coluna acima" + swapDown: "Trocar de posiรงรฃo com a coluna abaixo" + popRight: "Acoplar coluna ร direita" + profile: "Perfil" _columns: + main: "Principal" + widgets: "Widgets" notifications: "Notificaรงรตes" tl: "Timeline" + antenna: "Antenas" + list: "Listas" + mentions: "Menรงรตes" + direct: "Notas diretas" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 6b2ff19e8e..cc74756119 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -562,13 +562,87 @@ plugins: "Pluginuri" deck: "Deck" undeck: "Pฤrฤseศte Deck" useBlurEffectForModal: "Foloseศte efect de blur pentru modale" +width: "Lฤลฃime" +height: "รnฤlลฃime" +large: "Mare" +medium: "Mediu" +small: "Mic" +generateAccessToken: "Genereazฤ token de acces" +permission: "Permisiuni" +enableAll: "Acteveazฤ tot" +disableAll: "Dezactiveazฤ tot" +tokenRequested: "Acordฤ acces la cont" +pluginTokenRequestedDescription: "Acest plugin va putea sฤ foloseascฤ permisiunile setate aici." +notificationType: "Tipul notificฤrii" +edit: "Editeazฤ" +useStarForReactionFallback: "Foloseศte โ ca fallback dacฤ emoji-ul este necunoscut" +emailServer: "Server email" +enableEmail: "Activeazฤ distribuศia de emailuri" +emailConfigInfo: "Folosit pentru a confirma emailul tฤu รฎn timpul logฤri dacฤ รฎศi uiศi parola" +email: "Email" +emailAddress: "Adresฤ de email" +smtpConfig: "Configurare Server SMTP" smtpHost: "Gazdฤ" +smtpPort: "Port" smtpUser: "Nume de utilizator" smtpPass: "Parolฤ" +emptyToDisableSmtpAuth: "Lasฤ username-ul ศi parola necompletate pentru a dezactiva verificarea SMTP" +smtpSecure: "Foloseศte SSL/TLS implicit pentru conecศiunile SMTP" +smtpSecureInfo: "Opreศte opศiunea asta dacฤ STARTTLS este folosit" +testEmail: "Testeazฤ livrarea emailurilor" +wordMute: "Cuvinte pe mut" +regexpError: "Eroare de Expresie Regulatฤ" +regexpErrorDescription: "A apฤrut o eroare รฎn expresia regulatฤ pe linia {line} al cuvintelor {tab} setate pe mut:" +instanceMute: "Instanศe pe mut" +userSaysSomething: "{name} a spus ceva" +makeActive: "Activeazฤ" +display: "Aratฤ" +copy: "Copiazฤ" +metrics: "Metrici" +overview: "Privire de ansamblu" +logs: "Log-uri" +delayed: "รntรขrziate" +database: "Baza de date" +channel: "Canale" +create: "Creazฤ" +notificationSetting: "Setฤri notificฤri" +notificationSettingDesc: "Selecteazฤ tipurile de notificฤri care sฤ fie arฤtate" +useGlobalSetting: "Foloseศte setฤrile globale" +useGlobalSettingDesc: "Dacฤ opศiunea e pornitฤ, notificฤrile contului tฤu vor fi folosite. Dacฤ e opritฤ, configuraศia va fi individualฤ." +other: "Altele" +regenerateLoginToken: "Regenereazฤ token de login" +regenerateLoginTokenDescription: "Regenereazฤ token-ul folosit intern รฎn timpul logฤri. รn mod normal asta nu este necesar. Odatฤ regenerat, toate dispozitivele vor fi delogate." +setMultipleBySeparatingWithSpace: "Separฤ mai multe intrฤri cu spaศii." +fileIdOrUrl: "Introdu ID sau URL" +behavior: "Comportament" +sample: "exemplu" +abuseReports: "Rapoarte" +reportAbuse: "Raporteazฤ" +reportAbuseOf: "Raporteazฤ {name}" +fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacฤ este despre o notฤ specificฤ, te rog introdu URL-ul ei." +abuseReported: "Raportul tฤu a fost trimis. Mulศumim." +reporter: "Raportorul" +reporteeOrigin: "Originea raportatului" +reporterOrigin: "Originea raportorului" +forwardReport: "Redirecศioneazฤ raportul cฤtre instanศa externฤ" +forwardReportIsAnonymous: "รn locul contului tฤu, va fi afiศat un cont anonim, de sistem, ca raportor cฤtre instanศa externฤ." +send: "Trimite" +abuseMarkAsResolved: "Marcheazฤ raportul ca rezolvat" +openInNewTab: "Deschide รฎn tab nou" +openInSideView: "Deschide รฎn vedere lateralฤ" +defaultNavigationBehaviour: "Comportament de navigare implicit" +editTheseSettingsMayBreakAccount: "Editarea acestor setฤri รฎศi pot defecta contul." +waitingFor: "Aศteptรขnd pentru {x}" +random: "Aleator" +system: "Sistem" +switchUi: "Schimbฤ UI" +desktop: "Desktop" clearCache: "Goleศte cache-ul" info: "Despre" user: "Utilizatori" administration: "Gestionare" +middle: "Mediu" +sent: "Trimite" searchByGoogle: "Cautฤ" _email: _follow: @@ -641,6 +715,9 @@ _notification: renote: "Re-noteazฤ" quote: "Citeazฤ" reaction: "Reacศie" + _actions: + reply: "Rฤspunde" + renote: "Re-noteazฤ" _deck: _columns: notifications: "Notificฤri" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 877e1e185d..c44589a7e5 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -141,6 +141,8 @@ flagAsBot: "ะะบะบะฐัะฝั ะฑะพัะฐ" flagAsBotDescription: "ะะบะปััะธัะต, ะตัะปะธ ััะพั ะฐะบะบะฐัะฝั ัะฟัะฐะฒะปัะตััั ะฟัะพะณัะฐะผะผะพะน. ะญัะพ ะฟะพะทะฒะพะปะธั ัะธััะตะผะต Misskey ััะธััะฒะฐัั ััะพ, ะฐ ัะฐะบะถะต ะฟะพะผะพะถะตั ัะฐะทัะฐะฑะพััะธะบะฐะผ ะดััะณะธั ะฑะพัะพะฒ ะฟัะตะดะพัะฒัะฐัะธัั ะฑะตัะบะพะฝะตัะฝัะต ัะธะบะปั ะฒะทะฐะธะผะพะดะตะนััะฒะธั." flagAsCat: "ะะบะบะฐัะฝั ะบะพัะฐ" flagAsCatDescription: "ะะบะปััะธัะต, ะธ ััะพั ะฐะบะบะฐัะฝั ะฑัะดะตั ะฟะพะผะตัะตะฝ ะบะฐะบ ะบะพัะฐัะธะน." +flagShowTimelineReplies: "ะะพะบะฐะทัะฒะฐัั ะพัะฒะตัั ะฝะฐ ะทะฐะผะตัะบะธ ะฒ ะปะตะฝัะต" +flagShowTimelineRepliesDescription: "ะัะปะธ ััะพั ะฟะฐัะฐะผะตัั ะฒะบะปััะตะฝ, ัะพ ะฒ ะปะตะฝัะต, ะฒ ะดะพะฟะพะปะฝะตะฝะธะต ะบ ะทะฐะผะตัะบะฐะผ ะฟะพะปัะทะพะฒะฐัะตะปั, ะพัะพะฑัะฐะถะฐัััั ะพัะฒะตัั ะฝะฐ ะดััะณะธะต ะทะฐะผะตัะบะธ ะฟะพะปัะทะพะฒะฐัะตะปั." autoAcceptFollowed: "ะัะธะฝะธะผะฐัั ะฟะพะดะฟะธััะธะบะพะฒ ะฐะฒัะพะผะฐัะธัะตัะบะธ" addAccount: "ะะพะฑะฐะฒะธัั ััััะฝัั ะทะฐะฟะธัั" loginFailed: "ะะตัะดะฐัะฝะฐั ะฟะพะฟััะบะฐ ะฒั ะพะดะฐ" @@ -236,6 +238,7 @@ saved: "ะกะพั ัะฐะฝะตะฝะพ" messaging: "ะกะพะพะฑัะตะฝะธั" upload: "ะะฐะณััะทะธัั" keepOriginalUploading: "ะกะพั ัะฐะฝะธัั ะธัั ะพะดะฝะพะต ะธะทะพะฑัะฐะถะตะฝะธะต" +keepOriginalUploadingDescription: "ะกะพั ัะฐะฝัะตั ะธัั ะพะดะฝัั ะฒะตััะธั ะฟัะธ ะทะฐะณััะทะบะต ะธะทะพะฑัะฐะถะตะฝะธะน. ะัะปะธ ะฒัะบะปััะธัั, ัะพ ะฟัะธ ะทะฐะณััะทะบะต ะฑัะฐัะทะตั ะณะตะฝะตัะธััะตั ะธะทะพะฑัะฐะถะตะฝะธะต ะดะปั ะฟัะฑะปะธะบะฐัะธะธ." fromDrive: "ะก ยซะดะธัะบะฐยป" fromUrl: "ะะพ ัััะปะบะต" uploadFromUrl: "ะะฐะณััะทะธัั ะฟะพ ัััะปะบะต" @@ -589,6 +592,7 @@ smtpSecure: "ะัะฟะพะปัะทะพะฒะฐัั SSL/TLS ะดะปั SMTP-ัะพะตะดะธะฝะตะฝะธะน" smtpSecureInfo: "ะัะบะปััะธัะต ะฟัะธ ะธัะฟะพะปัะทะพะฒะฐะฝะธะธ STARTTLS." testEmail: "ะัะพะฒะตัะบะฐ ะดะพััะฐะฒะบะธ ัะปะตะบััะพะฝะฝะพะน ะฟะพััั" wordMute: "ะกะบัััะธะต ัะปะพะฒ" +regexpError: "ะัะธะฑะบะฐ ะฒ ัะตะณัะปััะฝะพะผ ะฒััะฐะถะตะฝะธะธ" instanceMute: "ะะปััะตะฝะธะต ะธะฝััะฐะฝัะพะฒ" userSaysSomething: "{name} ััะพ-ัะพ ัะพะพะฑัะฐะตั" makeActive: "ะะบัะธะฒะธัะพะฒะฐัั" @@ -619,6 +623,8 @@ fillAbuseReportDescription: "ะะฟะธัะธัะต, ะฟะพะถะฐะปัะนััะฐ, ะฟัะธัะธะฝ abuseReported: "ะะฐะปะพะฑะฐ ะพัะฟัะฐะฒะปะตะฝะฐ. ะะพะปััะพะต ัะฟะฐัะธะฑะพ ะทะฐ ะธะฝัะพัะผะฐัะธั." reporteeOrigin: "ะ ะบะพะผ ัะพะพะฑัะตะฝะพ" reporterOrigin: "ะัะพ ัะพะพะฑัะธะป" +forwardReport: "ะะตัะตะฝะฐะฟัะฐะฒะปะตะฝะธะต ะพััะตัะฐ ะฝะฐ ะธะฝััะฐะฝั." +forwardReportIsAnonymous: "ะฃะดะฐะปะตะฝะฝัะน ะธะฝััะฐะฝั ะฝะต ัะผะพะถะตั ัะฒะธะดะตัั ะฒะฐัั ะธะฝัะพัะผะฐัะธั ะธ ะฑัะดะตั ะพัะพะฑัะฐะถะฐัััั ะบะฐะบ ะฐะฝะพะฝะธะผะฝะฐั ัะธััะตะผะฝะฐั ััะตัะฝะฐั ะทะฐะฟะธัั." send: "ะัะฟัะฐะฒะธัั" abuseMarkAsResolved: "ะัะผะตัะธัั ะถะฐะปะพะฑั ะบะฐะบ ัะตััะฝะฝัั" openInNewTab: "ะัะบัััั ะฒ ะฝะพะฒะพะน ะฒะบะปะฐะดะบะต" @@ -815,7 +821,16 @@ leaveGroupConfirm: "ะะพะบะธะฝััั ะณััะฟะฟั ยซ{name}ยป?" useDrawerReactionPickerForMobile: "ะัะดะฒะธะถะฝะฐั ะฟะฐะปะธััะฐ ะฝะฐ ะผะพะฑะธะปัะฝะพะผ ััััะพะนััะฒะต" welcomeBackWithName: "ะก ะฒะพะทะฒัะฐัะตะฝะธะตะผ, {name}!" clickToFinishEmailVerification: "ะะพะถะฐะปัะนััะฐ, ะฝะฐะถะผะธัะต [{ok}], ััะพะฑั ะทะฐะฒะตััะธัั ะฟะพะดัะฒะตัะถะดะตะฝะธะต ะฐะดัะตัะฐ ัะปะตะบััะพะฝะฝะพะน ะฟะพััั." +overridedDeviceKind: "ะขะธะฟ ััััะพะนััะฒะฐ" +smartphone: "ะกะผะฐัััะพะฝ" +tablet: "ะะปะฐะฝัะตั" +auto: "ะะฒัะพะผะฐัะธัะตัะบะธ" +themeColor: "ะฆะฒะตั ัะตะผั" +size: "ะ ะฐะทะผะตั" +numberOfColumn: "ะะพะปะธัะตััะฒะพ ััะพะปะฑัะพะฒ" searchByGoogle: "ะะพะธัะบ" +instanceDefaultLightTheme: "ะกะฒะตัะปะฐั ัะตะผะฐ ะฟะพ ัะผะพะปัะฐะฝะธั" +instanceDefaultDarkTheme: "ะขะตะผะฝะฐั ัะตะผะฐ ะฟะพ ัะผะพะปัะฐะฝะธั" indefinitely: "ะฒะตัะฝะพ" _emailUnavailable: used: "ะฃะถะต ะธัะฟะพะปัะทัะตััั" @@ -1059,7 +1074,6 @@ _sfx: antenna: "ะะฝัะตะฝะฝะฐ" channel: "ะะฐะฝะฐะป" _ago: - unknown: "ะะพะณะดะฐ-ัะพ" future: "ะะท ะฑัะดััะตะณะพ" justNow: "ะขะพะปัะบะพ ััะพ" secondsAgo: "{n} ั ะฝะฐะทะฐะด" @@ -1599,6 +1613,9 @@ _notification: followRequestAccepted: "ะะฐะฟัะพั ะฝะฐ ะฟะพะดะฟะธัะบั ะพะดะพะฑัะตะฝ" groupInvited: "ะัะธะณะปะฐัะตะฝะธะต ะฒ ะณััะฟะฟั" app: "ะฃะฒะตะดะพะผะปะตะฝะธั ะธะท ะฟัะธะปะพะถะตะฝะธะน" + _actions: + reply: "ะัะฒะตัะธัั" + renote: "ะ ะตะฟะพัั" _deck: alwaysShowMainColumn: "ะัะตะณะดะฐ ะฟะพะบะฐะทัะฒะฐัั ะณะปะฐะฒะฝัั ะบะพะปะพะฝะบั" columnAlign: "ะััะฐะฒะฝะธะฒะฐะฝะธะต ะบะพะปะพะฝะพะบ" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index c6f2f59bdf..dc1151522e 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -841,6 +841,7 @@ oneDay: "1 deล" oneWeek: "1 tรฝลพdeล" reflectMayTakeTime: "Zmeny mรดลพu chvรญฤพu trvaลฅ kรฝm sa prejavia." failedToFetchAccountInformation: "Nepodarilo sa naฤรญtaลฅ informรกcie o รบฤte." +rateLimitExceeded: "Prekroฤenรฝ limit rรฝchlosti" _emailUnavailable: used: "Tรกto emailovรก adresa sa uลพ pouลพรญva" format: "Formรกt emailovej adresy je nesprรกvny" @@ -1086,7 +1087,6 @@ _sfx: antenna: "Antรฉny" channel: "Upozornenia kanรกla" _ago: - unknown: "Neznรกme" future: "Budรบcnosลฅ" justNow: "Teraz" secondsAgo: "pred {n} sekundami" @@ -1130,6 +1130,7 @@ _2fa: registerKey: "Registrovaลฅ bezpeฤnostnรฝ kฤพรบฤ" step1: "Najprv si nainลกtalujte autentifikaฤnรบ aplikรกciu (naprรญklad {a} alebo {b}) na svoje zariadenie." step2: "Potom, naskenujte QR kรณd zobrazenรฝ na obrazovke." + step2Url: "Do aplikรกcie zadajte nasledujรบcu URL adresu:" step3: "Nastavenie dokonฤรญte zadanรญm tokenu z vaลกej aplikรกcie." step4: "Od teraz, vลกetky ฤalลกie prihlรกsenia budรบ vyลพadovaลฅ prihlasovacรญ token." securityKeyInfo: "Okrem odtlaฤku prsta alebo PIN autentifikรกcie si mรดลพete nastaviลฅ autentifikรกciu cez hardvรฉrovรฝ bezpeฤnostnรฝ kฤพรบฤ podporujรบci FIDO2 a tak eลกte viac zabezpeฤiลฅ svoj รบฤet." @@ -1628,6 +1629,10 @@ _notification: followRequestAccepted: "Schvรกlenรฉ ลพiadosti o sledovanie" groupInvited: "Pozvรกnky do skupรญn" app: "Oznรกmenia z prepojenรฝch aplikรกciรญ" + _actions: + followBack: "Sledovaลฅ spรคลฅ\n" + reply: "Odpovedaลฅ" + renote: "Preposlaลฅ" _deck: alwaysShowMainColumn: "Vลพdy zobraziลฅ v hlavnom stฤบpci" columnAlign: "Zarovnaลฅ stฤบpce" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml new file mode 100644 index 0000000000..42bfa45f25 --- /dev/null +++ b/locales/sv-SE.yml @@ -0,0 +1,319 @@ +--- +_lang_: "Svenska" +headlineMisskey: "Ett nรคtverk kopplat av noter" +introMisskey: "Vรคlkommen! Misskey รคr en รถppen och decentraliserad mikrobloggningstjรคnst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. ๐ก\nMed \"reaktioner\" kan du snabbt uttrycka dina kรคnslor kring andras noter.๐\nLรฅt oss utforska en nya vรคrld!๐" +monthAndDay: "{day}/{month}" +search: "Sรถk" +notifications: "Notifikationer" +username: "Anvรคndarnamn" +password: "Lรถsenord" +forgotPassword: "Glรถmt lรถsenord" +fetchingAsApObject: "Hรคmtar frรฅn Fediversum..." +ok: "OK" +gotIt: "Uppfattat!" +cancel: "Avbryt" +enterUsername: "Ange anvรคndarnamn" +renotedBy: "Omnoterad av {user}" +noNotes: "Inga noteringar" +noNotifications: "Inga aviseringar" +instance: "Instanser" +settings: "Instรคllningar" +basicSettings: "Basinstรคllningar" +otherSettings: "Andra instรคllningar" +openInWindow: "รppna i ett fรถnster" +profile: "Profil" +timeline: "Tidslinje" +noAccountDescription: "Anvรคndaren har inte skrivit en biografi รคn." +login: "Logga in" +loggingIn: "Loggar in" +logout: "Logga ut" +signup: "Registrera" +uploading: "Uppladdning sker..." +save: "Spara" +users: "Anvรคndare" +addUser: "Lรคgg till anvรคndare" +favorite: "Lรคgg till i favoriter" +favorites: "Favoriter" +unfavorite: "Avfavorisera" +favorited: "Tillagd i favoriter." +alreadyFavorited: "Redan tillagd i favoriter." +cantFavorite: "Gick inte att lรคgga till i favoriter." +pin: "Fรคst till profil" +unpin: "Lossa frรฅn profil" +copyContent: "Kopiera innehรฅll" +copyLink: "Kopiera lรคnk" +delete: "Radera" +deleteAndEdit: "Radera och รคndra" +deleteAndEditConfirm: "รr du sรคker att du vill radera denna not och รคndra den? Du kommer fรถrlora alla reaktioner, omnoteringar och svar till den." +addToList: "Lรคgg till i lista" +sendMessage: "Skicka ett meddelande" +copyUsername: "Kopiera anvรคndarnamn" +searchUser: "Sรถk anvรคndare" +reply: "Svara" +loadMore: "Ladda mer" +showMore: "Visa mer" +youGotNewFollower: "fรถljde dig" +receiveFollowRequest: "Fรถljarfรถrfrรฅgan mottagen" +followRequestAccepted: "Fรถljarfรถrfrรฅgan accepterad" +mention: "Nรคmn" +mentions: "Omnรคmningar" +directNotes: "Direktnoter" +importAndExport: "Importera / Exportera" +import: "Importera" +export: "Exportera" +files: "Filer" +download: "Nedladdning" +driveFileDeleteConfirm: "รr du sรคker att du vill radera filen \"{name}\"? Noter med denna fil bifogad kommer ocksรฅ raderas." +unfollowConfirm: "รr du sรคker att du vill avfรถlja {name}?" +exportRequested: "Du har begรคrt en export. Detta kan ta lite tid. Den kommer lรคggas till i din Drive nรคr den blir klar." +importRequested: "Du har begรคrt en import. Detta kan ta lite tid." +lists: "Listor" +noLists: "Du har inga listor" +note: "Not" +notes: "Noter" +following: "Fรถljer" +followers: "Fรถljare" +followsYou: "Fรถljer dig" +createList: "Skapa lista" +manageLists: "Hantera lista" +error: "Fel!" +somethingHappened: "Ett fel har uppstรฅtt" +retry: "Fรถrsรถk igen" +pageLoadError: "Det gick inte att ladda sidan." +pageLoadErrorDescription: "Detta hรคnder oftast p.g.a. nรคtverksfel eller din webblรคsarcache. Fรถrsรถk tรถmma din cache och testa sedan igen efter en liten stund." +serverIsDead: "Servern svarar inte. Vรคnta ett litet tag och fรถrsรถk igen." +youShouldUpgradeClient: "Fรถr att kunna se denna sida, vรคnligen ladda om sidan fรถr att uppdatera din klient." +enterListName: "Skriv ett namn till listan" +privacy: "Integritet" +makeFollowManuallyApprove: "Fรถljarfรถrfrรฅgningar krรคver manuellt godkรคnnande" +defaultNoteVisibility: "Standardsynlighet" +follow: "Fรถlj" +followRequest: "Skicka fรถljarfรถrfrรฅgan" +followRequests: "Fรถljarfรถrfrรฅgningar" +unfollow: "Avfรถlj" +followRequestPending: "Fรถljarfรถrfrรฅgning avvaktar fรถr svar" +enterEmoji: "Skriv en emoji" +renote: "Omnotera" +unrenote: "Ta tillbaka omnotering" +renoted: "Omnoterad." +cantRenote: "Inlรคgget kunde inte bli omnoterat." +cantReRenote: "En omnotering kan inte bli omnoterad." +quote: "Citat" +pinnedNote: "Fรคstad not" +pinned: "Fรคst till profil" +you: "Du" +clickToShow: "Klicka fรถr att visa" +sensitive: "Kรคnsligt innehรฅll" +add: "Lรคgg till" +reaction: "Reaktioner" +reactionSetting: "Reaktioner som ska visas i reaktionsvรคljaren" +reactionSettingDescription2: "Dra fรถr att omordna, klicka fรถr att radera, tryck \"+\" fรถr att lรคgga till." +rememberNoteVisibility: "Komihรฅg notvisningsinstรคllningar" +attachCancel: "Ta bort bilaga" +markAsSensitive: "Markera som kรคnsligt innehรฅll" +unmarkAsSensitive: "Avmarkera som kรคnsligt innehรฅll" +enterFileName: "Ange filnamn" +mute: "Tysta" +unmute: "Avtysta" +block: "Blockera" +unblock: "Avblockera" +suspend: "Suspendera" +unsuspend: "Ta bort suspenderingen" +blockConfirm: "รr du sรคker att du vill blockera kontot?" +unblockConfirm: "รr du sรคkert att du vill avblockera kontot?" +suspendConfirm: "รr du sรคker att du vill suspendera detta konto?" +unsuspendConfirm: "รr du sรคker att du vill avsuspendera detta konto?" +selectList: "Vรคlj lista" +selectAntenna: "Vรคlj en antenn" +selectWidget: "Vรคlj en widget" +editWidgets: "Redigera widgets" +editWidgetsExit: "Avsluta redigering" +customEmojis: "Anpassa emoji" +emoji: "Emoji" +emojis: "Emoji" +emojiName: "Emoji namn" +emojiUrl: "Emoji lรคnk" +addEmoji: "Lรคgg till emoji" +settingGuide: "Rekommenderade instรคllningar" +cacheRemoteFiles: "Spara externa filer till cachen" +cacheRemoteFilesDescription: "Nรคr denna instรคllning รคr avstรคngd kommer externa filer laddas direkt frรฅn den externa instansen. Genom att stรคnga av detta kommer lagringsutrymme minska i anvรคndning men kommer รถka datatrafiken eftersom miniatyrer inte kommer genereras." +flagAsBot: "Markera konto som bot" +flagAsBotDescription: "Aktivera det hรคr alternativet om kontot รคr kontrollerat av ett program. Om aktiverat kommer den fungera som en flagga fรถr andra utvecklare fรถr att hindra รคndlรถsa kedjor med andra bottar. Det kommer ocksรฅ fรฅ Misskeys interna system att hantera kontot som en bot." +flagAsCat: "Markera konto som katt" +flagAsCatDescription: "Aktivera denna instรคllning fรถr att markera kontot som en katt." +flagShowTimelineReplies: "Visa svar i tidslinje" +flagShowTimelineRepliesDescription: "Visar anvรคndarsvar till andra anvรคndares noter i tidslinjen om pรฅslagen." +autoAcceptFollowed: "Godkรคnn fรถljarfรถrfrรฅgningar frรฅn anvรคndare du fรถljer automatiskt" +addAccount: "Lรคgg till konto" +loginFailed: "Inloggningen misslyckades" +showOnRemote: "Se pรฅ extern instans" +general: "Allmรคnt" +wallpaper: "Bakgrundsbild" +setWallpaper: "Vรคlj bakgrund" +removeWallpaper: "Ta bort bakgrund" +searchWith: "Sรถk: {q}" +youHaveNoLists: "Du har inga listor" +followConfirm: "รr du sรคker att du vill fรถlja {name}?" +proxyAccount: "Proxykonto" +proxyAccountDescription: "Ett proxykonto รคr ett konto som agerar som en extern fรถljare fรถr anvรคndare under vissa villkor. Till exempel, nรคr en anvรคndare lรคgger till en extern anvรคndare till en lista sรฅ kommer den externa anvรคndarens aktivitet inte levireras till instansen om ingen lokal anvรคndare fรถljer det kontot, sรฅ proxykontot anvรคnds istรคllet." +host: "Vรคrd" +selectUser: "Vรคlj anvรคndare" +recipient: "Mottagare" +annotation: "Kommentarer" +federation: "Federation" +instances: "Instanser" +registeredAt: "Registrerad pรฅ" +latestRequestSentAt: "Senaste fรถrfrรฅgan skickad" +latestRequestReceivedAt: "Senaste begรคran mottagen" +latestStatus: "Senaste status" +storageUsage: "Anvรคnt lagringsutrymme" +charts: "Diagram" +perHour: "Per timme" +perDay: "Per dag" +stopActivityDelivery: "Sluta skicka aktiviteter" +blockThisInstance: "Blockera instans" +operations: "Operationer" +software: "Mjukvara" +version: "Version" +metadata: "Metadata" +withNFiles: "{n} fil(er)" +monitor: "รvervakning" +jobQueue: "Jobbkรถ" +cpuAndMemory: "CPU och minne" +network: "Nรคtverk" +disk: "Disk" +instanceInfo: "Instansinformation" +statistics: "Statistik" +clearQueue: "Rensa kรถ" +clearQueueConfirmTitle: "รr du sรคker att du vill rensa kรถn?" +clearQueueConfirmText: "Om nรฅgon not รคr olevererad i kรถn kommer den inte federeras. Vanligtvis behรถvs inte denna handling." +clearCachedFiles: "Rensa cache" +clearCachedFilesConfirm: "รr du sรคker att du vill radera alla cachade externa filer?" +blockedInstances: "Blockerade instanser" +blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera. Listade instanser kommer inte lรคngre kommunicera med denna instans." +muteAndBlock: "Tystningar och blockeringar" +mutedUsers: "Tystade anvรคndare" +blockedUsers: "Blockerade anvรคndare" +noUsers: "Det finns inga anvรคndare" +editProfile: "Redigera profil" +noteDeleteConfirm: "รr du sรคker pรฅ att du vill ta bort denna not?" +pinLimitExceeded: "Du kan inte fรคsta fler noter" +intro: "Misskey har installerats! Vรคnligen skapa en adminanvรคndare." +done: "Klar" +processing: "Bearbetar..." +preview: "Fรถrhandsvisning" +default: "Standard" +noCustomEmojis: "Det finns ingen emoji" +noJobs: "Det finns inga jobb" +federating: "Federerar" +blocked: "Blockerad" +suspended: "Suspenderad" +all: "Allt" +subscribing: "Prenumererar" +publishing: "Publiceras" +notResponding: "Svarar inte" +instanceFollowing: "Fรถljer pรฅ instans" +instanceFollowers: "Fรถljare av instans" +instanceUsers: "Anvรคndare av denna instans" +changePassword: "รndra lรถsenord" +security: "Sรคkerhet" +retypedNotMatch: "Inmatningen matchar inte" +currentPassword: "Nuvarande lรถsenord" +newPassword: "Nytt lรถsenord" +newPasswordRetype: "Bekrรคfta lรถsenord" +attachFile: "Bifoga filer" +more: "Mer!" +featured: "Utvalda" +usernameOrUserId: "Anvรคndarnamn eller anvรคndar-id" +noSuchUser: "Kan inte hitta anvรคndaren" +lookup: "Sรถkning" +announcements: "Nyheter" +imageUrl: "Bild-URL" +remove: "Radera" +removed: "Borttaget" +removeAreYouSure: "รr du sรคker att du vill radera \"{x}\"?" +deleteAreYouSure: "รr du sรคker att du vill radera \"{x}\"?" +resetAreYouSure: "Vill du รฅterstรคlla?" +saved: "Sparad" +messaging: "Chatt" +upload: "Ladda upp" +keepOriginalUploading: "Behรฅll originalbild" +nsfw: "Kรคnsligt innehรฅll" +pinnedNotes: "Fรคstad not" +userList: "Listor" +smtpHost: "Vรคrd" +smtpUser: "Anvรคndarnamn" +smtpPass: "Lรถsenord" +clearCache: "Rensa cache" +user: "Anvรคndare" +searchByGoogle: "Sรถk" +_email: + _follow: + title: "fรถljde dig" +_mfm: + mention: "Nรคmn" + quote: "Citat" + emoji: "Anpassa emoji" + search: "Sรถk" +_theme: + keys: + mention: "Nรคmn" + renote: "Omnotera" +_sfx: + note: "Noter" + notification: "Notifikationer" + chat: "Chatt" +_widgets: + notifications: "Notifikationer" + timeline: "Tidslinje" + federation: "Federation" + jobQueue: "Jobbkรถ" +_cw: + show: "Ladda mer" +_visibility: + followers: "Fรถljare" +_profile: + username: "Anvรคndarnamn" +_exportOrImport: + followingList: "Fรถljer" + muteList: "Tysta" + blockingList: "Blockera" + userLists: "Listor" +_charts: + federation: "Federation" +_pages: + script: + categories: + list: "Listor" + blocks: + _join: + arg1: "Listor" + _randomPick: + arg1: "Listor" + _dailyRandomPick: + arg1: "Listor" + _seedRandomPick: + arg2: "Listor" + _pick: + arg1: "Listor" + _listLen: + arg1: "Listor" + types: + array: "Listor" +_notification: + youWereFollowed: "fรถljde dig" + _types: + follow: "Fรถljer" + mention: "Nรคmn" + renote: "Omnotera" + quote: "Citat" + reaction: "Reaktioner" + _actions: + reply: "Svara" + renote: "Omnotera" +_deck: + _columns: + notifications: "Notifikationer" + tl: "Tidslinje" + list: "Listor" + mentions: "Omnรคmningar" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 073b2c310e..7e7ef8685f 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -7,6 +7,7 @@ search: "ะะพััะบ" notifications: "ะกะฟะพะฒััะตะฝะฝั" username: "ะะผ'ั ะบะพัะธัััะฒะฐัะฐ" password: "ะะฐัะพะปั" +forgotPassword: "ะฏ ะทะฐะฑัะฒ ะฟะฐัะพะปั" fetchingAsApObject: "ะััะธะผััะผะพ ะท ัะตะดัะฒะตััั..." ok: "OK" gotIt: "ะัะพะทัะผัะปะพ!" @@ -80,6 +81,8 @@ somethingHappened: "ะฉะพัั ะฟััะปะพ ะฝะต ัะฐะบ" retry: "ะกะฟัะพะฑัะฒะฐัะธ ะทะฝะพะฒั" pageLoadError: "ะะพะผะธะปะบะฐ ะฟัะธ ะทะฐะฒะฐะฝัะฐะถะตะฝะฝั ััะพััะฝะบะธ" pageLoadErrorDescription: "ะะฐะทะฒะธัะฐะน ัะต ะฟะพะฒโัะทะฐะฝะพ ะท ะฟะพะผะธะปะบะฐะผะธ ะผะตัะตะถั ะฐะฑะพ ะบะตัะตะผ ะฑัะฐัะทะตัะฐ. ะัะธััััั ะบะตั ะฐะฑะพ ะฟะพัะตะบะฐะนัะต ััะพั ะธ ะน ัะฟัะพะฑัะนัะต ัะต ัะฐะท." +serverIsDead: "ะัะดะฟะพะฒัะดั ะฒัะด ัะตัะฒะตัะฐ ะฝะตะผะฐั. ะะฐัะตะบะฐะนัะต ะดะตัะบะธะน ัะฐั ั ะฟะพะฒัะพัััั ัะฟัะพะฑั." +youShouldUpgradeClient: "ะะตัะตะทะฐะฒะฐะฝัะฐะถัะต ัะฐ ะฒะธะบะพัะธััะพะฒัะนัะต ะฝะพะฒั ะฒะตัััั ะบะปััะฝัะฐ, ัะพะฑ ะฟะตัะตะณะปัะฝััะธ ัั ััะพััะฝะบั." enterListName: "ะะฒะตะดััั ะฝะฐะทะฒั ัะฟะธัะบั" privacy: "ะะพะฝััะดะตะฝััะนะฝัััั" makeFollowManuallyApprove: "ะัะดัะฒะตัะดะถัะฒะฐัะธ ะฟัะดะฟะธัะฝะธะบัะฒ ััััะฝั" @@ -103,6 +106,7 @@ clickToShow: "ะะฐัะธัะฝััั ะดะปั ะฟะตัะตะณะปัะดั" sensitive: "NSFW" add: "ะะพะดะฐัะธ" reaction: "ะ ะตะฐะบััั" +reactionSetting: "ะะฐะปะฐัััะฒะฐะฝะฝั ัะตะฐะบััะน" reactionSettingDescription2: "ะะตัะตะผัััะธัะธ ัะพะฑ ะทะผัะฝะธัะธ ะฟะพััะดะพะบ, ะะปะฐัะฝััะธ ะผะธัะพั ัะพะฑ ะฒะธะดะฐะปะธัะธ, ะะฐัะธัะฝััะธ \"+\" ัะพะฑ ะดะพะดะฐัะธ." rememberNoteVisibility: "ะะฐะผโััะฐัะธ ะฟะฐัะฐะผะตััะธ ะฒะธะดะธะผัััั" attachCancel: "ะะธะดะฐะปะธัะธ ะฒะบะปะฐะดะตะฝะฝั" @@ -137,7 +141,10 @@ flagAsBot: "ะะบะฐัะฝั ะฑะพัะฐ" flagAsBotDescription: "ะะฒัะผะบะฝััั ัะบัะพ ัะตะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฒะธะบะพัะธััะพะฒัััััั ะฑะพัะพะผ. ะฆั ะพะฟััั ะฟะพะทะฝะฐัะธัั ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ัะบ ะฑะพัะฐ. ะฆะต ะฟะพัััะฑะฝะพ ัะพะฑ ะฒะธะบะปััะธัะธ ะฑะตะทะบัะฝะตัะฝั ัะฝัะตัะฐะบััั ะผัะถ ะฑะพัะฐะผะธ ะฐ ัะฐะบะพะถ ะฒัะดะฟะพะฒัะดะฝะพะณะพ ะฟัะดะปะฐัััะฒะฐะฝะฝั Misskey." flagAsCat: "ะะบะฐัะฝั ะบะพัะฐ" flagAsCatDescription: "ะะฒัะผะบะฝััั, ัะพะฑ ะฟะพะทะฝะฐัะธัะธ, ัะพ ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ั ะบะพัะธะบะพะผ." +flagShowTimelineReplies: "ะะพะบะฐะทัะฒะฐัะธ ะฒัะดะฟะพะฒัะดั ะฝะฐ ะฝะพัะฐัะบะธ ะฝะฐ ัะฐัะพะฒัะน ัะบะฐะปั" +flagShowTimelineRepliesDescription: "ะะพะบะฐะทัั ะฒัะดะฟะพะฒัะดั ะบะพัะธัััะฒะฐััะฒ ะฝะฐ ะฝะพัะฐัะบะธ ัะฝัะธั ะบะพัะธัััะฒะฐััะฒ ะฝะฐ ัะฐัะพะฒัะน ัะบะฐะปั." autoAcceptFollowed: "ะะฒัะพะผะฐัะธัะฝะพ ะฟัะธะนะผะฐัะธ ะทะฐะฟะธัะธ ะฝะฐ ะฟัะดะฟะธัะบั ะฒัะด ะบะพัะธัััะฒะฐััะฒ, ะฝะฐ ัะบะธั ะฒะธ ะฟัะดะฟะธัะฐะฝั" +addAccount: "ะะพะดะฐัะธ ะฐะบะฐัะฝั" loginFailed: "ะะต ะฒะดะฐะปะพัั ัะฒัะนัะธ" showOnRemote: "ะะตัะตะณะปัะฝััะธ ะฒ ะพัะธะณัะฝะฐะปั" general: "ะะฐะณะฐะปัะฝะต" @@ -148,6 +155,7 @@ searchWith: "ะะพััะบ: {q}" youHaveNoLists: "ะฃ ะฒะฐั ะฝะตะผะฐั ัะฟะธัะบัะฒ" followConfirm: "ะัะดะฟะธัะฐัะธัั ะฝะฐ {name}?" proxyAccount: "ะัะพะบัั-ะฐะบะฐัะฝั" +proxyAccountDescription: "ะะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฟัะพะบัั โ ัะต ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั, ัะบะธะน ะดัั ัะบ ะฒัะดะดะฐะปะตะฝะธะน ะฟัะดะฟะธัะฝะธะบ ะดะปั ะบะพัะธัััะฒะฐััะฒ ะทะฐ ะฟะตะฒะฝะธั ัะผะพะฒ. ะะฐะฟัะธะบะปะฐะด, ะบะพะปะธ ะบะพัะธัััะฒะฐั ะดะพะดะฐั ะฒัะดะดะฐะปะตะฝะพะณะพ ะบะพัะธัััะฒะฐัะฐ ะดะพ ัะฟะธัะบั, ะฐะบัะธะฒะฝัััั ะฒัะดะดะฐะปะตะฝะพะณะพ ะบะพัะธัััะฒะฐัะฐ ะฝะต ะฑัะดะต ะดะพััะฐะฒะปะตะฝะฐ ะฝะฐ ัะตัะฒะตั, ัะบัะพ ะถะพะดะตะฝ ะปะพะบะฐะปัะฝะธะน ะบะพัะธัััะฒะฐั ะฝะต ััะตะถะธัั ะทะฐ ัะธะผ ะบะพัะธัััะฒะฐัะตะผ, ัะพ ะทะฐะผัััั ะฝัะพะณะพ ะฑัะดะต ะฒะธะบะพัะธััะพะฒัะฒะฐัะธัั ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฟัะพะบัั-ัะตัะฒะตัะฐ." host: "ะฅะพัั" selectUser: "ะะธะฑะตัััั ะบะพัะธัััะฒะฐัะฐ" recipient: "ะััะธะผัะฒะฐั" @@ -229,6 +237,8 @@ resetAreYouSure: "ะกะฟัะฐะฒะดั ัะบะธะฝััะธ?" saved: "ะะฑะตัะตะถะตะฝะพ" messaging: "ะงะฐัะธ" upload: "ะะฐะฒะฐะฝัะฐะถะธัะธ" +keepOriginalUploading: "ะะฑะตัะตะณัะธ ะพัะธะณัะฝะฐะปัะฝะต ะทะพะฑัะฐะถะตะฝะฝั" +keepOriginalUploadingDescription: "ะะฑะตััะณะฐั ะฟะพัะฐัะบะพะฒะพ ะทะฐะฒะฐะฝัะฐะถะตะฝะต ะทะพะฑัะฐะถะตะฝะฝั ัะบ ั. ะฏะบัะพ ะฒะธะผะบะฝะตะฝะพ, ะฒะตัััั ะดะปั ะฒัะดะพะฑัะฐะถะตะฝะฝั ะฒ ะะฝัะตัะฝะตัั ะฑัะดะต ััะฒะพัะตะฝะฐ ะฟัะด ัะฐั ะทะฐะฒะฐะฝัะฐะถะตะฝะฝั." fromDrive: "ะ ะดะธัะบะฐ" fromUrl: "ะ ะฟะพัะธะปะฐะฝะฝั" uploadFromUrl: "ะะฐะฒะฐะฝัะฐะถะธัะธ ะท ะฟะพัะธะปะฐะฝะฝั" @@ -275,6 +285,7 @@ emptyDrive: "ะะธัะบ ะฟะพัะพะถะฝัะน" emptyFolder: "ะขะตะบะฐ ะฟะพัะพะถะฝั" unableToDelete: "ะะธะดะฐะปะตะฝะฝั ะฝะตะผะพะถะปะธะฒะต" inputNewFileName: "ะะฒะตะดััั ัะผ'ั ะฝะพะฒะพะณะพ ัะฐะนะปั" +inputNewDescription: "ะะฒะตะดััั ะฝะพะฒะธะน ะทะฐะณะพะปะพะฒะพะบ" inputNewFolderName: "ะะฒะตะดััั ัะผ'ั ะฝะพะฒะพั ัะตะบะธ" circularReferenceFolder: "ะะธ ะฝะฐะผะฐะณะฐััะตัั ะฟะตัะตะผัััะธัะธ ะฟะฐะฟะบั ะฒ ัั ะฟัะดะฟะฐะฟะบั." hasChildFilesOrFolders: "ะฆั ัะตะบะฐ ะฝะต ะฟะพัะพะถะฝั ั ะฝะต ะผะพะถะต ะฑััะธ ะฒะธะดะฐะปะตะฝะฐ" @@ -306,6 +317,8 @@ monthX: "{month}" yearX: "{year}" pages: "ะกัะพััะฝะบะธ" integration: "ะะฝัะตะณัะฐััั" +connectService: "ะัะดโัะดะฝะฐัะธ" +disconnectService: "ะัะดะบะปััะธัะธัั" enableLocalTimeline: "ะฃะฒัะผะบะฝััะธ ะปะพะบะฐะปัะฝั ัััััะบั" enableGlobalTimeline: "ะฃะฒัะผะบะฝััะธ ะณะปะพะฑะฐะปัะฝั ัััััะบั" disablingTimelinesInfo: "ะะดะผัะฝััััะฐัะพัะธ ัะฐ ะผะพะดะตัะฐัะพัะธ ะทะฐะฒะถะดะธ ะผะฐััั ะดะพัััะฟ ะดะพ ะฒััั ัััััะพะบ, ะฝะฐะฒััั ัะบัะพ ะฒะพะฝะธ ะฒะธะผะบะฝััั." @@ -317,6 +330,7 @@ driveCapacityPerRemoteAccount: "ะะฑ'ัะผ ะดะธัะบะฐ ะฝะฐ ะพะดะฝะพะณะพ ะฒัะดะด inMb: "ะ ะผะตะณะฐะฑะฐะนัะฐั " iconUrl: "URL ะฐะฒะฐัะฐัะฐ" bannerUrl: "URL ะฑะฐะฝะตัะฐ" +backgroundImageUrl: "URL-ะฐะดัะตัะฐ ัะพะฝะพะฒะพะณะพ ะทะพะฑัะฐะถะตะฝะฝั" basicInfo: "ะัะฝะพะฒะฝะฐ ัะฝัะพัะผะฐััั" pinnedUsers: "ะะฐะบััะฟะปะตะฝั ะบะพัะธัััะฒะฐัั" pinnedUsersDescription: "ะะฟะธัััั ะฒ ัะฟะธัะพะบ ะบะพัะธัััะฒะฐััะฒ, ัะบะธั ั ะพัะตัะต ะทะฐะบััะฟะธัะธ ะฝะฐ ััะพััะฝัั \"ะะฝะฐะนัะธ\", ัะผ'ั ะฒ ััะพะฒะฟัะธะบ." @@ -332,6 +346,7 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "ะฃะฒัะผะบะฝััะธ reCAPTCHA" recaptchaSiteKey: "ะะปัั ัะฐะนัั" recaptchaSecretKey: "ะกะตะบัะตัะฝะธะน ะบะปัั" +avoidMultiCaptchaConfirm: "ะะธะบะพัะธััะฐะฝะฝั ะบัะปัะบะพั ัะธััะตะผ Captcha ะผะพะถะต ัะฟัะธัะธะฝะธัะธ ะฟะตัะตัะบะพะดะธ ะผัะถ ะฝะธะผะธ. ะะฐะถะฐััะต ะฒะธะผะบะฝััะธ ัะฝัั ะฐะบัะธะฒะฝั ัะธััะตะผะธ Captcha? ะฏะบัะพ ะฒะธ ั ะพัะตัะต, ัะพะฑ ะฒะพะฝะธ ะทะฐะปะธัะฐะปะธัั ะฒะฒัะผะบะฝะตะฝะธะผะธ, ะฝะฐัะธัะฝััั ยซะกะบะฐััะฒะฐัะธยป." antennas: "ะะฝัะตะฝะธ" manageAntennas: "ะะฐะปะฐัััะฒะฐะฝะฝั ะฐะฝัะตะฝ" name: "ะะผ'ั" @@ -428,10 +443,12 @@ signinWith: "ะฃะฒัะนัะธ ะทะฐ ะดะพะฟะพะผะพะณะพั {x}" signinFailed: "ะะต ะฒะดะฐะปะพัั ัะฒัะนัะธ. ะะฒะตะดะตะฝั ัะผโั ะบะพัะธัััะฒะฐัะฐ ะฐะฑะพ ะฟะฐัะพะปั ะฝะตะฟัะฐะฒะธะปัะฝi." tapSecurityKey: "ะขะพัะบะฝััััั ะบะปััะฐ ะฑะตะทะฟะตะบะธ" or: "ะฐะฑะพ" +language: "ะะพะฒะฐ" uiLanguage: "ะะพะฒะฐ ัะฝัะตััะตะนัั" groupInvited: "ะะฐะฟัะพัะตะฝะฝั ะดะพ ะณััะฟะธ" aboutX: "ะัะพ {x}" useOsNativeEmojis: "ะะธะบะพัะธััะพะฒัะฒะฐัะธ ะตะผะพะดะทั ะะก" +disableDrawer: "ะะต ะฒะธะบะพัะธััะพะฒัะฒะฐัะธ ะฒะธััะฒะฝั ะผะตะฝั" youHaveNoGroups: "ะะตะผะฐั ะณััะฟ" joinOrCreateGroup: "ะััะธะผัะนัะต ะทะฐะฟัะพัะตะฝะฝั ะดะพ ะณััะฟ ะฐะฑะพ ััะฒะพััะนัะต ัะฒะพั ะฒะปะฐัะฝั ะณััะฟะธ." noHistory: "ะััะพััั ะฟะพัะพะถะฝั" @@ -442,6 +459,7 @@ category: "ะะฐัะตะณะพััั" tags: "ะขะตะณะธ" docSource: "ะะถะตัะตะปะพ ััะพะณะพ ะดะพะบัะผะตะฝัะฐ" createAccount: "ะกัะฒะพัะธัะธ ะฐะบะฐัะฝั" +existingAccount: "ะัะฝัััะธะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั" regenerate: "ะะฝะพะฒะธัะธ" fontSize: "ะ ะพะทะผัั ััะธััั" noFollowRequests: "ะะตะผะฐั ะทะฐะฟะธััะฒ ะฝะฐ ะฟัะดะฟะธัะบั" @@ -463,6 +481,7 @@ showFeaturedNotesInTimeline: "ะะพะบะฐะทัะฒะฐัะธ ะฟะพะฟัะปััะฝั ะฝะพัะฐั objectStorage: "Object Storage" useObjectStorage: "ะะธะบะพัะธััะพะฒัะฒะฐัะธ object storage" objectStorageBaseUrl: "Base URL" +objectStorageBaseUrlDesc: "ะฆะต ะฟะพัะฐัะบะพะฒะฐ ัะฐััะธะฝะฐ ะฐะดัะตัะธ, ัะพ ะฒะธะบะพัะธััะพะฒัััััั CDN ะฐะฑะพ ะฟัะพะบัั, ะฝะฐะฟัะธะบะปะฐะด ะดะปั S3: https://<bucket>.s3.amazonaws.com, ะฐะฑะพ GCS: 'https://storage.googleapis.com/<bucket>'" objectStorageBucket: "Bucket" objectStorageBucketDesc: "ะัะดั ะปะฐัะบะฐ ะฒะบะฐะถััั ะฝะฐะทะฒั ะฒัะดัะฐ ะฒ ะฝะฐะปะฐััะพะฒะฐะฝะพะผั ัะตัะฒััั." objectStoragePrefix: "Prefix" @@ -513,6 +532,9 @@ removeAllFollowing: "ะกะบะฐััะฒะฐัะธ ะฒัั ะฟัะดะฟะธัะบะธ" removeAllFollowingDescription: "ะกะบะฐััะฒะฐัะธ ะฟัะดะฟะธัะบั ะฝะฐ ะฒัั ะฐะบะฐัะฝัะธ ะท {host}. ะัะดั ะปะฐัะบะฐ, ัะพะฑััั ัะต, ัะบัะพ ัะฝััะฐะฝั ะฑัะปััะต ะฝะต ััะฝัั." userSuspended: "ะะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะทะฐะฑะปะพะบะพะฒะฐะฝะธะน." userSilenced: "ะะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฟัะธะณะปััะตะฝะธะน." +yourAccountSuspendedTitle: "ะฆะตะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะทะฐะฑะปะพะบะพะฒะฐะฝะพ" +yourAccountSuspendedDescription: "ะฆะตะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฑัะปะพ ะทะฐะฑะปะพะบะพะฒะฐะฝะพ ัะตัะตะท ะฟะพัััะตะฝะฝั ัะผะพะฒ ะฝะฐะดะฐะฝะฝั ะฟะพัะปัะณ ัะตัะฒะตัะฐ. ะะฒ'ัะถััััั ะท ะฐะดะผัะฝััััะฐัะพัะพะผ, ัะบัะพ ะฒะธ ั ะพัะตัะต ะดัะทะฝะฐัะธัั ะดะพะบะปะฐะดะฝััั ะฟัะธัะธะฝั. ะัะดั ะปะฐัะบะฐ, ะฝะต ััะฒะพััะนัะต ะฝะพะฒะธะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั." +menu: "ะะตะฝั" divider: "ะ ะพะทะดัะปัะฒะฐั" addItem: "ะะพะดะฐัะธ ะตะปะตะผะตะฝั" relays: "ะ ะตััะฐะฝัะปััะพัะธ" @@ -531,6 +553,8 @@ disablePlayer: "ะะฐะบัะธัะธ ะฒัะดะตะพะฟะปะตัั" expandTweet: "ะ ะพะทะณะพัะฝััะธ ัะฒัั" themeEditor: "ะ ะตะดะฐะบัะพั ัะตะผ" description: "ะะฟะธั" +describeFile: "ะะพะดะฐัะธ ะฟัะดะฟะธั" +enterFileDescription: "ะะฒะตะดััั ะฟัะดะฟะธั" author: "ะะฒัะพั" leaveConfirm: "ะะผัะฝะธ ะฝะต ะทะฑะตัะตะถะตะฝั. ะะธ ะดัะนัะฝะพ ั ะพัะตัะต ัะบะฐััะฒะฐัะธ ะทะผัะฝะธ?" manage: "ะฃะฟัะฐะฒะปัะฝะฝั" @@ -553,6 +577,7 @@ pluginTokenRequestedDescription: "ะฆะตะน ะฟะปะฐะณัะฝ ะทะผะพะถะต ะฒะธะบะพัะธั notificationType: "ะขะธะฟ ัะฟะพะฒััะตะฝะฝั" edit: "ะ ะตะดะฐะณัะฒะฐัะธ" useStarForReactionFallback: "ะะธะบะพัะธััะพะฒัะฒะฐัะธ โ ัะบ ะทะฐะฟะฐัะฝะธะน ะฒะฐััะฐะฝั, ัะบัะพ ะตะผะพะดะทั ัะตะฐะบััั ะฝะตะฒัะดะพะผะธะน" +emailServer: "ะกะตัะฒะตั ะตะปะตะบััะพะฝะฝะพั ะฟะพััะธ" enableEmail: "ะฃะฒัะผะบะฝััะธ ััะฝะบััั ะดะพััะฐะฒะบะธ ะฟะพััะธ" emailConfigInfo: "ะะธะบะพัะธััะพะฒัััััั ะดะปั ะฟัะดัะฒะตัะดะถะตะฝะฝั ะตะปะตะบััะพะฝะฝะพั ะฟะพััะธ ะฟัะดัะฐั ัะตััััะฐััั, ะฐ ัะฐะบะพะถ ะดะปั ะฒัะดะฝะพะฒะปะตะฝะฝั ะฟะฐัะพะปั." email: "E-mail" @@ -567,6 +592,9 @@ smtpSecure: "ะะธะบะพัะธััะพะฒัะฒะฐัะธ ะฑะตะทัะผะพะฒะฝะต ัะธัััะฒะฐะฝ smtpSecureInfo: "ะะธะผะบะฝััั ะฟัะธ ะฒะธะบะพัะธััะฐะฝะฝั STARTTLS " testEmail: "ะขะตััะพะฒะธะน email" wordMute: "ะะปะพะบัะฒะฐะฝะฝั ัะปัะฒ" +regexpError: "ะะพะผะธะปะบะฐ ัะตะณัะปััะฝะพะณะพ ะฒะธัะฐะทั" +regexpErrorDescription: "ะกัะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะฒ ัะตะณัะปััะฝะพะผั ะฒะธัะฐะทั ะฒ ััะดะบั {line} ะฒะฐัะพะณะพ ัะปะพะฒะฐ {tab} ัะปะพะฒะฐ ัะพ ัะณะฝะพััััััั:" +instanceMute: "ะัะธะณะปััะตะฝะฝั ัะฝััะฐะฝััะฒ" userSaysSomething: "{name} ัะพัั ัะบะฐะทะฐะฒ(ะปะฐ)" makeActive: "ะะบัะธะฒัะฒะฐัะธ" display: "ะัะดะพะฑัะฐะถะตะฝะฝั" @@ -594,6 +622,11 @@ reportAbuse: "ะะพัะบะฐัะถะธัะธัั" reportAbuseOf: "ะะพัะบะฐัะถะธัะธัั ะฝะฐ {name}" fillAbuseReportDescription: "ะัะดั ะปะฐัะบะฐ ะฒะบะฐะถััั ะฟะพะดัะพะฑะธัั ัะบะฐัะณะธ. ะฏะบัะพ ัะบะฐัะณะฐ ััะพััััััั ะทะฐะฟะธัั, ะฒะบะฐะถััั ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ะฝัะพะณะพ." abuseReported: "ะัะบััะผะพ, ะฒะฐัั ัะบะฐัะณั ะฑัะปะพ ะฒัะดะฟัะฐะฒะปะตะฝะพ. " +reporter: "ะ ะตะฟะพััะตั" +reporteeOrigin: "ะัะพ ะบะพะณะพ ะฟะพะฒัะดะพะผะปะตะฝะพ" +reporterOrigin: "ะฅัะพ ะฟะพะฒัะดะพะผะธะฒ" +forwardReport: "ะะตัะตัะปะฐัะธ ะทะฒัั ะฝะฐ ะฒัะดะดะฐะปะตะฝะธะน ัะฝััะฐะฝั" +forwardReportIsAnonymous: "ะะฐะผัััั ะฒะฐัะพะณะพ ะพะฑะปัะบะพะฒะพะณะพ ะทะฐะฟะธัั ะฐะฝะพะฝัะผะฝะธะน ัะธััะตะผะฝะธะน ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั ะฑัะดะต ะฒัะดะพะฑัะฐะถะฐัะธัั ัะบ ะดะพะฟะพะฒัะดะฐั ะฝะฐ ะฒัะดะดะฐะปะตะฝะพะผั ัะฝััะฐะฝัั" send: "ะัะดะฟัะฐะฒะธัะธ" abuseMarkAsResolved: "ะะพะทะฝะฐัะธัะธ ัะบะฐัะณั ัะบ ะฒะธัััะตะฝั" openInNewTab: "ะัะดะบัะธัะธ ะฒ ะฝะพะฒัะน ะฒะบะปะฐะดัั" @@ -655,6 +688,7 @@ center: "ะฆะตะฝัั" wide: "ะจะธัะพะบะธะน" narrow: "ะัะทัะบะธะน" reloadToApplySetting: "ะะฐะปะฐัััะฒะฐะฝะฝั ะฒะฒัะนะดะต ะฒ ะดัั ะฟัะธ ะฟะตัะตะทะฐะฒะฐะฝัะฐะถะตะฝะฝั. ะะตัะตะทะฐะฒะฐะฝัะฐะถะธัะธ?" +needReloadToApply: "ะะผัะฝะธ ะฝะฐะฑัะดััั ัะธะฝะฝะพััั ะฟััะปั ะฟะตัะตะทะฐะฒะฐะฝัะฐะถะตะฝะฝั ััะพััะฝะบะธ." showTitlebar: "ะะพะบะฐะทะฐัะธ ัะธััะปัะฝะธะน ััะดะพะบ" clearCache: "ะัะธััะธัะธ ะบะตั" onlineUsersCount: "{n} ะบะพัะธัััะฒะฐััะฒ ะพะฝะปะฐะนะฝ" @@ -669,12 +703,28 @@ textColor: "ะขะตะบัั" saveAs: "ะะฑะตัะตะณัะธ ัะบโฆ" advanced: "ะ ะพะทัะธัะตะฝั" value: "ะะฝะฐัะตะฝะฝั" +createdAt: "ะกัะฒะพัะตะฝะพ" updatedAt: "ะััะฐะฝะฝั ะพะฝะพะฒะปะตะฝะฝั" saveConfirm: "ะะฑะตัะตะณัะธ ะทะผัะฝะธ?" deleteConfirm: "ะะธ ะดัะนัะฝะพ ะฑะฐะถะฐััะต ัะต ะฒะธะดะฐะปะธัะธ?" invalidValue: "ะะตะบะพัะตะบัะฝะต ะทะฝะฐัะตะฝะฝั." registry: "ะ ะตัััั" closeAccount: "ะะฐะบัะธัะธ ะพะฑะปัะบะพะฒะธะน ะทะฐะฟะธั" +currentVersion: "ะะตัััั, ัะพ ะฒะธะบะพัะธััะพะฒัััััั" +latestVersion: "ะกะฐะผะฐ ัะฒัะถะฐ ะฒะตัััั" +youAreRunningUpToDateClient: "ะฃ ะฒะฐั ะฝะฐะนัะฒัะถััะฐ ะฒะตัััั ะบะปััะฝัะฐ." +newVersionOfClientAvailable: "ะะพัััะฟะฝััะฐ ัะฒัะถะฐ ะฒะตัััั ะบะปััะฝัะฐ." +usageAmount: "ะะธะบะพัะธััะฐะฝะต" +capacity: "ะะผะฝัััั" +inUse: "ะะฐะนะฝััะพ" +editCode: "ะ ะตะดะฐะณัะฒะฐัะธ ะฒะธั ัะดะฝะธะน ัะตะบัั" +apply: "ะะฐััะพััะฒะฐัะธ" +receiveAnnouncementFromInstance: "ะััะธะผัะฒะฐัะธ ะพะฟะพะฒััะตะฝะฝั ะท ัะฝััะฐะฝัั" +emailNotification: "ะกะฟะพะฒััะตะฝะฝั ะตะปะตะบััะพะฝะฝะพั ะฟะพััะพั" +publish: "ะะฟัะฑะปัะบัะฒะฐัะธ" +inChannelSearch: "ะะพััะบ ะทะฐ ะบะฐะฝะฐะปะพะผ" +useReactionPickerForContextMenu: "ะัะดะบัะธะฒะฐัะธ ะฟะฐะปัััั ัะตะฐะบััะน ะฟัะฐะฒะพั ะบะฝะพะฟะบะพั" +typingUsers: "ะกััะบ ะบะปะฐะฒัั. ะฆะต {users}โฆ" goBack: "ะะฐะทะฐะด" info: "ะะฝัะพัะผะฐััั" user: "ะะพัะธัััะฒะฐัั" @@ -687,6 +737,8 @@ hashtags: "ะฅะตััะตา" hide: "ะกั ะพะฒะฐัะธ" searchByGoogle: "ะะพััะบ" indefinitely: "ะัะบะพะปะธ" +_ffVisibility: + public: "ะะฟัะฑะปัะบัะฒะฐัะธ" _ad: back: "ะะฐะทะฐะด" _gallery: @@ -867,7 +919,6 @@ _sfx: antenna: "ะัะธะนะพะผ ะฐะฝัะตะฝะธ" channel: "ะะพะฒัะดะพะผะปะตะฝะฝั ะบะฐะฝะฐะปั" _ago: - unknown: "ะะตะฒัะดะพะผะพ" future: "ะะฐะนะฑััะฝั" justNow: "ะฉะพะนะฝะพ" secondsAgo: "{n}ั ัะพะผั" @@ -1377,6 +1428,9 @@ _notification: followRequestAccepted: "ะัะธะนะฝััั ะฟัะดะฟะธัะบะธ" groupInvited: "ะะฐะฟัะพัะตะฝะฝั ะดะพ ะณััะฟ" app: "ะกะฟะพะฒััะตะฝะฝั ะฒัะด ะดะพะดะฐัะบัะฒ" + _actions: + reply: "ะัะดะฟะพะฒัััะธ" + renote: "ะะพัะธัะธัะธ" _deck: alwaysShowMainColumn: "ะะฐะฒะถะดะธ ะฟะพะบะฐะทัะฒะฐัะธ ะณะพะปะพะฒะฝั ะบะพะปะพะฝะบั" columnAlign: "ะะธััะฒะฝััะธ ััะพะฒะฟัั" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 42f86b3359..9919e0a0a4 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1,35 +1,1093 @@ --- _lang_: "Tiแบฟng Viแปt" headlineMisskey: "Mแบกng xรฃ hแปi liรชn hแปฃp" +introMisskey: "Xin chร o! Misskey lร mแปt nแปn tแบฃng tiแปu blog phi tแบญp trung mรฃ nguแปn mแป.\nViแบฟt \"tรบt\" ฤแป chia sแบป nhแปฏng suy nghฤฉ cแปงa bแบกn ๐ก\nBแบฑng \"biแปu cแบฃm\", bแบกn cรณ thแป bร y tแป nhanh chรณng cแบฃm xรบc cแปงa bแบกn vแปi cรกc tรบt ๐\nHรฃy khรกm phรก mแปt thแบฟ giแปi mแปi! ๐" monthAndDay: "{day} thรกng {month}" search: "Tรฌm kiแบฟm" notifications: "Thรดng bรกo" username: "Tรชn ngฦฐแปi dรนng" password: "Mแบญt khแบฉu" forgotPassword: "Quรชn mแบญt khแบฉu" +fetchingAsApObject: "ฤang nแบกp dแปฏ liแปu tแปซ Fediverse..." ok: "ฤแปng รฝ" +gotIt: "ฤรฃ hiแปu!" +cancel: "Hแปงy" +enterUsername: "Nhแบญp tรชn ngฦฐแปi dรนng" renotedBy: "Chia sแบป bแปi {user}" +noNotes: "Chฦฐa cรณ tรบt nร o." +noNotifications: "Khรดng cรณ thรดng bรกo" +instance: "Mรกy chแปง" +settings: "Cร i ฤแบทt" +basicSettings: "Thiแบฟt lแบญp chung" +otherSettings: "Thiแบฟt lแบญp khรกc" +openInWindow: "Mแป trong cแปญa sแป mแปi" +profile: "Trang cรก nhรขn" +timeline: "Bแบฃng tin" +noAccountDescription: "Ngฦฐแปi nร y chฦฐa viแบฟt mรด tแบฃ." +login: "ฤฤng nhแบญp" +loggingIn: "ฤang ฤฤng nhแบญp..." +logout: "ฤฤng xuแบฅt" +signup: "ฤฤng kรฝ" +uploading: "ฤang tแบฃi lรชnโฆ" +save: "Lฦฐu" +users: "Ngฦฐแปi dรนng" +addUser: "Thรชm ngฦฐแปi dรนng" +favorite: "Thรชm vร o yรชu thรญch" +favorites: "Lฦฐแปฃt thรญch" +unfavorite: "Bแป thรญch" +favorited: "ฤรฃ thรชm vร o yรชu thรญch." +alreadyFavorited: "ฤรฃ thรชm vร o yรชu thรญch rแปi." +cantFavorite: "Khรดng thแป thรชm vร o yรชu thรญch." +pin: "Ghim" +unpin: "Bแป ghim" +copyContent: "Chรฉp nแปi dung" +copyLink: "Chรฉp liรชn kแบฟt" +delete: "Xรณa" +deleteAndEdit: "Sแปญa" +deleteAndEditConfirm: "Bแบกn cรณ chแบฏc muแปn sแปญa tรบt nร y? Nhแปฏng biแปu cแบฃm, lฦฐแปฃt trแบฃ lแปi vร ฤฤng lแบกi sแบฝ bแป mแบฅt." +addToList: "Thรชm vร o danh sรกch" +sendMessage: "Gแปญi tin nhแบฏn" +copyUsername: "Chรฉp tรชn ngฦฐแปi dรนng" +searchUser: "Tรฌm kiแบฟm ngฦฐแปi dรนng" +reply: "Trแบฃ lแปi" +loadMore: "Tแบฃi thรชm" +showMore: "Xem thรชm" +youGotNewFollower: "ฤรฃ theo dรตi bแบกn" +receiveFollowRequest: "ฤรฃ yรชu cแบงu theo dรตi" +followRequestAccepted: "ฤรฃ chแบฅp nhแบญn yรชu cแบงu theo dรตi" +mention: "Nhแบฏc ฤแบฟn" +mentions: "Lฦฐแปฃt nhแบฏc" +directNotes: "Nhแบฏn riรชng" +importAndExport: "Nhแบญp vร xuแบฅt dแปฏ liแปu" +import: "Nhแบญp dแปฏ liแปu" +export: "Xuแบฅt dแปฏ liแปu" +files: "Tแบญp tin" +download: "Tแบฃi xuแปng" +driveFileDeleteConfirm: "Bแบกn cรณ chแบฏc muแปn xรณa tแบญp tin \"{name}\"? Tรบt liรชn quan cลฉng sแบฝ bแป xรณa theo." +unfollowConfirm: "Bแบกn cรณ chแบฏc muแปn ngฦฐng theo dรตi {name}?" +exportRequested: "ฤang chuแบฉn bแป xuแบฅt tแบญp tin. Quรก trรฌnh nร y cรณ thแป mแบฅt รญt phรบt. Nรณ sแบฝ ฤฦฐแปฃc tแปฑ ฤแปng thรชm vร o Drive sau khi hoร n thร nh." +importRequested: "Bแบกn vแปซa yรชu cแบงu nhแบญp dแปฏ liแปu. Quรก trรฌnh nร y cรณ thแป mแบฅt รญt phรบt." +lists: "Danh sรกch" +noLists: "Bแบกn chฦฐa cรณ danh sรกch nร o" +note: "Tรบt" +notes: "Tรบt" +following: "ฤang theo dรตi" +followers: "Ngฦฐแปi theo dรตi" +followsYou: "Theo dรตi bแบกn" +createList: "Tแบกo danh sรกch" +manageLists: "Quแบฃn lรฝ danh sรกch" +error: "Lแปi" +somethingHappened: "Xแบฃy ra lแปi" +retry: "Thแปญ lแบกi" +pageLoadError: "Xแบฃy ra lแปi khi tแบฃi trang." +pageLoadErrorDescription: "Cรณ thแป lร do bแป nhแป ฤแปm cแปงa trรฌnh duyแปt. Hรฃy thแปญ xรณa bแป nhแป ฤแปm vร thแปญ lแบกi sau รญt phรบt." +serverIsDead: "Mรกy chแปง khรดng phแบฃn hแปi. Vui lรฒng thแปญ lแบกi sau giรขy lรกt." +youShouldUpgradeClient: "ฤแป xem trang nร y, hรฃy lร m tฦฐฦกi ฤแป cแบญp nhแบญt แปฉng dแปฅng." +enterListName: "ฤแบทt tรชn cho danh sรกch" +privacy: "Bแบฃo mแบญt" +makeFollowManuallyApprove: "Yรชu cแบงu theo dรตi cแบงn ฤฦฐแปฃc duyแปt" +defaultNoteVisibility: "Kiแปu tรบt mแบทc ฤแปnh" +follow: "ฤang theo dรตi" +followRequest: "Gแปญi yรชu cแบงu theo dรตi" +followRequests: "Yรชu cแบงu theo dรตi" +unfollow: "Ngฦฐng theo dรตi" +followRequestPending: "Yรชu cแบงu theo dรตi ฤang chแป" +enterEmoji: "Chรจn emoji" +renote: "ฤฤng lแบกi" +unrenote: "Hแปงy ฤฤng lแบกi" +renoted: "ฤรฃ ฤฤng lแบกi." +cantRenote: "Khรดng thแป ฤฤng lแบกi tรบt nร y." +cantReRenote: "Khรดng thแป ฤฤng lแบกi mแปt tรบt ฤฤng lแบกi." +quote: "Trรญch dแบซn" +pinnedNote: "Tรบt ghim" +pinned: "Ghim" +you: "Bแบกn" +clickToShow: "Nhแบฅn ฤแป xem" +sensitive: "Nhแบกy cแบฃm" +add: "Thรชm" +reaction: "Biแปu cแบฃm" +reactionSetting: "Chแปn nhแปฏng biแปu cแบฃm hiแปn thแป" +reactionSettingDescription2: "Kรฉo ฤแป sแบฏp xแบฟp, nhแบฅn ฤแป xรณa, nhแบฅn \"+\" ฤแป thรชm." +rememberNoteVisibility: "Lฦฐu kiแปu tรบt mแบทc ฤแปnh" +attachCancel: "Gแปก tแบญp tin ฤรญnh kรจm" +markAsSensitive: "ฤรกnh dแบฅu lร nhแบกy cแบฃm" +unmarkAsSensitive: "Bแป ฤรกnh dแบฅu nhแบกy cแบฃm" +enterFileName: "Nhแบญp tรชn tแบญp tin" +mute: "แบจn" +unmute: "Bแป แบฉn" +block: "Chแบทn" +unblock: "Bแป chแบทn" +suspend: "Vรด hiแปu hรณa" +unsuspend: "Bแป vรด hiแปu hรณa" +blockConfirm: "Bแบกn cรณ chแบฏc muแปn chแบทn ngฦฐแปi nร y?" +unblockConfirm: "Bแบกn cรณ chแบฏc muแปn bแป chแบทn ngฦฐแปi nร y?" +suspendConfirm: "Bแบกn cรณ chแบฏc muแปn vรด hiแปu hรณa ngฦฐแปi nร y?" +unsuspendConfirm: "Bแบกn cรณ chแบฏc muแปn bแป vรด hiแปu hรณa ngฦฐแปi nร y?" +selectList: "Chแปn danh sรกch" +selectAntenna: "Chแปn mแปt antenna" +selectWidget: "Chแปn tiแปn รญch" +editWidgets: "Sแปญa tiแปn รญch" +editWidgetsExit: "Xong" +customEmojis: "Tรนy chแปnh emoji" +emoji: "Emoji" +emojis: "Emoji" +emojiName: "Tรชn emoji" +emojiUrl: "URL Emoji" +addEmoji: "Thรชm emoji" +settingGuide: "Cร i ฤแบทt ฤแป xuแบฅt" +cacheRemoteFiles: "Tแบญp tin cache tแปซ xa" +cacheRemoteFilesDescription: "Khi tรนy chแปn nร y bแป tแบฏt, cรกc tแบญp tin tแปซ xa sแบฝ ฤฦฐแปฃc tแบฃi trแปฑc tiแบฟp tแปซ mรกy chแปง khรกc. ฤiแปu nร y sแบฝ giรบp giแบฃm dung lฦฐแปฃng lฦฐu trแปฏ nhฦฐng lแบกi tฤng lฦฐu lฦฐแปฃng truy cแบญp, vรฌ hรฌnh thu nhแป sแบฝ khรดng ฤฦฐแปฃc tแบกo." flagAsBot: "ฤรกnh dแบฅu ฤรขy lร tร i khoแบฃn bot" +flagAsBotDescription: "Bแบญt tรนy chแปn nร y nแบฟu tร i khoแบฃn nร y ฤฦฐแปฃc kiแปm soรกt bแปi mแปt chฦฐฦกng trรฌnh. Nแบฟu ฤฦฐแปฃc bแบญt, nรณ sแบฝ ฤฦฐแปฃc ฤรกnh dแบฅu ฤแป cรกc nhร phรกt triแปn khรกc ngฤn chแบทn chuแปi tฦฐฦกng tรกc vรด tแบญn vแปi cรกc bot khรกc vร ฤiแปu chแปnh hแป thแปng nแปi bแป cแปงa Misskey ฤแป coi tร i khoแบฃn nร y nhฦฐ mแปt bot." +flagAsCat: "Tร i khoแบฃn nร y lร mรจo" +flagAsCatDescription: "Bแบญt tรนy chแปn nร y ฤแป ฤรกnh dแบฅu tร i khoแบฃn lร mแปt con mรจo." +flagShowTimelineReplies: "Hiแปn lฦฐแปฃt trแบฃ lแปi trong bแบฃng tin" +flagShowTimelineRepliesDescription: "Hiแปn lฦฐแปฃt trแบฃ lแปi cแปงa ngฦฐแปi bแบกn theo dรตi trรชn tรบt cแปงa nhแปฏng ngฦฐแปi khรกc." +autoAcceptFollowed: "Tแปฑ ฤแปng phรช duyแปt theo dรตi tแปซ nhแปฏng ngฦฐแปi mร bแบกn ฤang theo dรตi" +addAccount: "Thรชm tร i khoแบฃn" +loginFailed: "ฤฤng nhแบญp khรดng thร nh cรดng" +showOnRemote: "Truy cแบญp trang cแปงa ngฦฐแปi nร y" +general: "Tแปng quan" +wallpaper: "แบขnh bรฌa" +setWallpaper: "ฤแบทt แบฃnh bรฌa" +removeWallpaper: "Xรณa แบฃnh bรฌa" searchWith: "Tรฌm kiแบฟm: {q}" +youHaveNoLists: "Bแบกn chฦฐa cรณ danh sรกch nร o" followConfirm: "Bแบกn cรณ chแบฏc muแปn theo dรตi {name}๏ผ" +proxyAccount: "Tร i khoแบฃn proxy" +proxyAccountDescription: "Tร i khoแบฃn proxy lร tร i khoแบฃn hoแบกt ฤแปng nhฦฐ mแปt ngฦฐแปi theo dรตi tแปซ xa cho ngฦฐแปi dรนng trong nhแปฏng ฤiแปu kiแปn nhแบฅt ฤแปnh. Vรญ dแปฅ: khi ngฦฐแปi dรนng thรชm ngฦฐแปi dรนng tแปซ xa vร o danh sรกch, hoแบกt ฤแปng cแปงa ngฦฐแปi dรนng tแปซ xa sแบฝ khรดng ฤฦฐแปฃc chuyแปn ฤแบฟn phiรชn bแบฃn nแบฟu khรดng cรณ ngฦฐแปi dรนng cแปฅc bแป nร o theo dรตi ngฦฐแปi dรนng ฤรณ, vรฌ vแบญy tร i khoแบฃn proxy sแบฝ theo dรตi." +host: "Host" +selectUser: "Chแปn ngฦฐแปi dรนng" +recipient: "Ngฦฐแปi nhแบญn" +annotation: "Bรฌnh luแบญn" +federation: "Liรชn hแปฃp" +instances: "Mรกy chแปง" +registeredAt: "ฤฤng kรฝ vร o" +latestRequestSentAt: "Yรชu cแบงu cuแปi gแปญi lรบc" +latestRequestReceivedAt: "Yรชu cแบงu cuแปi nhแบญn lรบc" +latestStatus: "Trแบกng thรกi cuแปi cรนng" +storageUsage: "Dung lฦฐแปฃng lฦฐu trแปฏ" +charts: "ฤแป thแป" +perHour: "Mแปi Giแป" +perDay: "Mแปi Ngร y" +stopActivityDelivery: "Ngฦฐng gแปญi hoแบกt ฤแปng" +blockThisInstance: "Chแบทn mรกy chแปง nร y" +operations: "Vแบญn hร nh" +software: "Phแบงn mแปm" +version: "Phiรชn bแบฃn" +metadata: "Metadata" +withNFiles: "{n} tแบญp tin" +monitor: "Giรกm sรกt" +jobQueue: "Cรดng viแปc chแป xแปญ lรฝ" cpuAndMemory: "CPU vร Dung lฦฐแปฃng" +network: "Mแบกng" +disk: "แป ฤฤฉa" +instanceInfo: "Thรดng tin mรกy chแปง" +statistics: "Thแปng kรช" +clearQueue: "Xรณa hร ng ฤแปฃi" +clearQueueConfirmTitle: "Bแบกn cรณ chแบฏc muแปn xรณa hร ng ฤแปฃi?" +clearQueueConfirmText: "Mแปi tรบt chฦฐa ฤฦฐแปฃc gแปญi cรฒn lแบกi trong hร ng ฤแปฃi sแบฝ khรดng ฤฦฐแปฃc liรชn hแปฃp. Thรดng thฦฐแปng thao tรกc nร y khรดng cแบงn thiแบฟt." +clearCachedFiles: "Xรณa bแป nhแป ฤแปm" +clearCachedFilesConfirm: "Bแบกn cรณ chแบฏc muแปn xรณa sแบกch bแป nhแป ฤแปm?" +blockedInstances: "Mรกy chแปง ฤรฃ chแบทn" +blockedInstancesDescription: "Danh sรกch nhแปฏng mรกy chแปง bแบกn muแปn chแบทn. Chรบng sแบฝ khรดng thแป giao tiแบฟp vแปi mรกy chแปงy nร y nแปฏa." +muteAndBlock: "แบจn vร Chแบทn" +mutedUsers: "Ngฦฐแปi ฤรฃ แบฉn" +blockedUsers: "Ngฦฐแปi ฤรฃ chแบทn" +noUsers: "Chฦฐa cรณ ai" +editProfile: "Sแปญa hแป sฦก" +noteDeleteConfirm: "Bแบกn cรณ chแบฏc muแปn xรณa tรบt nร y?" +pinLimitExceeded: "Bแบกn ฤรฃ ฤแบกt giแปi hแบกn sแป lฦฐแปฃng tรบt cรณ thแป ghim" +intro: "ฤรฃ cร i ฤแบทt Misskey! Xin hรฃy tแบกo tร i khoแบฃn admin." +done: "Xong" +processing: "ฤang xแปญ lรฝ" +preview: "Xem trฦฐแปc" +default: "Mแบทc ฤแปnh" +noCustomEmojis: "Khรดng cรณ emoji" +noJobs: "Khรดng cรณ cรดng viแปc" +federating: "ฤang liรชn hแปฃp" +blocked: "ฤรฃ chแบทn" +suspended: "ฤรฃ vรด hiแปu hรณa" +all: "Tแบฅt cแบฃ" +subscribing: "ฤang ฤฤng kรฝ" +publishing: "ฤang ฤฤng" +notResponding: "Khรดng cรณ phแบฃn hแปi" +instanceFollowing: "ฤang theo dรตi mรกy chแปง" +instanceFollowers: "Ngฦฐแปi theo dรตi cแปงa mรกy chแปง" +instanceUsers: "Ngฦฐแปi dรนng trรชn mรกy chแปง nร y" +changePassword: "ฤแปi mแบญt khแบฉu" +security: "Bแบฃo mแบญt" +retypedNotMatch: "Mแบญt khแบฉu khรดng trรนng khแปp." +currentPassword: "Mแบญt khแบฉu hiแปn tแบกi" +newPassword: "Mแบญt khแบฉu mแปi" +newPasswordRetype: "Nhแบญp lแบกi mแบญt khแบฉu mแปi" +attachFile: "ฤรญnh kรจm tแบญp tin" +more: "Thรชm nแปฏa!" +featured: "Nแปi bแบญt" +usernameOrUserId: "Tรชn ngฦฐแปi dรนng hoแบทc ID" +noSuchUser: "Khรดng tรฌm thแบฅy ngฦฐแปi dรนng" +lookup: "Tรฌm kiแบฟm" +announcements: "Thรดng bรกo" +imageUrl: "URL แบฃnh" +remove: "Xรณa" +removed: "ฤรฃ xรณa" +removeAreYouSure: "Bแบกn cรณ chแบฏc muแปn gแปก \"{x}\"?" +deleteAreYouSure: "Bแบกn cรณ chแบฏc muแปn xรณa \"{x}\"?" +resetAreYouSure: "Bแบกn cรณ chแบฏc muแปn ฤแบทt lแบกi?" +saved: "ฤรฃ lฦฐu" +messaging: "Trรฒ chuyแปn" +upload: "Tแบฃi lรชn" +keepOriginalUploading: "Giแปฏ hรฌnh แบฃnh gแปc" +keepOriginalUploadingDescription: "Giแปฏ nguyรชn nhฦฐ hรฌnh แบฃnh ฤฦฐแปฃc tแบฃi lรชn ban ฤแบงu. Nแบฟu tแบฏt, mแปt phiรชn bแบฃn ฤแป hiแปn thแป trรชn web sแบฝ ฤฦฐแปฃc tแบกo khi tแบฃi lรชn." +fromDrive: "Tแปซ แป ฤฤฉa" +fromUrl: "Tแปซ URL" +uploadFromUrl: "Tแบฃi lรชn bแบฑng mแปt URL" +uploadFromUrlDescription: "URL cแปงa tแบญp tin bแบกn muแปn tแบฃi lรชn" +uploadFromUrlRequested: "ฤรฃ yรชu cแบงu tแบฃi lรชn" +uploadFromUrlMayTakeTime: "Sแบฝ mแบฅt mแปt khoแบฃng thแปi gian ฤแป tแบฃi lรชn xong." +explore: "Khรกm phรก" +messageRead: "ฤรฃ ฤแปc" +noMoreHistory: "Khรดng cรฒn gรฌ ฤแป ฤแปc" +startMessaging: "Bแบฏt ฤแบงu trรฒ chuyแปn" +nUsersRead: "ฤแปc bแปi {n}" +agreeTo: "Tรดi ฤแปng รฝ {0}" +tos: "ฤiแปu khoแบฃn dแปch vแปฅ" +start: "Bแบฏt ฤแบงu" +home: "Trang chรญnh" +remoteUserCaution: "Vรฌ ngฦฐแปi dรนng nร y แป mรกy chแปง khรกc, thรดng tin hiแปn thแป cรณ thแป khรดng ฤแบงy ฤแปง." +activity: "Hoแบกt ฤแปng" +images: "Hรฌnh แบฃnh" +birthday: "Sinh nhรขฬฃt" +yearsOld: "{age} tuแปi" +registeredDate: "Tham gia" +location: "ฤแบฟn tแปซ" +theme: "Chแปง ฤแป" +themeForLightMode: "Chแปง ฤแป dรนng trong trong chแบฟ ฤแป Sรกng" +themeForDarkMode: "Chแปง ฤแป dรนng trong chแบฟ ฤแป Tแปi" +light: "Sรกng" +dark: "Tแปi" +lightThemes: "Nhแปฏng chแปง ฤแป sรกng" +darkThemes: "Nhแปฏng chแปง ฤแป tแปi" +syncDeviceDarkMode: "ฤแปng bแป vแปi thiแบฟt bแป" +drive: "แป ฤฤฉa" +fileName: "Tรชn tแบญp tin" +selectFile: "Chแปn tแบญp tin" +selectFiles: "Chแปn nhiแปu tแบญp tin" +selectFolder: "Chแปn thฦฐ mแปฅc" +selectFolders: "Chแปn nhiแปu thฦฐ mแปฅc" +renameFile: "ฤแปi tรชn tแบญp tin" +folderName: "Tรชn thฦฐ mแปฅc" +createFolder: "Tแบกo thฦฐ mแปฅc" +renameFolder: "ฤแปi tรชn thฦฐ mแปฅc" +deleteFolder: "Xรณa thฦฐ mแปฅc" +addFile: "Thรชm tแบญp tin" +emptyDrive: "แป ฤฤฉa cแปงa bแบกn trแปng trฦกn" +emptyFolder: "Thฦฐ mแปฅc trแปng" +unableToDelete: "Khรดng thแป xรณa" +inputNewFileName: "Nhแบญp tรชn mแปi cho tแบญp tin" +inputNewDescription: "Nhแบญp mรด tแบฃ mแปi" +inputNewFolderName: "Nhแบญp tรชn mแปi cho thฦฐ mแปฅc" +circularReferenceFolder: "Thฦฐ mแปฅc ฤรญch lร mแปt thฦฐ mแปฅc con cแปงa thฦฐ mแปฅc bแบกn muแปn di chuyแปn." +hasChildFilesOrFolders: "Khรดng thแป xรณa cho ฤแบฟn khi khรดng cรฒn gรฌ trong thฦฐ mแปฅc." +copyUrl: "Sao chรฉp URL" +rename: "ฤแปi tรชn" +avatar: "แบขnh ฤแบกi diแปn" +banner: "แบขnh bรฌa" +nsfw: "Nhแบกy cแบฃm" +whenServerDisconnected: "Khi mแบฅt kแบฟt nแปi tแปi mรกy chแปง" +disconnectedFromServer: "Mแบฅt kแบฟt nแปi tแปi mรกy chแปง" +reload: "Tแบฃi lแบกi" +doNothing: "Bแป qua" +reloadConfirm: "Bแบกn cรณ muแปn thแปญ tแบฃi lแบกi bแบฃng tin?" +watch: "Xem" +unwatch: "Ngแปซng xem" +accept: "ฤแปng รฝ" +reject: "Tแปซ chแปi" +normal: "Bรฌnh thฦฐแปng" +instanceName: "Tรชn mรกy chแปง" +instanceDescription: "Mรด tแบฃ mรกy chแปง" +maintainerName: "ฤแปi ngลฉ vแบญn hร nh" +maintainerEmail: "Email ฤแปi ngลฉ" +tosUrl: "URL ฤiแปu khoแบฃn dแปch vแปฅ" +thisYear: "Nฤm" +thisMonth: "Thรกng" +today: "Hรดm nay" dayX: "{day}" +monthX: "{month}" yearX: "{year}" +pages: "Trang" +integration: "Tฦฐฦกng tรกc" +connectService: "Kแบฟt nแปi" +disconnectService: "Ngแบฏt kแบฟt nแปi" +enableLocalTimeline: "Bแบญt bแบฃng tin mรกy chแปง" +enableGlobalTimeline: "Bแบญt bแบฃng tin liรชn hแปฃp" +disablingTimelinesInfo: "Quแบฃn trแป viรชn vร Kiแปm duyแปt viรชn luรดn cรณ quyแปn truy cแบญp mแปi bแบฃng tin, kแป cแบฃ khi chรบng khรดng ฤฦฐแปฃc bแบญt." +registration: "ฤฤng kรฝ" +enableRegistration: "Cho phรฉp ฤฤng kรฝ mแปi" +invite: "Mแปi" +driveCapacityPerLocalAccount: "Dung lฦฐแปฃng แป ฤฤฉa tแปi ฤa cho mแปi ngฦฐแปi dรนng" +driveCapacityPerRemoteAccount: "Dung lฦฐแปฃng แป ฤฤฉa tแปi ฤa cho mแปi ngฦฐแปi dรนng tแปซ xa" +inMb: "Tรญnh bแบฑng MB" +iconUrl: "URL Icon" +bannerUrl: "URL แบขnh bรฌa" +backgroundImageUrl: "URL แบขnh nแปn" +basicInfo: "Thรดng tin cฦก bแบฃn" +pinnedUsers: "Nhแปฏng ngฦฐแปi thรบ vแป" +pinnedUsersDescription: "Liแปt kรช mแปi hร ng mแปt tรชn ngฦฐแปi dรนng xuแปng dรฒng ฤแป ghim trรชn tab \"Khรกm phรก\"." +pinnedPages: "Trang ฤรฃ ghim" +pinnedPagesDescription: "Liแปt kรช cรกc trang thรบ vแป ฤแป ghim trรชn mรกy chแปง." +pinnedClipId: "ID cแปงa clip muแปn ghim" +pinnedNotes: "Tรบt ghim" +hcaptcha: "hCaptcha" +enableHcaptcha: "Bแบญt hCaptcha" +hcaptchaSiteKey: "Khรณa cแปงa trang" +hcaptchaSecretKey: "Khรณa bรญ mแบญt" +recaptcha: "reCAPTCHA" +enableRecaptcha: "Bแบญt reCAPTCHA" +recaptchaSiteKey: "Khรณa cแปงa trang" +recaptchaSecretKey: "Khรณa bรญ mแบญt" +avoidMultiCaptchaConfirm: "Dรนng nhiแปu hแป thแปng Captcha cรณ thแป gรขy nhiแป u giแปฏa chรบng. Bแบกn cรณ muแปn tแบฏt cรกc hแป thแปng Captcha khรกc hiแปn ฤang hoแบกt ฤแปng khรดng? Nแบฟu bแบกn muแปn chรบng tiแบฟp tแปฅc ฤฦฐแปฃc bแบญt, hรฃy nhแบฅn hแปงy." +antennas: "Trแบกm phรกt sรณng" +manageAntennas: "Quแบฃn lรฝ trแบกm phรกt sรณng" +name: "Tรชn" +antennaSource: "Nguแปn trแบกm phรกt sรณng" +antennaKeywords: "Tแปซ khรณa ฤแป nghe" +antennaExcludeKeywords: "Tแปซ khรณa ฤแป lแปc ra" +antennaKeywordsDescription: "Phรขn cรกch bแบฑng dแบฅu cรกch cho ฤiแปu kiแปn AND hoแบทc bแบฑng xuแปng dรฒng cho ฤiแปu kiแปn OR." +notifyAntenna: "Thรดng bรกo cรณ tรบt mแปi" +withFileAntenna: "Chแป nhแปฏng tรบt cรณ media" +enableServiceworker: "Bแบญt ServiceWorker" +antennaUsersDescription: "Liแปt kรช mแปi hร ng mแปt tรชn ngฦฐแปi dรนng" +caseSensitive: "Trฦฐแปng hแปฃp nhแบกy cแบฃm" +withReplies: "Bao gแปm lฦฐแปฃt trแบฃ lแปi" +connectedTo: "Nhแปฏng tร i khoแบฃn sau ฤรฃ kแบฟt nแปi" +notesAndReplies: "Tรบt kรจm trแบฃ lแปi" +withFiles: "Media" +silence: "แบจn" +silenceConfirm: "Bแบกn cรณ chแบฏc muแปn แบฉn ngฦฐแปi nร y?" +unsilence: "Bแป แบฉn" +unsilenceConfirm: "Bแบกn cรณ chแบฏc muแปn bแป แบฉn ngฦฐแปi nร y?" +popularUsers: "Nhแปฏng ngฦฐแปi nแปi tiแบฟng" +recentlyUpdatedUsers: "Hoแบกt ฤแปng gแบงn ฤรขy" +recentlyRegisteredUsers: "Mแปi tham gia" +recentlyDiscoveredUsers: "Mแปi khรกm phรก" +exploreUsersCount: "Cรณ {count} ngฦฐแปi" +exploreFediverse: "Khรกm phรก Fediverse" +popularTags: "Hashtag thรดng dแปฅng" +userList: "Danh sรกch" +about: "Giแปi thiแปu" aboutMisskey: "Vแป Misskey" +administrator: "Quแบฃn trแป viรชn" +token: "Token" +twoStepAuthentication: "Xรกc minh 2 bฦฐแปc" +moderator: "Kiแปm duyแปt viรชn" +nUsersMentioned: "Dรนng bแปi {n} ngฦฐแปi" +securityKey: "Khรณa bแบฃo mแบญt" +securityKeyName: "Tรชn khoรก" +registerSecurityKey: "ฤฤng kรฝ khรณa bแบฃo mแบญt" +lastUsed: "Dรนng lแบงn cuแปi" +unregister: "Hแปงy ฤฤng kรฝ" +passwordLessLogin: "ฤฤng nhแบญp khรดng mแบญt khแบฉu" +resetPassword: "ฤแบทt lแบกi mแบญt khแบฉu" +newPasswordIs: "Mแบญt khแบฉu mแปi lร \"{password}\"" +reduceUiAnimation: "Giแบฃm chuyแปn ฤแปng UI" +share: "Chia sแบป" +notFound: "Khรดng tรฌm thแบฅy" +notFoundDescription: "Khรดng tรฌm thแบฅy trang nร o tฦฐฦกng แปฉng vแปi URL nร y." +uploadFolder: "Thฦฐ mแปฅc tแบฃi lรชn mแบทc ฤแปnh" +cacheClear: "Xรณa bแป nhแป ฤแปm" +markAsReadAllNotifications: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc thรดng bรกo lร ฤรฃ ฤแปc" +markAsReadAllUnreadNotes: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc tรบt lร ฤรฃ ฤแปc" +markAsReadAllTalkMessages: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc tin nhแบฏn lร ฤรฃ ฤแปc" +help: "Trแปฃ giรบp" +inputMessageHere: "Nhแบญp nแปi dung tin nhแบฏn" +close: "ฤรณng" +group: "Nhรณm" +groups: "Cรกc nhรณm" +createGroup: "Tแบกo nhรณm" +ownedGroups: "Nhรณm tรดi quแบฃn lรฝ" +joinedGroups: "Nhรณm tรดi tham gia" +invites: "Mแปi" +groupName: "Tรชn nhรณm" +members: "Thร nh viรชn" +transfer: "Chuyแปn giao" +messagingWithUser: "Nhแบฏn riรชng" +messagingWithGroup: "Chat nhรณm" +title: "Tแปฑa ฤแป" +text: "Nแปi dung" +enable: "Bแบญt" +next: "Kแบฟ tiแบฟp" +retype: "Nhแบญp lแบกi" +noteOf: "Tรบt cแปงa {user}" +inviteToGroup: "Mแปi vร o nhรณm" +quoteAttached: "Trรญch dแบซn" +quoteQuestion: "Trรญch dแบซn lแบกi?" +noMessagesYet: "Chฦฐa cรณ tin nhแบฏn" +newMessageExists: "Bแบกn cรณ tin nhแบฏn mแปi" +onlyOneFileCanBeAttached: "Bแบกn chแป cรณ thแป ฤรญnh kรจm mแปt tแบญp tin" +signinRequired: "Vui lรฒng ฤฤng nhแบญp" +invitations: "Mแปi" +invitationCode: "Mรฃ mแปi" +checking: "ฤang kiแปm tra..." +available: "Khแบฃ dแปฅng" +unavailable: "Khรดng khแบฃ dแปฅng" +usernameInvalidFormat: "Bแบกn cรณ thแป dรนng viแบฟt hoa/viแบฟt thฦฐแปng, chแปฏ sแป, vร dแบฅu gแบกch dฦฐแปi." +tooShort: "Quรก ngแบฏn" +tooLong: "Quรก dร i" +weakPassword: "Mแบญt khแบฉu yแบฟu" +normalPassword: "Mแบญt khแบฉu tแบกm ฤฦฐแปฃc" +strongPassword: "Mแบญt khแบฉu mแบกnh" +passwordMatched: "Trรนng khแปp" +passwordNotMatched: "Khรดng trรนng khแปp" +signinWith: "ฤฤng nhแบญp bแบฑng {x}" +signinFailed: "Khรดng thแป ฤฤng nhแบญp. Vui lรฒng kiแปm tra tรชn ngฦฐแปi dรนng vร mแบญt khแบฉu cแปงa bแบกn." +tapSecurityKey: "Nhแบฅn mรฃ bแบฃo mแบญt cแปงa bแบกn" +or: "Hoแบทc" +language: "Ngรดn ngแปฏ" +uiLanguage: "Ngรดn ngแปฏ giao diแปn" +groupInvited: "Bแบกn ฤรฃ ฤฦฐแปฃc mแปi tham gia nhรณm" +aboutX: "Giแปi thiแปu {x}" +useOsNativeEmojis: "Dรนng emoji hแป thแปng" +disableDrawer: "Khรดng dรนng menu thanh bรชn" +youHaveNoGroups: "Khรดng cรณ nhรณm nร o" +joinOrCreateGroup: "Tham gia hoแบทc tแบกo mแปt nhรณm mแปi." +noHistory: "Khรดng cรณ dแปฏ liแปu" +signinHistory: "Lแปch sแปญ ฤฤng nhแบญp" +disableAnimatedMfm: "Tแบฏt MFM vแปi chuyแปn ฤแปng" +doing: "ฤang xแปญ lรฝ..." +category: "Phรขn loแบกi" +tags: "Thแบป" +docSource: "Nguแปn tร i liแปu" +createAccount: "Tแบกo tร i khoแบฃn" +existingAccount: "Tร i khoแบฃn hiแปn cรณ" +regenerate: "Tแบกo lแบกi" +fontSize: "Cแปก chแปฏ" +noFollowRequests: "Bแบกn khรดng cรณ yรชu cแบงu theo dรตi nร o" +openImageInNewTab: "Mแป แบฃnh trong tab mแปi" +dashboard: "Trang chรญnh" +local: "Mรกy chแปง nร y" +remote: "Mรกy chแปง khรกc" +total: "Tแปng cแปng" +weekOverWeekChanges: "Thay ฤแปi tuแบงn rแปi" +dayOverDayChanges: "Thay ฤแปi hรดm qua" +appearance: "Giao diแปn" +clientSettings: "Cร i ฤแบทt Client" +accountSettings: "Cร i ฤแบทt tร i khoแบฃn" +promotion: "Quแบฃng cรกo" +promote: "Quแบฃng cรกo" +numberOfDays: "Sแป ngร y" +hideThisNote: "แบจn tรบt nร y" +showFeaturedNotesInTimeline: "Hiแปn tรบt nแปi bแบญt trong bแบฃng tin" +objectStorage: "ฤแปi tฦฐแปฃng lฦฐu trแปฏ" +useObjectStorage: "Dรนng ฤแปi tฦฐแปฃng lฦฐu trแปฏ" +objectStorageBaseUrl: "Base URL" +objectStorageBaseUrlDesc: "URL ฤฦฐแปฃc sแปญ dแปฅng lร m tham khแบฃo. Chแป ฤแปnh URL cแปงa CDN hoแบทc Proxy cแปงa bแบกn nแบฟu bแบกn ฤang sแปญ dแปฅng. Vแปi S3 dรนng 'https://<bucket>.s3.amazonaws.com', cรฒn GCS hoแบทc dแปch vแปฅ tฦฐฦกng tแปฑ dรนng 'https://storage.googleapis.com/<bucket>', etc." +objectStorageBucket: "Bucket" +objectStorageBucketDesc: "Nhแบญp tรชn bucket dรนng แป nhร cung cแบฅp cแปงa bแบกn." +objectStoragePrefix: "Tiแปn tแป" +objectStoragePrefixDesc: "Cรกc tแบญp tin sแบฝ ฤฦฐแปฃc lฦฐu trแปฏ trong cรกc thฦฐ mแปฅc cรณ tiแปn tแป nร y." +objectStorageEndpoint: "ฤแบงu cuแปi" +objectStorageEndpointDesc: "ฤแป trแปng nแบฟu bแบกn ฤang dรนng AWS S3, nแบฟu khรดng thรฌ chแป ฤแปnh ฤแบงu cuแปi lร '<host>' hoแบทc '<host>:<port>', tรนy thuแปc vร o nhร cung cแบฅp dแปch vแปฅ." +objectStorageRegion: "Khu vแปฑc" +objectStorageRegionDesc: "Nhแบญp mแปt khu vแปฑc cแปฅ thแป nhฦฐ 'xx-east-1'. Nแบฟu nhร cung cแบฅp dแปch vแปฅ cแปงa bแบกn khรดng phรขn biแปt giแปฏa cรกc khu vแปฑc, hรฃy ฤแป trแปng hoแบทc nhแบญp 'us-east-1'." +objectStorageUseSSL: "Dรนng SSL" +objectStorageUseSSLDesc: "Tแบฏt nแบฟu bแบกn khรดng dรนng HTTPS ฤแป kแบฟt nแปi API" +objectStorageUseProxy: "Kแบฟt nแปi thรดng qua Proxy" +objectStorageUseProxyDesc: "Tแบฏt nแบฟu bแบกn khรดng dรนng Proxy ฤแป kแบฟt nแปi API" +objectStorageSetPublicRead: "ฤแบทt \"public-read\" khi tแบฃi lรชn" +serverLogs: "Nhแบญt kรฝ mรกy chแปง" +deleteAll: "Xรณa tแบฅt cแบฃ" +showFixedPostForm: "Hiแปn khung soแบกn tรบt แป phรญa trรชn bแบฃng tin" +newNoteRecived: "ฤรฃ nhแบญn tรบt mแปi" +sounds: "รm thanh" +listen: "Nghe" +none: "Khรดng" +showInPage: "Hiแปn trong trang" +popout: "Pop-out" +volume: "รm lฦฐแปฃng" +masterVolume: "รm thanh chung" +details: "Chi tiแบฟt" +chooseEmoji: "Chแปn emoji" +unableToProcess: "Khรดng thแป hoร n tแบฅt hร nh ฤแปng" +recentUsed: "Sแปญ dแปฅng gแบงn ฤรขy" +install: "Cร i ฤแบทt" +uninstall: "Gแปก bแป" +installedApps: "แปจng dแปฅng ฤรฃ cร i ฤแบทt" +nothing: "Khรดng cรณ gรฌ แป ฤรขy" +installedDate: "Cho phรฉp vร o" +lastUsedDate: "Dรนng gแบงn nhแบฅt" +state: "Trแบกng thรกi" +sort: "Sแบฏp xแบฟp" +ascendingOrder: "Tฤng dแบงn" +descendingOrder: "Giแบฃm dแบงn" +scratchpad: "Scratchpad" +scratchpadDescription: "Scratchpad cung cแบฅp mรดi trฦฐแปng cho cรกc thแปญ nghiแปm AiScript. Bแบกn cรณ thแป viแบฟt, thแปฑc thi vร kiแปm tra kแบฟt quแบฃ tฦฐฦกng tรกc vแปi Misskey trong ฤรณ." +output: "Nguแปn ra" +script: "Kแปch bแบฃn" +disablePagesScript: "Tแบฏt AiScript trรชn Trang" +updateRemoteUser: "Cแบญp nhแบญt thรดng tin ngฦฐแปi dรนng แป mรกy chแปง khรกc" +deleteAllFiles: "Xรณa toร n bแป tแบญp tin" +deleteAllFilesConfirm: "Bแบกn cรณ chแบฏc xรณa toร n bแป tแบญp tin?" +removeAllFollowing: "Ngฦฐng theo dรตi tแบฅt cแบฃ mแปi ngฦฐแปi" +removeAllFollowingDescription: "Thแปฑc hiแปn ฤiแปu nร y sแบฝ ngฦฐng theo dรตi tแบฅt cแบฃ cรกc tร i khoแบฃn khแปi {host}. Chแป thแปฑc hiแปn ฤiแปu nร y nแบฟu mรกy chแปง khรดng cรฒn tแปn tแบกi." +userSuspended: "Ngฦฐแปi nร y ฤรฃ bแป vรด hiแปu hรณa." +userSilenced: "Ngฦฐแปi nร y ฤรฃ bแป แบฉn" +yourAccountSuspendedTitle: "Tร i khoแบฃn bแป vรด hiแปu hรณa" +yourAccountSuspendedDescription: "Tร i khoแบฃn nร y ฤรฃ bแป vรด hiแปu hรณa do vi phแบกm quy tแบฏc mรกy chแปง hoแบทc ฤiแปu tฦฐฦกng tแปฑ. Liรชn hแป vแปi quแบฃn trแป viรชn nแบฟu bแบกn muแปn biแบฟt lรฝ do chi tiแบฟt hฦกn. Vui lรฒng khรดng tแบกo tร i khoแบฃn mแปi." +menu: "Menu" +divider: "Phรขn chia" +addItem: "Thรชm mแปฅc" +relays: "Chuyแปn tiแบฟp" +addRelay: "Thรชm chuyแปn tiแบฟp" +inboxUrl: "URL Hแปp thฦฐ ฤแบฟn" +addedRelays: "ฤรฃ thรชm cรกc chuyแปn tiแบฟp" +serviceworkerInfo: "Phแบฃi ฤฦฐแปฃc bแบญt cho thรดng bรกo ฤแบฉy." +deletedNote: "Tรบt ฤรฃ bแป xรณa" +invisibleNote: "Tรบt รขฬn" +enableInfiniteScroll: "Tแปฑ ฤแปng tแบฃi tรบt mแปi" +visibility: "Hiแปn thแป" +poll: "Bรฌnh chแปn" +useCw: "แบจn nแปi dung" +enablePlayer: "Mแป trรฌnh phรกt video" +disablePlayer: "ฤรณng trรฌnh phรกt video" +expandTweet: "Mแป rแปng tweet" +themeEditor: "Cรดng cแปฅ thiแบฟt kแบฟ theme" +description: "Mรด tแบฃ" +describeFile: "Thรชm mรด tแบฃ" +enterFileDescription: "Nhแบญp mรด tแบฃ" +author: "Tรกc giแบฃ" +leaveConfirm: "Cรณ nhแปฏng thay ฤแปi chฦฐa ฤฦฐแปฃc lฦฐu. Bแบกn cรณ muแปn bแป chรบng khรดng?" +manage: "Quแบฃn lรฝ" +plugins: "Plugin" +deck: "Deck" +undeck: "Bแป Deck" +useBlurEffectForModal: "Sแปญ dแปฅng hiแปu แปฉng mแป cho cรกc hแปp thoแบกi" +useFullReactionPicker: "Dรนng bแป chแปn biแปu cแบฃm cแปก lแปn" +width: "Chiแปu rแปng" +height: "Chiแปu cao" +large: "Lแปn" +medium: "Vแปซa" +small: "Nhแป" +generateAccessToken: "Tแบกo mรฃ truy cแบญp" +permission: "Cho phรฉp " +enableAll: "Bแบญt toร n bแป" +disableAll: "Tแบฏt toร n bแป" +tokenRequested: "Cแบฅp quyแปn truy cแบญp vร o tร i khoแบฃn" +pluginTokenRequestedDescription: "Plugin nร y sแบฝ cรณ thแป sแปญ dแปฅng cรกc quyแปn ฤฦฐแปฃc ฤแบทt แป ฤรขy." +notificationType: "Loแบกi thรดng bรกo" +edit: "Sแปญa" +useStarForReactionFallback: "Dรนng โ nแบฟu emoji biแปu cแบฃm khรดng cรณ" +emailServer: "Email mรกy chแปง" +enableEmail: "Bแบญt phรขn phแปi email" +emailConfigInfo: "ฤฦฐแปฃc dรนng ฤแป xรกc minh email cแปงa bแบกn lรบc ฤฤng kรฝ hoแบทc nแบฟu bแบกn quรชn mแบญt khแบฉu cแปงa mรฌnh" +email: "Email" +emailAddress: "ฤแปa chแป email" +smtpConfig: "Cแบฅu hรฌnh mรกy chแปง SMTP" +smtpHost: "Host" +smtpPort: "Cแปng" smtpUser: "Tรชn ngฦฐแปi dรนng" smtpPass: "Mแบญt khแบฉu" +emptyToDisableSmtpAuth: "ฤแป trแปng tรชn ngฦฐแปi dรนng vร mแบญt khแบฉu ฤแป tแบฏt xรกc thแปฑc SMTP" +smtpSecure: "Dรนng SSL/TLS ngแบงm ฤแปnh cho cรกc kแบฟt nแปi SMTP" +smtpSecureInfo: "Tแบฏt cรกi nร y nแบฟu dรนng STARTTLS" +testEmail: "Kiแปm tra vแบญn chuyแปn email" +wordMute: "แบจn chแปฏ" +regexpError: "Lแปi biแปu thแปฉc" +regexpErrorDescription: "Xแบฃy ra lแปi biแปu thแปฉc แป dรฒng {line} cแปงa {tab} chแปฏ แบฉn:" +instanceMute: "Nhแปฏng mรกy chแปง แบฉn" +userSaysSomething: "{name} nรณi gรฌ ฤรณ" +makeActive: "Kรญch hoแบกt" +display: "Hiแปn thแป" +copy: "Sao chรฉp" +metrics: "Sแป liแปu" +overview: "Tแปng quan" +logs: "Nhแบญt kรฝ" +delayed: "ฤแป trแป " +database: "Cฦก sแป dแปฏ liแปu" +channel: "Kรชnh" +create: "Tแบกo" +notificationSetting: "Cร i ฤแบทt thรดng bรกo" +notificationSettingDesc: "Chแปn loแบกi thรดng bรกo bแบกn muแปn hiแปn thแป." +useGlobalSetting: "Dรนng thiแบฟt lแบญp chung" +useGlobalSettingDesc: "Nแบฟu ฤฦฐแปฃc bแบญt, cร i ฤแบทt thรดng bรกo cแปงa bแบกn sแบฝ ฤฦฐแปฃc รกp dแปฅng. Nแบฟu bแป tแบฏt, cรณ thแป thแปฑc hiแปn cรกc thiแบฟt lแบญp riรชng lแบป." +other: "Khรกc" +regenerateLoginToken: "Tแบกo lแบกi mรฃ ฤฤng nhแบญp" +regenerateLoginTokenDescription: "Tแบกo lแบกi mรฃ nแปi bแป cรณ thแป dรนng ฤแป ฤฤng nhแบญp. Thรดng thฦฐแปng hร nh ฤแปng nร y lร khรดng cแบงn thiแบฟt. Nแบฟu ฤฦฐแปฃc tแบกo lแบกi, tแบฅt cแบฃ cรกc thiแบฟt bแป sแบฝ bแป ฤฤng xuแบฅt." +setMultipleBySeparatingWithSpace: "Tรกch nhiแปu mแปฅc nhแบญp bแบฑng dแบฅu cรกch." +fileIdOrUrl: "ID tแบญp tin hoแบทc URL" +behavior: "Thao tรกc" +sample: "Vรญ dแปฅ" +abuseReports: "Lฦฐแปฃt bรกo cรกo" +reportAbuse: "Bรกo cรกo" reportAbuseOf: "Bรกo cรกo {name}" +fillAbuseReportDescription: "Vui lรฒng ฤiแปn thรดng tin chi tiแบฟt vแป bรกo cรกo nร y. Nแบฟu ฤรณ lร vแป mแปt tรบt cแปฅ thแป, hรฃy kรจm theo URL cแปงa tรบt." +abuseReported: "Bรกo cรกo ฤรฃ ฤฦฐแปฃc gแปญi. Cแบฃm ฦกn bแบกn nhiแปu." +reporter: "Ngฦฐแปi bรกo cรกo" +reporteeOrigin: "Bแป bรกo cรกo" +reporterOrigin: "Mรกy chแปง ngฦฐแปi bรกo cรกo" +forwardReport: "Chuyแปn tiแบฟp bรกo cรกo cho mรกy chแปง tแปซ xa" +forwardReportIsAnonymous: "Thay vรฌ tร i khoแบฃn cแปงa bแบกn, mแปt tร i khoแบฃn hแป thแปng แบฉn danh sแบฝ ฤฦฐแปฃc hiแปn thแป dฦฐแปi dแบกng ngฦฐแปi bรกo cรกo แป mรกy chแปง tแปซ xa." +send: "Gแปญi" +abuseMarkAsResolved: "ฤรกnh dแบฅu ฤรฃ xแปญ lรฝ" +openInNewTab: "Mแป trong tab mแปi" +openInSideView: "Mแป trong thanh bรชn" +defaultNavigationBehaviour: "Thao tรกc ฤiแปu hฦฐแปng mแบทc ฤแปnh" +editTheseSettingsMayBreakAccount: "Viแปc chแปnh sแปญa cรกc cร i ฤแบทt nร y cรณ thแป lร m hแปng tร i khoแบฃn cแปงa bแบกn." +instanceTicker: "Thรดng tin mรกy chแปง cแปงa tรบt" +waitingFor: "ฤang ฤแปฃi {x}" +random: "Ngแบซu nhiรชn" +system: "Hแป thแปng" +switchUi: "Chuyแปn ฤแปi giao diแปn ngฦฐแปi dรนng" +desktop: "Desktop" +clip: "Ghim" +createNew: "Tแบกo mแปi" +optional: "Khรดng bแบฏt buแปc" +createNewClip: "Tแบกo mแปt ghim mแปi" +public: "Cรดng khai" +i18nInfo: "Misskey ฤang ฤฦฐแปฃc cรกc tรฌnh nguyแปn viรชn dแปch sang nhiแปu thแปฉ tiแบฟng khรกc nhau. Bแบกn cรณ thแป hแป trแปฃ tแบกi {link}." +manageAccessTokens: "Tแบกo mรฃ truy cแบญp" +accountInfo: "Thรดng tin tร i khoแบฃn" +notesCount: "Sแป lฦฐแปฃng tรบt" +repliesCount: "Sแป lฦฐแปฃt trแบฃ lแปi ฤรฃ gแปญi" +renotesCount: "Sแป lฦฐแปฃt ฤฤng lแบกi ฤรฃ gแปญi" +repliedCount: "Sแป lฦฐแปฃt trแบฃ lแปi ฤรฃ nhแบญn" renotedCount: "Lฦฐแปฃt chia sแบป" +followingCount: "Sแป lฦฐแปฃng ngฦฐแปi tรดi theo dรตi" +followersCount: "Sแป lฦฐแปฃng ngฦฐแปi theo dรตi tรดi" +sentReactionsCount: "Sแป lฦฐแปฃng biแปu cแบฃm ฤรฃ gแปญi" +receivedReactionsCount: "Sแป lฦฐแปฃng biแปu cแบฃm ฤรฃ nhแบญn" +pollVotesCount: "Sแป lฦฐแปฃng bรฌnh chแปn ฤรฃ gแปญi" +pollVotedCount: "Sแป lฦฐแปฃng bรฌnh chแปn ฤรฃ nhแบญn" +yes: "ฤแปng รฝ" +no: "Tแปซ chแปi" +driveFilesCount: "Sแป tแบญp tin trong แป ฤฤฉa" +driveUsage: "Dung lฦฐแปฃng แป ฤฤฉa" +noCrawle: "Tแปซ chแปi lแบญp chแป mแปฅc" +noCrawleDescription: "Khรดng cho cรดng cแปฅ tรฌm kiแบฟm lแบญp chแป mแปฅc trang hแป sฦก, tรบt, Trang, etc." +lockedAccountInfo: "Ghi chรบ cแปงa bแบกn sแบฝ hiแปn thแป vแปi bแบฅt kแปณ ai, trแปซ khi bแบกn ฤแบทt chแบฟ ฤแป hiแปn thแป tรบt cแปงa mรฌnh thร nh \"Chแป ngฦฐแปi theo dรตi\"." +alwaysMarkSensitive: "Luรดn ฤรกnh dแบฅu NSFW" +loadRawImages: "Tแบฃi แบฃnh gแปc thay vรฌ แบฃnh thu nhแป" +disableShowingAnimatedImages: "Khรดng phรกt แบฃnh ฤแปng" +verificationEmailSent: "Mแปt email xรกc minh ฤรฃ ฤฦฐแปฃc gแปญi. Vui lรฒng nhแบฅn vร o liรชn kแบฟt ฤรญnh kรจm ฤแป hoร n tแบฅt xรกc minh." +notSet: "Chฦฐa ฤแบทt" +emailVerified: "Email ฤรฃ ฤฦฐแปฃc xรกc minh" +noteFavoritesCount: "Sแป lฦฐแปฃng tรบt yรชu thรญch" +pageLikesCount: "Sแป lฦฐแปฃng trang ฤรฃ thรญch" +pageLikedCount: "Sแป lฦฐแปฃng thรญch trang ฤรฃ nhแบญn" +contact: "Liรชn hแป" +useSystemFont: "Dรนng phรดng chแปฏ mแบทc ฤแปnh cแปงa hแป thแปng" +clips: "Ghim" +experimentalFeatures: "Tรญnh nฤng thแปญ nghiแปm" +developer: "Nhร phรกt triแปn" +makeExplorable: "Khรดng hiแปn tรดi trong \"Khรกm phรก\"" +makeExplorableDescription: "Nแบฟu bแบกn tแบฏt, tร i khoแบฃn cแปงa bแบกn sแบฝ khรดng hiแปn trong mแปฅc \"Khรกm phรก\"." +showGapBetweenNotesInTimeline: "Hiแปn dแบฃi phรขn cรกch giแปฏa cรกc tรบt trรชn bแบฃng tin" +duplicate: "Tแบกo bแบฃn sao" +left: "Bรชn traฬi" +center: "Giแปฏa" +wide: "Rแปng" +narrow: "Thu hแบนp" +reloadToApplySetting: "Cร i ฤแบทt nร y sแบฝ chแป รกp dแปฅng sau khi tแบฃi lแบกi trang. Tแบฃi lแบกi ngay bรขy giแป?" +needReloadToApply: "Cแบงn tแบฃi lแบกi ฤแป ฤiแปu nร y ฤฦฐแปฃc รกp dแปฅng." +showTitlebar: "Hiแปn thanh tแปฑa ฤแป" +clearCache: "Xรณa bแป nhแป ฤแปm" +onlineUsersCount: "{n} ngฦฐแปi ฤang online" +nUsers: "{n} Ngฦฐแปi" +nNotes: "{n} Tรบt" +sendErrorReports: "Bรกo lแปi" +sendErrorReportsDescription: "Khi ฤฦฐแปฃc bแบญt, thรดng tin chi tiแบฟt vแป lแปi sแบฝ ฤฦฐแปฃc chia sแบป vแปi Misskey khi xแบฃy ra sแปฑ cแป, giรบp nรขng cao chแบฅt lฦฐแปฃng cแปงa Misskey.\nBao gแปm thรดng tin nhฦฐ phiรชn bแบฃn hแป ฤiแปu hร nh cแปงa bแบกn, trรฌnh duyแปt bแบกn ฤang sแปญ dแปฅng, hoแบกt ฤแปng cแปงa bแบกn trong Misskey, v.v." +myTheme: "Theme cแปงa tรดi" +backgroundColor: "Mร u nแปn" +accentColor: "Mร u phแปฅ" +textColor: "Mร u chแปฏ" +saveAs: "Lฦฐu thร nh" +advanced: "Nรขng cao" +value: "Giรก trแป" +createdAt: "Ngร y tแบกo" +updatedAt: "Cแบญp nhแบญt lรบc" +saveConfirm: "Lฦฐu thay ฤแปi?" +deleteConfirm: "Bแบกn cรณ muแปn xรณa khรดng?" +invalidValue: "Giรก trแป khรดng hแปฃp lแป." +registry: "Registry" +closeAccount: "ฤรณng tร i khoแบฃn" +currentVersion: "Phiรชn bแบฃn hiแปn tแบกi" +latestVersion: "Phiรชn bแบฃn mแปi nhแบฅt" +youAreRunningUpToDateClient: "Bแบกn ฤang sแปญ dแปฅng phiรชn bแบฃn mแปi nhแบฅt." +newVersionOfClientAvailable: "Cรณ phiรชn bแบฃn mแปi cho bแบกn cแบญp nhแบญt." +usageAmount: "Sแปญ dแปฅng" +capacity: "Sแปฉc chแปฉa" +inUse: "ฤรฃ dรนng" +editCode: "Chแปnh sแปญa mรฃ" +apply: "รp dแปฅng" +receiveAnnouncementFromInstance: "Nhแบญn thรดng bรกo tแปซ mรกy chแปง nร y" +emailNotification: "Thรดng bรกo email" +publish: "ฤฤng" +inChannelSearch: "Tรฌm trong kรชnh" +useReactionPickerForContextMenu: "Nhแบฅn chuแปt phแบฃi ฤแป mแป bแป chแปn biแปu cแบฃm" +typingUsers: "{users} ฤang nhแบญpโฆ" +jumpToSpecifiedDate: "ฤแบฟn mแปt ngร y cแปฅ thแป" +showingPastTimeline: "Hiแปn ฤang hiแปn thแป dรฒng thแปi gian cลฉ" +clear: "Hoร n lแบกi" +markAllAsRead: "ฤรกnh dแบฅu tแบฅt cแบฃ ฤรฃ ฤแปc" +goBack: "Quay lแบกi" +unlikeConfirm: "Bแบกn cรณ chแบฏc muแปn bแป thรญch ?" +fullView: "Kรญch thฦฐแปc ฤแบงy ฤแปง" +quitFullView: "Thoรกt toร n mร n hรฌnh" +addDescription: "Thรชm mรด tแบฃ" +userPagePinTip: "Bแบกn cรณ thแป hiแปn thแป cรกc tรบt แป ฤรขy bแบฑng cรกch chแปn \"Ghim vร o hแป sฦก\" tแปซ menu cแปงa mแปi tรบt." +notSpecifiedMentionWarning: "Tรบt nร y cรณ ฤแป cแบญp ฤแบฟn nhแปฏng ngฦฐแปi khรดng mong muแปn" +info: "Giแปi thiแปu" +userInfo: "Thรดng tin ngฦฐแปi dรนng" +unknown: "Chฦฐa biแบฟt" +onlineStatus: "Trแบกng thรกi" +hideOnlineStatus: "แบจn trแบกng thรกi online" +hideOnlineStatusDescription: "แบจn trแบกng thรกi online cแปงa bแบกn lร m giแบฃm sแปฑ tiแปn lแปฃi cแปงa mแปt sแป tรญnh nฤng nhฦฐ tรฌm kiแบฟm." +online: "Online" +active: "Hoแบกt ฤแปng" +offline: "Offline" +notRecommended: "Khรดng ฤแป xuแบฅt" +botProtection: "Bแบฃo vแป Bot" +instanceBlocking: "Mรกy chแปง ฤรฃ chแบทn" +selectAccount: "Chแปn mแปt tร i khoแบฃn" +switchAccount: "Chuyแปn tร i khoแบฃn" +enabled: "ฤรฃ bแบญt" +disabled: "ฤรฃ tแบฏt" +quickAction: "Thao tรกc nhanh" +user: "Ngฦฐแปi dรนng" +administration: "Quแบฃn lรฝ" +accounts: "Tร i khoแบฃn cแปงa bแบกn" +switch: "Chuyแปn ฤแปi" +noMaintainerInformationWarning: "Chฦฐa thiแบฟt lแบญp thรดng tin vแบญn hร nh." +noBotProtectionWarning: "Bแบฃo vแป Bot chฦฐa thiแบฟt lแบญp." +configure: "Thiแบฟt lแบญp" +postToGallery: "Tแบกo tรบt cรณ แบฃnh" +gallery: "Thฦฐ viแปn แบฃnh" +recentPosts: "Tรบt gแบงn ฤรขy" +popularPosts: "Tรบt ฤฦฐแปฃc xem nhiแปu nhแบฅt" +shareWithNote: "Chia sแบป kรจm vแปi tรบt" +ads: "Quแบฃng cรกo" +expiration: "Thแปi hแบกn" +memo: "Lฦฐu รฝ" +priority: "ฦฏu tiรชn" +high: "Cao" +middle: "Vแปซa" +low: "Thแบฅp" +emailNotConfiguredWarning: "Chฦฐa ฤแบทt ฤแปa chแป email." +ratio: "Tแปท lแป" +previewNoteText: "Hiแปn xem trฦฐแปc" +customCss: "Tรนy chแปnh CSS" +customCssWarn: "Chแป sแปญ dแปฅng nhแปฏng cร i ฤแบทt nร y nแบฟu bแบกn biแบฟt rรต vแป nรณ. Viแปc nhแบญp cรกc giรก trแป khรดng ฤรบng cรณ thแป khiแบฟn mรกy chแปง hoแบกt ฤแปng khรดng bรฌnh thฦฐแปng." +global: "Toร n cแบงu" +squareAvatars: "แบขnh ฤแบกi diแปn vuรดng" +sent: "Gแปญi" +received: "ฤรฃ nhแบญn" +searchResult: "Kแบฟt quแบฃ tรฌm kiแบฟm" +hashtags: "Hashtag" +troubleshooting: "Khแบฏc phแปฅc sแปฑ cแป" +useBlurEffect: "Dรนng hiแปu แปฉng lร m mแป trong giao diแปn" +learnMore: "Tรฌm hiแปu thรชm" +misskeyUpdated: "Misskey vแปซa ฤฦฐแปฃc cแบญp nhแบญt!" +whatIsNew: "Hiแปn nhแปฏng thay ฤแปi" +translate: "Dแปch" translatedFrom: "Dแปch tแปซ {x}" +accountDeletionInProgress: "ฤang xแปญ lรฝ viแปc xรณa tร i khoแบฃn" +usernameInfo: "Bแบกn cรณ thแป sแปญ dแปฅng chแปฏ cรกi (a ~ z, A ~ Z), chแปฏ sแป (0 ~ 9) hoแบทc dแบฅu gแบกch dฦฐแปi (_). Tรชn ngฦฐแปi dรนng khรดng thแป thay ฤแปi sau nร y." +aiChanMode: "Chแบฟ ฤแป Ai" +keepCw: "Giแปฏ cแบฃnh bรกo nแปi dung" +pubSub: "Tร i khoแบฃn Chรญnh/Phแปฅ" +lastCommunication: "Lแบงn giao tiแบฟp cuแปi" +resolved: "ฤรฃ xแปญ lรฝ" +unresolved: "Chแป xแปญ lรฝ" +breakFollow: "Xรณa ngฦฐแปi theo dรตi" +itsOn: "ฤรฃ bแบญt" +itsOff: "ฤรฃ tแบฏt" +emailRequiredForSignup: "Yรชu cแบงu ฤแปa chแป email khi ฤฤng kรฝ" +unread: "Chฦฐa ฤแปc" +filter: "Bแป lแปc" +controlPanel: "Bแบฃng ฤiแปu khiแปn" +manageAccounts: "Quแบฃn lรฝ tร i khoแบฃn" +makeReactionsPublic: "ฤแบทt lแปch sแปญ biแปu cแบฃm cรดng khai" +makeReactionsPublicDescription: "ฤiแปu nร y sแบฝ hiแปn thแป cรดng khai danh sรกch tแบฅt cแบฃ cรกc biแปu cแบฃm trฦฐแปc ฤรขy cแปงa bแบกn." +classic: "Cแป ฤiแปn" +muteThread: "Khรดng quan tรขm nแปฏa" +unmuteThread: "Quan tรขm tรบt nร y" +ffVisibility: "Hiแปn thแป Theo dรตi/Ngฦฐแปi theo dรตi" +ffVisibilityDescription: "Quyแบฟt ฤแปnh ai cรณ thแป xem nhแปฏng ngฦฐแปi bแบกn theo dรตi vร nhแปฏng ngฦฐแปi theo dรตi bแบกn." +continueThread: "Tiแบฟp tแปฅc xem chuแปi tรบt" +deleteAccountConfirm: "ฤiแปu nร y sแบฝ khiแบฟn tร i khoแบฃn bแป xรณa vฤฉnh viแป n. Vแบซn tiแบฟp tแปฅc?" +incorrectPassword: "Sai mแบญt khแบฉu." +voteConfirm: "Xรกc nhแบญn bรฌnh chแปn \"{choice}\"?" +hide: "แบจn" +leaveGroup: "Rแปi khแปi nhรณm" +leaveGroupConfirm: "Bแบกn cรณ chแบฏc muแปn rแปi khแปi nhรณm \"{name}\"?" +useDrawerReactionPickerForMobile: "Hiแปn bแป chแปn biแปu cแบฃm dแบกng xแป ra trรชn ฤiแปn thoแบกi" +welcomeBackWithName: "Chร o mแปซng trแป lแบกi, {name}" +clickToFinishEmailVerification: "Vui lรฒng nhแบฅn [{ok}] ฤแป hoร n tแบฅt viแปc ฤฤng kรฝ." +overridedDeviceKind: "Loแบกi thiแบฟt bแป" +smartphone: "ฤiแปn thoแบกi" +tablet: "Mรกy tรญnh bแบฃng" +auto: "Tแปฑ ฤแปng" +themeColor: "Mร u theme" +size: "Kรญch thฦฐแปc" +numberOfColumn: "Sแป lฦฐแปฃng cแปt" searchByGoogle: "Google" +instanceDefaultLightTheme: "Theme mรกy chแปง Sรกng-Rแปng" +instanceDefaultDarkTheme: "Theme mรกy chแปง Tแปi-Rแปng" +instanceDefaultThemeDescription: "Nhแบญp mรฃ theme trong ฤแปnh dแบกng ฤแปi tฦฐแปฃng." +mutePeriod: "Thแปi hแบกn แบฉn" +indefinitely: "Vฤฉnh viแป n" +tenMinutes: "10 phรบt" +oneHour: "1 giแป" +oneDay: "1 ngร y" +oneWeek: "1 tuแบงn" +reflectMayTakeTime: "Cรณ thแป mแบฅt mแปt thแปi gian ฤแป ฤiแปu nร y ฤฦฐแปฃc รกp dแปฅng." +failedToFetchAccountInformation: "Khรดng thแป lแบฅy thรดng tin tร i khoแบฃn" +rateLimitExceeded: "Giแปi hแบกn quรก mแปฉc" +_emailUnavailable: + used: "ฤแปa chแป email ฤรฃ ฤฦฐแปฃc sแปญ dแปฅng" + format: "ฤแปa chแป email khรดng hแปฃp lแป" + disposable: "Cแบฅm sแปญ dแปฅng ฤแปa chแป email dรนng mแปt lแบงn" + mx: "Mรกy chแปง email khรดng hแปฃp lแป" + smtp: "Mรกy chแปง email khรดng phแบฃn hแปi" +_ffVisibility: + public: "ฤฤng" + followers: "Chแป ngฦฐแปi theo dรตi mแปi xem ฤฦฐแปฃc" + private: "Riรชng tฦฐ" +_signup: + almostThere: "Gแบงn xong rแปi" + emailAddressInfo: "Hรฃy ฤiแปn ฤแปa chแป email cแปงa bแบกn. Nรณ sแบฝ khรดng ฤฦฐแปฃc cรดng khai." + emailSent: "Mแปt email xรกc minh ฤรฃ ฤฦฐแปฃc gแปญi ฤแบฟn ฤแปa chแป email ({email}) cแปงa bแบกn. Vui lรฒng nhแบฅn vร o liรชn kแบฟt trong ฤรณ ฤแป hoร n tแบฅt viแปc tแบกo tร i khoแบฃn." +_accountDelete: + accountDelete: "Xรณa tร i khoแบฃn" + mayTakeTime: "Vรฌ xรณa tร i khoแบฃn lร mแปt quรก trรฌnh tแปn nhiแปu tร i nguyรชn nรชn cรณ thแป mแบฅt mแปt khoแบฃng thแปi gian ฤแป hoร n thร nh, tรนy thuแปc vร o lฦฐแปฃng nแปi dung bแบกn ฤรฃ tแบกo vร sแป lฦฐแปฃng tแบญp tin bแบกn ฤรฃ tแบฃi lรชn." + sendEmail: "Sau khi hoร n tแบฅt viแปc xรณa tร i khoแบฃn, mแปt email sแบฝ ฤฦฐแปฃc gแปญi ฤแบฟn ฤแปa chแป email ฤรฃ ฤฤng kรฝ tร i khoแบฃn nร y." + requestAccountDelete: "Yรชu cแบงu xรณa tร i khoแบฃn" + started: "ฤang bแบฏt ฤแบงu xรณa tร i khoแบฃn." + inProgress: "ฤang xรณa dแบงn tร i khoแบฃn." +_ad: + back: "Quay lแบกi" + reduceFrequencyOfThisAd: "Hiแปn รญt lแบกi" +_forgotPassword: + enterEmail: "Nhแบญp ฤแปa chแป email bแบกn ฤรฃ sแปญ dแปฅng ฤแป ฤฤng kรฝ. Mแปt liรชn kแบฟt mร bแบกn cรณ thแป ฤแบทt lแบกi mแบญt khแบฉu cแปงa mรฌnh sau ฤรณ sแบฝ ฤฦฐแปฃc gแปญi ฤแบฟn nรณ." + ifNoEmail: "Nแบฟu bแบกn khรดng sแปญ dแปฅng email lรบc ฤฤng kรฝ, vui lรฒng liรชn hแป vแปi quแบฃn trแป viรชn." + contactAdmin: "Mรกy chแปง nร y khรดng hแป trแปฃ sแปญ dแปฅng ฤแปa chแป email, vui lรฒng liรชn hแป vแปi quแบฃn trแป viรชn ฤแป ฤแบทt lแบกi mแบญt khแบฉu cแปงa bแบกn." +_gallery: + my: "Kho แบขnh" + liked: "Tรบt ฤรฃ Thรญch" + like: "Thรญch" + unlike: "Bแป thรญch" +_email: + _follow: + title: "ฤรฃ theo dรตi bแบกn" + _receiveFollowRequest: + title: "Chแบฅp nhแบญn yรชu cแบงu theo dรตi" +_plugin: + install: "Cร i ฤแบทt tiแปn รญch" + installWarn: "Vui lรฒng khรดng cร i ฤแบทt nhแปฏng tiแปn รญch ฤรกng ngแป." + manage: "Quแบฃn lรฝ plugin" +_registry: + scope: "Phแบกm vi" + key: "Mรฃ" + keys: "Cรกc mรฃ" + domain: "Tรชn miแปn" + createKey: "Tแบกo mรฃ" +_aboutMisskey: + about: "Misskey lร phแบงn mแปm mรฃ nguแปn mแป ฤฦฐแปฃc phรกt triแปn bแปi syuilo tแปซ nฤm 2014." + contributors: "Nhแปฏng ngฦฐแปi ฤรณng gรณp nแปi bแบญt" + allContributors: "Toร n bแป ngฦฐแปi ฤรณng gรณp" + source: "Mรฃ nguแปn" + translation: "Dแปch Misskey" + donate: "แปฆng hแป Misskey" + morePatrons: "Chรบng tรดi cลฉng trรขn trแปng sแปฑ hแป trแปฃ cแปงa nhiแปu ngฦฐแปi ฤรณng gรณp khรกc khรดng ฤฦฐแปฃc liแปt kรช แป ฤรขy. Cแบฃm ฦกn! ๐ฅฐ" + patrons: "Ngฦฐแปi แปงng hแป" +_nsfw: + respect: "แบจn nแปi dung NSFW" + ignore: "Hiแปn nแปi dung NSFW" + force: "แบจn mแปi media" _mfm: + cheatSheet: "MFM Cheatsheet" + intro: "MFM lร ngรดn ngแปฏ phรกt triแปn ฤแปc quyแปn cแปงa Misskey cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng แป nhiแปu nฦกi. Tแบกi ฤรขy bแบกn cรณ thแป xem danh sรกch tแบฅt cแบฃ cรกc cรบ phรกp MFM cรณ sแบตn." + dummy: "Misskey mแป rแปng thแบฟ giแปi Fediverse" + mention: "Nhแบฏc ฤแบฟn" + mentionDescription: "Bแบกn cรณ thแป nhแบฏc ฤแบฟn ai ฤรณ bแบฑng cรกch sแปญ dแปฅng @tรชn ngฦฐแปi dรนng." + hashtag: "Hashtag" + hashtagDescription: "Bแบกn cรณ thแป tแบกo mแปt hashtag bแบฑng #chแปฏ hoแบทc #sแป." + url: "URL" + urlDescription: "Nhแปฏng URL cรณ thแป hiแปn thแป." + link: "ฤฦฐแปng dแบซn" + linkDescription: "Cรกc phแบงn cแปฅ thแป cแปงa vฤn bแบฃn cรณ thแป ฤฦฐแปฃc hiแปn thแป dฦฐแปi dแบกng URL." + bold: "In ฤแบญm" + boldDescription: "Nแปi bแบญt cรกc chแปฏ cรกi bแบฑng cรกch lร m chรบng dร y hฦกn." + small: "Nhแป" + smallDescription: "Hiแปn thแป nแปi dung nhแป vร mแปng." + center: "Giแปฏa" + centerDescription: "Hiแปn thแป nแปi dung cฤn giแปฏa." + inlineCode: "Mรฃ (Trong dรฒng)" + inlineCodeDescription: "Hiแปn thแป tรด sรกng cรบ phรกp trong dรฒng cho mรฃ (chฦฐฦกng trรฌnh)." + blockCode: "Mรฃ (Khแปi)" + blockCodeDescription: "Hiแปn thแป tรด sรกng cรบ phรกp cho mรฃ nhiแปu dรฒng (chฦฐฦกng trรฌnh) trong mแปt khแปi." + inlineMath: "Toรกn hแปc (Trong dรฒng)" + inlineMathDescription: "Hiแปn thแป cรดng thแปฉc toรกn (KaTeX) trong dรฒng" + blockMath: "Toรกn hแปc (Khแปi)" + blockMathDescription: "Hiแปn thแป cรดng thแปฉc toรกn hแปc nhiแปu dรฒng (KaTeX) trong mแปt khแปi" + quote: "Trรญch dแบซn" + quoteDescription: "Hiแปn thแป nแปi dung dแบกng lแปi trรญch dแบกng." + emoji: "Tรนy chแปnh emoji" + emojiDescription: "Hiแปn thแป emoji vแปi cรบ phรกp :tรชn emoji:" search: "Tรฌm kiแบฟm" + searchDescription: "Hiแปn thแป hแปp tรฌm kiแบฟm vแปi vฤn bแบฃn ฤฦฐแปฃc nhแบญp trฦฐแปc." + flip: "Lแบญt" + flipDescription: "Lแบญt nแปi dung theo chiแปu ngang hoแบทc chiแปu dแปc." + jelly: "Chuyแปn ฤแปng (Thแบกch rau cรขu)" + jellyDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng giแปng nhฦฐ thแบกch rau cรขu." + tada: "Chuyแปn ฤแปng (Tada)" + tadaDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng kiแปu \"Tada!\"." + jump: "Chuyแปn ฤแปng (Nhแบฃy mรบa)" + jumpDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng nhแบฃy nhรณt." + bounce: "Chuyแปn ฤแปng (Cร tฦฐng)" + bounceDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng cร tฦฐng." + shake: "Chuyแปn ฤแปng (Rung)" + shakeDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng rung lแบฏc." + twitch: "Chuyแปn ฤแปng (Co rรบt)" + twitchDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng co rรบt." + spin: "Chuyแปn ฤแปng (Xoay tรญt)" + spinDescription: "Cho phรฉp nแปi dung chuyแปn ฤแปng xoay tรญt." + x2: "Lฦกฬn" + x2Description: "Hiแปn thแป nแปi dung cแปก lแปn hฦกn." + x3: "Rแบฅt lแปn" + x3Description: "Hiแปn thแป nแปi dung cแปก lแปn hฦกn nแปฏa." + x4: "Khแปng lแป" + x4Description: "Hiแปn thแป nแปi dung cแปก khแปng lแป." + blur: "Lร m mแป" + blurDescription: "Lร m mแป nแปi dung. Nรณ sแบฝ ฤฦฐแปฃc hiแปn thแป rรต rร ng khi di chuแปt qua." + font: "Phรดng chแปฏ" + fontDescription: "Chแปn phรดng chแปฏ ฤแป hiแปn thแป nแปi dung." + rainbow: "Cแบงu vแปng" + rainbowDescription: "Lร m cho nแปi dung hiแปn thแป vแปi mร u sแบฏc cแบงu vแปng." + sparkle: "Lแบฅp lรกnh" + sparkleDescription: "Lร m cho nแปi dung hiแปu แปฉng hแบกt lแบฅp lรกnh." + rotate: "Xoay" + rotateDescription: "Xoay nแปi dung theo mแปt gรณc cแปฅ thแป." +_instanceTicker: + none: "Khรดng hiแปn thแป" + remote: "Hiแปn cho ngฦฐแปi dรนng tแปซ mรกy chแปง khรกc" + always: "Luรดn hiแปn" +_serverDisconnectedBehavior: + reload: "Tแปฑ ฤแปng tแบฃi lแบกi" + dialog: "Hiแปn hแปp thoแบกi cแบฃnh bรกo" + quiet: "Hiแปn thแป cแบฃnh bรกo khรดng phรด trฦฐฦกng" +_channel: + create: "Tแบกo kรชnh" + edit: "Chแปnh sแปญa kรชnh" + setBanner: "ฤแบทt แบฃnh bรฌa" + removeBanner: "Xรณa แบฃnh bรฌa" + featured: "Xu hฦฐฦกฬng" + owned: "Do tรดi quแบฃn lรฝ" + following: "ฤang theo dรตi" + usersCount: "{n} Thร nh viรชn" + notesCount: "{n} Tรบt" +_menuDisplay: + sideFull: "Thanh bรชn" + sideIcon: "Thanh bรชn (Biแปu tฦฐแปฃng)" + top: "Trรชn cรนng" + hide: "แบจn" +_wordMute: + muteWords: "แบจn tแปซ ngแปฏ" + muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition." + muteWordsDescription2: "Bao quanh cรกc tแปซ khรณa bแบฑng dแบฅu gแบกch chรฉo ฤแป sแปญ dแปฅng cแปฅm tแปซ thรดng dแปฅng." + softDescription: "แบจn cรกc tรบt phรน hแปฃp ฤiแปu kiแปn ฤรฃ ฤแบทt khแปi bแบฃng tin." + hardDescription: "Ngฤn cรกc tรบt ฤรกp แปฉng cรกc ฤiแปu kiแปn ฤรฃ ฤแบทt xuแบฅt hiแปn trรชn bแบฃng tin. Lฦฐu รฝ, nhแปฏng tรบt nร y sแบฝ khรดng ฤฦฐแปฃc thรชm vร o bแบฃng tin ngay cแบฃ khi cรกc ฤiแปu kiแปn ฤฦฐแปฃc thay ฤแปi." + soft: "Yแบฟu" + hard: "Mแบกnh" + mutedNotes: "Nhแปฏng tรบt ฤรฃ แบฉn" +_instanceMute: + instanceMuteDescription: "Thao tรกc nร y sแบฝ แบฉn mแปi tรบt/lฦฐแปฃt ฤฤng lแบกi tแปซ cรกc mรกy chแปง ฤฦฐแปฃc liแปt kรช, bao gแปm cแบฃ nhแปฏng tรบt dแบกng trแบฃ lแปi tแปซ mรกy chแปง bแป แบฉn." + instanceMuteDescription2: "Tรกch bแบฑng cรกch xuแปng dรฒng" + title: "แบจn tรบt tแปซ nhแปฏng mรกy chแปง ฤรฃ liแปt kรช." + heading: "Danh sรกch nhแปฏng mรกy chแปง bแป แบฉn" _theme: + explore: "Khรกm phรก theme" + install: "Cร i ฤแบทt theme" + manage: "Quแบฃn lรฝ theme" + code: "Mรฃ theme" + description: "Mรด tแบฃ" installed: "{name} ฤรฃ ฤฦฐแปฃc cร i ฤแบทt" + installedThemes: "Theme ฤรฃ cร i ฤแบทt" + builtinThemes: "Theme tรญch hแปฃp sแบตn" + alreadyInstalled: "Theme nร y ฤรฃ ฤฦฐแปฃc cร i ฤแบทt" + invalid: "ฤแปnh dแบกng cแปงa theme nร y khรดng hแปฃp lแป" + make: "Tแบกo theme" + base: "Dแปฑa trรชn cรณ sแบตn" + addConstant: "Thรชm hแบฑng sแป" + constant: "Hแบฑng sแป" + defaultValue: "Giรก trแป mแบทc ฤแปnh" + color: "Mร u sแบฏc" + refProp: "Tham chiแบฟu mแปt thuแปc tรญnh" + refConst: "Tham chiแบฟu mแปt hแบฑng sแป" + key: "Khรณa" + func: "Hร m" + funcKind: "Loแบกi hร m" + argument: "Tham sแป" + basedProp: "Thuแปc tรญnh tham chiแบฟu" + alpha: "ฤแป trong suแปt" + darken: "ฤแป tแปi" + lighten: "ฤรดฬฃ saฬng" + inputConstantName: "Nhแบญp tรชn cho hแบฑng sแป nร y" + importInfo: "Nแบฟu bแบกn nhแบญp mรฃ theme แป ฤรขy, bแบกn cรณ thแป nhแบญp mรฃ ฤรณ vร o trรฌnh chแปnh sแปญa theme" + deleteConstantConfirm: "Bแบกn cรณ chแบฏc muแปn xรณa hแบฑng sแป {const} khรดng?" + keys: + accent: "Mร u phแปฅ" + bg: "Mร u nแปn" + fg: "Mร u chแปฏ" + focus: "Trแปng tรขm" + indicator: "Chแป bรกo" + panel: "Thanh bรชn" + shadow: "Bรณng mแป" + header: "แบขnh bรฌa" + navBg: "Nแปn thanh bรชn" + navFg: "Chแปฏ thanh bรชn" + navHoverFg: "Chแปฏ thanh bรชn (Khi chแบกm)" + navActive: "Chแปฏ thanh bรชn (Khi chแปn)" + navIndicator: "Chแป bรกo thanh bรชn" + link: "ฤฦฐแปng dแบซn" + hashtag: "Hashtag" + mention: "Nhแบฏc ฤแบฟn" + mentionMe: "Lฦฐแปฃt nhแบฏc (Tรดi)" + renote: "ฤฤng lแบกi" + modalBg: "Nแปn phฦฐฦกng thแปฉc" + divider: "Phรขn chia" + scrollbarHandle: "Thanh cuแปn khi giแปฏ" + scrollbarHandleHover: "Thanh cuแปn khi chแบกm" + dateLabelFg: "Mร u ngร y thรกng nฤm" + infoBg: "Nแปn thรดng tin" + infoFg: "Chแปฏ thรดng tin" + infoWarnBg: "Nแปn cแบฃnh bรกo" + infoWarnFg: "Chแปฏ cแบฃnh bรกo" + cwBg: "Nแปn nรบt nแปi dung แบฉn" + cwFg: "Chแปฏ nรบt nแปi dung แบฉn" + cwHoverBg: "Nแปn nรบt nแปi dung แบฉn (Chแบกm)" + toastBg: "Nแปn thรดng bรกo" + toastFg: "Chแปฏ thรดng bรกo" + buttonBg: "Nแปn nรบt" + buttonHoverBg: "Nแปn nรบt (Chแบกm)" + inputBorder: "ฤฦฐแปng viแปn khung soแบกn thแบฃo" + listItemHoverBg: "Nแปn mแปฅc liแปt kรช (Chแบกm)" + driveFolderBg: "Nแปn thฦฐ mแปฅc แป ฤฤฉa" + wallpaperOverlay: "Lแปp phแปง hรฌnh nแปn" + badge: "Huy hiแปu" + messageBg: "Nแปn chat" + accentDarken: "Mร u phแปฅ (Tแปi)" + accentLighten: "Mร u phแปฅ (Sรกng)" + fgHighlighted: "Chแปฏ nแปi bแบญt" _sfx: + note: "Tรบt" + noteMy: "Tรบt cแปงa tรดi" notification: "Thรดng bรกo" + chat: "Trรฒ chuyแปn" + chatBg: "Chat (Nแปn)" + antenna: "Trแบกm phรกt sรณng" + channel: "Kรชnh" _ago: - unknown: "Khรดng rรต" future: "Tฦฐฦกng lai" justNow: "Vแปซa xong" secondsAgo: "{n}s trฦฐแปc" @@ -39,10 +1097,563 @@ _ago: weeksAgo: "{n} tuแบงn trฦฐแปc" monthsAgo: "{n} thรกng trฦฐแปc" yearsAgo: "{n} nฤm trฦฐแปc" +_time: + second: "s" + minute: "phรบt" + hour: "giแป" + day: "ngร y" +_tutorial: + title: "Cรกch dรนng Misskey" + step1_1: "Xin chร o!" + step1_2: "Trang nร y gแปi lร \"bแบฃng tin\". Nรณ hiแปn \"tรบt\" tแปซ nhแปฏng ngฦฐแปi mร bแบกn \"theo dรตi\" theo thแปฉ tแปฑ thแปi gian." + step1_3: "Bแบฃng tin cแปงa bแบกn ฤang trแปng, bแปi vรฌ bแบกn chฦฐa ฤฤng tรบt nร o hoแบทc chฦฐa theo dรตi ai." + step2_1: "Hรฃy hoร n thร nh viแปc thiแบฟt lแบญp hแป sฦก cแปงa bแบกn trฦฐแปc khi viแบฟt tรบt hoแบทc theo dรตi bแบฅt kแปณ ai." + step2_2: "Cung cแบฅp mแปt sแป thรดng tin giแปi thiแปu bแบกn lร ai sแบฝ giรบp ngฦฐแปi khรกc dแป dร ng biแบฟt ฤฦฐแปฃc hแป muแปn ฤแปc tรบt hay theo dรตi bแบกn." + step3_1: "Hoร n thร nh thiแบฟt lแบญp hแป sฦก cแปงa bแบกn?" + step3_2: "Sau ฤรณ, hรฃy thแปญ ฤฤng mแปt tรบt tiแบฟp theo. Bแบกn cรณ thแป lร m nhฦฐ vแบญy bแบฑng cรกch nhแบฅn vร o nรบt cรณ biแปu tฦฐแปฃng bรบt chรฌ trรชn mร n hรฌnh." + step3_3: "Nhแบญp nแปi dung vร o khung soแบกn thแบฃo vร nhแบฅn nรบt ฤฤng แป gรณc trรชn." + step3_4: "Chฦฐa biแบฟt nรณi gรฌ? Thแปญ \"Tรดi mแปi tham gia Misskey\"!" + step4_1: "ฤฤng xong tรบt ฤแบงu tiรชn cแปงa bแบกn?" + step4_2: "De! Tรบt ฤแบงu tiรชn cแปงa bแบกn ฤรฃ hiแปn trรชn bแบฃng tin." + step5_1: "Bรขy giแป, hรฃy thแปญ lร m cho bแบฃng tin cแปงa bแบกn sinh ฤแปng hฦกn bแบฑng cรกch theo dรตi nhแปฏng ngฦฐแปi khรกc." + step5_2: "{feature} sแบฝ hiแปn thแป cho bแบกn cรกc tรบt nแปi bแบญt trรชn mรกy chแปง nร y. {explore} sแบฝ cho phรฉp bแบกn tรฌm thแบฅy nhแปฏng ngฦฐแปi dรนng thรบ vแป. Hรฃy thแปญ tรฌm nhแปฏng ngฦฐแปi bแบกn muแปn theo dรตi แป ฤรณ!" + step5_3: "ฤแป theo dรตi nhแปฏng ngฦฐแปi dรนng khรกc, hรฃy nhแบฅn vร o แบฃnh ฤแบกi diแปn cแปงa hแป vร nhแบฅn nรบt \"Theo dรตi\" trรชn hแป sฦก cแปงa hแป." + step5_4: "Nแบฟu ngฦฐแปi dรนng khรกc cรณ biแปu tฦฐแปฃng แป khรณa bรชn cแบกnh tรชn cแปงa hแป, cรณ thแป mแบฅt mแปt khoแบฃng thแปi gian ฤแป ngฦฐแปi dรนng ฤรณ phรช duyแปt yรชu cแบงu theo dรตi cแปงa bแบกn theo cรกch thแปง cรดng." + step6_1: "Bแบกn sแบฝ cรณ thแป xem tรบt cแปงa nhแปฏng ngฦฐแปi dรนng khรกc trรชn bแบฃng tin cแปงa mรฌnh ngay bรขy giแป." + step6_2: "Bแบกn cลฉng cรณ thแป ฤแบทt \"biแปu cแบฃm\" trรชn tรบt cแปงa ngฦฐแปi khรกc ฤแป phแบฃn hแปi nhanh chรบng." + step6_3: "ฤแป ฤรญnh kรจm \"biแปu cแบฃm\", hรฃy nhแบฅn vร o dแบฅu \"+\" trรชn tรบt cแปงa ngฦฐแปi dรนng khรกc rแปi chแปn biแปu tฦฐแปฃng cแบฃm xรบc mร bแบกn muแปn dรนng." + step7_1: "Xin chรบc mแปซng! Bรขy giแป bแบกn ฤรฃ hoร n thร nh phแบงn hฦฐแปng dแบซn cฦก bแบฃn cแปงa Misskey." + step7_2: "Nแบฟu bแบกn muแปn tรฌm hiแปu thรชm vแป Misskey, hรฃy thแปญ phแบงn {help}." + step7_3: "Bรขy giแป, chรบc may mแบฏn vร vui vแบป vแปi Misskey! ๐" +_2fa: + alreadyRegistered: "Bแบกn ฤรฃ ฤฤng kรฝ thiแบฟt bแป xรกc minh 2 bฦฐแปc." + registerDevice: "ฤฤng kรฝ mแปt thiแบฟt bแป" + registerKey: "ฤฤng kรฝ mแปt mรฃ bแบฃo vแป" + step1: "Trฦฐแปc tiรชn, hรฃy cร i ฤแบทt mแปt แปฉng dแปฅng xรกc minh (chแบณng hแบกn nhฦฐ {a} hoแบทc {b}) trรชn thiแบฟt bแป cแปงa bแบกn." + step2: "Sau ฤรณ, quรฉt mรฃ QR hiแปn thแป trรชn mร n hรฌnh nร y." + step2Url: "Bแบกn cลฉng cรณ thแป nhแบญp URL nร y nแบฟu sแปญ dแปฅng mแปt chฦฐฦกng trรฌnh mรกy tรญnh:" + step3: "Nhแบญp mรฃ token do แปฉng dแปฅng cแปงa bแบกn cung cแบฅp ฤแป hoร n tแบฅt thiแบฟt lแบญp." + step4: "Kแป tแปซ bรขy giแป, nhแปฏng lแบงn ฤฤng nhแบญp trong tฦฐฦกng lai sแบฝ yรชu cแบงu mรฃ token ฤฤng nhแบญp ฤรณ." + securityKeyInfo: "Bรชn cแบกnh xรกc minh bแบฑng vรขn tay hoแบทc mรฃ PIN, bแบกn cลฉng cรณ thแป thiแบฟt lแบญp xรกc minh thรดng qua khรณa bแบฃo mแบญt phแบงn cแปฉng hแป trแปฃ FIDO2 ฤแป bแบฃo mแบญt hฦกn nแปฏa cho tร i khoแบฃn cแปงa mรฌnh." +_permissions: + "read:account": "Xem thรดng tin tร i khoแบฃn cแปงa bแบกn" + "write:account": "Sแปญa thรดng tin tร i khoแบฃn cแปงa bแบกn" + "read:blocks": "Xem danh sรกch ngฦฐแปi bแบกn chแบทn" + "write:blocks": "Sแปญa danh sรกch ngฦฐแปi bแบกn chแบทn" + "read:drive": "Truy cแบญp tแบญp tin, thฦฐ mแปฅc trong แป ฤฤฉa" + "write:drive": "Sแปญa vร xรณa tแบญp tin, thฦฐ mแปฅc trong แป ฤฤฉa" + "read:favorites": "Xem lฦฐแปฃt thรญch cแปงa tรดi" + "write:favorites": "Sแปญa lฦฐแปฃt thรญch cแปงa tรดi" + "read:following": "Xem nhแปฏng ngฦฐแปi bแบกn theo dรตi" + "write:following": "Theo dรตi hoแบทc ngฦฐng theo dรตi ai ฤรณ" + "read:messaging": "Xem lแปch sแปญ chat" + "write:messaging": "Soแบกn hoแบทc xรณa tin nhแบฏn" + "read:mutes": "Xem nhแปฏng ngฦฐแปi bแบกn แบฉn" + "write:mutes": "Sแปญa nhแปฏng ngฦฐแปi bแบกn แบฉn" + "write:notes": "Soแบกn hoแบทc xรณa tรบt" + "read:notifications": "Xem thรดng bรกo cแปงa tรดi" + "write:notifications": "Quแบฃn lรฝ thรดng bรกo cแปงa tรดi" + "read:reactions": "Xem lฦฐแปฃt biแปu cแบฃm cแปงa tรดi" + "write:reactions": "Sแปญa lฦฐแปฃt biแปu cแบฃm cแปงa tรดi" + "write:votes": "Bรฌnh chแปn" + "read:pages": "Xem trang cแปงa tรดi" + "write:pages": "Sแปญa hoแบทc xรณa trang cแปงa tรดi" + "read:page-likes": "Xem lฦฐแปฃt thรญch trรชn trang cแปงa tรดi" + "write:page-likes": "Sแปญa lฦฐแปฃt thรญch cแปงa tรดi trรชn trang" + "read:user-groups": "Xem nhรณm cแปงa tรดi" + "write:user-groups": "Sแปญa hoแบทc xรณa nhรณm cแปงa tรดi" + "read:channels": "Xem kรชnh cแปงa tรดi" + "write:channels": "Sแปญa kรชnh cแปงa tรดi" + "read:gallery": "Xem kho แบฃnh cแปงa tรดi" + "write:gallery": "Sแปญa kho แบฃnh cแปงa tรดi" + "read:gallery-likes": "Xem danh sรกch cรกc tรบt ฤรฃ thรญch trong thฦฐ viแปn cแปงa tรดi" + "write:gallery-likes": "Sแปญa danh sรกch cรกc tรบt ฤรฃ thรญch trong thฦฐ viแปn cแปงa tรดi" +_auth: + shareAccess: "Bแบกn cรณ muแปn cho phรฉp \"{name}\" truy cแบญp vร o tร i khoแบฃn nร y khรดng?" + shareAccessAsk: "Bแบกn cรณ chแบฏc muแปn cho phรฉp แปฉng dแปฅng nร y truy cแบญp vร o tร i khoแบฃn cแปงa mรฌnh khรดng?" + permissionAsk: "แปจng dแปฅng nร y yรชu cแบงu cรกc quyแปn sau" + pleaseGoBack: "Vui lรฒng quay lแบกi แปฉng dแปฅng" + callback: "Quay lแบกi แปฉng dแปฅng" + denied: "Truy cแบญp bแป tแปซ chแปi" +_antennaSources: + all: "Toร n bแป tรบt" + homeTimeline: "Tรบt tแปซ nhแปฏng ngฦฐแปi ฤรฃ theo dรตi" + users: "Tรบt tแปซ nhแปฏng ngฦฐแปi cแปฅ thแป" + userList: "Tรบt tแปซ danh sรกch ngฦฐแปi dรนng cแปฅ thแป" + userGroup: "Tรบt tแปซ ngฦฐแปi dรนng trong mแปt nhรณm cแปฅ thแป" +_weekday: + sunday: "Chแปง Nhแบญt" + monday: "Thแปฉ Hai" + tuesday: "Thแปฉ Ba" + wednesday: "Thแปฉ Tฦฐ" + thursday: "Thแปฉ Nฤm" + friday: "Thแปฉ Sรกu" + saturday: "Thแปฉ Bแบฃy" _widgets: + memo: "Tรบt ฤรฃ ghim" notifications: "Thรดng bรกo" + timeline: "Bแบฃng tin" + calendar: "Lแปch" + trends: "Xu hฦฐฦกฬng" + clock: "ฤแปng hแป" + rss: "Trรฌnh ฤแปc RSS" + activity: "Hoแบกt ฤแปng" + photos: "Kho แบฃnh" + digitalClock: "ฤแปng hแป sแป" + federation: "Liรชn hแปฃp" + postForm: "Mแบซu ฤฤng" + slideshow: "Trรฌnh chiแบฟu" + button: "Nรบt" + onlineUsers: "Ai ฤang online" + jobQueue: "Cรดng viแปc chแป xแปญ lรฝ" + serverMetric: "Thแปng kรช mรกy chแปง" + aiscript: "AiScript console" + aichan: "Ai" +_cw: + hide: "แบจn" + show: "Tแบฃi thรชm" + chars: "{count} kรฝ tแปฑ" + files: "{count} tแบญp tin" +_poll: + noOnlyOneChoice: "Cแบงn รญt nhแบฅt hai lแปฑa chแปn." + choiceN: "Lแปฑa chแปn {n}" + noMore: "Bแบกn khรดng thแป thรชm lแปฑa chแปn" + canMultipleVote: "Cho phรฉp chแปn nhiแปu lแปฑa chแปn" + expiration: "Thแปi hแบกn" + infinite: "Vฤฉnh viแป n" + at: "Kแบฟt thรบc vร o..." + after: "Kแบฟt thรบc sau..." + deadlineDate: "Ngร y kแบฟt thรบc" + deadlineTime: "giแป" + duration: "Thแปi hแบกn" + votesCount: "{n} bรฌnh chแปn" + totalVotes: "{n} tแปng bรฌnh chแปn" + vote: "Bรฌnh chแปn" + showResult: "Xem kแบฟt quแบฃ" + voted: "ฤรฃ bรฌnh chแปn" + closed: "ฤรฃ kแบฟt thรบc" + remainingDays: "{d} ngร y {h} giแป cรฒn lแบกi" + remainingHours: "{h} giแป {m} phรบt cรฒn lแบกi" + remainingMinutes: "{m} phรบt {s}s cรฒn lแบกi" + remainingSeconds: "{s}s cรฒn lแบกi" +_visibility: + public: "Cรดng khai" + publicDescription: "Mแปi ngฦฐแปi ฤแปu cรณ thแป ฤแปc tรบt cแปงa bแบกn" + home: "Trang chรญnh" + homeDescription: "Chแป ฤฤng lรชn bแบฃng tin nhร " + followers: "Ngฦฐแปi theo dรตi" + followersDescription: "Dร nh riรชng cho ngฦฐแปi theo dรตi" + specified: "Nhแบฏn riรชng" + specifiedDescription: "Chแป ngฦฐแปi ฤฦฐแปฃc nhแบฏc ฤแบฟn mแปi thแบฅy" + localOnly: "Chแป trรชn mรกy chแปง" + localOnlyDescription: "Khรดng hiแปn thแป vแปi ngฦฐแปi แป mรกy chแปง khรกc" +_postForm: + replyPlaceholder: "Trแบฃ lแปi tรบt nร y" + quotePlaceholder: "Trรญch dแบซn tรบt nร y" + channelPlaceholder: "ฤฤng lรชn mแปt kรชnh" + _placeholders: + a: "Bแบกn ฤang ฤแปnh lร m gรฌ?" + b: "Hรดm nay bแบกn cรณ gรฌ vui?" + c: "Bแบกn ฤang nghฤฉ gรฌ?" + d: "Bแบกn muแปn nรณi gรฌ?" + e: "Bแบฏt ฤแบงu viแบฟt..." + f: "ฤang chแป bแบกn viแบฟt..." _profile: + name: "Tรชn" username: "Tรชn ngฦฐแปi dรนng" + description: "Tiแปu sแปญ" + youCanIncludeHashtags: "Bแบกn cรณ thแป dรนng hashtag trong tiแปu sแปญ." + metadata: "Thรดng tin bแป sung" + metadataEdit: "Sแปญa thรดng tin bแป sung" + metadataDescription: "Sแปญ dแปฅng phแบงn nร y, bแบกn cรณ thแป hiแปn thแป cรกc mแปฅc thรดng tin bแป sung trong hแป sฦก cแปงa mรฌnh." + metadataLabel: "Nhรฃn" + metadataContent: "Nแปi dung" + changeAvatar: "ฤแปi แบฃnh ฤแบกi diแปn" + changeBanner: "ฤแปi แบฃnh bรฌa" +_exportOrImport: + allNotes: "Toร n bแป tรบt" + followingList: "ฤang theo dรตi" + muteList: "แบจn" + blockingList: "Chแบทn" + userLists: "Danh sรกch" + excludeMutingUsers: "Loแบกi trแปซ nhแปฏng ngฦฐแปi dรนng bแป แบฉn" + excludeInactiveUsers: "Loแบกi trแปซ nhแปฏng ngฦฐแปi dรนng khรดng hoแบกt ฤแปng" +_charts: + federation: "Liรชn hแปฃp" + apRequest: "Yรชu cแบงu" + usersIncDec: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng ngฦฐแปi dรนng" + usersTotal: "Tแปng sแป ngฦฐแปi dรนng" + activeUsers: "Sแป ngฦฐแปi ฤang hoแบกt ฤแปng" + notesIncDec: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tรบt" + localNotesIncDec: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tรบt mรกy chแปง nร y" + remoteNotesIncDec: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tรบt tแปซ mรกy chแปง khรกc" + notesTotal: "Tแปng sแป sรบt" + filesIncDec: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tแบญp tin" + filesTotal: "Tแปng sแป tแบญp tin" + storageUsageIncDec: "Sแปฑ khรกc biแปt vแป dung lฦฐแปฃng lฦฐu trแปฏ" + storageUsageTotal: "Tแปng dung lฦฐแปฃng lฦฐu trแปฏ" +_instanceCharts: + requests: "Lฦฐแปฃt yรชu cแบงu" + users: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng ngฦฐแปi dรนng" + usersTotal: "Sแป lฦฐแปฃng ngฦฐแปi dรนng tรญch lลฉy" + notes: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tรบt" + notesTotal: "Sแป lฦฐแปฃng tรบt tรญch lลฉy" + ff: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng ngฦฐแปi dรนng ฤฦฐแปฃc theo dรตi/ngฦฐแปi theo dรตi" + ffTotal: "Sแป lฦฐแปฃng ngฦฐแปi dรนng ฤฦฐแปฃc theo dรตi/ngฦฐแปi theo dรตi tรญch lลฉy" + cacheSize: "Sแปฑ khรกc biแปt vแป dung lฦฐแปฃng bแป nhแป ฤแปm" + cacheSizeTotal: "Dung lฦฐแปฃng bแป nhแป ฤแปm tรญch lลฉy" + files: "Sแปฑ khรกc biแปt vแป sแป lฦฐแปฃng tแบญp tin" + filesTotal: "Sแป lฦฐแปฃng tแบญp tin tรญch lลฉy" +_timelines: + home: "Trang chรญnh" + local: "Mรกy chแปง nร y" + social: "Xรฃ hแปi" + global: "Liรชn hแปฃp" +_pages: + newPage: "Tแบกo Trang mแปi" + editPage: "Sแปญa Trang nร y" + readPage: "Xem mรฃ nguแปn Trang nร y" + created: "Trang ฤรฃ ฤฦฐแปฃc tแบกo thร nh cรดng" + updated: "Trang ฤรฃ ฤฦฐแปฃc cแบญp nhแบญt thร nh cรดng" + deleted: "Trang ฤรฃ ฤฦฐแปฃc xรณa thร nh cรดng" + pageSetting: "Cร i ฤแบทt trang" + nameAlreadyExists: "URL Trang ฤรฃ tแปn tแบกi" + invalidNameTitle: "URL Trang khรดng hแปฃp lแป" + invalidNameText: "Khรดng ฤฦฐแปฃc ฤแป trแปng tแปฑa ฤแป Trang" + editThisPage: "Sแปญa Trang nร y" + viewSource: "Xem maฬ nguรดฬn" + viewPage: "Xem trang cแปงa tรดi" + like: "Thรญch" + unlike: "Bแป thรญch" + my: "Trang cแปงa tรดi" + liked: "Trang ฤรฃ thรญch" + featured: "Nแปi tiแบฟng" + inspector: "Thanh tra" + contents: "Nแปi dung" + content: "Chแบทn Trang" + variables: "Biแบฟn thแป" + title: "Tแปฑa ฤแป" + url: "URL Trang" + summary: "Mรด tแบฃ Trang" + alignCenter: "Cฤn giแปฏa" + hideTitleWhenPinned: "แบจn tแปฑa ฤแป Trang khi ghim lรชn hแป sฦก" + font: "Phรดng chแปฏ" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + eyeCatchingImageSet: "ฤแบทt แบฃnh thu nhแป" + eyeCatchingImageRemove: "Xรณa แบฃnh thu nhแป" + chooseBlock: "Thรชm khแปi" + selectType: "Chแปn kiแปu" + enterVariableName: "Nhแบญp tรชn mแปt biแบฟn thแป" + variableNameIsAlreadyUsed: "Tรชn biแบฟn thแป nร y ฤรฃ ฤฦฐแปฃc sแปญ dแปฅng" + contentBlocks: "Nแปi dung" + inputBlocks: "Nhแบญp" + specialBlocks: "ฤแบทc biแปt" + blocks: + text: "Vฤn bแบฃn" + textarea: "Khu vแปฑc vฤn bแบฃn" + section: "Mแปฅc " + image: "Hรฌnh แบฃnh" + button: "Nรบt" + if: "Nแบฟu" + _if: + variable: "Biแบฟn thแป" + post: "Mแบซu ฤฤng" + _post: + text: "Nแปi dung" + attachCanvasImage: "ฤรญnh kรจm hรฌnh canva" + canvasId: "ID Canva" + textInput: "Vฤn bแบฃn ฤแบงu vร o" + _textInput: + name: "Tรชn biแบฟn thแป" + text: "Tแปฑa ฤแป" + default: "Giรก trแป mแบทc ฤแปnh" + textareaInput: "Vฤn bแบฃn nhiแปu dรฒng ฤแบงu vร o" + _textareaInput: + name: "Tรชn biแบฟn thแป" + text: "Tแปฑa ฤแป" + default: "Giรก trแป mแบทc ฤแปnh" + numberInput: "ฤแบงu vร o sแป" + _numberInput: + name: "Tรชn biแบฟn thแป" + text: "Tแปฑa ฤแป" + default: "Giรก trแป mแบทc ฤแปnh" + canvas: "Canva" + _canvas: + id: "ID Canva" + width: "Chiแปu rแปng" + height: "Chiแปu cao" + note: "Tรบt ฤรฃ nhรบng" + _note: + id: "ID tรบt" + idDescription: "Ngoร i ra, bแบกn cรณ thแป dรกn URL tรบt vร o ฤรขy." + detailed: "Xem chi tiแบฟt" + switch: "Chuyแปn ฤแปi" + _switch: + name: "Tรชn biแบฟn thแป" + text: "Tแปฑa ฤแป" + default: "Giรก trแป mแบทc ฤแปnh" + counter: "Bแป ฤแบฟm" + _counter: + name: "Tรชn biแบฟn thแป" + text: "Tแปฑa ฤแป" + inc: "Bฦฐแปc" + _button: + text: "Tแปฑa ฤแป" + colored: "Vแปi mร u" + action: "Thao tรกc khi nhแบฅn nรบt" + _action: + dialog: "Hiแปn hแปp thoแบกi" + _dialog: + content: "Nแปi dung" + resetRandom: "ฤแบทt lแบกi seed ngแบซu nhiรชn" + pushEvent: "Gแปญi mแปt sแปฑ kiแปn" + _pushEvent: + event: "Tรชn sแปฑ kiแปn" + message: "Tin nhแบฏn hiแปn thแป khi kรญch hoแบกt" + variable: "Biแปn thแป ฤแป gแปญi" + no-variable: "Khรดng" + callAiScript: "Gแปi AiScript" + _callAiScript: + functionName: "Tรชn tรญnh nฤng" + radioButton: "Lแปฑa chแปn" + _radioButton: + name: "Tรชn biแบฟn thแป" + title: "Tแปฑa ฤแป" + values: "Phรขn tรกch cรกc mแปฅc bแบฑng cรกch xuแปng dรฒng" + default: "Giรก trแป mแบทc ฤแปnh" + script: + categories: + flow: "ฤiแปu khiแปn" + logical: "Hoแบกt ฤแปng logic" + operation: "Tรญnh toรกn" + comparison: "So sรกnh" + random: "Ngแบซu nhiรชn" + value: "Giรก trแป" + fn: "Tรญnh nฤng" + text: "Tรกc vแปฅ vฤn bแบฃn" + convert: "Chuyแปn ฤแปi" + list: "Danh sรกch" + blocks: + text: "Vฤn bแบฃn" + multiLineText: "Vฤn bแบฃn (nhiแปu dรฒng)" + textList: "Vฤn bแบฃn liแปt kรช" + _textList: + info: "Phรขn tรกch mแปฅc bแบฑng cรกch xuแปng dรฒng" + strLen: "ฤแป dร i vฤn bแบฃn" + _strLen: + arg1: "Vฤn bแบฃn" + strPick: "Trรญch xuแบฅt chuแปi" + _strPick: + arg1: "Vฤn bแบฃn" + arg2: "Vแป trรญ chuแปi" + strReplace: "Thay thแบฟ chuแปi" + _strReplace: + arg1: "Nแปi dung" + arg2: "Vฤn bแบฃn thay thแบฟ" + arg3: "Thay thแบฟ bแบฑng" + strReverse: "Lแบญt vฤn bแบฃn" + _strReverse: + arg1: "Vฤn bแบฃn" + join: "Nแปi vฤn bแบฃn" + _join: + arg1: "Danh sรกch" + arg2: "Phรขn cรกch" + add: "Cแปng" + _add: + arg1: "A" + arg2: "B" + subtract: "Trแปซ" + _subtract: + arg1: "A" + arg2: "B" + multiply: "Nhรขn" + _multiply: + arg1: "A" + arg2: "B" + divide: "Chia" + _divide: + arg1: "A" + arg2: "B" + mod: "Phแบงn cรฒn lแบกi" + _mod: + arg1: "A" + arg2: "B" + round: "Lร m trรฒn thแบญp phรขn" + _round: + arg1: "Sแป" + eq: "A vร B bแบฑng nhau" + _eq: + arg1: "A" + arg2: "B" + notEq: "A vร B khรกc nhau" + _notEq: + arg1: "A" + arg2: "B" + and: "A Vร B" + _and: + arg1: "A" + arg2: "B" + or: "A HOแบถC B" + _or: + arg1: "A" + arg2: "B" + lt: "< A nhแป hฦกn B" + _lt: + arg1: "A" + arg2: "B" + gt: "> A lแปn hฦกn B" + _gt: + arg1: "A" + arg2: "B" + ltEq: "<= A nhแป hฦกn hoแบทc bแบฑng B" + _ltEq: + arg1: "A" + arg2: "B" + gtEq: ">= A lแปn hฦกn hoแบทc bแบฑng B" + _gtEq: + arg1: "A" + arg2: "B" + if: "Nhรกnh" + _if: + arg1: "Nแบฟu" + arg2: "Sau ฤรณ" + arg3: "Khรกc" + not: "KHรNG" + _not: + arg1: "KHรNG" + random: "Ngแบซu nhiรชn" + _random: + arg1: "Xรกc suแบฅt" + rannum: "Sแป ngแบซu nhiรชn" + _rannum: + arg1: "Giรก trแป tแปi thiแปu" + arg2: "Giรก trแป tแปi ฤa" + randomPick: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch" + _randomPick: + arg1: "Danh sรกch" + dailyRandom: "Ngแบซu nhiรชn (ฤแปi mแปi ngฦฐแปi mแปt lแบงn mแปi ngร y)" + _dailyRandom: + arg1: "Xรกc suแบฅt" + dailyRannum: "Sแป ngแบซu nhiรชn (ฤแปi mแปi ngฦฐแปi mแปt lแบงn mแปi ngร y)" + _dailyRannum: + arg1: "Giรก trแป tแปi thiแปu" + arg2: "Giรก trแป tแปi ฤa" + dailyRandomPick: "Chแปn ngแบซu nhiรชn tแปซ mแปt danh sรกch (ฤแปi mแปi ngฦฐแปi mแปt lแบงn mแปi ngร y)" + _dailyRandomPick: + arg1: "Danh sรกch" + seedRandom: "Ngแบซu nhiรชn (vแปi seed)" + _seedRandom: + arg1: "Seed" + arg2: "Xรกc suแบฅt" + seedRannum: "Sแป ngแบซu nhiรชn (vแปi seed)" + _seedRannum: + arg1: "Seed" + arg2: "Giรก trแป tแปi thiแปu" + arg3: "Giรก trแป tแปi ฤa" + seedRandomPick: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch (vแปi seed)" + _seedRandomPick: + arg1: "Seed" + arg2: "Danh sรกch" + DRPWPM: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch nแบทng (ฤแปi mแปi ngฦฐแปi mแปt lแบงn mแปi ngร y)" + _DRPWPM: + arg1: "Vฤn bแบฃn liแปt kรช" + pick: "Chแปn tแปซ danh sรกch" + _pick: + arg1: "Danh sรกch" + arg2: "Vแป trรญ" + listLen: "Lแบฅy ฤแป dร i danh sรกch" + _listLen: + arg1: "Danh sรกch" + number: "Sแป" + stringToNumber: "Chแปฏ thร nh sแป" + _stringToNumber: + arg1: "Vฤn bแบฃn" + numberToString: "Sแป thร nh chแปฏ" + _numberToString: + arg1: "Sแป" + splitStrByLine: "Phรขn cรกch vฤn bแบฃn bแบฑng cรกch xuแปng dรฒng" + _splitStrByLine: + arg1: "Vฤn bแบฃn" + ref: "Biแบฟn thแป" + aiScriptVar: "Biแปn thแป AiScript" + fn: "Tรญnh nฤng" + _fn: + slots: "Chแป" + slots-info: "Phรขn cรกch chแป bแบฑng cรกch xuแปng dรฒng" + arg1: "ฤแบงu ra" + for: "ฤแป-Lแบทp lแบกi" + _for: + arg1: "Sแป lแบงn lแบทp lแบกi" + arg2: "Hร nh ฤแปng" + typeError: "Chแป {slot} chแบฅp nhแบญn cรกc giรก trแป thuแปc loแบกi \"{expect}\", nhฦฐng giรก trแป ฤฦฐแปฃc cung cแบฅp thuแปc loแบกi \"{actual}\"!" + thereIsEmptySlot: "Chแป {slot} ฤang trแปng!" + types: + string: "Vฤn bแบฃn" + number: "Sแป" + boolean: "Cแป" + array: "Danh sรกch" + stringArray: "Vฤn bแบฃn liแปt kรช" + emptySlot: "Chแป trแปng" + enviromentVariables: "Biแบฟn mรดi trฦฐแปng" + pageVariables: "Biแบฟn trang" + argVariables: "ฤแบงu vร o chแป" +_relayStatus: + requesting: "ฤang chแป" + accepted: "ฤรฃ duyแปt" + rejected: "ฤรฃ tแปซ chแปi" +_notification: + fileUploaded: "ฤรฃ tแบฃi lรชn tแบญp tin" + youGotMention: "{name} nhแบฏc ฤแบฟn bแบกn" + youGotReply: "{name} trแบฃ lแปi bแบกn" + youGotQuote: "{name} trรญch dแบซn tรบt cแปงa bแบกn" + youRenoted: "{name} ฤฤng lแบกi tรบt cแปงa bแบกn" + youGotPoll: "{name} bรฌnh chแปn tรบt cแปงa bแบกn" + youGotMessagingMessageFromUser: "{name} nhแบฏn tin cho bแบกn" + youGotMessagingMessageFromGroup: "Mแปt tin nhแบฏn trong nhรณm {name}" + youWereFollowed: "ฤรฃ theo dรตi bแบกn" + youReceivedFollowRequest: "Bแบกn vแปซa cรณ mแปt yรชu cแบงu theo dรตi" + yourFollowRequestAccepted: "Yรชu cแบงu theo dรตi cแปงa bแบกn ฤรฃ ฤฦฐแปฃc chแบฅp nhแบญn" + youWereInvitedToGroup: "Bแบกn ฤรฃ ฤฦฐแปฃc mแปi tham gia nhรณm" + pollEnded: "Cuแปc bรฌnh chแปn ฤรฃ kแบฟt thรบc" + emptyPushNotificationMessage: "ฤรฃ cแบญp nhแบญt thรดng bรกo ฤแบฉy" + _types: + all: "Toร n bแป" + follow: "ฤang theo dรตi" + mention: "Nhแบฏc ฤแบฟn" + reply: "Lฦฐแปฃt trแบฃ lแปi" + renote: "ฤฤng lแบกi" + quote: "Trรญch dแบซn" + reaction: "Biแปu cแบฃm" + pollVote: "Lฦฐแปฃt bรฌnh chแปn" + pollEnded: "Bรฌnh chแปn kแบฟt thรบc" + receiveFollowRequest: "Yรชu cแบงu theo dรตi" + followRequestAccepted: "Yรชu cแบงu theo dรตi ฤฦฐแปฃc chแบฅp nhแบญn" + groupInvited: "Mแปi vร o nhรณm" + app: "Tแปซ app liรชn kแบฟt" + _actions: + followBack: "ฤรฃ theo dรตi lแบกi bแบกn" + reply: "Trแบฃ lแปi" + renote: "ฤฤng lแบกi" _deck: + alwaysShowMainColumn: "Luรดn hiแปn cแปt chรญnh" + columnAlign: "Cฤn cแปt" + columnMargin: "Cฤn lแป giแปฏa cรกc cแปt" + columnHeaderHeight: "Chiแปu rแปng cแปt แบฃnh bรฌa" + addColumn: "Thรชm cแปt" + swapLeft: "Hoรกn ฤแปi vแปi cแปt bรชn trรกi" + swapRight: "Hoรกn ฤแปi vแปi cแปt bรชn phแบฃi" + swapUp: "Hoรกn ฤแปi vแปi cแปt trรชn" + swapDown: "Hoรกn ฤแปi vแปi cแปt dฦฐแปi" + stackLeft: "Xแบฟp chแปng vแปi cแปt bรชn trรกi" + popRight: "Xแบฟp chแปng vแปi cแปt bรชn trรกi" + profile: "Hแป sฦก" _columns: + main: "Chรญnh" + widgets: "Tiแปn รญch" notifications: "Thรดng bรกo" + tl: "Bแบฃng tin" + antenna: "Trแบกm phรกt sรณng" + list: "Danh sรกch" + mentions: "Lฦฐแปฃt nhแบฏc" + direct: "Nhแบฏn riรชng" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index f644585835..4953f55280 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -8,7 +8,7 @@ notifications: "้็ฅ" username: "็จๆทๅ" password: "ๅฏ็ " forgotPassword: "ๅฟ่ฎฐๅฏ็ " -fetchingAsApObject: "ๅจ่้ฆๅฎๅฎๆฅ่ฏขไธญ..." +fetchingAsApObject: "ๆญฃๅจ่้ฆๅฎๅฎๆฅ่ฏขไธญ..." ok: "OK" gotIt: "ๆๆ็ฝไบ" cancel: "ๅๆถ" @@ -69,7 +69,7 @@ exportRequested: "ๅฏผๅบ่ฏทๆฑๅทฒๆไบค๏ผ่ฟๅฏ่ฝ้่ฆ่ฑไธไบๆถ้ด๏ผๅฏผ importRequested: "ๅฏผๅ ฅ่ฏทๆฑๅทฒๆไบค๏ผ่ฟๅฏ่ฝ้่ฆ่ฑไธ็นๆถ้ดใ" lists: "ๅ่กจ" noLists: "ๅ่กจไธบ็ฉบ" -note: "ๅๅธ" +note: "ๅธๅญ" notes: "ๅธๅญ" following: "ๅ ณๆณจไธญ" followers: "ๅ ณๆณจ่ " @@ -96,7 +96,7 @@ enterEmoji: "่พๅ ฅ่กจๆ ็ฌฆๅท" renote: "่ฝฌๅ" unrenote: "ๅๆถ่ฝฌๅ" renoted: "ๅทฒ่ฝฌๅใ" -cantRenote: "่ฏฅๅธๅญๆ ๆณ่ฝฌๅใ" +cantRenote: "่ฏฅๅธๆ ๆณ่ฝฌๅใ" cantReRenote: "่ฝฌๅๆ ๆณ่ขซๅๆฌก่ฝฌๅใ" quote: "ๅผ็จ" pinnedNote: "ๅทฒ็ฝฎ้กถ็ๅธๅญ" @@ -155,7 +155,7 @@ searchWith: "ๆ็ดข:{q}" youHaveNoLists: "ๅ่กจไธบ็ฉบ" followConfirm: "ไฝ ็กฎๅฎ่ฆๅ ณๆณจ{name}ๅ๏ผ" proxyAccount: "ไปฃ็่ดฆๆท" -proxyAccountDescription: "ไปฃ็ๅธๆทๆฏๅจๆไบๆ ๅตไธๅ ๅฝ็จๆท็่ฟ็จๅ ณๆณจ่ ็ๅธๆทใ ไพๅฆ๏ผๅฝไธไธช็จๆทๅๅบไธไธช่ฟ็จ็จๆทๆถ๏ผๅฆๆๆฒกๆไบบ่ท้่ฏฅๅๅบ็็จๆท๏ผๅ่ฏฅๆดปๅจๅฐไธไผไผ ้ๅฐ่ฏฅๅฎไพ๏ผๅ ๆญคๅฐไปฃไนไปฅไปฃ็ๅธๆทใ" +proxyAccountDescription: "ไปฃ็่ดฆๆทๆฏๅจๆไบๆ ๅตไธๅ ๅฝ็จๆท็่ฟ็จๅ ณๆณจ่ ็่ดฆๆทใ ไพๅฆ๏ผๅฝไธไธช็จๆทๅๅบไธไธช่ฟ็จ็จๆทๆถ๏ผๅฆๆๆฒกๆไบบ่ท้่ฏฅๅๅบ็็จๆท๏ผๅ่ฏฅๆดปๅจๅฐไธไผไผ ้ๅฐ่ฏฅๅฎไพ๏ผๅ ๆญคๅฐไปฃไนไปฅไปฃ็่ดฆๆทใ" host: "ไธปๆบๅ" selectUser: "้ๆฉ็จๆท" recipient: "ๆถไปถไบบ" @@ -171,7 +171,7 @@ charts: "ๅพ่กจ" perHour: "ๆฏๅฐๆถ" perDay: "ๆฏๅคฉ" stopActivityDelivery: "ๅๆญขๅ้ๆดปๅจ" -blockThisInstance: "้ปๆญขๆญคๅฎไพ" +blockThisInstance: "้ปๆญขๆญคๅฎไพๅๆฌๅฎไพๆจๆต" operations: "ๆไฝ" software: "่ฝฏไปถ" version: "็ๆฌ" @@ -250,7 +250,7 @@ messageRead: "ๅทฒ่ฏป" noMoreHistory: "ๆฒกๆๆดๅค็ๅๅฒ่ฎฐๅฝ" startMessaging: "ๆทปๅ ่ๅคฉ" nUsersRead: "{n}ไบบๅทฒ่ฏป" -agreeTo: "{0}ไบบๅๆ" +agreeTo: "{0}ๅพ้ๅ่กจ็คบๅทฒ้ ่ฏปๅนถๅๆ" tos: "ๆๅกๆกๆฌพ" start: "ๅผๅง" home: "้ฆ้กต" @@ -321,7 +321,7 @@ connectService: "่ฟๆฅ" disconnectService: "ๆญๅผ่ฟๆฅ" enableLocalTimeline: "ๅฏ็จๆฌๅฐๆถ้ด็บฟๅ่ฝ" enableGlobalTimeline: "ๅฏ็จๅ จๅฑๆถ้ด็บฟ" -disablingTimelinesInfo: "ๅณไฝฟๆถ้ด็บฟๅ่ฝ่ขซ็ฆ็จ๏ผๅบไบไพฟๅฉๆง็ๅๅ ๏ผ็ฎก็ๅๅๆฐๆฎๅพ่กจไนๅฏไปฅ็ปง็ปญไฝฟ็จใ" +disablingTimelinesInfo: "ๅณไฝฟๆถ้ด็บฟๅ่ฝ่ขซ็ฆ็จ๏ผๅบไบๆนไพฟ๏ผ็ฎก็ๅๅๆฐๆฎๅพ่กจไนๅฏไปฅ็ปง็ปญไฝฟ็จใ" registration: "ๆณจๅ" enableRegistration: "ๅ ่ฎธๆฐ็จๆทๆณจๅ" invite: "้่ฏท" @@ -440,7 +440,7 @@ strongPassword: "ๅฏ็ ๅผบๅบฆ๏ผๅผบ" passwordMatched: "ๅฏ็ ไธ่ด" passwordNotMatched: "ๅฏ็ ไธไธ่ด" signinWith: "ไปฅ{x}็ปๅฝ" -signinFailed: "ๆ ๆณ็ปๅฝ๏ผ่ฏทๆฃๆฅๆจ็็จๆทๅๅๅฏ็ ใ" +signinFailed: "ๆ ๆณ็ปๅฝ๏ผ่ฏทๆฃๆฅๆจ็็จๆทๅๅๅฏ็ ๆฏๅฆๆญฃ็กฎใ" tapSecurityKey: "่ฝป่งฆ็กฌไปถๅฎๅ จๅฏ้ฅ" or: "ๆ่ " language: "่ฏญ่จ" @@ -459,7 +459,7 @@ category: "็ฑปๅซ" tags: "ๆ ็ญพ" docSource: "ๆไปถๆฅๆบ" createAccount: "ๆณจๅ่ดฆๆท" -existingAccount: "็ฐๆ็ๅธๆท" +existingAccount: "็ฐๆ็่ดฆๆท" regenerate: "้ๆฐ็ๆ" fontSize: "ๅญไฝๅคงๅฐ" noFollowRequests: "ๆฒกๆๅ ณๆณจ็ณ่ฏท" @@ -533,7 +533,7 @@ removeAllFollowingDescription: "ๅๆถ{host}็ๆๆๅ ณๆณจ่ ใๅฝๅฎไพไธๅญ userSuspended: "่ฏฅ็จๆทๅทฒ่ขซๅป็ปใ" userSilenced: "่ฏฅ็จๆทๅทฒ่ขซ็ฆ่จใ" yourAccountSuspendedTitle: "่ดฆๆทๅทฒ่ขซๅป็ป" -yourAccountSuspendedDescription: "็ฑไบ่ฟๅไบๆๅกๅจ็ๆๅกๆกๆฌพๆๅ ถไปๅๅ ๏ผ่ฏฅ่ดฆๆทๅทฒ่ขซๅป็ปใ ๆจๅฏไปฅไธ็ฎก็ๅ่็ณปไปฅไบ่งฃๆดๅคไฟกๆฏใ ่ฏทไธ่ฆๅๅปบไธไธชๆฐ็ๅธๆทใ" +yourAccountSuspendedDescription: "็ฑไบ่ฟๅไบๆๅกๅจ็ๆๅกๆกๆฌพๆๅ ถไปๅๅ ๏ผ่ฏฅ่ดฆๆทๅทฒ่ขซๅป็ปใ ๆจๅฏไปฅไธ็ฎก็ๅ่็ณปไปฅไบ่งฃๆดๅคไฟกๆฏใ ่ฏทไธ่ฆๅๅปบไธไธชๆฐ็่ดฆๆทใ" menu: "่ๅ" divider: "ๅๅฒ็บฟ" addItem: "ๆทปๅ ้กน็ฎ" @@ -609,7 +609,7 @@ create: "ๅๅปบ" notificationSetting: "้็ฅ่ฎพ็ฝฎ" notificationSettingDesc: "้ๆฉ่ฆๆพ็คบ็้็ฅ็ฑปๅใ" useGlobalSetting: "ไฝฟ็จๅ จๅฑ่ฎพ็ฝฎ" -useGlobalSettingDesc: "ๅฏ็จๆถ๏ผๅฐไฝฟ็จๅธๆท้็ฅ่ฎพ็ฝฎใๅ ณ้ญๆถ๏ผๅๅฏไปฅๅ็ฌ่ฎพ็ฝฎใ" +useGlobalSettingDesc: "ๅฏ็จๆถ๏ผๅฐไฝฟ็จ่ดฆๆท้็ฅ่ฎพ็ฝฎใๅ ณ้ญๆถ๏ผๅๅฏไปฅๅ็ฌ่ฎพ็ฝฎใ" other: "ๅ ถไป" regenerateLoginToken: "้ๆฐ็ๆ็ปๅฝไปค็" regenerateLoginTokenDescription: "้ๆฐ็ๆ็จไบ็ปๅฝ็ๅ ้จไปค็ใ้ๅธธๆจไธ้่ฆ่ฟๆ ทๅใ้ๆฐ็ๆๅ๏ผๆจๅฐๅจๆๆ่ฎพๅคไธ็ปๅบใ" @@ -621,12 +621,12 @@ abuseReports: "ไธพๆฅ" reportAbuse: "ไธพๆฅ" reportAbuseOf: "ไธพๆฅ{name}" fillAbuseReportDescription: "่ฏทๅกซๅไธพๆฅ็่ฏฆ็ปๅๅ ใๅฆๆๆๅฏนๆนๅ็ๅธๅญ๏ผ่ฏทๅๆถๅกซๅURLๅฐๅใ" -abuseReported: "ๅ ๅฎนๅทฒๅ้ใๆ่ฐขๆจ็ๆฅๅใ" -reporter: "ๆฅๅ่ " +abuseReported: "ๅ ๅฎนๅทฒๅ้ใๆ่ฐขๆจๆไบคไฟกๆฏใ" +reporter: "ไธพๆฅ่ " reporteeOrigin: "ไธพๆฅๆฅๆบ" reporterOrigin: "ไธพๆฅ่ ๆฅๆบ" -forwardReport: "ๅฐๆฅๅ่ฝฌๅ็ป่ฟ็จๅฎไพ" -forwardReportIsAnonymous: "ๅจ่ฟ็จๅฎไพไธๆพ็คบ็ๆฅๅ่ ๆฏๅฟๅ็็ณป็ป่ดฆๅท๏ผ่ไธๆฏๆจ็่ดฆๅทใ" +forwardReport: "ๅฐ่ฏฅไธพๆฅไฟกๆฏ่ฝฌๅ็ป่ฟ็จๅฎไพ" +forwardReportIsAnonymous: "ๅพ้ๅๅจ่ฟ็จๅฎไพไธๆพ็คบ็ไธพๆฅ่ ๆฏๅฟๅ็็ณป็ป่ดฆๅท๏ผ่ไธๆฏๆจ็่ดฆๅทใ" send: "ๅ้" abuseMarkAsResolved: "ๅค็ๅฎๆฏ" openInNewTab: "ๅจๆฐๆ ็ญพ้กตไธญๆๅผ" @@ -644,9 +644,9 @@ createNew: "ๆฐๅปบ" optional: "ๅฏ้" createNewClip: "ๆฐๅปบไนฆ็ญพ" public: "ๅ ฌๅผ" -i18nInfo: "Misskeyๅทฒ็ป่ขซๅฟๆฟ่ ไปฌ็ฟป่ฏๅฐไบๅ็ง่ฏญ่จใๅฆๆไฝ ไนๆๅ ด่ถฃ๏ผๅฏไปฅ้่ฟ{link}ๅธฎๅฉ็ฟป่ฏใ" +i18nInfo: "Misskeyๅทฒ็ป่ขซๅฟๆฟ่ ไปฌ็ฟป่ฏๆไบๅ็ง่ฏญ่จใๅฆๆไฝ ไนๆๅ ด่ถฃ๏ผๅฏไปฅ้่ฟ{link}ๅธฎๅฉ็ฟป่ฏใ" manageAccessTokens: "็ฎก็ Access Tokens" -accountInfo: "ๅธๆทไฟกๆฏ" +accountInfo: "่ดฆๆทไฟกๆฏ" notesCount: "ๅธๅญๆฐ้" repliesCount: "ๅๅคๆฐ้" renotesCount: "่ฝฌๅธๆฐ้" @@ -662,7 +662,7 @@ yes: "ๆฏ" no: "ๅฆ" driveFilesCount: "็ฝ็็ๆไปถๆฐ" driveUsage: "็ฝ็็็ฉบ้ด็จ้" -noCrawle: "ๆ็ปๆ็ดขๅผๆ็็ดขๅผ" +noCrawle: "่ฆๆฑๆ็ดขๅผๆไธ็ดขๅผ่ฏฅ็ซ็น" noCrawleDescription: "่ฆๆฑๆ็ดขๅผๆไธ่ฆๆถๅฝ๏ผ็ดขๅผ๏ผๆจ็็จๆท้กต้ข๏ผๅธๅญ๏ผ้กต้ข็ญใ" lockedAccountInfo: "ๅณไฝฟ้่ฟไบๅ ณๆณจ่ฏทๆฑ๏ผๅช่ฆๆจไธๅฐๅธๅญๅฏ่ง่ๅด่ฎพ็ฝฎๆโๅ ณๆณจ่ โ๏ผไปปไฝไบบ้ฝๅฏไปฅ็ๅฐๆจ็ๅธๅญใ" alwaysMarkSensitive: "้ป่ฎคๅฐๅชไฝๆไปถๆ ่ฎฐไธบๆๆๅ ๅฎน" @@ -1087,7 +1087,6 @@ _sfx: antenna: "ๅคฉ็บฟๆฅๆถ" channel: "้ข้้็ฅ" _ago: - unknown: "ๆช็ฅ" future: "ๆชๆฅ" justNow: "ๆ่ฟ" secondsAgo: "{n}็งๅ" @@ -1131,6 +1130,7 @@ _2fa: registerKey: "ๆณจๅๅฏ้ฅ" step1: "้ฆๅ ๏ผๅจๆจ็่ฎพๅคไธๅฎ่ฃ ้ช่ฏๅบ็จ๏ผไพๅฆ{a}ๆ{b}ใ" step2: "็ถๅ๏ผๆซๆๅฑๅนไธๆพ็คบ็ไบ็ปด็ ใ" + step2Url: "ๅจๆก้ขๅบ็จ็จๅบไธญ่พๅ ฅไปฅไธURL๏ผ" step3: "่พๅ ฅๆจ็ๅบ็จๆไพ็ๅจๆๅฃไปคไปฅๅฎๆ่ฎพ็ฝฎใ" step4: "ไป็ฐๅจๅผๅง๏ผไปปไฝ็ปๅฝๆไฝ้ฝๅฐ่ฆๆฑๆจๆไพๅจๆๅฃไปคใ" securityKeyInfo: "ๆจๅฏไปฅ่ฎพ็ฝฎไฝฟ็จๆฏๆFIDO2็็กฌไปถๅฎๅ จๅฏ้ฅใ่ฎพๅคไธ็ๆ็บนๆPINๆฅไฟๆคๆจ็็ปๅฝ่ฟ็จใ" @@ -1615,6 +1615,7 @@ _notification: yourFollowRequestAccepted: "ๆจ็ๅ ณๆณจ่ฏทๆฑๅทฒ้่ฟ" youWereInvitedToGroup: "ๆจๆๆฐ็็พค็ป้่ฏท" pollEnded: "้ฎๅท่ฐๆฅ็ปๆๅทฒ็ๆใ" + emptyPushNotificationMessage: "ๆจ้้็ฅๅทฒๆดๆฐ" _types: all: "ๅ จ้จ" follow: "ๅ ณๆณจไธญ" @@ -1629,6 +1630,10 @@ _notification: followRequestAccepted: "ๅ ณๆณจ่ฏทๆฑๅทฒ้่ฟ" groupInvited: "ๅ ๅ ฅ็พค็ป้่ฏท" app: "ๅ ณ่ๅบ็จ็้็ฅ" + _actions: + followBack: "ๅๅ ณ" + reply: "ๅๅค" + renote: "่ฝฌๅ" _deck: alwaysShowMainColumn: "ๆปๆฏๆพ็คบไธปๅ" columnAlign: "ๅๅฏน้ฝ" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 18c6f17154..f088fdc0e9 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -135,13 +135,14 @@ emojiName: "่กจๆ ็ฌฆ่ๅ็จฑ" emojiUrl: "่กจๆ ็ฌฆ่URL" addEmoji: "ๅ ๅ ฅ่กจๆ ็ฌฆ่" settingGuide: "ๆจ่ฆ่จญๅฎ" -cacheRemoteFiles: "็ทฉๅญ้้ ็จๆชๆก" +cacheRemoteFiles: "ๅฟซๅ้ ็ซฏๆชๆก" cacheRemoteFilesDescription: "็ฆ็จๆญค่จญๅฎๆๅๆญข้ ็ซฏๆชๆก็็ทฉๅญ๏ผๅพ่็ฏ็ๅฒๅญ็ฉบ้๏ผไฝ่ณๆๆๅ ็ดๆฅ้ฃ็ทๅพ่็ข็้กๅค้ฃๆฅๆธๆใ" flagAsBot: "ๆญคไฝฟ็จ่ ๆฏๆฉๅจไบบ" flagAsBotDescription: "ๅฆๆๆฌๅธณๆถๆฏ็ฑ็จๅผๆงๅถ๏ผ่ซๅ็จๆญค้ธ้ ใๅ็จๅพ๏ผๆไฝ็บๆจ็คบๅนซๅฉๅ ถไป้็ผ่ ้ฒๆญขๆฉๅจไบบไน้็ข็็ก้ไบๅ็่ก็บ๏ผไธฆๆ่ชฟๆดMisskeyๅ ง้จ็ณป็ตฑๅฐๆฌๅธณๆถ่ญๅฅ็บๆฉๅจไบบ" flagAsCat: "ๆญคไฝฟ็จ่ ๆฏ่ฒ" flagAsCatDescription: "ๅฆๆๆณๅฐๆฌๅธณๆถๆจ็คบ็บไธ้ป่ฒ๏ผ่ซ้ๅๆญคๆจ็คบ" flagShowTimelineReplies: "ๅจๆ้่ปธไธ้กฏ็คบ่ฒผๆ็ๅ่ฆ" +flagShowTimelineRepliesDescription: "ๅ็จๆ๏ผๆ้็ท้คไบ้กฏ็คบ็จๆถ็่ฒผๆไปฅๅค๏ผ้ๆ้กฏ็คบ็จๆถๅฐๅ ถไป่ฒผๆ็ๅ่ฆใ" autoAcceptFollowed: "่ชๅ่ฟฝ้จไธญไฝฟ็จ่ ็่ฟฝ้จ่ซๆฑ" addAccount: "ๆทปๅ ๅธณๆถ" loginFailed: "็ปๅ ฅๅคฑๆ" @@ -153,8 +154,8 @@ removeWallpaper: "็งป้คๆกๅธ" searchWith: "ๆๅฐ: {q}" youHaveNoLists: "ไฝ ๆฒๆไปปไฝๆธ ๅฎ" followConfirm: "ไฝ ็็่ฆ่ฟฝ้จ{name}ๅ๏ผ" -proxyAccount: "ไปฃ็ๅธณ่" -proxyAccountDescription: "ไปฃ็ๅธณ่ๆฏๅจๆไบๆ ๆณไธๅ ็ถๅ ถไปไผบๆๅจ็จๆถ็ๅธณ่ใไพๅฆ๏ผ็ถไฝฟ็จ่ ๅฐไธๅไพ่ชๅ ถไปไผบๆๅจ็ๅธณ่ๆพๅจๅ่กจไธญๆ๏ผ็ฑๆผๆฒๆๅ ถไปไฝฟ็จ่ ้ๆณจ่ฉฒๅธณ่๏ผ่ฉฒๆไปคไธๆๅณ้ๅฐ่ฉฒไผบๆๅจไธ๏ผๅ ๆญคๆ็ฑไปฃ็ๅธณๆถ้ๆณจใ" +proxyAccount: "ไปฃ็ๅธณๆถ" +proxyAccountDescription: "ไปฃ็ๅธณๆถๆฏๅจๆไบๆ ๆณไธๅ ็ถๅ ถไปไผบๆๅจ็จๆถ็ๅธณๆถใไพๅฆ๏ผ็ถไฝฟ็จ่ ๅฐไธๅไพ่ชๅ ถไปไผบๆๅจ็ๅธณๆถๆพๅจๅ่กจไธญๆ๏ผ็ฑๆผๆฒๆๅ ถไปไฝฟ็จ่ ้ๆณจ่ฉฒๅธณๆถ๏ผ่ฉฒๆไปคไธๆๅณ้ๅฐ่ฉฒไผบๆๅจไธ๏ผๅ ๆญคๆ็ฑไปฃ็ๅธณๆถ้ๆณจใ" host: "ไธปๆฉ" selectUser: "้ธๅไฝฟ็จ่ " recipient: "ๆถไปถไบบ" @@ -197,7 +198,7 @@ noUsers: "ๆฒๆไปปไฝไฝฟ็จ่ " editProfile: "็ทจ่ผฏๅไบบๆชๆก" noteDeleteConfirm: "็ขบๅฎๅช้คๆญค่ฒผๆๅ๏ผ" pinLimitExceeded: "ไธ่ฝ็ฝฎ้ ๆดๅค่ฒผๆไบ" -intro: "Misskey ้จ็ฝฒๅฎๆ๏ผ่ซๅปบ็ซ็ฎก็ๅกๅธณ่๏ผ" +intro: "Misskey ้จ็ฝฒๅฎๆ๏ผ่ซๅปบ็ซ็ฎก็ๅกๅธณๆถใ" done: "ๅฎๆ" processing: "่็ไธญ" preview: "้ ่ฆฝ" @@ -236,6 +237,8 @@ resetAreYouSure: "็ขบๅฎ่ฆ้่จญๅ๏ผ" saved: "ๅทฒๅฒๅญ" messaging: "ๅณ้่จๆฏ" upload: "ไธๅณ" +keepOriginalUploading: "ไฟ็ๅๅ" +keepOriginalUploadingDescription: "ไธๅณๅ็ๆไฟ็ๅๅงๅ็ใ้้ๆ๏ผ็่ฆฝๅจๆๅจไธๅณๆ็ๆไธๅผต็จๆผweb็ผๅธ็ๅ็ใ" fromDrive: "ๅพ้ฒ็ซฏ็ฉบ้" fromUrl: "ๅพURL" uploadFromUrl: "ๅพ็ถฒๅไธๅณ" @@ -357,7 +360,7 @@ enableServiceworker: "้ๅ ServiceWorker" antennaUsersDescription: "ๆๅฎ็จๆ่ก็ฌฆๅ้็็จๆถๅ" caseSensitive: "ๅๅๅคงๅฐๅฏซ" withReplies: "ๅ ๅซๅ่ฆ" -connectedTo: "ๆจ็ๅธณ่ๅทฒ้ฃๆฅๅฐไปฅไธ็คพไบคๅธณ่" +connectedTo: "ๆจ็ๅธณๆถๅทฒ้ฃๆฅๅฐไปฅไธ็คพไบคๅธณๆถ" notesAndReplies: "่ฒผๆ่ๅ่ฆ" withFiles: "้ไปถ" silence: "็ฆ่จ" @@ -445,6 +448,7 @@ uiLanguage: "ไป้ข่ช่จ" groupInvited: "ๆจๆๆฐ็็พค็ต้่ซ" aboutX: "้ๆผ{x}" useOsNativeEmojis: "ไฝฟ็จOSๅ็่กจๆ ็ฌฆ่" +disableDrawer: "ไธ้กฏ็คบไธๆๅผ้ธๅฎ" youHaveNoGroups: "ๆพไธๅฐ็พค็ต" joinOrCreateGroup: "่ซๅ ๅ ฅ็พๆ็พค็ต๏ผๆๅตๅปบๆฐ็พค็ตใ" noHistory: "ๆฒๆๆญทๅฒ็ด้" @@ -468,7 +472,7 @@ weekOverWeekChanges: "่ไธ้ฑ็ธๆฏ" dayOverDayChanges: "่ๅไธๆฅ็ธๆฏ" appearance: "ๅค่ง" clientSettings: "็จๆถ็ซฏ่จญๅฎ" -accountSettings: "ๅธณ่่จญๅฎ" +accountSettings: "ๅธณๆถ่จญๅฎ" promotion: "ๆจๅปฃ" promote: "ๆจๅปฃ" numberOfDays: "ๆๆๅคฉๆธ" @@ -477,6 +481,7 @@ showFeaturedNotesInTimeline: "ๅจๆ้่ปธไธ้กฏ็คบ็ฑ้ๆจ่ฆ" objectStorage: "Object Storage (็ฉไปถๅฒๅญ)" useObjectStorage: "ไฝฟ็จObject Storage" objectStorageBaseUrl: "Base URL" +objectStorageBaseUrlDesc: "ๅผ็จๆ็URLใๅฆๆๆจไฝฟ็จ็ๆฏCDNๆๅๅไปฃ็๏ผ่ฏทๆๅฎๅ ถURL๏ผไพๅฆS3๏ผโhttps://<bucket>.s3.amazonaws.comโ๏ผGCS๏ผโhttps://storage.googleapis.com/<bucket>โ" objectStorageBucket: "ๅฒๅญ็ฉบ้๏ผBucket๏ผ" objectStorageBucketDesc: "่ซๆๅฎๆจๆญฃๅจไฝฟ็จ็ๆๅ็ๅญๅฒๆกถๅ็จฑใ " objectStoragePrefix: "ๅ็ถด" @@ -484,8 +489,11 @@ objectStoragePrefixDesc: "ๅฎๅญๅฒๅจๆญคๅ็ถด็ฎ้ไธใ" objectStorageEndpoint: "็ซฏ้ป๏ผEndpoint๏ผ" objectStorageEndpointDesc: "ๅฆ่ฆไฝฟ็จAWS S3๏ผ่ซ็็ฉบใๅฆๅ่ซไพ็ งไฝ ไฝฟ็จ็ๆๅๅ็่ชชๆๆธ้ฒ่ก่จญๅฎ๏ผไปฅ'<host>'ๆ '<host>:<port>'็ๅฝขๅผ่จญๅฎ็ซฏ้ป๏ผEndpoint๏ผใ" objectStorageRegion: "ๅฐๅ๏ผRegion๏ผ" +objectStorageRegionDesc: "ๆๅฎไธๅๅๅ๏ผไพๅฆโxx-east-1โใ ๅฆๆๆจไฝฟ็จ็ๆๅๆฒๆๅๅ็ๆฆๅฟต๏ผ่ซ็็ฉบๆๅกซๅฏซโus-east-1โใ" objectStorageUseSSL: "ไฝฟ็จSSL" +objectStorageUseSSLDesc: "ๅฆๆไธไฝฟ็จhttps้ฒ่กAPI้ฃๆฅ๏ผ่ซ้้" objectStorageUseProxy: "ไฝฟ็จ็ถฒ่ทฏไปฃ็" +objectStorageUseProxyDesc: "ๅฆๆไธไฝฟ็จไปฃ็้ฒ่กAPI้ฃๆฅ๏ผ่ซ้้" objectStorageSetPublicRead: "ไธๅณๆ่จญๅฎ็บ\"public-read\"" serverLogs: "ไผบๆๅจๆฅ่ช" deleteAll: "ๅช้คๆๆ่จ้" @@ -513,6 +521,7 @@ sort: "ๆๅบ" ascendingOrder: "ๆๅช" descendingOrder: "้ๅช" scratchpad: "ๆซๅญ่จๆถ้ซ" +scratchpadDescription: "AiScriptๆงๅถๅฐ็บAiScriptๆไพไบๅฏฆ้ฉ็ฐๅขใๆจๅฏไปฅๅจๆญค็ทจๅฏซใๅท่กๅ็ขบ่ชไปฃ็ขผ่Misskeyไบๅ็็ปๆใ" output: "่ผธๅบ" script: "่ ณๆฌ" disablePagesScript: "ๅ็จ้ ้ข็AiScript่ ณๆฌ" @@ -523,6 +532,9 @@ removeAllFollowing: "่งฃ้คๆๆ่ฟฝ่นค" removeAllFollowingDescription: "่งฃ้ค{host}ๆๆ็่ฟฝ่นคใๅจๅฏฆไพไธๅๅญๅจๆๅท่กใ" userSuspended: "่ฉฒไฝฟ็จ่ ๅทฒ่ขซๅ็จ" userSilenced: "่ฉฒ็จๆถๅทฒ่ขซ็ฆ่จใ" +yourAccountSuspendedTitle: "ๅธณๆถๅทฒ่ขซๅ็ต" +yourAccountSuspendedDescription: "็ฑๆผ้ๅไบไผบๆๅจ็ๆๅๆขๆฌพๆๅ ถไปๅๅ ๏ผ่ฉฒๅธณๆถๅทฒ่ขซๅ็ตใ ๆจๅฏไปฅ่็ฎก็ๅก้ฃ็นซไปฅไบ่งฃๆดๅค่จๆฏใ ่ซไธ่ฆๅตๅปบไธๅๆฐ็ๅธณๆถใ" +menu: "้ธๅฎ" divider: "ๅๅฒ็ท" addItem: "ๆฐๅข้ ็ฎ" relays: "ไธญ็นผ" @@ -546,7 +558,7 @@ enterFileDescription: "่ผธๅ ฅๆจ้ก " author: "ไฝ่ " leaveConfirm: "ๆๆชไฟๅญ็ๆดๆนใ่ฆๆพๆฃๅ๏ผ" manage: "็ฎก็" -plugins: "ๆไปถ" +plugins: "ๅคๆ" deck: "ๅคๆฌๆจกๅผ" undeck: "ๅๆถๅคๆฌๆจกๅผ" useBlurEffectForModal: "ๅจๆจกๆ ๆกไฝฟ็จๆจก็ณๆๆ" @@ -556,10 +568,12 @@ height: "้ซๅบฆ" large: "ๅคง" medium: "ไธญ" small: "ๅฐ" +generateAccessToken: "็ผ่กๅญๅๆฌๆ" permission: "ๆฌ้" enableAll: "ๅ็จๅ จ้จ" disableAll: "ๅ็จๅ จ้จ" -tokenRequested: "ๅ ่จฑๅญๅๅธณ่" +tokenRequested: "ๅ ่จฑๅญๅๅธณๆถ" +pluginTokenRequestedDescription: "ๆญคๅคๆๅฐๆๆๅจๆญค่จญๅฎ็ๆฌ้ใ" notificationType: "้็ฅๅฝขๅผ" edit: "็ทจ่ผฏ" useStarForReactionFallback: "ไปฅโ ไปฃๆฟๆช็ฅ็่กจๆ ็ฌฆ่" @@ -574,8 +588,13 @@ smtpPort: "ๅ " smtpUser: "ไฝฟ็จ่ ๅ็จฑ" smtpPass: "ๅฏ็ขผ" emptyToDisableSmtpAuth: "็็ฉบไฝฟ็จ่ ๅ็จฑๅๅฏ็ขผไปฅ้้SMTP้ฉ่ญใ" +smtpSecure: "ๅจ SMTP ้ฃๆฅไธญไฝฟ็จ้ฑๅผ SSL/TLS" +smtpSecureInfo: "ไฝฟ็จSTARTTLSๆ้้ใ" testEmail: "ๆธฌ่ฉฆ้ตไปถ็ผ้" -wordMute: "้้ณๆๅญ" +wordMute: "่ขซ้้ณ็ๆๅญ" +regexpError: "ๆญฃ่ฆ่กจ้ๅผ้ฏ่ชค" +regexpErrorDescription: "{tab} ้้ณๆๅญ็็ฌฌ {line} ่ก็ๆญฃ่ฆ่กจ้ๅผๆ้ฏ่ชค๏ผ" +instanceMute: "ๅฏฆไพ็้้ณ" userSaysSomething: "{name}่ชชไบไป้บผ" makeActive: "ๅ็จ" display: "ๆชข่ฆ" @@ -606,6 +625,8 @@ abuseReported: "ๅๅ ฑๅทฒ้ๅบใๆ่ฌๆจ็ๅ ฑๅใ" reporter: "ๆชข่่ " reporteeOrigin: "ๆชข่ไพๆบ" reporterOrigin: "ๆชข่่ ไพๆบ" +forwardReport: "ๅฐๅ ฑๅ่ฝ้็ตฆ้ ็ซฏๅฏฆไพ" +forwardReportIsAnonymous: "ๅจ้ ็ซฏๅฏฆไพไธ็ไธๅฐๆจ็่ณ่จ๏ผ้กฏ็คบ็ๅ ฑๅ่ ๆฏๅฟๅ็็ณป็ปๅธณๆถใ" send: "็ผ้" abuseMarkAsResolved: "่็ๅฎ็ข" openInNewTab: "ๅจๆฐๅ้ ไธญ้ๅ" @@ -667,6 +688,7 @@ center: "็ฝฎไธญ" wide: "ๅฏฌ" narrow: "็ช" reloadToApplySetting: "่จญๅฎๅฐๆๅจ้ ้ข้ๆฐ่ผๅ ฅไนๅพ็ๆใ่ฆ็พๅจๅฐฑ้่ผ้ ้ขๅ๏ผ" +needReloadToApply: "ๅฟ ้ ้ๆฐ่ผๅ ฅๆๆ็ๆใ" showTitlebar: "้กฏ็คบๆจ้กๅ" clearCache: "ๆธ ้คๅฟซๅ่ณๆ" onlineUsersCount: "{n}ไบบๆญฃๅจ็ทไธ" @@ -727,6 +749,7 @@ notRecommended: "ไธๆจ่ฆ" botProtection: "Bot้ฒ่ญท" instanceBlocking: "ๅทฒๅฐ้็ๅฏฆไพ" selectAccount: "้ธๆๅธณๆถ" +switchAccount: "ๅๆๅธณๆถ" enabled: "ๅทฒๅ็จ" disabled: "ๅทฒๅ็จ" quickAction: "ๅฟซๆทๆไฝ" @@ -753,32 +776,92 @@ emailNotConfiguredWarning: "ๆฒๆ่จญๅฎ้ปๅญ้ตไปถๅฐๅ" ratio: "%" previewNoteText: "้ ่ฆฝๆๆฌ" customCss: "่ชๅฎ็พฉ CSS" +customCssWarn: "้ๅ่จญๅฎๅฟ ้ ็ฑๅ ทๅ็ธ้็ฅ่ญ็ไบบๅกๆไฝ๏ผไธ็ถ็่จญๅฎๅฏ่ฝๅฏผ่ดๅฎขๆถ็ซฏ็กๆณๆญฃๅธธไฝฟ็จใ" global: "ๅ ฌ้" +squareAvatars: "้ ญๅไปฅๆนๅฝข้กฏ็คบ" sent: "็ผ้" received: "ๆถๅ" searchResult: "ๆๅฐ็ตๆ" hashtags: "#tag" troubleshooting: "ๆ ้ๆ้ค" useBlurEffect: "ๅจ UI ไธไฝฟ็จๆจก็ณๆๆ" +learnMore: "ๆดๅค่ณ่จ" misskeyUpdated: "Misskey ๆดๆฐๅฎๆ๏ผ" +whatIsNew: "้กฏ็คบๆดๆฐ่ณ่จ" translate: "็ฟป่ญฏ" translatedFrom: "ๅพ {x} ็ฟป่ญฏ" accountDeletionInProgress: "ๆญฃๅจๅช้คๅธณๆถ" +usernameInfo: "ๅจไผบๆๅจไธๆจ็ๅธณๆถๆฏๅฏไธ็่ญๅฅๅ็จฑใๆจๅฏไปฅไฝฟ็จๅญๆฏ (a ~ z, A ~ Z)ใๆธๅญ (0 ~ 9) ๅไธๅบ็ท (_)ใไนๅพๅธณๆถๅๆฏไธ่ฝๆดๆน็ใ" +aiChanMode: "ๅฐ่ๆจกๅผ" +keepCw: "ไฟๆCW" pubSub: "Pub/Sub ๅธณๆถ" +lastCommunication: "ๆ่ฟ็้ไฟก" resolved: "ๅทฒ่งฃๆฑบ" unresolved: "ๆช่งฃๆฑบ" breakFollow: "็งป้ค่ฟฝ่นค่ " +itsOn: "ๅทฒ้ๅ" +itsOff: "ๅทฒ้้" +emailRequiredForSignup: "่จปๅๅธณๆถ้่ฆ้ปๅญ้ตไปถๅฐๅ" +unread: "ๆช่ฎ" +filter: "็ฏฉ้ธ" +controlPanel: "ๆงๅถๅฐ" +manageAccounts: "็ฎก็ๅธณๆถ" +makeReactionsPublic: "ๅฐๅๆ่จญ็บๅ ฌ้" +makeReactionsPublicDescription: "ๅฐๆจๅ้็ๅๆ่จญ็บๅ ฌ้ๅฏ่ฆใ" +classic: "็ถๅ ธ" +muteThread: "ๅฐ่ฒผๆไธฒ่จญ็บ้้ณ" +unmuteThread: "ๅฐ่ฒผๆไธฒ็้้ณ่งฃ้ค" +ffVisibility: "้ฃๆฅ็ๅ ฌ้็ฏๅ" +ffVisibilityDescription: "ๆจๅฏไปฅ่จญๅฎๆจ็้ๆณจ/้ๆณจ่ ่ณ่จ็ๅ ฌ้็ฏๅ" +continueThread: "ๆฅ็ๆดๅค่ฒผๆ" +deleteAccountConfirm: "ๅฐ่ฆๅช้คๅธณๆถใๆฏๅฆ็ขบๅฎ๏ผ" +incorrectPassword: "ๅฏ็ขผ้ฏ่ชคใ" +voteConfirm: "็ขบๅฎๆ็ตฆใ{choice}ใ๏ผ" hide: "้ฑ่" +leaveGroup: "้ข้็พค็ต" leaveGroupConfirm: "็ขบๅฎ้ข้ใ{name}ใ๏ผ" +useDrawerReactionPickerForMobile: "ๅจ็งปๅ่จญๅไธไฝฟ็จๆฝๅฑ้กฏ็คบ" +welcomeBackWithName: "ๆญก่ฟๅไพ๏ผ{name}" +clickToFinishEmailVerification: "้ปๆ [{ok}] ๅฎๆ้ปๅญ้ตไปถๅฐๅ่ช่ญใ" +overridedDeviceKind: "่ฃ็ฝฎ้กๅ" +smartphone: "ๆบๆ งๅๆๆฉ" +tablet: "ๅนณๆฟ" auto: "่ชๅ" +themeColor: "ไธป้ก้ก่ฒ" +size: "ๅคงๅฐ" +numberOfColumn: "ๅๆธ" searchByGoogle: "ๆๅฐ" +instanceDefaultLightTheme: "ๅฏฆไพ้ ่จญ็ๆทบ่ฒไธป้ก" +instanceDefaultDarkTheme: "ๅฏฆไพ้ ่จญ็ๆทฑ่ฒไธป้ก" +instanceDefaultThemeDescription: "่ผธๅ ฅ็ฉไปถๅฝขๅผ็ไธป้ขไปฃ็ขผ" +mutePeriod: "้้ณ็ๆ้" indefinitely: "็กๆ้" +tenMinutes: "10ๅ้" +oneHour: "1ๅฐๆ" +oneDay: "1ๅคฉ" +oneWeek: "1้ฑ" +reflectMayTakeTime: "ๅฏ่ฝ้่ฆไธไบๆ้ๆๆๅบ็พๆๆใ" +failedToFetchAccountInformation: "ๅๅพๅธณๆถ่ณ่จๅคฑๆ" +_emailUnavailable: + used: "ๅทฒ็ถๅจไฝฟ็จไธญ" + format: "ๆ ผๅผ็กๆ" + disposable: "ไธๆฏๆฐธไน ๅฏ็จ็ๅฐๅ" + mx: "้ตไปถไผบๆๅจไธๆญฃ็ขบ" + smtp: "้ตไปถไผบๆๅจๆฒๆๆ็ญ" _ffVisibility: public: "็ผไฝ" + followers: "ๅชๆ้ๆณจไฝ ็็จๆถ่ฝ็ๅฐ" private: "็งๅฏ" _signup: almostThere: "ๅณๅฐๅฎๆ" + emailAddressInfo: "่ซ่ผธๅ ฅๆจๆไฝฟ็จ็้ปๅญ้ตไปถๅฐๅใ้ปๅญ้ตไปถๅฐๅไธๆ่ขซๅ ฌ้ใ" + emailSent: "ๅทฒๅฐ็ขบ่ช้ตไปถ็ผ้่ณๆจ่ผธๅ ฅ็้ปๅญ้ตไปถๅฐๅ ({email})ใ่ซ้ๅ้ปๅญ้ตไปถไธญ็้ฃ็ตไปฅๅฎๆๅธณๆถๅตๅปบใ" _accountDelete: + accountDelete: "ๅช้คๅธณๆถ" + mayTakeTime: "ๅช้คๅธณๆถ็่็่ฒ ่ท่ผๅคง๏ผๅฆๆๅธณๆถ็ข็็ๅ งๅฎนๆธ้ไธ่น็ๆชๆกๆธ้่ผๅค็่ฉฑ๏ผๅฐฑ้่ฆ่ฑ่ดนไธๆฎตๆ้ๆ่ฝๅฎๆใ" + sendEmail: "ๅธณๆถๅ ้คๅฎๆๅพ๏ผๅฐๅ่จปๅๅฐ้ปๅญ้ตไปถๅฐๅ็ผ้้็ฅใ" + requestAccountDelete: "ๅช้คๅธณๆถ่ซๆฑ" + started: "ๅทฒ้ๅงๅช้คไฝๆฅญใ" inProgress: "ๆญฃๅจๅช้ค" _ad: back: "่ฟๅ" @@ -800,7 +883,7 @@ _email: _plugin: install: "ๅฎ่ฃๅคๆ็ตไปถ" installWarn: "่ซไธ่ฆๅฎ่ฃไพๆบไธๆ็ๅคๆ็ตไปถใ" - manage: "็ฎก็ๆไปถ" + manage: "็ฎก็ๅคๆ" _registry: scope: "็ฏๅ" key: "ๆฉ็ขผ" @@ -833,14 +916,21 @@ _mfm: link: "้ๆฅ" linkDescription: "ๆจๅฏไปฅๅฐ็นๅฎ็ฏๅ็ๆ็ซ ่ URL ็ธ้่ฏใ " bold: "็ฒ้ซ" + boldDescription: "ๅฏไปฅๅฐๆๅญ้กฏ็คบไธบ็ฒ้ซๆฅๅผท่ชฟใ" small: "็ธฎๅฐ" + smallDescription: "ๅฏไปฅไฝฟๅ งๅฎนๆๅญ่ฎๅฐใ่ฎๆทกใ" center: "็ฝฎไธญ" + centerDescription: "ๅฏไปฅๅฐๅ งๅฎน็ฝฎไธญ้กฏ็คบใ" inlineCode: "็จๅผ็ขผ(ๅ ๅต)" + inlineCodeDescription: "ๅจ่กๅ ง็จ้ซไบฎๅบฆ้กฏ็คบ๏ผไพๅฆ็จๅผ็ขผ่ชๆณใ" blockCode: "็จๅผ็ขผ(ๅๅก)" + blockCodeDescription: "ๅจๅๅกไธญ็จ้ซไบฎๅบฆ้กฏ็คบ๏ผไพๅฆ่คๆธ่ก็็จๅผ็ขผ่ชๆณใ" inlineMath: "ๆธๅญธๅ ฌๅผ(ๅ งๅต)" inlineMathDescription: "้กฏ็คบๅ งๅต็KaTexๆธๅญธๅ ฌๅผใ" blockMath: "ๆธๅญธๅ ฌๅผ(ๆนๅก)" + blockMathDescription: "ไปฅๅๅก้กฏ็คบ่คๆธ่ก็KaTexๆธๅญธๅผใ" quote: "ๅผ็จ" + quoteDescription: "ๅฏไปฅ็จไพ่กจ็คบๅผ็จ็ๅ ๅฎนใ" emoji: "่ช่จ่กจๆ ็ฌฆ่" emojiDescription: "ๆจๅฏไปฅ้้ๅฐ่ชๅฎ็พฉ่กจๆ ็ฌฆ่ๅ็จฑๆฌๅจๅ่ไธญไพ้กฏ็คบ่ชๅฎ็พฉ่กจๆ ็ฌฆ่ใ " search: "ๆๅฐ" @@ -849,22 +939,34 @@ _mfm: flipDescription: "ๅฐๅ งๅฎนไธไธๆๅทฆๅณ็ฟป่ฝใ" jelly: "ๅ็ซ(ๆๅ)" jellyDescription: "้กฏ็คบๆๅไธๆจฃ็ๅ็ซๆๆใ" + tada: "ๅ็ซ๏ผ้๏ฝ๏ผ" + tadaDescription: "้กฏ็คบใ้๏ฝ๏ผใ้็จฎๆ่ฆบ็ๅ็ซๆๆใ" jump: "ๅ็ซ(่ทณๅ)" + jumpDescription: "้กฏ็คบ่ทณๅ็ๅ็ซๆๆใ" bounce: "ๅ็ซ(ๅๅฝ)" + bounceDescription: "้กฏ็คบๆๅฝๆง็ๅ็ซๆๆใ" shake: "ๅ็ซ(ๆๆ)" + shakeDescription: "้กฏ็คบ้กซๆ็ๅ็ซๆๆใ" twitch: "ๅ็ซ(้กซๆ)" twitchDescription: "้กฏ็คบๅผท็้กซๆ็ๅ็ซๆๆใ" spin: "ๅ็ซ(ๆ่ฝ)" spinDescription: "้กฏ็คบๆ่ฝ็ๅ็ซๆๆใ" x2: "ๅคง" + x2Description: "ๆพๅคง้กฏ็คบๅ งๅฎนใ" x3: "่ผๅคง" x3Description: "ๆพๅคง้กฏ็คบๅ งๅฎนใ" x4: "ๆๅคง" x4Description: "ๅฐ้กฏ็คบๅ งๅฎนๆพ่ณๆๅคงใ" blur: "ๆจก็ณ" + blurDescription: "็ข็ๆจก็ณๆๆใๅฐๆธธๆจๆพๅจไธ้ขๅณๅฏๅฐๅ ๅฎน้กฏ็คบๅบไพใ" font: "ๅญๅ" fontDescription: "ๆจๅฏไปฅ่จญๅฎ้กฏ็คบๅ งๅฎน็ๅญๅ" + rainbow: "ๅฝฉ่น" + rainbowDescription: "็จๅฝฉ่น่ฒไพ้กฏ็คบๅ งๅฎนใ" + sparkle: "้้็ผๅ " + sparkleDescription: "ๆทปๅ ้้็ผๅ ็็ฒๅญๆๆใ" rotate: "ๆ่ฝ" + rotateDescription: "ไปฅๆๅฎ็่งๅบฆๆ่ฝใ" _instanceTicker: none: "้ฑ่" remote: "ๅ้ ็ซฏไฝฟ็จ่ ้กฏ็คบ" @@ -884,11 +986,24 @@ _channel: usersCount: "ๆ{n}ไบบๅ่" notesCount: "ๆ{n}ๅ่ฒผๆ" _menuDisplay: + sideFull: "ๅดๅ" + sideIcon: "ๅดๅ๏ผๅ็คบ๏ผ" + top: "้ ้จ" hide: "้ฑ่" _wordMute: muteWords: "ๅ ๅ ฅ้้ณๆๅญ" + muteWordsDescription: "็จ็ฉบๆ ผๅ้ๆๅฎAND๏ผ็จๆ่กๅ้ๆๅฎORใ" + muteWordsDescription2: "ๅฐ้้ตๅญ็จๆ็ทๆฌ่ตทไพ่กจ็คบๆญฃ่ฆ่กจ้ๅผใ" softDescription: "้ฑ่ๆ้่ปธไธญๆๅฎๆขไปถ็่ฒผๆใ" + hardDescription: "ๅ ทๆๆๅฎๆขไปถ็่ฒผๆๅฐไธๆทปๅ ๅฐๆ้่ปธใ ๅณไฝฟๆจๆดๆนๆขไปถ๏ผๆช่ขซๆทปๅ ็่ฒผๆไนๆ่ขซๆ้คๅจๅคใ" + soft: "่ปๆง้้ณ" + hard: "็กฌๆง้้ณ" mutedNotes: "ๅทฒ้้ณ็่ฒผๆ" +_instanceMute: + instanceMuteDescription: "ๅ ๆฌๅฐ่ขซ้้ณๅฏฆไพไธ็็จๆถ็ๅ่ฆ๏ผ่ขซ่จญๅฎ็ๅฏฆไพไธๆๆ่ฒผๆๅ่ฝ็ผ้ฝๆ่ขซ้้ณใ" + instanceMuteDescription2: "่จญๅฎๆไปฅๆ่ก้ฒ่กๅ้" + title: "่ขซ่จญๅฎ็ๅฏฆไพ๏ผ่ฒผๆๅฐ่ขซ้ฑ่ใ" + heading: "ๅฐๅฏฆไพ้้ณ" _theme: explore: "ๅๅพไฝๆฏไธป้ก" install: "ๅฎ่ฃไฝๆฏไธป้ก" @@ -902,10 +1017,12 @@ _theme: invalid: "ไธป้กๆ ผๅผ้ฏ่ชค" make: "่ฃฝไฝไธป้ก" base: "ๅบๆผ" + addConstant: "ๆทปๅ ๅธธๆธ" constant: "ๅธธๆธ" defaultValue: "้ ่จญๅผ" color: "้ก่ฒ" refProp: "ๆฅ็ๅฑฌๆง " + refConst: "ๆฅ็ๅธธๆธ" key: "ๆ้ต" func: "ๅฝๆฐ" funcKind: "ๅ่ฝ้กๅ" @@ -914,6 +1031,9 @@ _theme: alpha: "้ๆๅบฆ" darken: "ๆๅบฆ" lighten: "ไบฎๅบฆ" + inputConstantName: "่ซ่ผธๅ ฅๅธธๆธ็ๅ็จฑ" + importInfo: "ๆจๅฏไปฅๅจๆญค่ฒผไธไธป้กไปฃ็ขผ๏ผๅฐๅ ถๅฏๅ ฅ็ทจ่ผฏๅจไธญ" + deleteConstantConfirm: "็ขบๅฎ่ฆๅ ้คๅธธๆธ{const}ๅ๏ผ" keys: accent: "้้ป่ฒๅฝฉ" bg: "่ๆฏ" @@ -933,6 +1053,7 @@ _theme: mention: "ๆๅฐ" mentionMe: "ๆๅฐไบๆ" renote: "่ฝ็ผ่ฒผๆ" + modalBg: "ๅฐ่ฉฑๆก่ๆฏ" divider: "ๅๅฒ็ท" scrollbarHandle: "ๆฒๅๆข" scrollbarHandleHover: "ๆฒๅๆข (ๆผๆตฎ)" @@ -966,7 +1087,6 @@ _sfx: antenna: "ๅคฉ็ทๆฅๆถ" channel: "้ ป้้็ฅ" _ago: - unknown: "ๆช็ฅ" future: "ๆชไพ" justNow: "ๅๅ" secondsAgo: "{n}็งๅ" @@ -1010,9 +1130,13 @@ _2fa: registerKey: "่จปๅ้ต" step1: "้ฆๅ ๏ผๅจๆจ็่จญๅไธๅฎ่ฃไบๆญฅ้ฉ่ญ็จๅผ๏ผไพๅฆ{a}ๆ{b}ใ" step2: "็ถๅพ๏ผๆๆ่ขๅนไธ็QR codeใ" + step2Url: "ๅจๆก้ข็ๆ็จไธญ๏ผ่ซ่ผธๅ ฅไปฅไธ็URL๏ผ" + step3: "่ผธๅ ฅๆจ็Appๆไพ็ๆฌๆไปฅๅฎๆ่จญๅฎใ" + step4: "ๅพ็พๅจ้ๅง๏ผไปปไฝ็ปๅ ฅๆไฝ้ฝๅฐ่ฆๆฑๆจๆไพๆฌๆใ" + securityKeyInfo: "ๆจๅฏไปฅ่จญๅฎไฝฟ็จๆฏๆดFIDO2็็กฌ้ซๅฎๅ จ้ใ็ต็ซฏ่จญๅ็ๆ็บน่ช่ญๆ่ PIN็ขผไพ็ปๅ ฅใ" _permissions: - "read:account": "ๆฅ็ๅธณๆถไฟกๆฏ" - "write:account": "ๆดๆนๅธณๆถไฟกๆฏ" + "read:account": "ๆฅ็ๆ็ๅธณๆถ่ณ่จ" + "write:account": "ๆดๆนๆ็ๅธณๆถ่ณ่จ" "read:blocks": "ๅทฒๅฐ้็จๆถๅๅฎ" "write:blocks": "็ทจ่ผฏๅทฒๅฐ้็จๆถๅๅฎ" "read:drive": "ๅญๅ้ฒ็ซฏ็กฌ็ข" @@ -1039,6 +1163,10 @@ _permissions: "write:user-groups": "็ทจ่ผฏไฝฟ็จ่ ็พค็ต" "read:channels": "ๅทฒๆฅ็็้ ป้" "write:channels": "็ทจ่ผฏ้ ป้" + "read:gallery": "็่ฆฝๅๅบซ" + "write:gallery": "ๆไฝๅๅบซ" + "read:gallery-likes": "่ฎๅๅๆญก็ๅ็" + "write:gallery-likes": "ๆไฝๅๆญก็ๅ็" _auth: shareAccess: "่ฆๆๆฌใโ{name}โใๅญๅๆจ็ๅธณๆถๅ๏ผ" shareAccessAsk: "ๆจ็ขบๅฎ่ฆๆๆฌ้ๅๆ็จ็จๅผไฝฟ็จๆจ็ๅธณๆถๅ๏ผ" @@ -1078,6 +1206,8 @@ _widgets: onlineUsers: "็ทไธ็็จๆถ" jobQueue: "ไฝๅ" serverMetric: "ๆๅๅจๆๆจ " + aiscript: "AiScriptๆงๅถๅฐ" + aichan: "ๅฐ่" _cw: hide: "้ฑ่" show: "็่ฆฝๆดๅค" @@ -1103,12 +1233,15 @@ _poll: closed: "ๅทฒ็ตๆ" remainingDays: "{d}ๅคฉ{h}ๅฐๆๅพ็ตๆ" remainingHours: "{h}ๅฐๆ{m}ๅๅพ็ตๆ" + remainingMinutes: "{m}ๅ{s}็งๅพ็ตๆ" remainingSeconds: "{s}็งๅพๆชๆญข" _visibility: public: "ๅ ฌ้" publicDescription: "็ผๅธ็ตฆๆๆ็จๆถ " home: "้ฆ้ " + homeDescription: "ๅ ็ผ้่ณ้ฆ้ ็ๆ้่ปธ" followers: "่ฟฝ้จ่ " + followersDescription: "ๅ ็ผ้่ณ้ๆณจ่ " specified: "ๆๅฎไฝฟ็จ่ " specifiedDescription: "ๅ ็ผ้่ณๆๅฎไฝฟ็จ่ " localOnly: "ๅ ้ๆฌๅฐ" @@ -1131,6 +1264,7 @@ _profile: youCanIncludeHashtags: "ไฝ ไนๅฏไปฅๅจใ้ๆผๆใไธญๅ ไธ #tag" metadata: "้ฒ้่ณ่จ" metadataEdit: "็ทจ่ผฏ้ฒ้่ณ่จ" + metadataDescription: "ๅฏไปฅๅจๅไบบ่ณๆไธญไปฅ่กจๆ ผๅฝขๅผ้กฏ็คบๅ ถไป่ณ่จใ" metadataLabel: "ๆจ็ฑค" metadataContent: "ๅ ๅฎน" changeAvatar: "ๆดๆๅคง้ ญ่ฒผ" @@ -1141,6 +1275,8 @@ _exportOrImport: muteList: "้้ณ" blockingList: "ๅฐ้" userLists: "ๆธ ๅฎ" + excludeMutingUsers: "ๆ้ค่ขซ้้ณ็็จๆถ" + excludeInactiveUsers: "ๆ้คไธๆดป่บๅธณๆถ" _charts: federation: "็ซๅฐ่ฏ้ฆ" apRequest: "่ซๆฑ" @@ -1418,6 +1554,7 @@ _pages: _seedRandomPick: arg1: "็จฎๅญ" arg2: "ๆธ ๅฎ" + DRPWPM: "ไปๆฉ็ๅ่กจไธญ้จๆฉ้ธๆ๏ผๆฏๅ็จๆทๆฏๅคฉ๏ผ" _DRPWPM: arg1: "ๅญไธฒไธฒๅ" pick: "ๅพๆธ ๅฎไธญ้ธๅ" @@ -1448,6 +1585,8 @@ _pages: _for: arg1: "้่คๆฌกๆธ" arg2: "่็" + typeError: "ๆงฝๅๆธ{slot}้่ฆๅณๅ ฅโ{expect}โ๏ผไฝๆฏๅฏฆ้ๅณๅ ฅ็บโ{actual}โ๏ผ" + thereIsEmptySlot: "ๅๆธ{slot}ๆฏ็ฉบ็๏ผ" types: string: "ๅญไธฒ" number: "ๆฐๅผ" @@ -1470,10 +1609,13 @@ _notification: youRenoted: "{name} ่ฝ็ผไบไฝ ็่ฒผๆ" youGotPoll: "{name}ๅทฒๆ็ฅจ" youGotMessagingMessageFromUser: "{name}็ผ้็ตฆๆจ็่จๆฏ" + youGotMessagingMessageFromGroup: "{name}็ผ้็ตฆๆจ็่จๆฏ" youWereFollowed: "ๆจๆๆฐ็่ฟฝ้จ่ " youReceivedFollowRequest: "ๆจๆๆฐ็่ฟฝ้จ่ซๆฑ" yourFollowRequestAccepted: "ๆจ็่ฟฝ้จ่ซๆฑๅทฒ้้" youWereInvitedToGroup: "ๆจๆๆฐ็็พค็ต้่ซ" + pollEnded: "ๅๅท่ชฟๆฅๅทฒ็ข็็ตๆ" + emptyPushNotificationMessage: "ๆจ้้็ฅๅทฒๆดๆฐ" _types: all: "ๅ จ้จ " follow: "่ฟฝ้จไธญ" @@ -1483,10 +1625,15 @@ _notification: quote: "ๅผ็จ" reaction: "ๅๆ" pollVote: "็ตฑ่จๅทฒๆ็ฅจๆธ" + pollEnded: "ๅๅท่ชฟๆฅ็ตๆ" receiveFollowRequest: "ๅทฒๆถๅฐ่ฟฝ้จ่ซๆฑ" followRequestAccepted: "่ฟฝ้จ่ซๆฑๅทฒๆฅๅ" groupInvited: "ๅ ๅ ฅ็คพ็พค้่ซ" app: "ๆ็จ็จๅผ้็ฅ" + _actions: + followBack: "ๅ้" + reply: "ๅ่ฆ" + renote: "่ฝ็ผ" _deck: alwaysShowMainColumn: "็ธฝๆฏ้กฏ็คบไธปๆฌ" columnAlign: "ๅฐ้ฝๆฌไฝ" diff --git a/package.json b/package.json index 606e2b7332..e3340005ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.110.1", + "version": "12.111.0", "codename": "indigo", "repository": { "type": "git", @@ -19,10 +19,10 @@ "watch": "npm run dev", "dev": "node ./scripts/dev.js", "lint": "node ./scripts/lint.js", - "cy:open": "cypress open", + "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "cypress run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run", - "mocha": "cd packages/backend && cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha", + "mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha", "test": "npm run mocha", "format": "gulp format", "clean": "node ./scripts/clean.js", @@ -30,8 +30,6 @@ "cleanall": "npm run clean-all" }, "dependencies": { - "@types/gulp": "4.0.9", - "@types/gulp-rename": "2.0.1", "execa": "5.1.1", "gulp": "4.0.2", "gulp-cssnano": "2.1.3", @@ -41,10 +39,12 @@ "js-yaml": "4.1.0" }, "devDependencies": { - "@typescript-eslint/parser": "5.18.0", + "@types/gulp": "4.0.9", + "@types/gulp-rename": "2.0.1", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "cypress": "9.5.3", + "cypress": "10.0.3", "start-server-and-test": "1.14.0", - "typescript": "4.6.3" + "typescript": "4.7.3" } } diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs index e2e31e9e33..5a06889dcd 100644 --- a/packages/backend/.eslintrc.cjs +++ b/packages/backend/.eslintrc.cjs @@ -6,4 +6,27 @@ module.exports = { extends: [ '../shared/.eslintrc.js', ], + rules: { + 'import/order': ['warn', { + 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + 'pathGroups': [ + { + 'pattern': '@/**', + 'group': 'external', + 'position': 'after' + } + ], + }], + 'no-restricted-globals': [ + 'error', + { + 'name': '__dirname', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + }, + { + 'name': '__filename', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + } + ] + }, }; diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json index 26628066eb..87c571cfd6 100644 --- a/packages/backend/.mocharc.json +++ b/packages/backend/.mocharc.json @@ -5,6 +5,6 @@ "loader=./test/loader.js" ], "slow": 1000, - "timeout": 35000, + "timeout": 10000, "exit": true } diff --git a/packages/backend/.vscode/settings.json b/packages/backend/.vscode/settings.json index df3bf05071..9fb3b29d4a 100644 --- a/packages/backend/.vscode/settings.json +++ b/packages/backend/.vscode/settings.json @@ -2,5 +2,9 @@ "typescript.tsdk": "node_modules\\typescript\\lib", "path-intellisense.mappings": { "@": "${workspaceRoot}/packages/backend/src/" + }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": true } } diff --git a/packages/backend/migration/1651224615271-foreign-key.js b/packages/backend/migration/1651224615271-foreign-key.js new file mode 100644 index 0000000000..44ba7fb6c4 --- /dev/null +++ b/packages/backend/migration/1651224615271-foreign-key.js @@ -0,0 +1,89 @@ +export class foreignKeyReports1651224615271 { + name = 'foreignKeyReports1651224615271' + + async up(queryRunner) { + await Promise.all([ + queryRunner.query(`ALTER INDEX "public"."IDX_seoignmeoprigmkpodgrjmkpormg" RENAME TO "IDX_c8cc87bd0f2f4487d17c651fbf"`), + queryRunner.query(`DROP INDEX "public"."IDX_note_on_channelId_and_id_desc"`), + + // remove unnecessary default null, see also down + queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "followersUri" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "session" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "name" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "iconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareName" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareVersion" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "name" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerName" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerEmail" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "themeColor" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "channelId" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" DROP DEFAULT`), + + queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`), + queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`), + queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`), + + queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => { + queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + }), + + queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`), + queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`), + + queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" TO "FK_a9ca79ad939bf06066b81c9d3aa"`), + + queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" ADD VALUE 'pollEnded' AFTER 'pollVote'`), + ]); + } + + async down(queryRunner) { + await Promise.all([ + // There is no ALTER TYPE REMOVE VALUE query, so the reverse operation is a bit more complex + queryRunner.query(`UPDATE "user_profile" SET "mutingNotificationTypes" = array_remove("mutingNotificationTypes", 'pollEnded')`) + .then(() => + queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`) + ).then(() => + queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`) + ).then(() => + queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`) + ), + + queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" TO "FK_3126dd7c502c9e4d7597ef7ef10"`), + + queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`), + queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`), + queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`), + queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`), + queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`), + + queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" SET DEFAULT '{}'`), + queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`), + + queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`), + queryRunner.query(`DROP INDEX "public"."IDX_f22169eb10657bded6d875ac8f"`), + queryRunner.query(`DROP INDEX "public"."IDX_315c779174fe8247ab324f036e"`), + + /* DEFAULT's are not set again because if the column can be NULL, then DEFAULT NULL is not necessary. + see also https://github.com/typeorm/typeorm/issues/7579#issuecomment-835423615 */ + + queryRunner.query(`CREATE INDEX "IDX_note_on_channelId_and_id_desc" ON "note" ("id", "channelId") `), + queryRunner.query(`ALTER INDEX "public"."IDX_c8cc87bd0f2f4487d17c651fbf" RENAME TO "IDX_seoignmeoprigmkpodgrjmkpormg"`), + ]); + } +} diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js new file mode 100644 index 0000000000..8da1fd7fbb --- /dev/null +++ b/packages/backend/migration/1652859567549-uniform-themecolor.js @@ -0,0 +1,36 @@ +import tinycolor from 'tinycolor2'; + +export class uniformThemecolor1652859567549 { + name = 'uniformThemecolor1652859567549' + + async up(queryRunner) { + const formatColor = (color) => { + let tc = new tinycolor(color); + if (tc.isValid()) { + return tc.toHexString(); + } else { + return null; + } + }; + + await queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL') + .then(instances => Promise.all(instances.map(instance => { + // update theme color to uniform format, e.g. #00ff00 + // invalid theme colors get set to null + return queryRunner.query('UPDATE "instance" SET "themeColor" = $1 WHERE "id" = $2', [formatColor(instance.themeColor), instance.id]); + }))); + + // also fix own theme color + await queryRunner.query('SELECT "themeColor" FROM "meta" WHERE "themeColor" IS NOT NULL LIMIT 1') + .then(metas => { + if (metas.length > 0) { + return queryRunner.query('UPDATE "meta" SET "themeColor" = $1', [formatColor(metas[0].themeColor)]); + } + }); + } + + async down(queryRunner) { + // The original representation is not stored, so migrating back is not possible. + // The new format also works in older versions so this is not a problem. + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 314818f80b..2186dcc6a9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -6,7 +6,7 @@ "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", "lint": "eslint --quiet \"src/**/*.ts\"", - "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "test": "npm run mocha" }, "resolutions": { @@ -14,71 +14,25 @@ "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.1", + "@bull-board/koa": "3.11.1", + "@discordapp/twemoji": "14.0.2", "@elastic/elasticsearch": "7.11.0", "@koa/cors": "3.1.0", "@koa/multer": "3.0.0", "@koa/router": "9.0.1", - "@sinonjs/fake-timers": "9.1.1", + "@peertube/http-signature": "1.6.0", + "@sinonjs/fake-timers": "9.1.2", "@syuilo/aiscript": "0.11.1", - "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.8", - "@types/cbor": "6.0.0", - "@types/escape-regexp": "0.0.1", - "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.5", - "@types/jsdom": "16.2.14", - "@types/jsonld": "1.5.6", - "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.7", - "@types/koa-cors": "0.0.2", - "@types/koa-favicon": "2.0.21", - "@types/koa-logger": "3.1.2", - "@types/koa-mount": "4.0.1", - "@types/koa-send": "4.1.3", - "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.1.1", - "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.11", - "@types/mocha": "9.1.0", - "@types/node": "17.0.23", - "@types/node-fetch": "3.0.3", - "@types/nodemailer": "6.4.4", - "@types/oauth": "0.9.1", - "@types/parse5": "6.0.3", - "@types/portscanner": "2.1.1", - "@types/pug": "2.0.6", - "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.2", - "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.3", - "@types/redis": "4.0.11", - "@types/rename": "1.0.4", - "@types/sanitize-html": "2.6.2", - "@types/sharp": "0.30.1", - "@types/sinonjs__fake-timers": "8.1.2", - "@types/speakeasy": "2.0.7", - "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.3", - "@types/uuid": "8.3.4", - "@types/web-push": "3.3.2", - "@types/websocket": "1.0.5", - "@types/ws": "8.5.3", - "@typescript-eslint/eslint-plugin": "5.18.0", - "@typescript-eslint/parser": "5.18.0", - "@bull-board/koa": "3.10.3", "abort-controller": "3.0.0", "ajv": "8.11.0", - "archiver": "5.3.0", + "archiver": "5.3.1", "autobind-decorator": "2.4.0", "autwh": "0.1.0", - "aws-sdk": "2.1111.0", + "aws-sdk": "2.1152.0", "bcryptjs": "2.4.3", "blurhash": "1.1.5", - "broadcast-channel": "4.10.0", - "bull": "4.8.1", + "bull": "4.8.3", "cacheable-lookup": "6.0.4", - "cafy": "15.2.1", "cbor": "8.1.0", "chalk": "5.0.1", "chalk-template": "0.4.0", @@ -88,22 +42,19 @@ "date-fns": "2.28.0", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.13.0", - "eslint-plugin-import": "2.26.0", "feed": "4.2.2", - "file-type": "17.1.1", + "file-type": "17.1.2", "fluent-ffmpeg": "2.1.2", - "got": "12.0.3", + "got": "12.1.0", "hpagent": "0.1.2", - "http-signature": "1.3.6", - "ip-cidr": "3.0.4", + "ip-cidr": "3.0.10", "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "19.0.0", "json5": "2.2.1", "json5-loader": "4.0.1", - "jsonld": "5.2.0", - "jsrsasign": "8.0.20", + "jsonld": "6.0.0", + "jsrsasign": "10.5.24", "koa": "2.13.4", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", @@ -113,19 +64,18 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "mfm-js": "0.21.0", + "mfm-js": "0.22.1", "mime-types": "2.1.35", "misskey-js": "0.0.14", - "mocha": "9.2.2", + "mocha": "10.0.0", "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "3.2.3", - "nodemailer": "6.7.3", + "node-fetch": "3.2.6", + "nodemailer": "6.7.5", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.3", - "portscanner": "2.2.0", "private-ip": "2.3.3", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -144,35 +94,83 @@ "rndstr": "1.0.0", "s-age": "1.1.2", "sanitize-html": "2.7.0", - "semver": "7.3.6", - "sharp": "0.30.3", + "semver": "7.3.7", + "sharp": "0.29.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "style-loader": "3.3.1", - "summaly": "2.5.0", + "summaly": "2.5.1", "syslog-pro": "1.0.0", - "systeminformation": "5.11.9", + "systeminformation": "5.11.16", "tinycolor2": "1.4.2", "tmp": "0.2.1", - "ts-loader": "9.2.8", - "ts-node": "10.7.0", - "tsc-alias": "1.4.1", - "tsconfig-paths": "3.14.1", + "ts-loader": "9.3.0", + "ts-node": "10.8.1", + "tsc-alias": "1.6.9", + "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", - "typeorm": "0.3.5", - "typescript": "4.6.3", + "typeorm": "0.3.6", "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "8.3.2", - "web-push": "3.4.5", + "web-push": "3.5.0", "websocket": "1.0.34", - "ws": "8.5.0", - "xev": "2.0.1" + "ws": "8.8.0", + "xev": "3.0.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.93", + "@redocly/openapi-core": "1.0.0-beta.97", + "@types/semver": "7.3.9", + "@types/bcryptjs": "2.4.2", + "@types/bull": "3.15.8", + "@types/cbor": "6.0.0", + "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.20", + "@types/is-url": "1.2.30", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", + "@types/jsonld": "1.5.6", + "@types/jsrsasign": "10.5.1", + "@types/koa": "2.13.4", + "@types/koa-bodyparser": "4.3.7", + "@types/koa-cors": "0.0.2", + "@types/koa-favicon": "2.0.21", + "@types/koa-logger": "3.1.2", + "@types/koa-mount": "4.0.1", + "@types/koa-send": "4.1.3", + "@types/koa-views": "7.0.0", + "@types/koa__cors": "3.1.1", + "@types/koa__multer": "2.0.4", + "@types/koa__router": "8.0.11", + "@types/mocha": "9.1.1", + "@types/node": "17.0.41", + "@types/node-fetch": "3.0.3", + "@types/nodemailer": "6.4.4", + "@types/oauth": "0.9.1", + "@types/parse5": "6.0.3", + "@types/pug": "2.0.6", + "@types/punycode": "2.1.0", + "@types/qrcode": "1.4.2", + "@types/random-seed": "0.3.3", + "@types/ratelimiter": "3.4.3", + "@types/redis": "4.0.11", + "@types/rename": "1.0.4", + "@types/sanitize-html": "2.6.2", + "@types/sharp": "0.30.2", + "@types/sinonjs__fake-timers": "8.1.2", + "@types/speakeasy": "2.0.7", + "@types/tinycolor2": "1.4.3", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", + "@types/web-push": "3.3.2", + "@types/websocket": "1.0.5", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", + "typescript": "4.7.3", + "eslint": "8.17.0", + "eslint-plugin-import": "2.26.0", "cross-env": "7.0.3", "execa": "6.1.0" } diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts index 8d484312dc..d1f9cd9552 100644 --- a/packages/backend/src/@types/http-signature.d.ts +++ b/packages/backend/src/@types/http-signature.d.ts @@ -1,5 +1,5 @@ -declare module 'http-signature' { - import { IncomingMessage, ClientRequest } from 'http'; +declare module '@peertube/http-signature' { + import { IncomingMessage, ClientRequest } from 'node:http'; interface ISignature { keyId: string; diff --git a/packages/backend/src/@types/jsrsasign.d.ts b/packages/backend/src/@types/jsrsasign.d.ts deleted file mode 100644 index bb52f8f64e..0000000000 --- a/packages/backend/src/@types/jsrsasign.d.ts +++ /dev/null @@ -1,800 +0,0 @@ -// Attention: Partial Type Definition - -declare module 'jsrsasign' { - //// HELPER TYPES - - /** - * Attention: The value might be changed by the function. - */ - type Mutable<T> = T; - - /** - * Deprecated: The function might be deleted in future release. - */ - type Deprecated<T> = T; - - //// COMMON TYPES - - /** - * byte number - */ - type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255; - - /** - * hexadecimal string /[0-9A-F]/ - */ - type HexString = string; - - /** - * binary string /[01]/ - */ - type BinString = string; - - /** - * base64 string /[A-Za-z0-9+/]=+/ - */ - type Base64String = string; - - /** - * base64 URL encoded string /[A-Za-z0-9_-]/ - */ - type Base64URLString = string; - - /** - * time value (ex. "151231235959Z") - */ - type TimeValue = string; - - /** - * OID string (ex. '1.2.3.4.567') - */ - type OID = string; - - /** - * OID name - */ - type OIDName = string; - - /** - * PEM formatted string - */ - type PEM = string; - - //// ASN1 TYPES - - class ASN1Object { - public isModified: boolean; - - public hTLV: ASN1TLV; - - public hT: ASN1T; - - public hL: ASN1L; - - public hV: ASN1V; - - public getLengthHexFromValue(): HexString; - - public getEncodedHex(): ASN1TLV; - - public getValueHex(): ASN1V; - - public getFreshValueHex(): ASN1V; - } - - class DERAbstractStructured extends ASN1Object { - constructor(params?: Partial<Record<'array', ASN1Object[]>>); - - public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void; - - public appendASN1Object(asn1Object: ASN1Object): void; - } - - class DERSequence extends DERAbstractStructured { - constructor(params?: Partial<Record<'array', ASN1Object[]>>); - - public getFreshValueHex(): ASN1V; - } - - //// ASN1HEX TYPES - - /** - * ASN.1 DER encoded data (hexadecimal string) - */ - type ASN1S = HexString; - - /** - * index of something - */ - type Idx<T extends { [idx: string]: unknown } | { [idx: number]: unknown }> = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never; - - /** - * byte length of something - */ - type ByteLength<T extends { length: unknown }> = T['length']; - - /** - * ASN.1 L(length) (hexadecimal string) - */ - type ASN1L = HexString; - - /** - * ASN.1 T(tag) (hexadecimal string) - */ - type ASN1T = HexString; - - /** - * ASN.1 V(value) (hexadecimal string) - */ - type ASN1V = HexString; - - /** - * ASN.1 TLV (hexadecimal string) - */ - type ASN1TLV = HexString; - - /** - * ASN.1 object string - */ - type ASN1ObjectString = string; - - /** - * nth - */ - type Nth = number; - - /** - * ASN.1 DER encoded OID value (hexadecimal string) - */ - type ASN1OIDV = HexString; - - class ASN1HEX { - public static getLblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1L>; - - public static getL(s: ASN1S, idx: Idx<ASN1S>): ASN1L; - - public static getVblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1V>; - - public static getVidx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1V>; - - public static getV(s: ASN1S, idx: Idx<ASN1S>): ASN1V; - - public static getTLV(s: ASN1S, idx: Idx<ASN1S>): ASN1TLV; - - public static getNextSiblingIdx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1ObjectString>; - - public static getChildIdx(h: ASN1S, pos: Idx<ASN1S>): Idx<ASN1ObjectString>[]; - - public static getNthChildIdx(h: ASN1S, idx: Idx<ASN1S>, nth: Nth): Idx<ASN1ObjectString>; - - public static getIdxbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): Idx<Mutable<Nth[]>>; - - public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV; - - // eslint:disable-next-line:bool-param-default - public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V; - - public static hextooidstr(hex: ASN1OIDV): OID; - - public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record<string, unknown>, idx?: Idx<ASN1S>, indent?: string): string; - - public static isASN1HEX(hex: string): hex is HexString; - - public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName; - } - - //// BIG INTEGER TYPES (PARTIAL) - - class BigInteger { - constructor(a: null); - - constructor(a: number, b: SecureRandom); - - constructor(a: number, b: number, c: SecureRandom); - - constructor(a: unknown); - - constructor(a: string, b: number); - - public am(i: number, x: number, w: number, j: number, c: number, n: number): number; - - public DB: number; - - public DM: number; - - public DV: number; - - public FV: number; - - public F1: number; - - public F2: number; - - protected copyTo(r: Mutable<BigInteger>): void; - - protected fromInt(x: number): void; - - protected fromString(s: string, b: number): void; - - protected clamp(): void; - - public toString(b: number): string; - - public negate(): BigInteger; - - public abs(): BigInteger; - - public compareTo(a: BigInteger): number; - - public bitLength(): number; - - protected dlShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected drShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected lShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected rShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected subTo(a: BigInteger, r: Mutable<BigInteger>): void; - - protected multiplyTo(a: BigInteger, r: Mutable<BigInteger>): void; - - protected squareTo(r: Mutable<BigInteger>): void; - - protected divRemTo(m: BigInteger, q: Mutable<BigInteger>, r: Mutable<BigInteger>): void; - - public mod(a: BigInteger): BigInteger; - - protected invDigit(): number; - - protected isEven(): boolean; - - protected exp(e: number, z: Classic | Montgomery): BigInteger; - - public modPowInt(e: number, m: BigInteger): BigInteger; - - public static ZERO: BigInteger; - - public static ONE: BigInteger; - } - - class Classic { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable<BigInteger>): void; - - public mulTo(x: BigInteger, r: Mutable<BigInteger>): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void; - } - - class Montgomery { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable<BigInteger>): void; - - public mulTo(x: BigInteger, r: Mutable<BigInteger>): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void; - } - - //// KEYUTIL TYPES - - type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type AlgList = { - 'AES-256-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 32; ivlen: 16; }; - 'AES-192-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 24; ivlen: 16; }; - 'AES-128-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 16; ivlen: 16; }; - 'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8; }; - 'DES-CBC': { 'proc': DecryptDES; 'eproc': EncryptDES; keylen: 8; ivlen: 8; }; - }; - - type AlgName = keyof AlgList; - - type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA'; - - type GetKeyRSAParam = RSAKey | { - n: BigInteger; - e: number; - } | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | { - n: BigInteger; - e: number; - d: BigInteger; - } | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd', Base64URLString>; - - type GetKeyECDSAParam = KJUR.crypto.ECDSA | { - curve: KJUR.crypto.CurveName; - xy: HexString; - } | { - curve: KJUR.crypto.CurveName; - d: HexString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - }; - - type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>; - - type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string; - - class KEYUTIL { - public version: '1.0.0'; - - public parsePKCS5PEM(sPKCS5PEM: PEM): Partial<Record<'type' | 's', string>> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>); - - public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>; - - public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String; - - public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString; - - public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM; - - public parseHexOfEncryptedPKCS8(sHEX: HexString): { - ciphertext: ASN1V; - encryptionSchemeAlg: 'TripleDES'; - encryptionSchemeIV: ASN1V; - pbkdf2Salt: ASN1V; - pbkdf2Iter: number; - }; - - public getPBKDF2KeyHexFromParam(info: ReturnType<this['parseHexOfEncryptedPKCS8']>, passcode: string): HexString; - - private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString; - - public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>; - - public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): { - algparam: ASN1V | null; - algoid: ASN1V; - keyidx: Idx<ASN1V>; - }; - - public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>; - - public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>; - - public parsePublicPKCS8Hex(pkcs8PubHex: HexString): { - algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null; - algoid: ASN1V; - key: ASN1V; - }; - - public static getKey(param: GetKeyRSAParam): RSAKey; - - public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA; - - public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA; - - public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>; - - public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>; - - public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do - - public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>; - - public static getJWKFromKey(keyObj: RSAKey): { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString>; - - public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - }; - } - - //// KJUR NAMESPACE (PARTIAL) - - namespace KJUR { - namespace crypto { - type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1'; - - class DSA { - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'DSA'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void; - - public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void; - - public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void; - - public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void; - - public signWithMessageHash(sHashHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - } - - class ECDSA { - constructor(params?: { - curve?: CurveName; - prv?: HexString; - pub?: HexString; - }); - - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'EC'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public getBigRandom(limit: BigInteger): BigInteger; - - public setNamedCurve(curveName: CurveName): void; - - public setPrivateKeyHex(prvKeyHex: HexString): void; - - public setPublicKeyHex(pubKeyHex: HexString): void; - - public getPublicKeyXYHex(): Record<'x' | 'y', HexString>; - - public getShortNISTPCurveName(): 'P-256' | 'P-384' | null; - - public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>; - - public signWithMessageHash(hashHex: HexString): HexString; - - public signHex(hashHex: HexString, privHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - - public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>; - - public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>; - - public static asn1SigToConcatSig(asn1Sig: HexString): HexString; - - public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV; - - public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV; - - public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV; - - public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null; - } - - class Signature { - constructor(params?: ({ - alg: string; - prov?: string; - } | {}) & ({ - psssaltlen: number; - } | {}) & ({ - prvkeypem: PEM; - prvkeypas?: never; - } | {})); - - private _setAlgNames(): void; - - private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString; - - public setAlgAndProvider(alg: string, prov: string): void; - - public init(key: GetKeyParam, pass?: string): void; - - public updateString(str: string): void; - - public updateHex(hex: HexString): void; - - public sign(): HexString; - - public signString(str: string): HexString; - - public signHex(hex: HexString): HexString; - - public verify(hSigVal: string): boolean | 0; - } - } - } - - //// RSAKEY TYPES - - class RSAKey { - public n: BigInteger | null; - - public e: number; - - public d: BigInteger | null; - - public p: BigInteger | null; - - public q: BigInteger | null; - - public dmp1: BigInteger | null; - - public dmq1: BigInteger | null; - - public coeff: BigInteger | null; - - public type: 'RSA'; - - public isPrivate?: boolean; - - public isPublic?: boolean; - - //// RSA PUBLIC - - protected doPublic(x: BigInteger): BigInteger; - - public setPublic(N: BigInteger, E: number): void; - - public setPublic(N: HexString, E: HexString): void; - - public encrypt(text: string): HexString | null; - - public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null; - - //// RSA PRIVATE - - protected doPrivate(x: BigInteger): BigInteger; - - public setPrivate(N: BigInteger, E: number, D: BigInteger): void; - - public setPrivate(N: HexString, E: HexString, D: HexString): void; - - public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void; - - public generate(B: number, E: HexString): void; - - public decrypt(ctext: HexString): string; - - public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null; - - //// RSA PEM - - public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[]; - - public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[]; - - public readPrivateKeyFromPEMString(keyPEM: PEM): void; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS5PubKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: Nth): void; - - //// RSA SIGN - - public sign(s: string, hashAlg: string): HexString; - - public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString; - - public signPSS(s: string, hashAlg: string, sLen: number): HexString; - - public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString; - - public verify(sMsg: string, hSig: HexString): boolean | 0; - - public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0; - - public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public static SALT_LEN_HLEN: -1; - - public static SALT_LEN_MAX: -2; - - public static SALT_LEN_RECOVER: -2; - } - - /// RNG TYPES - class SecureRandom { - public nextBytes(ba: Mutable<ByteNumber[]>): void; - } - - //// X509 TYPES - - type ExtInfo = { - critical: boolean; - oid: OID; - vidx: Idx<ASN1V>; - }; - - type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>; - - type ExtCertificatePolicy = { - id: OIDName; - } & Partial<{ - cps: string; - } | { - unotice: string; - }>; - - class X509 { - public hex: HexString | null; - - public version: number; - - public foffset: number; - - public aExtInfo: null; - - public getVersion(): number; - - public getSerialNumberHex(): ASN1V; - - public getSignatureAlgorithmField(): OIDName; - - public getIssuerHex(): ASN1TLV; - - public getIssuerString(): HexString; - - public getSubjectHex(): ASN1TLV; - - public getSubjectString(): HexString; - - public getNotBefore(): TimeValue; - - public getNotAfter(): TimeValue; - - public getPublicKeyHex(): ASN1TLV; - - public getPublicKeyIdx(): Idx<Mutable<Nth[]>>; - - public getPublicKeyContentIdx(): Idx<Mutable<Nth[]>>; - - public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public getSignatureAlgorithmName(): OIDName; - - public getSignatureValueHex(): ASN1V; - - public verifySignature(pubKey: GetKeyParam): boolean | 0; - - public parseExt(): void; - - public getExtInfo(oidOrName: OID | string): ExtInfo | undefined; - - public getExtBasicConstraints(): ExtInfo | {} | { - cA: true; - pathLen?: number; - }; - - public getExtKeyUsageBin(): BinString; - - public getExtKeyUsageString(): string; - - public getExtSubjectKeyIdentifier(): ASN1V | undefined; - - public getExtAuthorityKeyIdentifier(): { - kid: ASN1V; - } | undefined; - - public getExtExtKeyUsageName(): OIDName[] | undefined; - - public getExtSubjectAltName(): Deprecated<string[]>; - - public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined; - - public getExtCRLDistributionPointsURI(): string[] | undefined; - - public getExtAIAInfo(): ExtAIAInfo | undefined; - - public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined; - - public readCertPEM(sCertPEM: PEM): void; - - public readCertHex(sCertHex: HexString): void; - - public getInfo(): string; - - public static hex2dn(hex: HexString, idx?: Idx<HexString>): string; - - public static hex2rdn(hex: HexString, idx?: Idx<HexString>): string; - - public static hex2attrTypeValue(hex: HexString, idx?: Idx<HexString>): string; - - public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): { - algparam: ASN1V | null; - leyhex: ASN1V; - algoid: ASN1V; - }; - } -} diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 5bb20a729f..c3d0592256 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -1,6 +1,6 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Logger from '@/services/logger.js'; import { envOption } from '../env.js'; @@ -12,7 +12,7 @@ import { workerMain } from './worker.js'; const logger = new Logger('core', 'cyan'); const clusterLogger = logger.createSubLogger('cluster', 'orange', false); -const ev = new Xev.default(); +const ev = new Xev(); /** * Init process diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 09d20f9361..bf51960482 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -5,7 +5,6 @@ import * as os from 'node:os'; import cluster from 'node:cluster'; import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; -import * as portscanner from 'portscanner'; import semver from 'semver'; import Logger from '@/services/logger.js'; @@ -48,11 +47,6 @@ function greet() { bootLogger.info(`Misskey v${meta.version}`, null, true); } -function isRoot() { - // maybe process.getuid will be undefined under not POSIX environment (e.g. Windows) - return process.getuid != null && process.getuid() === 0; -} - /** * Init master process */ @@ -67,7 +61,6 @@ export async function masterMain() { showNodejsVersion(); config = loadConfigBoot(); await connectDb(); - await validatePort(config); } catch (e) { bootLogger.error('Fatal error occurred during initialization', null, true); process.exit(1); @@ -97,8 +90,6 @@ function showEnvironment(): void { logger.warn('The environment is not in production mode.'); logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true); } - - logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`); } function showNodejsVersion(): void { @@ -152,29 +143,6 @@ async function connectDb(): Promise<void> { } } -async function validatePort(config: Config): Promise<void> { - const isWellKnownPort = (port: number) => port < 1024; - - async function isPortAvailable(port: number): Promise<boolean> { - return await portscanner.checkPortStatus(port, '127.0.0.1') === 'closed'; - } - - if (config.port == null || Number.isNaN(config.port)) { - bootLogger.error('The port is not configured. Please configure port.', null, true); - process.exit(1); - } - - if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) { - bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true); - process.exit(1); - } - - if (!await isPortAvailable(config.port)) { - bootLogger.error(`Port ${config.port} is already in use`, null, true); - process.exit(1); - } -} - async function spawnWorkers(limit: number = 1) { const workers = Math.min(limit, os.cpus().length); bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); @@ -186,6 +154,10 @@ function spawnWorker(): Promise<void> { return new Promise(res => { const worker = cluster.fork(); worker.on('message', message => { + if (message === 'listenFailed') { + bootLogger.error(`The server Listen failed due to the previous error.`); + process.exit(1); + } if (message !== 'ready') return; res(); }); diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts index 7f765463e4..9654a4f3b0 100644 --- a/packages/backend/src/config/load.ts +++ b/packages/backend/src/config/load.ts @@ -25,6 +25,7 @@ const path = process.env.NODE_ENV === 'test' export default function load() { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); + const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8')); const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const mixin = {} as Mixin; @@ -45,6 +46,7 @@ export default function load() { mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; mixin.userAgent = `Misskey/${meta.version} (${config.url})`; + mixin.clientEntry = clientManifest['src/init.ts']; if (!config.redis.prefix) config.redis.prefix = mixin.host; diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 58a27803cb..948545db7a 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -80,6 +80,7 @@ export type Mixin = { authUrl: string; driveUrl: string; userAgent: string; + clientEntry: string; }; export type Config = Source & Mixin; diff --git a/packages/backend/src/daemons/queue-stats.ts b/packages/backend/src/daemons/queue-stats.ts index bfef110545..1535abc6af 100644 --- a/packages/backend/src/daemons/queue-stats.ts +++ b/packages/backend/src/daemons/queue-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import { deliverQueue, inboxQueue } from '../queue/queues.js'; -const ev = new Xev.default(); +const ev = new Xev(); const interval = 10000; diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts index 327305ccc5..faf4e6e4a4 100644 --- a/packages/backend/src/daemons/server-stats.ts +++ b/packages/backend/src/daemons/server-stats.ts @@ -1,8 +1,8 @@ import si from 'systeminformation'; -import { default as Xev } from 'xev'; +import Xev from 'xev'; import * as osUtils from 'os-utils'; -const ev = new Xev.default(); +const ev = new Xev(); const interval = 2000; diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index eb5fc2e186..298f6713ea 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -5,9 +5,6 @@ pg.types.setTypeParser(20, Number); import { Logger, DataSource } from 'typeorm'; import * as highlight from 'cli-highlight'; import config from '@/config/index.js'; -import { envOption } from '../env.js'; - -import { dbLogger } from './logger.js'; import { User } from '@/models/entities/user.js'; import { DriveFile } from '@/models/entities/drive-file.js'; @@ -74,6 +71,9 @@ import { UserPending } from '@/models/entities/user-pending.js'; import { entities as charts } from '@/services/chart/entities.js'; import { Webhook } from '@/models/entities/webhook.js'; +import { envOption } from '../env.js'; +import { dbLogger } from './logger.js'; +import { redisClient } from './redis.js'; const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); @@ -208,16 +208,25 @@ export const db = new DataSource({ migrations: ['../../migration/*.js'], }); -export async function initDb() { +export async function initDb(force = false) { + if (force) { + if (db.isInitialized) { + await db.destroy(); + } + await db.initialize(); + return; + } + if (db.isInitialized) { // nop } else { - await db.connect(); + await db.initialize(); } } export async function resetDb() { const reset = async () => { + await redisClient.FLUSHDB(); const tables = await db.query(`SELECT relname AS "table" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts index 623cb0e71c..15110b6b70 100644 --- a/packages/backend/src/mfm/from-html.ts +++ b/packages/backend/src/mfm/from-html.ts @@ -6,6 +6,9 @@ const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; export function fromHtml(html: string, hashtagNames?: string[]): string { + // some AP servers like Pixelfed use br tags as well as newlines + html = html.replace(/<br\s?\/?>\r?\n/gi, '\n'); + const dom = parse5.parseFragment(html); let text = ''; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 01bbe98a85..e5b911ed32 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -48,6 +48,7 @@ export class Cache<T> { // Cache MISS const value = await fetcher(); + this.set(key, value); return value; } diff --git a/packages/backend/src/misc/cafy-id.ts b/packages/backend/src/misc/cafy-id.ts deleted file mode 100644 index dd81c5c4cf..0000000000 --- a/packages/backend/src/misc/cafy-id.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Context } from 'cafy'; - -// eslint-disable-next-line @typescript-eslint/ban-types -export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> { - public readonly name = 'ID'; - - constructor(optional = false, nullable = false) { - super(optional, nullable); - - this.push((v: any) => { - if (typeof v !== 'string') { - return new Error('must-be-an-id'); - } - return true; - }); - } - - public getType() { - return super.getType('String'); - } - - public makeOptional(): ID<undefined> { - return new ID(true, false); - } - - public makeNullable(): ID<null> { - return new ID(false, true); - } - - public makeOptionalNullable(): ID<undefined | null> { - return new ID(true, true); - } -} diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts index 04604cf7d0..f07be634fb 100644 --- a/packages/backend/src/misc/create-temp.ts +++ b/packages/backend/src/misc/create-temp.ts @@ -1,10 +1,19 @@ import * as tmp from 'tmp'; -export function createTemp(): Promise<[string, any]> { - return new Promise<[string, any]>((res, rej) => { +export function createTemp(): Promise<[string, () => void]> { + return new Promise<[string, () => void]>((res, rej) => { tmp.file((e, path, fd, cleanup) => { if (e) return rej(e); res([path, cleanup]); }); }); } + +export function createTempDir(): Promise<[string, () => void]> { + return new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); +} diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts index 5417c10962..e855ac28ee 100644 --- a/packages/backend/src/misc/fetch-meta.ts +++ b/packages/backend/src/misc/fetch-meta.ts @@ -20,9 +20,16 @@ export async function fetchMeta(noCache = false): Promise<Meta> { cache = meta; return meta; } else { - const saved = await transactionalEntityManager.save(Meta, { - id: 'x', - }) as Meta; + // metaใ็ฉบใฎใจใfetchMetaใๅๆใซๅผใฐใใใจใใใๅๆใซๅผใฐใใฆใใพใใใจใใใใฎใงใใงใคใซใปใผใใชupsertใไฝฟใ + const saved = await transactionalEntityManager + .upsert( + Meta, + { + id: 'x', + }, + ['id'], + ) + .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); cache = saved; return saved; diff --git a/packages/backend/src/misc/fetch.ts b/packages/backend/src/misc/fetch.ts index 4b1013c9f5..af6bf2fca7 100644 --- a/packages/backend/src/misc/fetch.ts +++ b/packages/backend/src/misc/fetch.ts @@ -1,10 +1,10 @@ -import * as http from 'http'; -import * as https from 'https'; +import * as http from 'node:http'; +import * as https from 'node:https'; +import { URL } from 'node:url'; import CacheableLookup from 'cacheable-lookup'; import fetch from 'node-fetch'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import config from '@/config/index.js'; -import { URL } from 'node:url'; export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>) { const res = await getResponse({ @@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout = } export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) { - const timeout = args?.timeout || 10 * 1000; + const timeout = args.timeout || 10 * 1000; const controller = new AbortController(); setTimeout(() => { @@ -47,7 +47,7 @@ export async function getResponse(args: { url: string, method: string, body?: st headers: args.headers, body: args.body, timeout, - size: args?.size || 10 * 1024 * 1024, + size: args.size || 10 * 1024 * 1024, agent: getAgentByUrl, signal: controller.signal, }); diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts new file mode 100644 index 0000000000..379325bb13 --- /dev/null +++ b/packages/backend/src/misc/get-ip-hash.ts @@ -0,0 +1,9 @@ +import IPCIDR from 'ip-cidr'; + +export function getIpHash(ip: string) { + // because a single person may control many IPv6 addresses, + // only a /64 subnet prefix of any IP will be taken into account. + // (this means for IPv4 the entire address is used) + const prefix = IPCIDR.createAddress(ip).mask(64); + return 'ip-' + BigInt('0b' + prefix).toString(36); +} diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index 86f1356c31..6a185d09f6 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -63,7 +63,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu const isLocal = emoji.host == null; const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl ใใฆใใฎใฏๅพๆนไบๆๆงใฎใใ - const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`; + const url = isLocal ? emojiUrl : `${config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`; return { name: emojiName, diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 5b69812090..fdecc278d4 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -89,7 +89,7 @@ export interface Schema extends OfSchema { readonly optional?: boolean; readonly items?: Schema; readonly properties?: Obj; - readonly required?: ReadonlyArray<keyof NonNullable<this['properties']>>; + readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>; readonly description?: string; readonly example?: any; readonly format?: string; @@ -98,6 +98,9 @@ export interface Schema extends OfSchema { readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; readonly maxLength?: number; readonly minLength?: number; + readonly maximum?: number; + readonly minimum?: number; + readonly pattern?: string; } type RequiredPropertyNames<s extends Obj> = { @@ -105,24 +108,26 @@ type RequiredPropertyNames<s extends Obj> = { // K is not optional s[K]['optional'] extends false ? K : // K has default value - s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never + s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : + never }[keyof s]; -export interface Obj { [key: string]: Schema; } +export type Obj = Record<string, Schema>; +// https://github.com/misskey-dev/misskey/issues/8535 +// To avoid excessive stack depth error, +// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it). export type ObjType<s extends Obj, RequiredProps extends keyof s> = - { -readonly [P in keyof s]?: SchemaType<s[P]> } & - { -readonly [P in RequiredProps]: SchemaType<s[P]> } & - { -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> }; + UnionToIntersection< + { -readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]> } & + { -readonly [R in RequiredProps]-?: SchemaType<s[R]> } & + { -readonly [P in keyof s]?: SchemaType<s[P]> } + >; type NullOrUndefined<p extends Schema, T> = - p['nullable'] extends true - ? p['optional'] extends true - ? (T | null | undefined) - : (T | null) - : p['optional'] extends true - ? (T | undefined) - : T; + | (p['nullable'] extends true ? null : never) + | (p['optional'] extends true ? undefined : never) + | T; // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection // Get intersection from union @@ -139,9 +144,9 @@ export type SchemaTypeDef<p extends Schema> = p['type'] extends 'number' ? number : p['type'] extends 'string' ? ( p['enum'] extends readonly string[] ? - p['enum'][number] : - p['format'] extends 'date-time' ? string : // Dateใซใใ๏ผ๏ผ - string + p['enum'][number] : + p['format'] extends 'date-time' ? string : // Dateใซใใ๏ผ๏ผ + string ) : p['type'] extends 'boolean' ? boolean : p['type'] extends 'object' ? ( diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts index 69cdc49cec..c6e2141a46 100644 --- a/packages/backend/src/models/entities/access-token.ts +++ b/packages/backend/src/models/entities/access-token.ts @@ -15,7 +15,6 @@ export class AccessToken { @Column('timestamp with time zone', { nullable: true, - default: null, }) public lastUsedAt: Date | null; @@ -29,7 +28,6 @@ export class AccessToken { @Column('varchar', { length: 128, nullable: true, - default: null, }) public session: string | null; @@ -52,7 +50,6 @@ export class AccessToken { @Column({ ...id(), nullable: true, - default: null, }) public appId: App['id'] | null; @@ -65,21 +62,18 @@ export class AccessToken { @Column('varchar', { length: 128, nullable: true, - default: null, }) public name: string | null; @Column('varchar', { length: 512, nullable: true, - default: null, }) public description: string | null; @Column('varchar', { length: 512, nullable: true, - default: null, }) public iconUrl: string | null; diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts index b825856201..295d1b486c 100644 --- a/packages/backend/src/models/entities/auth-session.ts +++ b/packages/backend/src/models/entities/auth-session.ts @@ -23,7 +23,7 @@ export class AuthSession { ...id(), nullable: true, }) - public userId: User['id']; + public userId: User['id'] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts index da6b3c7a7f..1386684c32 100644 --- a/packages/backend/src/models/entities/clip.ts +++ b/packages/backend/src/models/entities/clip.ts @@ -37,7 +37,7 @@ export class Clip { public isPublic: boolean; @Column('varchar', { - length: 2048, nullable: true, default: null, + length: 2048, nullable: true, comment: 'The description of the Clip.', }) public description: string | null; diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 3d375f0e35..a636d1d519 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -79,7 +79,6 @@ export class DriveFile { }) public properties: { width?: number; height?: number; orientation?: number; avgColor?: string }; - @Index() @Column('boolean') public storedInternal: boolean; diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index b72ca72331..7332dd1857 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -36,6 +36,7 @@ export class Emoji { @Column('varchar', { length: 512, + default: '', }) public publicUrl: string; diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts index bb24d6b30f..7ea9234384 100644 --- a/packages/backend/src/models/entities/instance.ts +++ b/packages/backend/src/models/entities/instance.ts @@ -107,53 +107,53 @@ export class Instance { public isSuspended: boolean; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, comment: 'The software of the Instance.', }) public softwareName: string | null; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, }) public softwareVersion: string | null; @Column('boolean', { - nullable: true, default: null, + nullable: true, }) public openRegistrations: boolean | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public name: string | null; @Column('varchar', { - length: 4096, nullable: true, default: null, + length: 4096, nullable: true, }) public description: string | null; @Column('varchar', { - length: 128, nullable: true, default: null, + length: 128, nullable: true, }) public maintainerName: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public maintainerEmail: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public iconUrl: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public faviconUrl: string | null; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, }) public themeColor: string | null; diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 4d58b5f04f..80b5228bcd 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -78,7 +78,7 @@ export class Meta { public blockedHosts: string[]; @Column('varchar', { - length: 512, array: true, default: '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}', + length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}', }) public pinnedPages: string[]; @@ -346,14 +346,12 @@ export class Meta { @Column('varchar', { length: 8192, - default: null, nullable: true, }) public defaultLightTheme: string | null; @Column('varchar', { length: 8192, - default: null, nullable: true, }) public defaultDarkTheme: string | null; diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts index b3a7e7a671..8f9e69063a 100644 --- a/packages/backend/src/models/entities/muting.ts +++ b/packages/backend/src/models/entities/muting.ts @@ -17,7 +17,6 @@ export class Muting { @Index() @Column('timestamp with time zone', { nullable: true, - default: null, }) public expiresAt: Date | null; diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index da49d53b69..0ffeb85f69 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -53,8 +53,8 @@ export class Note { }) public threadId: string | null; - @Column('varchar', { - length: 8192, nullable: true, + @Column('text', { + nullable: true, }) public text: string | null; @@ -179,7 +179,7 @@ export class Note { @Index() @Column({ ...id(), - nullable: true, default: null, + nullable: true, comment: 'The ID of source channel.', }) public channelId: Channel['id'] | null; diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index f95cb144c5..1778742ea2 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -192,6 +192,7 @@ export class UserProfile { @Column('jsonb', { default: [], + comment: 'List of instances muted by the user.', }) public mutedInstances: string[]; diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 29d9a0c2ca..df92fb8259 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -207,7 +207,7 @@ export class User { @Column('boolean', { default: false, - comment: 'Whether to show users replying to other users in the timeline', + comment: 'Whether to show users replying to other users in the timeline.', }) public showTimelineReplies: boolean; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 69dc1721c2..0d589d4f11 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -1,6 +1,5 @@ import { db } from '@/db/postgre.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { Users, DriveFolders } from '../index.js'; import { User } from '@/models/entities/user.js'; import { toPuny } from '@/misc/convert-host.js'; import { awaitAll, Promiseable } from '@/prelude/await-all.js'; @@ -9,6 +8,7 @@ import config from '@/config/index.js'; import { query, appendQuery } from '@/prelude/url.js'; import { Meta } from '@/models/entities/meta.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Users, DriveFolders } from '../index.js'; type PackOptions = { detail?: boolean, @@ -29,6 +29,8 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { + // TODO + //const properties = structuredClone(file.properties); const properties = JSON.parse(JSON.stringify(file.properties)); if (file.properties.orientation >= 5) { [properties.width, properties.height] = [properties.height, properties.width]; @@ -111,7 +113,40 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ async pack( src: DriveFile['id'] | DriveFile, - options?: PackOptions + options?: PackOptions, + ): Promise<Packed<'DriveFile'>> { + const opts = Object.assign({ + detail: false, + self: false, + }, options); + + const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + + return await awaitAll<Packed<'DriveFile'>>({ + id: file.id, + createdAt: file.createdAt.toISOString(), + name: file.name, + type: file.type, + md5: file.md5, + size: file.size, + isSensitive: file.isSensitive, + blurhash: file.blurhash, + properties: opts.self ? file.properties : this.getPublicProperties(file), + url: opts.self ? file.url : this.getPublicUrl(file, false), + thumbnailUrl: this.getPublicUrl(file, true), + comment: file.comment, + folderId: file.folderId, + folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { + detail: true, + }) : null, + userId: opts.withUser ? file.userId : null, + user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, + }); + }, + + async packNullable( + src: DriveFile['id'] | DriveFile, + options?: PackOptions, ): Promise<Packed<'DriveFile'> | null> { const opts = Object.assign({ detail: false, @@ -145,9 +180,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ async packMany( files: (DriveFile['id'] | DriveFile)[], - options?: PackOptions - ) { - const items = await Promise.all(files.map(f => this.pack(f, options))); - return items.filter(x => x != null); + options?: PackOptions, + ): Promise<Packed<'DriveFile'>[]> { + const items = await Promise.all(files.map(f => this.packNullable(f, options))); + return items.filter((x): x is Packed<'DriveFile'> => x != null); }, }); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index cf5fcb1787..3fefab0319 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -136,6 +136,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { export const NoteRepository = db.getRepository(Note).extend({ async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> { + // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility ใ specified ใใค่ชๅใๆๅฎใใใฆใใชใใฃใใ้่กจ็คบ if (note.visibility === 'specified') { if (meId == null) { @@ -144,13 +145,7 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } else { // ๆๅฎใใใฆใใใใฉใใ - const specified = note.visibleUserIds.some((id: any) => meId === id); - - if (specified) { - return true; - } else { - return false; - } + return note.visibleUserIds.some((id: any) => meId === id); } } @@ -168,16 +163,25 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } else { // ใใฉใญใฏใผใใฉใใ - const following = await Followings.findOneBy({ - followeeId: note.userId, - followerId: meId, - }); + const [following, user] = await Promise.all([ + Followings.count({ + where: { + followeeId: note.userId, + followerId: meId, + }, + take: 1, + }), + Users.findOneByOrFail({ id: meId }), + ]); - if (following == null) { - return false; - } else { - return true; - } + /* If we know the following, everyhting is fine. + + But if we do not know the following, it might be that both the + author of the note and the author of the like are remote users, + in which case we can never know the following. Instead we have + to assume that the users are following each other. + */ + return following > 0 || (note.userHost != null && user.host != null); } } diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 1bffb23fa2..092b26b396 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -1,10 +1,10 @@ import { db } from '@/db/postgre.js'; import { Page } from '@/models/entities/page.js'; import { Packed } from '@/misc/schema.js'; -import { Users, DriveFiles, PageLikes } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { User } from '@/models/entities/user.js'; +import { Users, DriveFiles, PageLikes } from '../index.js'; export const PageRepository = db.getRepository(Page).extend({ async pack( @@ -14,7 +14,7 @@ export const PageRepository = db.getRepository(Page).extend({ const meId = me ? me.id : null; const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const attachedFiles: Promise<DriveFile | undefined>[] = []; + const attachedFiles: Promise<DriveFile | null>[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { if (x.type === 'image') { @@ -73,7 +73,7 @@ export const PageRepository = db.getRepository(Page).extend({ script: page.script, eyeCatchingImageId: page.eyeCatchingImageId, eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, - attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), + attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)), likedCount: page.likedCount, isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, }); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 541fbaf003..8a4e48efdd 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -61,47 +61,58 @@ export const UserRepository = db.getRepository(User).extend({ //#endregion async getRelation(me: User['id'], target: User['id']) { - const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ - Followings.findOneBy({ - followerId: me, - followeeId: target, - }), - Followings.findOneBy({ - followerId: target, - followeeId: me, - }), - FollowRequests.findOneBy({ - followerId: me, - followeeId: target, - }), - FollowRequests.findOneBy({ - followerId: target, - followeeId: me, - }), - Blockings.findOneBy({ - blockerId: me, - blockeeId: target, - }), - Blockings.findOneBy({ - blockerId: target, - blockeeId: me, - }), - Mutings.findOneBy({ - muterId: me, - muteeId: target, - }), - ]); - - return { + return awaitAll({ id: target, - isFollowing: following1 != null, - hasPendingFollowRequestFromYou: followReq1 != null, - hasPendingFollowRequestToYou: followReq2 != null, - isFollowed: following2 != null, - isBlocking: toBlocking != null, - isBlocked: fromBlocked != null, - isMuted: mute != null, - }; + isFollowing: Followings.count({ + where: { + followerId: me, + followeeId: target, + }, + take: 1, + }).then(n => n > 0), + isFollowed: Followings.count({ + where: { + followerId: target, + followeeId: me, + }, + take: 1, + }).then(n => n > 0), + hasPendingFollowRequestFromYou: FollowRequests.count({ + where: { + followerId: me, + followeeId: target, + }, + take: 1, + }).then(n => n > 0), + hasPendingFollowRequestToYou: FollowRequests.count({ + where: { + followerId: target, + followeeId: me, + }, + take: 1, + }).then(n => n > 0), + isBlocking: Blockings.count({ + where: { + blockerId: me, + blockeeId: target, + }, + take: 1, + }).then(n => n > 0), + isBlocked: Blockings.count({ + where: { + blockerId: target, + blockeeId: me, + }, + take: 1, + }).then(n => n > 0), + isMuted: Mutings.count({ + where: { + muterId: me, + muteeId: target, + }, + take: 1, + }).then(n => n > 0), + }); }, async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> { diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2d40290e4c..c5fd7de1cb 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -1,4 +1,4 @@ -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import { v4 as uuid } from 'uuid'; import config from '@/config/index.js'; @@ -305,11 +305,13 @@ export default function() { systemQueue.add('resyncCharts', { }, { repeat: { cron: '0 0 * * *' }, + removeOnComplete: true, }); systemQueue.add('cleanCharts', { }, { repeat: { cron: '0 0 * * *' }, + removeOnComplete: true, }); systemQueue.add('checkExpiredMutings', { diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 166c9e4cd3..f5e0424a79 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Blockings } from '@/models/index.js'; import { MoreThan } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -22,73 +22,72 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let exportedCount = 0; - let cursor: any = null; + let exportedCount = 0; + let cursor: any = null; - while (true) { - const blockings = await Blockings.find({ - where: { - blockerId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }); + while (true) { + const blockings = await Blockings.find({ + where: { + blockerId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); - if (blockings.length === 0) { - job.progress(100); - break; - } - - cursor = blockings[blockings.length - 1].id; - - for (const block of blockings) { - const u = await Users.findOneBy({ id: block.blockeeId }); - if (u == null) { - exportedCount++; continue; + if (blockings.length === 0) { + job.progress(100); + break; } - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + cursor = blockings[blockings.length - 1].id; + + for (const block of blockings) { + const u = await Users.findOneBy({ id: block.blockeeId }); + if (u == null) { + exportedCount++; continue; + } + + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); + exportedCount++; + } + + const total = await Blockings.countBy({ + blockerId: user.id, }); - exportedCount++; + + job.progress(exportedCount / total); } - const total = await Blockings.countBy({ - blockerId: user.id, - }); + stream.end(); + logger.succ(`Exported to: ${path}`); - job.progress(exportedCount / total); + const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); } - stream.end(); - logger.succ(`Exported to: ${path}`); - - const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); - - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index c2467fb5f0..8ce1d05272 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -1,5 +1,4 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { ulid } from 'ulid'; @@ -10,6 +9,7 @@ import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { Users, Emojis } from '@/models/index.js'; import { } from '@/queue/types.js'; +import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import config from '@/config/index.js'; import { IsNull } from 'typeorm'; @@ -25,13 +25,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi return; } - // Create temp dir - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTempDir(); logger.info(`Temp dir is ${path}`); @@ -98,12 +92,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi metaStream.end(); // Create archive - const [archivePath, archiveCleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [archivePath, archiveCleanup] = await createTemp(); const archiveStream = fs.createWriteStream(archivePath); const archive = archiver('zip', { zlib: { level: 0 }, diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 965500ac27..4ac165567b 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Followings, Mutings } from '@/models/index.js'; import { In, MoreThan, Not } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -23,73 +23,72 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => } // Create temp file - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let cursor: Following['id'] | null = null; + let cursor: Following['id'] | null = null; - const mutings = job.data.excludeMuting ? await Mutings.findBy({ - muterId: user.id, - }) : []; + const mutings = job.data.excludeMuting ? await Mutings.findBy({ + muterId: user.id, + }) : []; - while (true) { - const followings = await Followings.find({ - where: { - followerId: user.id, - ...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}), - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }) as Following[]; + while (true) { + const followings = await Followings.find({ + where: { + followerId: user.id, + ...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}), + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }) as Following[]; - if (followings.length === 0) { - break; - } - - cursor = followings[followings.length - 1].id; - - for (const following of followings) { - const u = await Users.findOneBy({ id: following.followeeId }); - if (u == null) { - continue; + if (followings.length === 0) { + break; } - if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { - continue; - } + cursor = followings[followings.length - 1].id; - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + for (const following of followings) { + const u = await Users.findOneBy({ id: following.followeeId }); + if (u == null) { + continue; + } + + if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { + continue; + } + + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); - }); + } } + + stream.end(); + logger.succ(`Exported to: ${path}`); + + const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); } - stream.end(); - logger.succ(`Exported to: ${path}`); - - const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); - - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 0ef81971f1..6a36cfa072 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Mutings } from '@/models/index.js'; import { IsNull, MoreThan } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -22,74 +22,73 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let exportedCount = 0; - let cursor: any = null; + let exportedCount = 0; + let cursor: any = null; - while (true) { - const mutes = await Mutings.find({ - where: { - muterId: user.id, - expiresAt: IsNull(), - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }); + while (true) { + const mutes = await Mutings.find({ + where: { + muterId: user.id, + expiresAt: IsNull(), + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); - if (mutes.length === 0) { - job.progress(100); - break; - } - - cursor = mutes[mutes.length - 1].id; - - for (const mute of mutes) { - const u = await Users.findOneBy({ id: mute.muteeId }); - if (u == null) { - exportedCount++; continue; + if (mutes.length === 0) { + job.progress(100); + break; } - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + cursor = mutes[mutes.length - 1].id; + + for (const mute of mutes) { + const u = await Users.findOneBy({ id: mute.muteeId }); + if (u == null) { + exportedCount++; continue; + } + + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); + exportedCount++; + } + + const total = await Mutings.countBy({ + muterId: user.id, }); - exportedCount++; + + job.progress(exportedCount / total); } - const total = await Mutings.countBy({ - muterId: user.id, - }); + stream.end(); + logger.succ(`Exported to: ${path}`); - job.progress(exportedCount / total); + const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); } - stream.end(); - logger.succ(`Exported to: ${path}`); - - const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); - - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index 7e12a6fac2..051fcdf385 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -1,5 +1,4 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; @@ -10,6 +9,7 @@ import { MoreThan } from 'typeorm'; import { Note } from '@/models/entities/note.js'; import { Poll } from '@/models/entities/poll.js'; import { DbUserJobData } from '@/queue/types.js'; +import { createTemp } from '@/misc/create-temp.js'; const logger = queueLogger.createSubLogger('export-notes'); @@ -23,82 +23,81 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - const write = (text: string): Promise<void> => { - return new Promise<void>((res, rej) => { - stream.write(text, err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + const write = (text: string): Promise<void> => { + return new Promise<void>((res, rej) => { + stream.write(text, err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); - }); - }; + }; - await write('['); + await write('['); - let exportedNotesCount = 0; - let cursor: Note['id'] | null = null; + let exportedNotesCount = 0; + let cursor: Note['id'] | null = null; - while (true) { - const notes = await Notes.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }) as Note[]; + while (true) { + const notes = await Notes.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }) as Note[]; - if (notes.length === 0) { - job.progress(100); - break; - } - - cursor = notes[notes.length - 1].id; - - for (const note of notes) { - let poll: Poll | undefined; - if (note.hasPoll) { - poll = await Polls.findOneByOrFail({ noteId: note.id }); + if (notes.length === 0) { + job.progress(100); + break; } - const content = JSON.stringify(serialize(note, poll)); - const isFirst = exportedNotesCount === 0; - await write(isFirst ? content : ',\n' + content); - exportedNotesCount++; + + cursor = notes[notes.length - 1].id; + + for (const note of notes) { + let poll: Poll | undefined; + if (note.hasPoll) { + poll = await Polls.findOneByOrFail({ noteId: note.id }); + } + const content = JSON.stringify(serialize(note, poll)); + const isFirst = exportedNotesCount === 0; + await write(isFirst ? content : ',\n' + content); + exportedNotesCount++; + } + + const total = await Notes.countBy({ + userId: user.id, + }); + + job.progress(exportedNotesCount / total); } - const total = await Notes.countBy({ - userId: user.id, - }); + await write(']'); - job.progress(exportedNotesCount / total); + stream.end(); + logger.succ(`Exported to: ${path}`); + + const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); } - await write(']'); - - stream.end(); - logger.succ(`Exported to: ${path}`); - - const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); - - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 45852a6038..71dd72df27 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, UserLists, UserListJoinings } from '@/models/index.js'; import { In } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -26,46 +26,45 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): }); // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - for (const list of lists) { - const joinings = await UserListJoinings.findBy({ userListId: list.id }); - const users = await Users.findBy({ - id: In(joinings.map(j => j.userId)), - }); - - for (const u of users) { - const acct = getFullApAccount(u.username, u.host); - const content = `${list.name},${acct}`; - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } - }); + for (const list of lists) { + const joinings = await UserListJoinings.findBy({ userListId: list.id }); + const users = await Users.findBy({ + id: In(joinings.map(j => j.userId)), }); + + for (const u of users) { + const acct = getFullApAccount(u.username, u.host); + const content = `${list.name},${acct}`; + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); + }); + } } + + stream.end(); + logger.succ(`Exported to: ${path}`); + + const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); } - stream.end(); - logger.succ(`Exported to: ${path}`); - - const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); - - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index 28e0b867a4..64dfe85374 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -1,9 +1,9 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import unzipper from 'unzipper'; import { queueLogger } from '../../logger.js'; +import { createTempDir } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { DriveFiles, Emojis } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; @@ -25,13 +25,7 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don return; } - // Create temp dir - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTempDir(); logger.info(`Temp dir is ${path}`); diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 4fbfdb234f..198dde6050 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -1,6 +1,6 @@ import { URL } from 'node:url'; import Bull from 'bull'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import perform from '@/remote/activitypub/perform.js'; import Logger from '@/services/logger.js'; import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 6c0b9d9bf6..5ea4725561 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -3,7 +3,7 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user.js'; import { Webhook } from '@/models/entities/webhook'; import { IActivity } from '@/remote/activitypub/type.js'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; export type DeliverJobData = { /** Actor */ diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index ef07966e42..1a02f675ca 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -5,14 +5,52 @@ import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/ import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; -import { IObject, getApId } from './type.js'; -import { resolvePerson } from './models/person.js'; import { Cache } from '@/misc/cache.js'; import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; +import { IObject, getApId } from './type.js'; +import { resolvePerson } from './models/person.js'; const publicKeyCache = new Cache<UserPublickey | null>(Infinity); const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity); +export type UriParseResult = { + /** wether the URI was generated by us */ + local: true; + /** id in DB */ + id: string; + /** hint of type, e.g. "notes", "users" */ + type: string; + /** any remaining text after type and id, not including the slash after id. undefined if empty */ + rest?: string; +} | { + /** wether the URI was generated by us */ + local: false; + /** uri in DB */ + uri: string; +}; + +export function parseUri(value: string | IObject): UriParseResult { + const uri = getApId(value); + + // the host part of a URL is case insensitive, so use the 'i' flag. + const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); + const matchLocal = uri.match(localRegex); + + if (matchLocal) { + return { + local: true, + type: matchLocal[1], + id: matchLocal[2], + rest: matchLocal[3], + }; + } else { + return { + local: false, + uri, + }; + } +} + export default class DbResolver { constructor() { } @@ -21,60 +59,54 @@ export default class DbResolver { * AP Note => Misskey Note in DB */ public async getNoteFromApId(value: string | IObject): Promise<Note | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; - if (parsed.id) { return await Notes.findOneBy({ id: parsed.id, }); - } - - if (parsed.uri) { + } else { return await Notes.findOneBy({ uri: parsed.uri, }); } - - return null; } public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; - if (parsed.id) { return await MessagingMessages.findOneBy({ id: parsed.id, }); - } - - if (parsed.uri) { + } else { return await MessagingMessages.findOneBy({ uri: parsed.uri, }); } - - return null; } /** * AP Person => Misskey User in DB */ public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'users') return null; - if (parsed.id) { return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({ id: parsed.id, }).then(x => x ?? undefined)) ?? null; - } - - if (parsed.uri) { + } else { return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({ uri: parsed.uri, })); } - - return null; } /** @@ -120,31 +152,4 @@ export default class DbResolver { key, }; } - - public parseUri(value: string | IObject): UriParseResult { - const uri = getApId(value); - - const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)'); - const matchLocal = uri.match(localRegex); - - if (matchLocal) { - return { - type: matchLocal[1], - id: matchLocal[2], - }; - } else { - return { - uri, - }; - } - } } - -type UriParseResult = { - /** id in DB (local object only) */ - id?: string; - /** uri in DB (remote object only) */ - uri?: string; - /** hint of type (local object only, ex: notes, users) */ - type?: string -}; diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 680749f4d8..759cb4ae83 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -9,6 +9,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { getApLock } from '@/misc/app-lock.js'; import { parseAudience } from '../../audience.js'; import { StatusError } from '@/misc/fetch.js'; +import { Notes } from '@/models/index.js'; const logger = apLogger; @@ -52,6 +53,8 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac throw e; } + if (!await Notes.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; + logger.info(`Creating the (Re)Note: ${uri}`); const activityAudience = await parseAudience(actor, activity.to, activity.cc); diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index 4c06a9de0b..c7064f553b 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -13,37 +13,37 @@ export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<st } // ๅ้คๅฏพ่ฑกobjectใฎtype - let formarType: string | undefined; + let formerType: string | undefined; if (typeof activity.object === 'string') { // typeใไธๆใ ใใฉใใฉใใๆถใใฆใใฎใงremote resolveใใชใ - formarType = undefined; + formerType = undefined; } else { const object = activity.object as IObject; if (isTombstone(object)) { - formarType = toSingle(object.formerType); + formerType = toSingle(object.formerType); } else { - formarType = toSingle(object.type); + formerType = toSingle(object.type); } } const uri = getApId(activity.object); // typeไธๆใงใactorใจobjectใๅใใชใใฐใใใฏPersonใซ้ใใชใ - if (!formarType && actor.uri === uri) { - formarType = 'Person'; + if (!formerType && actor.uri === uri) { + formerType = 'Person'; } // ใใใงใใชใใฃใใใใใใNote - if (!formarType) { - formarType = 'Note'; + if (!formerType) { + formerType = 'Note'; } - if (validPost.includes(formarType)) { + if (validPost.includes(formerType)) { return await deleteNote(actor, uri); - } else if (validActor.includes(formarType)) { + } else if (validActor.includes(formerType)) { return await deleteActor(actor, uri); } else { - return `Unknown type ${formarType}`; + return `Unknown type ${formerType}`; } }; diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index c2ac31bf8d..417f39722f 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -8,6 +8,7 @@ export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnoun const note = await Notes.findOneBy({ uri, + userId: actor.id, }); if (!note) return 'skip: no such Announce'; diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts index 3800b40608..389039ebed 100644 --- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts +++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts @@ -3,8 +3,6 @@ import { Note } from '@/models/entities/note.js'; import { toHtml } from '../../../mfm/to-html.js'; export default function(note: Note) { - let html = note.text ? toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)) : null; - if (html == null) html = '<p>.</p>'; - - return html; + if (!note.text) return ''; + return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); } diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index a160092969..13f77424ec 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -1,9 +1,9 @@ -import { toArray, unique } from '@/prelude/array.js'; -import { IObject, isMention, IApMention } from '../type.js'; -import { resolvePerson } from './person.js'; import promiseLimit from 'promise-limit'; -import Resolver from '../resolver.js'; +import { toArray, unique } from '@/prelude/array.js'; import { CacheableUser, User } from '@/models/entities/user.js'; +import { IObject, isMention, IApMention } from '../type.js'; +import Resolver from '../resolver.js'; +import { resolvePerson } from './person.js'; export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); @@ -12,7 +12,7 @@ export async function extractApMentions(tags: IObject | IObject[] | null | undef const limit = promiseLimit<CacheableUser | null>(2); const mentionedUsers = (await Promise.all( - hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))) + hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), )).filter((x): x is CacheableUser => x != null); return mentionedUsers; diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 097a716614..56c1a483ad 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -3,9 +3,9 @@ import promiseLimit from 'promise-limit'; import config from '@/config/index.js'; import Resolver from '../resolver.js'; import post from '@/services/note/create.js'; -import { resolvePerson, updatePerson } from './person.js'; +import { resolvePerson } from './person.js'; import { resolveImage } from './image.js'; -import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { htmlToMfm } from '../misc/html-to-mfm.js'; import { extractApHashtags } from './tag.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js'; -import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js'; +import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { Emoji } from '@/models/entities/emoji.js'; @@ -197,7 +197,14 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; // ใใญในใใฎใใผใน - const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null); + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content === 'string') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = htmlToMfm(note.content, note.tag); + } // vote if (reply && reply.hasPoll) { diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 88661865da..6097e3b6ed 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -1,17 +1,8 @@ import { URL } from 'node:url'; import promiseLimit from 'promise-limit'; -import $, { Context } from 'cafy'; import config from '@/config/index.js'; -import Resolver from '../resolver.js'; -import { resolveImage } from './image.js'; -import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; -import { fromHtml } from '../../../mfm/from-html.js'; -import { htmlToMfm } from '../misc/html-to-mfm.js'; -import { resolveNote, extractEmojis } from './note.js'; import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; -import { extractApHashtags } from './tag.js'; -import { apLogger } from '../logger.js'; import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; @@ -32,6 +23,14 @@ import { StatusError } from '@/misc/fetch.js'; import { uriPersonCache } from '@/services/user-cache.js'; import { publishInternalEvent } from '@/services/stream.js'; import { db } from '@/db/postgre.js'; +import { apLogger } from '../logger.js'; +import { htmlToMfm } from '../misc/html-to-mfm.js'; +import { fromHtml } from '../../../mfm/from-html.js'; +import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; +import Resolver from '../resolver.js'; +import { extractApHashtags } from './tag.js'; +import { resolveNote, extractEmojis } from './note.js'; +import { resolveImage } from './image.js'; const logger = apLogger; @@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor { throw new Error(`invalid Actor type '${x.type}'`); } - const validate = (name: string, value: any, validater: Context) => { - const e = validater.test(value); - if (e) throw new Error(`invalid Actor: ${name} ${e.message}`); - }; + if (!(typeof x.id === 'string' && x.id.length > 0)) { + throw new Error('invalid Actor: wrong id'); + } - validate('id', x.id, $.default.str.min(1)); - validate('inbox', x.inbox, $.default.str.min(1)); - validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/)); + if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { + throw new Error('invalid Actor: wrong inbox'); + } + + if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { + throw new Error('invalid Actor: wrong username'); + } // These fields are only informational, and some AP software allows these // fields to be very long. If they are too long, we cut them off. This way // we can at least see these users and their activities. - validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str); - validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str); + if (x.name) { + if (!(typeof x.name === 'string' && x.name.length > 0)) { + throw new Error('invalid Actor: wrong name'); + } + x.name = truncate(x.name, nameLength); + } + if (x.summary) { + if (!(typeof x.summary === 'string' && x.summary.length > 0)) { + throw new Error('invalid Actor: wrong summary'); + } + x.summary = truncate(x.summary, summaryLength); + } const idHost = toPuny(new URL(x.id!).hostname); if (idHost !== expectHost) { @@ -271,7 +283,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us * @param resolver Resolver * @param hint Hint of Person object (ใใฎๅคใๆญฃๅฝใชPersonใฎๅ ดๅใRemote resolveใใใใซๆดๆฐใซๅฉ็จใใพใ) */ -export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: Record<string, unknown>): Promise<void> { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIใใใฎใตใผใใผใๆใใฆใใใชใในใญใใ @@ -289,7 +301,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint if (resolver == null) resolver = new Resolver(); - const object = hint || await resolver.resolve(uri) as any; + const object = hint || await resolver.resolve(uri); const person = validateActor(object, uri); @@ -400,10 +412,10 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C const services: { [x: string]: (id: string, username: string) => any } = { - 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), - 'misskey:authentication:github': (id, login) => ({ id, login }), - 'misskey:authentication:discord': (id, name) => $discord(id, name), -}; + 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), + 'misskey:authentication:github': (id, login) => ({ id, login }), + 'misskey:authentication:discord': (id, name) => $discord(id, name), + }; const $discord = (id: string, name: string) => { if (typeof name !== 'string') { @@ -461,7 +473,7 @@ export async function updateFeatured(userId: User['id']) { // Resolve to (Ordered)Collection Object const collection = await resolver.resolveCollection(user.featured); - if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`); + if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); // Resolve to Object(may be Note) arrays const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts index 10a4fde517..13815fb76f 100644 --- a/packages/backend/src/remote/activitypub/renderer/block.ts +++ b/packages/backend/src/remote/activitypub/renderer/block.ts @@ -1,8 +1,20 @@ import config from '@/config/index.js'; -import { ILocalUser, IRemoteUser } from '@/models/entities/user.js'; +import { Blocking } from '@/models/entities/blocking.js'; -export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ - type: 'Block', - actor: `${config.url}/users/${blocker.id}`, - object: blockee.uri, -}); +/** + * Renders a block into its ActivityPub representation. + * + * @param block The block to be rendered. The blockee relation must be loaded. + */ +export function renderBlock(block: Blocking) { + if (block.blockee?.url == null) { + throw new Error('renderBlock: missing blockee uri'); + } + + return { + type: 'Block', + id: `${config.url}/blocks/${block.id}`, + actor: `${config.url}/users/${block.blockerId}`, + object: block.blockee.uri, + }; +} diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts index 6fbc11580f..58eadddbaa 100644 --- a/packages/backend/src/remote/activitypub/renderer/flag.ts +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js'; // to anonymise reporters, the reporting actor must be a system user // object has to be a uri or array of uris -export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => { +export const renderFlag = (user: ILocalUser, object: [string], content: string) => { return { type: 'Flag', actor: `${config.url}/users/${user.id}`, diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts index 9e9692b77a..00fac18ad5 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow.ts @@ -4,12 +4,11 @@ import { Users } from '@/models/index.js'; export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => { const follow = { + id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`, type: 'Follow', actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri, object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri, } as any; - if (requestId) follow.id = requestId; - return follow; }; diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 5f69332266..f100b77ce5 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -8,7 +8,7 @@ import { User } from '@/models/entities/user.js'; export const renderActivity = (x: any): IActivity | null => { if (x == null) return null; - if (x !== null && typeof x === 'object' && x.id == null) { + if (typeof x === 'object' && x.id == null) { x.id = `${config.url}/${uuid()}`; } diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index 679c8bbfe4..df2ae65205 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -1,15 +1,15 @@ -import renderDocument from './document.js'; -import renderHashtag from './hashtag.js'; -import renderMention from './mention.js'; -import renderEmoji from './emoji.js'; +import { In, IsNull } from 'typeorm'; import config from '@/config/index.js'; -import toHtml from '../misc/get-note-html.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; -import { In, IsNull } from 'typeorm'; import { Emoji } from '@/models/entities/emoji.js'; import { Poll } from '@/models/entities/poll.js'; +import toHtml from '../misc/get-note-html.js'; +import renderEmoji from './emoji.js'; +import renderMention from './mention.js'; +import renderHashtag from './hashtag.js'; +import renderDocument from './document.js'; export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<Record<string, unknown>> { const getPromisedFiles = async (ids: string[]) => { @@ -82,15 +82,15 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const files = await getPromisedFiles(note.fileIds); - const text = note.text; - let poll: Poll | null; + // text should never be undefined + const text = note.text ?? null; + let poll: Poll | null = null; if (note.hasPoll) { poll = await Polls.findOneBy({ noteId: note.id }); } - let apText = text; - if (apText == null) apText = ''; + let apText = text ?? ''; if (quote) { apText += `\n\nRE: ${quote}`; @@ -138,6 +138,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false summary, content, _misskey_content: text, + source: { + content: text, + mediaType: "text/x.misskeymarkdown", + }, _misskey_quote: quote, quoteUrl: quote, published: note.createdAt.toISOString(), @@ -159,7 +163,7 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> { names.map(name => Emojis.findOneBy({ name, host: IsNull(), - })) + })), ); return emojis.filter(emoji => emoji != null) as Emoji[]; diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index c1269c75c5..2f9af43c0c 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -2,10 +2,19 @@ import config from '@/config/index.js'; import { getJson } from '@/misc/fetch.js'; import { ILocalUser } from '@/models/entities/user.js'; import { getInstanceActor } from '@/services/instance-actor.js'; +import { fetchMeta } from '@/misc/fetch-meta.js'; +import { extractDbHost, isSelfHost } from '@/misc/convert-host.js'; import { signedGet } from './request.js'; import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { extractDbHost } from '@/misc/convert-host.js'; +import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js'; +import { parseUri } from './db-resolver.js'; +import renderNote from '@/remote/activitypub/renderer/note.js'; +import { renderLike } from '@/remote/activitypub/renderer/like.js'; +import { renderPerson } from '@/remote/activitypub/renderer/person.js'; +import renderQuestion from '@/remote/activitypub/renderer/question.js'; +import renderCreate from '@/remote/activitypub/renderer/create.js'; +import { renderActivity } from '@/remote/activitypub/renderer/index.js'; +import renderFollow from '@/remote/activitypub/renderer/follow.js'; export default class Resolver { private history: Set<string>; @@ -40,14 +49,25 @@ export default class Resolver { return value; } + if (value.includes('#')) { + // URLs with fragment parts cannot be resolved correctly because + // the fragment part does not get transmitted over HTTP(S). + // Avoid strange behaviour by not trying to resolve these at all. + throw new Error(`cannot resolve URL with fragment: ${value}`); + } + if (this.history.has(value)) { throw new Error('cannot resolve already resolved one'); } this.history.add(value); - const meta = await fetchMeta(); const host = extractDbHost(value); + if (isSelfHost(host)) { + return await this.resolveLocal(value); + } + + const meta = await fetchMeta(); if (meta.blockedHosts.includes(host)) { throw new Error('Instance is blocked'); } @@ -56,13 +76,13 @@ export default class Resolver { this.user = await getInstanceActor(); } - const object = this.user + const object = (this.user ? await signedGet(value, this.user) - : await getJson(value, 'application/activity+json, application/ld+json'); + : await getJson(value, 'application/activity+json, application/ld+json')) as IObject; if (object == null || ( Array.isArray(object['@context']) ? - !object['@context'].includes('https://www.w3.org/ns/activitystreams') : + !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' )) { throw new Error('invalid response'); @@ -70,4 +90,44 @@ export default class Resolver { return object; } + + private resolveLocal(url: string): Promise<IObject> { + const parsed = parseUri(url); + if (!parsed.local) throw new Error('resolveLocal: not local'); + + switch (parsed.type) { + case 'notes': + return Notes.findOneByOrFail({ id: parsed.id }) + .then(note => { + if (parsed.rest === 'activity') { + // this refers to the create activity and not the note itself + return renderActivity(renderCreate(renderNote(note))); + } else { + return renderNote(note); + } + }); + case 'users': + return Users.findOneByOrFail({ id: parsed.id }) + .then(user => renderPerson(user as ILocalUser)); + case 'questions': + // Polls are indexed by the note they are attached to. + return Promise.all([ + Notes.findOneByOrFail({ id: parsed.id }), + Polls.findOneByOrFail({ noteId: parsed.id }), + ]) + .then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll)); + case 'likes': + return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null }))); + case 'follows': + // rest should be <followee id> + if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); + + return Promise.all( + [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })) + ) + .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url))); + default: + throw new Error(`resolveLocal: type ${type} unhandled`); + } + } } diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 2051d2624d..5d00481b75 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -2,7 +2,7 @@ export type obj = { [x: string]: any }; export type ApObject = IObject | string | (IObject | string)[]; export interface IObject { - '@context': string | obj | obj[]; + '@context': string | string[] | obj | obj[]; type: string | string[]; id?: string; summary?: string; @@ -48,7 +48,7 @@ export function getOneApId(value: ApObject): string { export function getApId(value: string | IObject): string { if (typeof value === 'string') return value; if (typeof value.id === 'string') return value.id; - throw new Error(`cannot detemine id`); + throw new Error('cannot detemine id'); } /** @@ -57,7 +57,7 @@ export function getApId(value: string | IObject): string { export function getApType(value: IObject): string { if (typeof value.type === 'string') return value.type; if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; - throw new Error(`cannot detect type`); + throw new Error('cannot detect type'); } export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { @@ -106,7 +106,10 @@ export const isPost = (object: IObject): object is IPost => export interface IPost extends IObject { type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; _misskey_talk: boolean; @@ -114,7 +117,10 @@ export interface IPost extends IObject { export interface IQuestion extends IObject { type: 'Note' | 'Question'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; oneOf?: IQuestionChoice[]; diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 133dd36066..cd5f917c40 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -1,6 +1,6 @@ import Router from '@koa/router'; import json from 'koa-json-body'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; @@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js'; import { isSelfHost } from '@/misc/convert-host.js'; import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; -import { In, IsNull } from 'typeorm'; +import { In, IsNull, Not } from 'typeorm'; import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { getUserKeypair } from '@/misc/keypair-store.js'; +import renderFollow from '@/remote/activitypub/renderer/follow.js'; // Init router const router = new Router(); @@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => { setResponseType(ctx); }); +// follow +router.get('/follows/:follower/:followee', async ctx => { + // This may be used before the follow is completed, so we do not + // check if the following exists. + + const [follower, followee] = await Promise.all([ + Users.findOneBy({ + id: ctx.params.follower, + host: IsNull(), + }), + Users.findOneBy({ + id: ctx.params.followee, + host: Not(IsNull()), + }), + ]); + + if (follower == null || followee == null) { + ctx.status = 404; + return; + } + + ctx.body = renderActivity(renderFollow(follower, followee)); + ctx.set('Cache-Control', 'public, max-age=180'); + setResponseType(ctx); +}); + export default router; diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 4d4f733162..beb48713a6 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -1,32 +1,26 @@ import Router from '@koa/router'; +import { FindOptionsWhere, IsNull, LessThan } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import * as url from '@/prelude/url.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { IsNull, LessThan } from 'typeorm'; +import { Following } from '@/models/entities/following.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'cursor' parameter - const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor); - - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; - - // Validate parameters - if (cursorErr || pageErr) { + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { ctx.status = 400; return; } + const page = ctx.request.query.page === 'true'; + const user = await Users.findOneBy({ id: userId, host: IsNull(), @@ -57,7 +51,7 @@ export default async (ctx: Router.RouterContext) => { if (page) { const query = { followeeId: user.id, - } as any; + } as FindOptionsWhere<Following>; // ใซใผใฝใซใๆๅฎใใใฆใใๅ ดๅ if (cursor) { @@ -86,7 +80,7 @@ export default async (ctx: Router.RouterContext) => { inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 0af1f424f9..3a25a6316c 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -1,33 +1,26 @@ import Router from '@koa/router'; +import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import * as url from '@/prelude/url.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import { Following } from '@/models/entities/following.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'cursor' parameter - const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor); - - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; - - // Validate parameters - if (cursorErr || pageErr) { + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { ctx.status = 400; return; } + const page = ctx.request.query.page === 'true'; + const user = await Users.findOneBy({ id: userId, host: IsNull(), @@ -87,7 +80,7 @@ export default async (ctx: Router.RouterContext) => { inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 6b9592bcf3..7a2586998a 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -1,36 +1,37 @@ import Router from '@koa/router'; +import { Brackets, IsNull } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import { setResponseType } from '../activitypub.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; import renderCreate from '@/remote/activitypub/renderer/create.js'; import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; import { countIf } from '@/prelude/array.js'; import * as url from '@/prelude/url.js'; import { Users, Notes } from '@/models/index.js'; -import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { Brackets, IsNull } from 'typeorm'; import { Note } from '@/models/entities/note.js'; +import { makePaginationQuery } from '../api/common/make-pagination-query.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.default.optional.type(ID).get(ctx.request.query.since_id); + const sinceId = ctx.request.query.since_id; + if (sinceId != null && typeof sinceId !== 'string') { + ctx.status = 400; + return; + } - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.default.optional.type(ID).get(ctx.request.query.until_id); + const untilId = ctx.request.query.until_id; + if (untilId != null && typeof untilId !== 'string') { + ctx.status = 400; + return; + } - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; + const page = ctx.request.query.page === 'true'; - // Validate parameters - if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) { + if (countIf(x => x != null, [sinceId, untilId]) > 1) { ctx.status = 400; return; } @@ -52,8 +53,8 @@ export default async (ctx: Router.RouterContext) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId) .andWhere('note.userId = :userId', { userId: user.id }) .andWhere(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); + .where('note.visibility = \'public\'') + .orWhere('note.visibility = \'home\''); })) .andWhere('note.localOnly = FALSE'); @@ -76,7 +77,7 @@ export default async (ctx: Router.RouterContext) => { notes.length ? `${partOf}?${url.query({ page: 'true', until_id: notes[notes.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); @@ -85,7 +86,7 @@ export default async (ctx: Router.RouterContext) => { // index page const rendered = renderOrderedCollection(partOf, user.notesCount, `${partOf}?page=true`, - `${partOf}?page=true&since_id=000000000000000000000000` + `${partOf}?page=true&since_id=000000000000000000000000`, ); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts index dce8accaac..96b9316e47 100644 --- a/packages/backend/src/server/api/2fa.ts +++ b/packages/backend/src/server/api/2fa.ts @@ -1,6 +1,6 @@ import * as crypto from 'node:crypto'; -import config from '@/config/index.js'; import * as jsrsasign from 'jsrsasign'; +import config from '@/config/index.js'; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); @@ -145,7 +145,7 @@ export function verifyLogin({ export const procedures = { none: { - verify({ publicKey }: {publicKey: Map<number, Buffer>}) { + verify({ publicKey }: { publicKey: Map<number, Buffer> }) { const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 9a85e4565b..cd3e0abc06 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -2,10 +2,11 @@ import Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import endpoints, { IEndpoint } from './endpoints.js'; +import endpoints, { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; import { AccessToken } from '@/models/entities/access-token.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; const accessDenied = { message: 'Access denied.', @@ -15,6 +16,7 @@ const accessDenied = { export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; + const isModerator = user != null && (user.isModerator || user.isAdmin); const ep = endpoints.find(e => e.name === endpoint); @@ -31,6 +33,32 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi throw new ApiError(accessDenied); } + if (ep.meta.limit && !isModerator) { + // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. + let limitActor: string; + if (user) { + limitActor = user.id; + } else { + limitActor = getIpHash(ctx!.ip); + } + + const limit = Object.assign({}, ep.meta.limit); + + if (!limit.key) { + limit.key = ep.name; + } + + // Rate limit + await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => { + throw new ApiError({ + message: 'Rate limit exceeded. Please try again later.', + code: 'RATE_LIMIT_EXCEEDED', + id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + httpStatusCode: 429, + }); + }); + } + if (ep.meta.requireCredential && user == null) { throw new ApiError({ message: 'Credential required.', @@ -53,7 +81,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); } - if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) { + if (ep.meta.requireModerator && !isModerator) { throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); } @@ -65,18 +93,6 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi }); } - if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) { - // Rate limit - await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => { - throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', - httpStatusCode: 429, - }); - }); - } - // Cast non JSON input if (ep.meta.requireFile && ep.params.properties) { for (const k of Object.keys(ep.params.properties)) { diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts index 715982934c..b50b6812f4 100644 --- a/packages/backend/src/server/api/common/generate-visibility-query.ts +++ b/packages/backend/src/server/api/common/generate-visibility-query.ts @@ -3,6 +3,7 @@ import { Followings } from '@/models/index.js'; import { Brackets, SelectQueryBuilder } from 'typeorm'; export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) { + // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { q.andWhere(new Brackets(qb => { qb .where(`note.visibility = 'public'`) @@ -11,7 +12,7 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U } else { const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + .where('following.followerId = :meId'); q.andWhere(new Brackets(qb => { qb // ๅ ฌ้ๆ็จฟใงใใ @@ -20,21 +21,22 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U .orWhere(`note.visibility = 'home'`); })) // ใพใใฏ ่ชๅ่ช่บซ - .orWhere('note.userId = :userId1', { userId1: me.id }) + .orWhere('note.userId = :meId') // ใพใใฏ ่ชๅๅฎใฆ - .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`) + .orWhere(':meId = ANY(note.visibleUserIds)') + .orWhere(':meId = ANY(note.mentions)') .orWhere(new Brackets(qb => { qb // ใพใใฏ ใใฉใญใฏใผๅฎใฆใฎๆ็จฟใงใใใ - .where('note.visibility = \'followers\'') + .where(`note.visibility = 'followers'`) .andWhere(new Brackets(qb => { qb // ่ชๅใใใฉใญใฏใผใงใใ .where(`note.userId IN (${ followingQuery.getQuery() })`) // ใพใใฏ ่ชๅใฎๆ็จฟใธใฎใชใใฉใค - .orWhere('note.replyUserId = :userId3', { userId3: me.id }); + .orWhere('note.replyUserId = :meId'); })); })); })); - q.setParameters(followingQuery.getParameters()); + q.setParameters({ meId: me.id }); } } diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index 3638518e67..c4c18ffa06 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -1,6 +1,7 @@ import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; import { publishMessagingStream } from '@/services/stream.js'; import { publishMessagingIndexStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js'; @@ -50,6 +51,21 @@ export async function readUserMessagingMessage( if (!await Users.getHasUnreadMessagingMessage(userId)) { // ๅ จใฆใฎ(ใใพใพใงๆช่ชญใ ใฃใ)่ชๅๅฎใฆใฎใกใใปใผใธใ(ใใใง)่ชญใฟใพใใใใจใใใคใใณใใ็บ่ก publishMainStream(userId, 'readAllMessagingMessages'); + pushNotification(userId, 'readAllMessagingMessages', undefined); + } else { + // ใใฎใฆใผใถใผใจใฎใกใใปใผใธใงๆช่ชญใใชใใใฐใคใใณใ็บ่ก + const count = await MessagingMessages.count({ + where: { + userId: otherpartyId, + recipientId: userId, + isRead: false, + }, + take: 1 + }); + + if (!count) { + pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId }); + } } } @@ -104,6 +120,19 @@ export async function readGroupMessagingMessage( if (!await Users.getHasUnreadMessagingMessage(userId)) { // ๅ จใฆใฎ(ใใพใพใงๆช่ชญใ ใฃใ)่ชๅๅฎใฆใฎใกใใปใผใธใ(ใใใง)่ชญใฟใพใใใใจใใใคใใณใใ็บ่ก publishMainStream(userId, 'readAllMessagingMessages'); + pushNotification(userId, 'readAllMessagingMessages', undefined); + } else { + // ใใฎใฐใซใผใใซใใใฆๆช่ชญใใชใใใฐใคใใณใ็บ่ก + const unreadExist = await MessagingMessages.createQueryBuilder('message') + .where(`message.groupId = :groupId`, { groupId: groupId }) + .andWhere('message.userId != :userId', { userId: userId }) + .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) + .andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // ่ชๅใๅ ๅ ฅใใๅใฎไผ่ฉฑใซใคใใฆใฏใๆช่ชญๆฑใใใชใ + .getOne().then(x => x != null); + + if (!unreadExist) { + pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId }); + } } } diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts index 1f575042a0..0dad35bcc2 100644 --- a/packages/backend/src/server/api/common/read-notification.ts +++ b/packages/backend/src/server/api/common/read-notification.ts @@ -1,4 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import { User } from '@/models/entities/user.js'; import { Notification } from '@/models/entities/notification.js'; import { Notifications, Users } from '@/models/index.js'; @@ -16,28 +17,29 @@ export async function readNotification( isRead: true, }); - post(userId); + if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId); + else return postReadNotifications(userId, notificationIds); } export async function readNotificationByQuery( userId: User['id'], query: Record<string, any> ) { - // Update documents - await Notifications.update({ + const notificationIds = await Notifications.find({ ...query, notifieeId: userId, isRead: false, - }, { - isRead: true, - }); + }).then(notifications => notifications.map(notification => notification.id)); - post(userId); + return readNotification(userId, notificationIds); } -async function post(userId: User['id']) { - if (!await Users.getHasUnreadNotification(userId)) { - // ๅ จใฆใฎ(ใใพใพใงๆช่ชญใ ใฃใ)้็ฅใ(ใใใง)่ชญใฟใพใใใใจใใใคใใณใใ็บ่ก - publishMainStream(userId, 'readAllNotifications'); - } +function postReadAllNotifications(userId: User['id']) { + publishMainStream(userId, 'readAllNotifications'); + return pushNotification(userId, 'readAllNotifications', undefined); +} + +function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { + publishMainStream(userId, 'readNotifications', notificationIds); + return pushNotification(userId, 'readNotifications', { notificationIds }); } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index e2db03f13a..1e7afd8cdd 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -654,7 +654,6 @@ export interface IEndpointMeta { /** * ใจใณใใใคใณใใฎใชใใใผใทใงใณใซ้ขใใใใค * ็็ฅใใๅ ดๅใฏใชใใใผใทใงใณใฏ็กใใใฎใจใใฆ่งฃ้ใใใพใใ - * ใพใใwithCredential ใ false ใฎๅ ดๅใฏใชใใใผใทใงใณใ่กใใใจใฏใงใใพใใใ */ readonly limit?: { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 1d8eb1d618..7a5758d75b 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; import { Announcements, AnnouncementReads } from '@/models/index.js'; +import { Announcement } from '@/models/entities/announcement.js'; +import define from '../../../define.js'; import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { @@ -68,11 +69,21 @@ export default define(meta, paramDef, async (ps) => { const announcements = await query.take(ps.limit).getMany(); + const reads = new Map<Announcement, number>(); + for (const announcement of announcements) { - (announcement as any).reads = await AnnouncementReads.countBy({ + reads.set(announcement, await AnnouncementReads.countBy({ announcementId: announcement.id, - }); + })); } - return announcements; + return announcements.map(announcement => ({ + id: announcement.id, + createdAt: announcement.createdAt.toISOString(), + updatedAt: announcement.updatedAt?.toISOString() ?? null, + title: announcement.title, + text: announcement.text, + imageUrl: announcement.imageUrl, + reads: reads.get(announcement)!, + })); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index bf6cc16532..78033aed58 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,5 @@ +import { Signins, UserProfiles, Users } from '@/models/index.js'; import define from '../../define.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['admin'], @@ -23,9 +23,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); + const [user, profile] = await Promise.all([ + Users.findOneBy({ id: ps.userId }), + UserProfiles.findOneBy({ userId: ps.userId }) + ]); - if (user == null) { + if (user == null || profile == null) { throw new Error('user not found'); } @@ -34,8 +37,37 @@ export default define(meta, paramDef, async (ps, me) => { throw new Error('cannot show info of admin'); } + if (!_me.isAdmin) { + return { + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + }; + } + + const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; + Object.keys(profile.integrations).forEach(integration => { + maskedKeys.forEach(key => profile.integrations[integration][key] = '<MASKED>'); + }); + + const signins = await Signins.findBy({ userId: user.id }); + return { - ...user, - token: user.token != null ? '<MASKED>' : user.token, + email: profile.email, + emailVerified: profile.emailVerified, + autoAcceptFollowed: profile.autoAcceptFollowed, + noCrawle: profile.noCrawle, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + carefulBot: profile.carefulBot, + injectFeaturedNote: profile.injectFeaturedNote, + receiveAnnouncementEmail: profile.receiveAnnouncementEmail, + integrations: profile.integrations, + mutedWords: profile.mutedWords, + mutedInstances: profile.mutedInstances, + mutingNotificationTypes: profile.mutingNotificationTypes, + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + signins, }; }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 2703b4b9db..1575d81d5d 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { Users } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['admin'], @@ -24,8 +24,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, - state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, username: { type: 'string', nullable: true, default: null }, hostname: { type: 'string', diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index b23ee9e3df..09e43301b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -27,7 +27,7 @@ export const paramDef = { blockedHosts: { type: 'array', nullable: true, items: { type: 'string', } }, - themeColor: { type: 'string', nullable: true }, + themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, errorImageUrl: { type: 'string', nullable: true }, diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 7ffe89a1e5..415a8cc693 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Find the notes to which the given file is attached.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 80293df5d9..bbae9bf4e4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Check if a given file exists.', + res: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 0939ae3365..7397fd9ce9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -20,6 +20,8 @@ export const meta = { kind: 'write:drive', + description: 'Upload a new drive file.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 61c56e6314..6108ae7da9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Delete an existing drive file.', + errors: { noSuchFile: { message: 'No such file.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index f9b4ea89ea..f2bc7348c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['drive'], @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by a hash of the contents.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 4938a69d11..245fb45a65 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by the given parameters.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index a2bc0c7aa4..2c604c54c8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,7 +1,7 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Users } from '@/models/index.js'; +import define from '../../../define.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['drive'], @@ -10,6 +10,8 @@ export const meta = { kind: 'read:drive', + description: 'Show the properties of a drive file.', + res: { type: 'object', optional: false, nullable: false, @@ -51,7 +53,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - let file: DriveFile | undefined; + let file: DriveFile | null = null; if (ps.fileId) { file = await DriveFiles.findOneBy({ id: ps.fileId }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 4b3f5f2dc9..e3debe0b4f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Update the properties of a drive file.', + errors: { invalidFileName: { message: 'Invalid file name.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 3bfecac802..53f2298f21 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -13,6 +13,8 @@ export const meta = { max: 60, }, + description: 'Request the server to download a new drive file from the specified URL.', + requireCredential: true, kind: 'write:drive', diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index d5e1b19e54..33f5717728 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -2,8 +2,8 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import config from '@/config/index.js'; -import define from '../../../define.js'; import { UserProfiles } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { requireCredential: true, @@ -40,15 +40,17 @@ export default define(meta, paramDef, async (ps, user) => { }); // Get the data URL of the authenticator URL - const dataUrl = await QRCode.toDataURL(speakeasy.otpauthURL({ + const url = speakeasy.otpauthURL({ secret: secret.base32, encoding: 'base32', label: user.username, issuer: config.host, - })); + }); + const dataUrl = await QRCode.toDataURL(url); return { qr: dataUrl, + url, secret: secret.base32, label: user.username, issuer: config.host, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 9de05918c0..a133294169 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,15 +1,15 @@ import ms from 'ms'; +import { In } from 'typeorm'; import create from '@/services/note/create.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; import { User } from '@/models/entities/user.js'; import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Note } from '@/models/entities/note.js'; -import { noteVisibilities } from '../../../../types.js'; import { Channel } from '@/models/entities/channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { In } from 'typeorm'; +import { noteVisibilities } from '../../../../types.js'; +import { ApiError } from '../../error.js'; +import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -83,7 +83,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" }, + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, @@ -134,7 +134,7 @@ export const paramDef = { { // (re)note with text, files and poll are optional properties: { - text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, + text: { type: 'string', minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, }, required: ['text'], }, @@ -149,7 +149,7 @@ export const paramDef = { { // (re)note with poll, text and files are optional properties: { - poll: { type: 'object', nullable: false, }, + poll: { type: 'object', nullable: false }, }, required: ['poll'], }, @@ -172,20 +172,24 @@ export default define(meta, paramDef, async (ps, user) => { let files: DriveFile[] = []; const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { - files = await DriveFiles.findBy({ - userId: user.id, - id: In(fileIds), - }); + files = await DriveFiles.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: user.id, + fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds }) + .getMany(); } - let renote: Note | null; + let renote: Note | null = null; if (ps.renoteId != null) { // Fetch renote to note renote = await Notes.findOneBy({ id: ps.renoteId }); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.poll) { + } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { throw new ApiError(meta.errors.cannotReRenote); } @@ -201,14 +205,14 @@ export default define(meta, paramDef, async (ps, user) => { } } - let reply: Note | null; + let reply: Note | null = null; if (ps.replyId != null) { // Fetch reply reply = await Notes.findOneBy({ id: ps.replyId }); if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !renote.poll) { + } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } @@ -234,7 +238,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - let channel: Channel | undefined; + let channel: Channel | null = null; if (ps.channelId != null) { channel = await Channels.findOneBy({ id: ps.channelId }); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 3555424fa6..fbb065329c 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,8 +1,8 @@ +import { DeepPartial, FindOptionsWhere } from 'typeorm'; +import { NoteReactions } from '@/models/index.js'; +import { NoteReaction } from '@/models/entities/note-reaction.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { NoteReactions } from '@/models/index.js'; -import { DeepPartial } from 'typeorm'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; export const meta = { tags: ['notes', 'reactions'], @@ -45,7 +45,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const query = { noteId: ps.noteId, - } as DeepPartial<NoteReaction>; + } as FindOptionsWhere<NoteReaction>; if (ps.type) { // ใญใผใซใซใชใขใฏใทใงใณใฏใในใๅใ . ใจใใใฆใใใ diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c602981b30..5e40e7106f 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,12 +1,12 @@ -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; -import { ApiError } from '../../error.js'; +import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; import config from '@/config/index.js'; import { getAgentByUrl } from '@/misc/fetch.js'; -import { URLSearchParams } from 'node:url'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Notes } from '@/models/index.js'; +import { ApiError } from '../../error.js'; +import { getNote } from '../../common/getters.js'; +import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -80,7 +80,12 @@ export default define(meta, paramDef, async (ps, user) => { agent: getAgentByUrl, }); - const json = await res.json(); + const json = (await res.json()) as { + translations: { + detected_source_language: string; + text: string; + }[]; + }; return { sourceLang: json.translations[0].detected_source_language, diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index abefe07be6..4575cba43f 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,4 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import define from '../../define.js'; import { Notifications } from '@/models/index.js'; @@ -28,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => { // ๅ จใฆใฎ้็ฅใ่ชญใฟใพใใใใจใใใคใใณใใ็บ่ก publishMainStream(user.id, 'readAllNotifications'); + pushNotification(user.id, 'readAllNotifications', undefined); }); diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index c7bc5dc0a5..65e96d4862 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -1,10 +1,12 @@ -import { publishMainStream } from '@/services/stream.js'; import define from '../../define.js'; -import { Notifications } from '@/models/index.js'; import { readNotification } from '../../common/read-notification.js'; -import { ApiError } from '../../error.js'; export const meta = { + desc: { + 'ja-JP': '้็ฅใๆข่ชญใซใใพใใ', + 'en-US': 'Mark a notification as read.' + }, + tags: ['notifications', 'account'], requireCredential: true, @@ -21,23 +23,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - notificationId: { type: 'string', format: 'misskey:id' }, - }, - required: ['notificationId'], + oneOf: [ + { + type: 'object', + properties: { + notificationId: { type: 'string', format: 'misskey:id' }, + }, + required: ['notificationId'], + }, + { + type: 'object', + properties: { + notificationIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } }, + }, + required: ['notificationIds'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const notification = await Notifications.findOneBy({ - notifieeId: user.id, - id: ps.notificationId, - }); - - if (notification == null) { - throw new ApiError(meta.errors.noSuchNotification); - } - - readNotification(user.id, [notification.id]); + if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]); + return readNotification(user.id, ps.notificationIds); }); diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 3dcce8550f..5d37e86b91 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,8 +1,8 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { IsNull } from 'typeorm'; import { Pages, Users } from '@/models/index.js'; import { Page } from '@/models/entities/page.js'; -import { IsNull } from 'typeorm'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['pages'], @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - let page: Page | undefined; + let page: Page | null = null; if (ps.pageId) { page = await Pages.findOneBy({ id: ps.pageId }); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 046337f040..12ce7a9834 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -10,8 +10,12 @@ import { genId } from '@/misc/gen-id.js'; import { IsNull } from 'typeorm'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Request a users password to be reset.', + limit: { duration: ms('1hour'), max: 3, diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index dbe64e9a13..5ff115dab5 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -3,8 +3,12 @@ import { ApiError } from '../error.js'; import { resetDb } from '@/db/postgre.js'; export const meta = { + tags: ['non-productive'], + requireCredential: false, + description: 'Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 7acc545c40..3dcb0b9b83 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -5,8 +5,12 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; import { ApiError } from '../error.js'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Complete the password reset that was previously requested.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index a48973a0df..5bc3b9b6a1 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -8,6 +8,8 @@ export const meta = { requireCredential: true, + description: 'Register to receive push notifications.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 9748f2a222..c21856d28f 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -5,6 +5,8 @@ export const meta = { tags: ['account'], requireCredential: true, + + description: 'Unregister from receiving push notifications.', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 256da1a66f..9949237a7e 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,6 +1,10 @@ import define from '../define.js'; export const meta = { + tags: ['non-productive'], + + description: 'Endpoint for testing input validation.', + requireCredential: false, } as const; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 424c594749..37d4153950 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'clips'], + + description: 'Show all clips this user owns.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Clip', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 26b1f20df0..b1fb656208 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Show everyone that follows this user.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 42cf5216e8..429a5e80e5 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Show everyone that this user is following.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index d7c435256c..35bf2df598 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'gallery'], + + description: 'Show all gallery posts by the given user.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'GalleryPost', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 73cadc0df7..ab5837b3f3 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Get a list of other users that the specified user frequently replies to.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index fc775d7cc1..fcaf4af3c3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Create a new group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index f68006994c..1bf253ae3f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 75c1acc302..eafd7f592c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Join a group the authenticated user has been invited to.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 46bc780ab0..08d3a3804b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group invitation for the authenticated user without joining the group.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 30a5beb1d9..cc82e43f21 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -13,6 +13,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Invite a user to an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 77dc59d3e5..6a2862ee5a 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is a member of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 33abd5439f..2343cdf857 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index b1289e601f..de030193cc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is the owner of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index b31990b2e3..703dad6d3b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Removes a specified user from a group. The owner can not be removed.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 3ffb0f5ba9..e1cee5fcf7 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'Show the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 41ceee3b2e..1496e766ca 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Transfer ownership of a group from the authenticated user to another user.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 1016aa8926..43cf3e484e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Update the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index d5260256d5..d2941a0af5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:account', + description: 'Create a new list of users.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index b7ad96eef0..8cd02ee02a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Delete an existing list of users.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 78311292cb..b337f879b1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:account', + description: 'Show all lists that the authenticated user has created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 76863f07d1..fa7033b02e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Remove a user from a list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 260665c63a..1db10afc80 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Add a user to an existing list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 5f51980e95..94d24e1274 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:account', + description: 'Show the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 52353a14cc..c21cdcf679 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Update the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 16318d2225..57dcdfaa88 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -12,6 +12,8 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance export const meta = { tags: ['users', 'notes'], + description: 'Show all notes that this user created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b8b3e8192e..85d122c24f 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'pages'], + + description: 'Show all pages this user created.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Page', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index c2d1994343..64994aae49 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: false, + description: 'Show all reactions this user made.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index a8f18de522..6fff94ddcf 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'read:account', + description: 'Show users that the authenticated user might be interested to follow.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index c6262122d4..87cab5fcf1 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -6,6 +6,8 @@ export const meta = { requireCredential: true, + description: 'Show the different kinds of relations between the authenticated user and the specified user(s).', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 0be385dbbf..c7c7a3f591 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + description: 'File a report.', + errors: { noSuchUser: { message: 'No such user.', diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index f74d80e2ae..6cbf12b3b5 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: false, + description: 'Search for a user by username and/or host.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index a72a58a843..19c1a2c690 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -8,6 +8,8 @@ export const meta = { requireCredential: false, + description: 'Search for users.', + res: { type: 'array', optional: false, nullable: false, @@ -61,7 +63,14 @@ export default define(meta, paramDef, async (ps, me) => { .getMany(); } else { const nameQuery = Users.createQueryBuilder('user') - .where('user.name ILIKE :query', { query: '%' + ps.query + '%' }) + .where(new Brackets(qb => { + qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); + + // Also search username if it qualifies as username + if (Users.validateLocalUsername(ps.query)) { + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); + } + })) .andWhere(new Brackets(qb => { qb .where('user.updatedAt IS NULL') .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 183ff1b8bb..b31ca30647 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: false, + description: 'Show the properties of a user.', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index d138019a72..d17e8b64b5 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,12 +1,15 @@ import define from '../../define.js'; import { ApiError } from '../../error.js'; import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; +import { awaitAll } from '@/prelude/await-all.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show statistics about a user.', + errors: { noSuchUser: { message: 'No such user.', @@ -14,6 +17,94 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + notesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliesCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliedCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + followingCount: { + type: 'integer', + optional: false, nullable: false, + }, + followersCount: { + type: 'integer', + optional: false, nullable: false, + }, + sentReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + receivedReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + noteFavoritesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikedCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveFilesCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveUsage: { + type: 'integer', + optional: false, nullable: false, + description: 'Drive usage in bytes', + }, + }, + }, } as const; export const paramDef = { @@ -31,109 +122,72 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchUser); } - const [ - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - ] = await Promise.all([ - Notes.createQueryBuilder('note') + const result = await awaitAll({ + notesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + repliesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.replyId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + renotesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.renoteId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + repliedCount: Notes.createQueryBuilder('note') .where('note.replyUserId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + renotedCount: Notes.createQueryBuilder('note') .where('note.renoteUserId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotesCount: PollVotes.createQueryBuilder('vote') .where('vote.userId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotedCount: PollVotes.createQueryBuilder('vote') .innerJoin('vote.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Followings.createQueryBuilder('following') + localFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NOT NULL') .getCount(), - Followings.createQueryBuilder('following') + localFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NOT NULL') .getCount(), - NoteReactions.createQueryBuilder('reaction') + sentReactionsCount: NoteReactions.createQueryBuilder('reaction') .where('reaction.userId = :userId', { userId: user.id }) .getCount(), - NoteReactions.createQueryBuilder('reaction') + receivedReactionsCount: NoteReactions.createQueryBuilder('reaction') .innerJoin('reaction.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - NoteFavorites.createQueryBuilder('favorite') + noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') .where('favorite.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikesCount: PageLikes.createQueryBuilder('like') .where('like.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikedCount: PageLikes.createQueryBuilder('like') .innerJoin('like.page', 'page') .where('page.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.createQueryBuilder('file') + driveFilesCount: DriveFiles.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.calcDriveUsageOf(user), - ]); + driveUsage: DriveFiles.calcDriveUsageOf(user), + }); - return { - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - followingCount: localFollowingCount + remoteFollowingCount, - followersCount: localFollowersCount + remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - }; + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + + return result; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index e74db8466e..23430cf8b6 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -1,25 +1,17 @@ import Limiter from 'ratelimiter'; import { redisClient } from '../../db/redis.js'; -import { IEndpoint } from './endpoints.js'; -import * as Acct from '@/misc/acct.js'; +import { IEndpointMeta } from './endpoints.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; const logger = new Logger('limiter'); -export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => { - const limitation = endpoint.meta.limit; - - const key = Object.prototype.hasOwnProperty.call(limitation, 'key') - ? limitation.key - : endpoint.name; - - const hasShortTermLimit = - Object.prototype.hasOwnProperty.call(limitation, 'minInterval'); +export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((ok, reject) => { + const hasShortTermLimit = typeof limitation.minInterval === 'number'; const hasLongTermLimit = - Object.prototype.hasOwnProperty.call(limitation, 'duration') && - Object.prototype.hasOwnProperty.call(limitation, 'max'); + typeof limitation.duration === 'number' && + typeof limitation.max === 'number'; if (hasShortTermLimit) { min(); @@ -32,7 +24,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp // Short-term limit function min(): void { const minIntervalLimiter = new Limiter({ - id: `${user.id}:${key}:min`, + id: `${actor}:${limitation.key}:min`, duration: limitation.minInterval, max: 1, db: redisClient, @@ -43,7 +35,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp return reject('ERR'); } - logger.debug(`@${Acct.toString(user)} ${endpoint.name} min remaining: ${info.remaining}`); + logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); @@ -60,7 +52,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp // Long term limit function max(): void { const limiter = new Limiter({ - id: `${user.id}:${key}`, + id: `${actor}:${limitation.key}`, duration: limitation.duration, max: limitation.max, db: redisClient, @@ -71,7 +63,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp return reject('ERR'); } - logger.debug(`@${Acct.toString(user)} ${endpoint.name} max remaining: ${info.remaining}`); + logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index c6e557aefb..3929fff3f7 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -59,6 +59,18 @@ export function genOpenapiSpec(lang = 'ja-JP') { desc += ` / **Permission**: *${kind}*`; } + const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; + const schema = endpoint.params; + + if (endpoint.meta.requireFile) { + schema.properties.file = { + type: 'string', + format: 'binary', + description: 'The file contents.', + }; + schema.required.push('file'); + } + const info = { operationId: endpoint.name, summary: endpoint.name, @@ -78,8 +90,8 @@ export function genOpenapiSpec(lang = 'ja-JP') { requestBody: { required: true, content: { - 'application/json': { - schema: endpoint.params, + [requestType]: { + schema, }, }, }, diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index 7b66657ad8..79b31764fd 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -9,6 +9,8 @@ import { genId } from '@/misc/gen-id.js'; import { verifyLogin, hash } from '../2fa.js'; import { randomBytes } from 'node:crypto'; import { IsNull } from 'typeorm'; +import { limiter } from '../limiter.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -24,6 +26,21 @@ export default async (ctx: Koa.Context) => { ctx.body = { error }; } + try { + // not more than 1 attempt per second and not more than 10 attempts per hour + await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); + } catch (err) { + ctx.status = 429; + ctx.body = { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + return; + } + if (typeof username !== 'string') { ctx.status = 400; return; diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts index 04197574c2..97cbcbecdb 100644 --- a/packages/backend/src/server/api/service/discord.ts +++ b/packages/backend/src/server/api/service/discord.ts @@ -1,16 +1,16 @@ import Koa from 'koa'; import Router from '@koa/router'; -import { getJson } from '@/misc/fetch.js'; import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { getJson } from '@/misc/fetch.js'; import config from '@/config/index.js'; import { publishMainStream } from '@/services/stream.js'; -import { redisClient } from '../../../db/redis.js'; -import { v4 as uuid } from 'uuid'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import { redisClient } from '../../../db/redis.js'; +import signin from '../common/signin.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -54,7 +54,7 @@ router.get('/disconnect/discord', async ctx => { integrations: profile.integrations, }); - ctx.body = `Discordใฎ้ฃๆบใ่งฃ้คใใพใใ :v:`; + ctx.body = 'Discordใฎ้ฃๆบใ่งฃ้คใใพใใ :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -140,7 +140,7 @@ router.get('/dc/cb', async ctx => { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -174,17 +174,17 @@ router.get('/dc/cb', async ctx => { } })); - const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, - }); + })) as Record<string, unknown>; - if (!id || !username || !discriminator) { + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { ctx.throw(400, 'invalid session'); return; } const profile = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'discord'->>'id' = :id`, { id: id }) + .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id }) .andWhere('"userHost" IS NULL') .getOne(); @@ -211,7 +211,7 @@ router.get('/dc/cb', async ctx => { } else { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -245,10 +245,10 @@ router.get('/dc/cb', async ctx => { } })); - const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, - }); - if (!id || !username || !discriminator) { + })) as Record<string, unknown>; + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts index 61bb768a63..04dbd1f7ab 100644 --- a/packages/backend/src/server/api/service/github.ts +++ b/packages/backend/src/server/api/service/github.ts @@ -1,16 +1,16 @@ import Koa from 'koa'; import Router from '@koa/router'; -import { getJson } from '@/misc/fetch.js'; import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { getJson } from '@/misc/fetch.js'; import config from '@/config/index.js'; import { publishMainStream } from '@/services/stream.js'; -import { redisClient } from '../../../db/redis.js'; -import { v4 as uuid } from 'uuid'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import { redisClient } from '../../../db/redis.js'; +import signin from '../common/signin.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -54,7 +54,7 @@ router.get('/disconnect/github', async ctx => { integrations: profile.integrations, }); - ctx.body = `GitHubใฎ้ฃๆบใ่งฃ้คใใพใใ :v:`; + ctx.body = 'GitHubใฎ้ฃๆบใ่งฃ้คใใพใใ :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -138,7 +138,7 @@ router.get('/gh/cb', async ctx => { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -167,16 +167,16 @@ router.get('/gh/cb', async ctx => { } })); - const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, - }); - if (!login || !id) { + })) as Record<string, unknown>; + if (typeof login !== 'string' || typeof id !== 'string') { ctx.throw(400, 'invalid session'); return; } const link = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'github'->>'id' = :id`, { id: id }) + .where('"integrations"->\'github\'->>\'id\' = :id', { id: id }) .andWhere('"userHost" IS NULL') .getOne(); @@ -189,7 +189,7 @@ router.get('/gh/cb', async ctx => { } else { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -219,11 +219,11 @@ router.get('/gh/cb', async ctx => { } })); - const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, - }); + })) as Record<string, unknown>; - if (!login || !id) { + if (typeof login !== 'string' || typeof id !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts index e72b71e2f7..2b4f9f6daa 100644 --- a/packages/backend/src/server/api/service/twitter.ts +++ b/packages/backend/src/server/api/service/twitter.ts @@ -2,14 +2,14 @@ import Koa from 'koa'; import Router from '@koa/router'; import { v4 as uuid } from 'uuid'; import autwh from 'autwh'; -import { redisClient } from '../../../db/redis.js'; +import { IsNull } from 'typeorm'; import { publishMainStream } from '@/services/stream.js'; import config from '@/config/index.js'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import signin from '../common/signin.js'; +import { redisClient } from '../../../db/redis.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -53,7 +53,7 @@ router.get('/disconnect/twitter', async ctx => { integrations: profile.integrations, }); - ctx.body = `Twitterใฎ้ฃๆบใ่งฃ้คใใพใใ :v:`; + ctx.body = 'Twitterใฎ้ฃๆบใ่งฃ้คใใพใใ :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -132,10 +132,16 @@ router.get('/tw/cb', async ctx => { const twCtx = await get; - const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier); + const verifier = ctx.query.oauth_verifier; + if (!verifier || typeof verifier !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const result = await twAuth!.done(JSON.parse(twCtx), verifier); const link = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'twitter'->>'userId' = :id`, { id: result.userId }) + .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId }) .andWhere('"userHost" IS NULL') .getOne(); @@ -148,7 +154,7 @@ router.get('/tw/cb', async ctx => { } else { const verifier = ctx.query.oauth_verifier; - if (verifier == null) { + if (!verifier || typeof verifier !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 043d03ab8d..b67600474b 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Channel from '../channel.js'; -const ev = new Xev.default(); +const ev = new Xev(); export default class extends Channel { public readonly chName = 'queueStats'; diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index 0da1895767..db75a6fa38 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Channel from '../channel.js'; -const ev = new Xev.default(); +const ev = new Xev(); export default class extends Channel { public readonly chName = 'serverStats'; diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index b803478281..2d23145f14 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,27 +1,25 @@ -import * as websocket from 'websocket'; -import { readNotification } from '../common/read-notification.js'; -import call from '../call.js'; -import readNote from '@/services/note/read.js'; -import Channel from './channel.js'; -import channels from './channels/index.js'; import { EventEmitter } from 'events'; +import * as websocket from 'websocket'; +import readNote from '@/services/note/read.js'; import { User } from '@/models/entities/user.js'; import { Channel as ChannelModel } from '@/models/entities/channel.js'; import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; -import { ApiError } from '../error.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { UserProfile } from '@/models/entities/user-profile.js'; import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; import { UserGroup } from '@/models/entities/user-group.js'; -import { StreamEventEmitter, StreamMessages } from './types.js'; import { Packed } from '@/misc/schema.js'; +import { readNotification } from '../common/read-notification.js'; +import channels from './channels/index.js'; +import Channel from './channel.js'; +import { StreamEventEmitter, StreamMessages } from './types.js'; /** * Main stream connection */ export default class Connection { public user?: User; - public userProfile?: UserProfile; + public userProfile?: UserProfile | null; public following: Set<User['id']> = new Set(); public muting: Set<User['id']> = new Set(); public blocking: Set<User['id']> = new Set(); // "่ขซ"blocking @@ -84,7 +82,7 @@ export default class Connection { this.muting.delete(data.body.id); break; - // TODO: block events + // TODO: block events case 'followChannel': this.followingChannels.add(data.body.id); @@ -126,7 +124,6 @@ export default class Connection { const { type, body } = obj; switch (type) { - case 'api': this.onApiRequest(body); break; case 'readNotification': this.onReadNotification(body); break; case 'subNote': this.onSubscribeNote(body); break; case 's': this.onSubscribeNote(body); break; // alias @@ -183,31 +180,6 @@ export default class Connection { } } - /** - * APIใชใฏใจในใ่ฆๆฑๆ - */ - private async onApiRequest(payload: any) { - // ๆฐ้ฎฎใชใใผใฟใๅฉ็จใใใใใซใฆใผใถใผใใใงใใ - const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null; - - const endpoint = payload.endpoint || payload.ep; // alias - - // ๅผใณๅบใ - call(endpoint, user, this.token, payload.data).then(res => { - this.sendMessageToWs(`api:${payload.id}`, { res }); - }).catch((e: ApiError) => { - this.sendMessageToWs(`api:${payload.id}`, { - error: { - message: e.message, - code: e.code, - id: e.id, - kind: e.kind, - ...(e.info ? { info: e.info } : {}), - }, - }); - }); - } - private onReadNotification(payload: any) { if (!payload.id) return; readNotification(this.user!.id, [payload.id]); diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 2a34edac67..f8e42d27fe 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -1,4 +1,4 @@ -import * as http from 'http'; +import * as http from 'node:http'; import * as websocket from 'websocket'; import MainStreamConnection from './stream/index.js'; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 6bc220b362..c34e043145 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -4,14 +4,14 @@ import { dirname } from 'node:path'; import Koa from 'koa'; import send from 'koa-send'; import rename from 'rename'; -import * as tmp from 'tmp'; import { serverLogger } from '../index.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { DriveFiles } from '@/models/index.js'; import { InternalStorage } from '@/services/drive/internal-storage.js'; +import { createTemp } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { detectType } from '@/misc/get-file-info.js'; -import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor.js'; +import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js'; import { StatusError } from '@/misc/fetch.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; @@ -50,12 +50,7 @@ export default async function(ctx: Koa.Context) { if (!file.storedInternal) { if (file.isLink && file.uri) { // ๆ้ๅใใชใขใผใใใกใคใซ - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); try { await downloadUrl(file.uri, path); @@ -64,10 +59,8 @@ export default async function(ctx: Koa.Context) { const convertFile = async () => { if (isThumbnail) { - if (['image/jpeg', 'image/webp'].includes(mime)) { - return await convertToJpeg(path, 498, 280); - } else if (['image/png', 'image/svg+xml'].includes(mime)) { - return await convertToPngOrJpeg(path, 498, 280); + if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) { + return await convertToWebp(path, 498, 280); } else if (mime.startsWith('video/')) { return await GenerateVideoThumbnail(path); } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index b50e38a63b..f31de2b7f4 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -2,8 +2,9 @@ * Core Server */ +import cluster from 'node:cluster'; import * as fs from 'node:fs'; -import * as http from 'http'; +import * as http from 'node:http'; import Koa from 'koa'; import Router from '@koa/router'; import mount from 'koa-mount'; @@ -88,10 +89,10 @@ router.get('/avatar/@:acct', async ctx => { }); router.get('/identicon/:x', async ctx => { - const [temp] = await createTemp(); + const [temp, cleanup] = await createTemp(); await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); - ctx.body = fs.createReadStream(temp); + ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); }); router.get('/verify-email/:code', async ctx => { @@ -142,5 +143,26 @@ export default () => new Promise(resolve => { initializeStreamingServer(server); + server.on('error', e => { + switch ((e as any).code) { + case 'EACCES': + serverLogger.error(`You do not have permission to listen on port ${config.port}.`); + break; + case 'EADDRINUSE': + serverLogger.error(`Port ${config.port} is already in use by another process.`); + break; + default: + serverLogger.error(e); + break; + } + + if (cluster.isWorker) { + process.send!('listenFailed'); + } else { + // disableClustering + process.exit(1); + } + }); + server.listen(config.port, resolve); }); diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index 3cc5b827a6..48887bf12f 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs'; import Koa from 'koa'; import { serverLogger } from '../index.js'; -import { IImage, convertToPng, convertToJpeg } from '@/services/drive/image-processor.js'; +import { IImage, convertToWebp } from '@/services/drive/image-processor.js'; import { createTemp } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { detectType } from '@/misc/get-file-info.js'; @@ -27,11 +27,11 @@ export async function proxyMedia(ctx: Koa.Context) { let image: IImage; if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) { - image = await convertToPng(path, 498, 280); + image = await convertToWebp(path, 498, 280); } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) { - image = await convertToJpeg(path, 200, 200); + image = await convertToWebp(path, 200, 200); } else if (['image/svg+xml'].includes(mime)) { - image = await convertToPng(path, 2048, 2048); + image = await convertToWebp(path, 2048, 2048, 1); } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { throw new StatusError('Rejected type', 403, 'Rejected type'); } else { diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 751e8619bf..94329e11c9 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -54,19 +54,11 @@ //#endregion //#region Script - const salt = localStorage.getItem('salt') - ? `?salt=${localStorage.getItem('salt')}` - : ''; - - const script = document.createElement('script'); - script.setAttribute('src', `/assets/app.${v}.js${salt}`); - script.setAttribute('async', 'true'); - script.setAttribute('defer', 'true'); - script.addEventListener('error', async () => { - await checkUpdate(); - renderError('APP_FETCH_FAILED'); - }); - document.head.appendChild(script); + import(`/assets/${CLIENT_ENTRY}`) + .catch(async e => { + await checkUpdate(); + renderError('APP_FETCH_FAILED', JSON.stringify(e)); + }) //#endregion //#region Theme @@ -146,9 +138,6 @@ // eslint-disable-next-line no-inner-declarations function refresh() { - // Random - localStorage.setItem('salt', Math.random().toString().substr(2, 8)); - // Clear cache (service worker) try { navigator.serviceWorker.controller.postMessage('clear'); diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 34d56cfd0c..2feee72be7 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -4,6 +4,7 @@ import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { PathOrFileDescriptor, readFileSync } from 'node:fs'; import ms from 'ms'; import Koa from 'koa'; import Router from '@koa/router'; @@ -14,7 +15,7 @@ import { createBullBoard } from '@bull-board/api'; import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { KoaAdapter } from '@bull-board/koa'; -import { IsNull } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { fetchMeta } from '@/misc/fetch-meta.js'; import config from '@/config/index.js'; import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js'; @@ -32,6 +33,7 @@ const _dirname = dirname(_filename); const staticAssets = `${_dirname}/../../../assets/`; const clientAssets = `${_dirname}/../../../../client/assets/`; const assets = `${_dirname}/../../../../../built/_client_dist_/`; +const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; // Init app const app = new Koa(); @@ -72,6 +74,9 @@ app.use(views(_dirname + '/views', { extension: 'pug', options: { version: config.version, + getClientEntry: () => process.env.NODE_ENV === 'production' ? + config.clientEntry : + JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], config, }, })); @@ -136,9 +141,10 @@ router.get('/twemoji/(.*)', async ctx => { }); // ServiceWorker -router.get('/sw.js', async ctx => { - await send(ctx as any, `/sw.${config.version}.js`, { - root: assets, +router.get(`/sw.js`, async ctx => { + await send(ctx as any, `/sw.js`, { + root: swAssets, + maxage: ms('10 minutes'), }); }); @@ -241,7 +247,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set('Cache-Control', 'public, max-age=15'); } else { // ใชใขใผใใฆใผใถใผใชใฎใง // ใขใใฌใผใฟใAPI็ต็ฑใงๅ็ งๅฏ่ฝใซใใใใใซ404ใซใฏใใชใ @@ -266,7 +272,10 @@ router.get('/users/:user', async ctx => { // Note router.get('/notes/:note', async (ctx, next) => { - const note = await Notes.findOneBy({ id: ctx.params.note }); + const note = await Notes.findOneBy({ + id: ctx.params.note, + visibility: In(['public', 'home']), + }); if (note) { const _note = await Notes.pack(note); @@ -283,11 +292,7 @@ router.get('/notes/:note', async (ctx, next) => { themeColor: meta.themeColor, }); - if (['public', 'home'].includes(note.visibility)) { - ctx.set('Cache-Control', 'public, max-age=180'); - } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - } + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -324,7 +329,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => { }); if (['public'].includes(page.visibility)) { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); } else { ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); } @@ -355,7 +360,7 @@ router.get('/clips/:clip', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -380,7 +385,7 @@ router.get('/gallery/:post', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -404,7 +409,7 @@ router.get('/channels/:channel', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -463,7 +468,7 @@ router.get('(.*)', async ctx => { icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=300'); + ctx.set('Cache-Control', 'public, max-age=15'); }); // Register router diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts index bcbf9b76a7..ee568b8077 100644 --- a/packages/backend/src/server/web/manifest.ts +++ b/packages/backend/src/server/web/manifest.ts @@ -1,16 +1,18 @@ import Koa from 'koa'; -import manifest from './manifest.json' assert { type: 'json' }; import { fetchMeta } from '@/misc/fetch-meta.js'; +import manifest from './manifest.json' assert { type: 'json' }; export const manifestHandler = async (ctx: Koa.Context) => { - const json = JSON.parse(JSON.stringify(manifest)); + // TODO + //const res = structuredClone(manifest); + const res = JSON.parse(JSON.stringify(manifest)); const instance = await fetchMeta(true); - json.short_name = instance.name || 'Misskey'; - json.name = instance.name || 'Misskey'; - if (instance.themeColor) json.theme_color = instance.themeColor; + res.short_name = instance.name || 'Misskey'; + res.name = instance.name || 'Misskey'; + if (instance.themeColor) res.theme_color = instance.themeColor; ctx.set('Cache-Control', 'max-age=300'); - ctx.body = json; + ctx.body = res; }; diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index 9c4cd4b9bf..d59f00fe16 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -39,28 +39,24 @@ html { width: 28px; height: 28px; transform: translateY(70px); + color: var(--accent); } - -#splashSpinner:before, -#splashSpinner:after { - content: " "; - display: block; - box-sizing: border-box; - width: 28px; - height: 28px; - border-radius: 50%; - border: solid 4px; -} - -#splashSpinner:before { - border-color: currentColor; - opacity: 0.3; -} - -#splashSpinner:after { +#splashSpinner > .spinner { position: absolute; top: 0; - border-color: currentColor transparent transparent transparent; + left: 0; + width: 28px; + height: 28px; + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; +} +#splashSpinner > .spinner.bg { + opacity: 0.275; +} +#splashSpinner > .spinner.fg { animation: splashSpinner 0.5s linear infinite; } diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index 6bd8ead5b5..1e259649f9 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -56,7 +56,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => { function wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) - ? `${config.url}/proxy/preview.jpg?${query({ + ? `${config.url}/proxy/preview.webp?${query({ url, preview: '1', })}` diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 1513208310..5bb156f0f4 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -1,17 +1,21 @@ block vars +block loadClientEntry + - const clientEntry = getClientEntry(); + doctype html -!= '<!--\n' -!= ' _____ _ _ \n' -!= ' | |_|___ ___| |_ ___ _ _ \n' -!= ' | | | | |_ -|_ -| \'_| -_| | |\n' -!= ' |_|_|_|_|___|___|_,_|___|_ |\n' -!= ' |___|\n' -!= ' Thank you for using Misskey!\n' -!= ' If you are reading this message... how about joining the development?\n' -!= ' https://github.com/misskey-dev/misskey' -!= '\n-->\n' +// + - + _____ _ _ + | |_|___ ___| |_ ___ _ _ + | | | | |_ -|_ -| '_| -_| | | + |_|_|_|_|___|___|_,_|___|_ | + |___| + Thank you for using Misskey! + If you are reading this message... how about joining the development? + https://github.com/misskey-dev/misskey + html @@ -30,8 +34,14 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') - link(rel='preload' href='/assets/fontawesome/css/all.css' as='style') link(rel='stylesheet' href='/assets/fontawesome/css/all.css') + link(rel='modulepreload' href=`/assets/${clientEntry.file}`) + + each href in clientEntry.css + link(rel='preload' href=`/assets/${href}` as='style') + + each href in clientEntry.css + link(rel='preload' href=`/assets/${href}` as='style') title block title @@ -50,6 +60,10 @@ html style include ../style.css + script. + var VERSION = "#{version}"; + var CLIENT_ENTRY = "#{clientEntry.file}"; + script include ../boot.js @@ -61,4 +75,14 @@ html div#splash img#splashIcon(src= icon || '/static-assets/splash.png') div#splashSpinner + <svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> + <svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> block content diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 7530b4e0ba..1d094f2edd 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -41,6 +41,7 @@ router.options(allPath, async ctx => { router.get('/.well-known/host-meta', async ctx => { ctx.set('Content-Type', xrd); ctx.body = XRD({ element: 'Link', attributes: { + rel: 'lrdd', type: xrd, template: `${config.url}${webFingerPath}?resource={uri}`, } }); diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 5e96e5037f..a2c61cca22 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import renderBlock from '@/remote/activitypub/renderer/block.js'; +import { renderBlock } from '@/remote/activitypub/renderer/block.js'; import { deliver } from '@/queue/index.js'; import renderReject from '@/remote/activitypub/renderer/reject.js'; +import { Blocking } from '@/models/entities/blocking.js'; import { User } from '@/models/entities/user.js'; import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js'; import { perUserFollowingChart } from '@/services/chart/index.js'; @@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) { removeFromList(blockee, blocker), ]); - await Blockings.insert({ + const blocking = { id: genId(), createdAt: new Date(), + blocker, blockerId: blocker.id, + blockee, blockeeId: blockee.id, - }); + } as Blocking; + + await Blockings.insert(blocking); if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { - const content = renderActivity(renderBlock(blocker, blockee)); + const content = renderActivity(renderBlock(blocking)); deliver(blocker, content, blockee.inbox); } } @@ -95,17 +100,12 @@ async function unFollow(follower: User, followee: User) { return; } - Followings.delete(following.id); - - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); - //#endregion - - perUserFollowingChart.update(follower, followee, false); + await Promise.all([ + Followings.delete(following.id), + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + perUserFollowingChart.update(follower, followee, false), + ]); // Publish unfollow event if (Users.isLocalUser(follower)) { diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index d7b5ddd5ff..cb16651bc0 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -1,5 +1,5 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderBlock from '@/remote/activitypub/renderer/block.js'; +import { renderBlock } from '@/remote/activitypub/renderer/block.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { deliver } from '@/queue/index.js'; import Logger from '../logger.js'; @@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) { return; } + // Since we already have the blocker and blockee, we do not need to fetch + // them in the query above and can just manually insert them here. + blocking.blocker = blocker; + blocking.blockee = blockee; + Blockings.delete(blocking.id); // deliver if remote bloking if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { - const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker)); + const content = renderActivity(renderUndo(renderBlock(blocking), blocker)); deliver(blocker, content, blockee.inbox); } } diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index cf69e2194d..2960bac8f7 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -91,27 +91,20 @@ type ToJsonSchema<S> = { }; export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> { - const object = {}; - for (const [k, v] of Object.entries(schema)) { - nestedProperty.set(object, k, null); - } + const jsonSchema = { + type: 'object', + properties: {} as Record<string, unknown>, + required: [], + }; - function f(obj: Record<string, null | Record<string, unknown>>) { - const jsonSchema = { - type: 'object', - properties: {} as Record<string, unknown>, - required: [], + for (const k in schema) { + jsonSchema.properties[k] = { + type: 'array', + items: { type: 'number' }, }; - for (const [k, v] of Object.entries(obj)) { - jsonSchema.properties[k] = v === null ? { - type: 'array', - items: { type: 'number' }, - } : f(v as Record<string, null | Record<string, unknown>>); - } - return jsonSchema; } - return f(object) as ToJsonSchema<Unflatten<ChartResult<S>>>; + return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>; } /** diff --git a/packages/backend/src/services/chart/entities.ts b/packages/backend/src/services/chart/entities.ts index 13e994cb65..a9eeabd639 100644 --- a/packages/backend/src/services/chart/entities.ts +++ b/packages/backend/src/services/chart/entities.ts @@ -11,6 +11,11 @@ import { entity as PerUserFollowingChart } from './charts/entities/per-user-foll import { entity as PerUserDriveChart } from './charts/entities/per-user-drive.js'; import { entity as ApRequestChart } from './charts/entities/ap-request.js'; +import { entity as TestChart } from './charts/entities/test.js'; +import { entity as TestGroupedChart } from './charts/entities/test-grouped.js'; +import { entity as TestUniqueChart } from './charts/entities/test-unique.js'; +import { entity as TestIntersectionChart } from './charts/entities/test-intersection.js'; + export const entities = [ FederationChart.hour, FederationChart.day, NotesChart.hour, NotesChart.day, @@ -24,4 +29,11 @@ export const entities = [ PerUserFollowingChart.hour, PerUserFollowingChart.day, PerUserDriveChart.hour, PerUserDriveChart.day, ApRequestChart.hour, ApRequestChart.day, + + ...(process.env.NODE_ENV === 'test' ? [ + TestChart.hour, TestChart.day, + TestGroupedChart.hour, TestGroupedChart.day, + TestUniqueChart.hour, TestUniqueChart.day, + TestIntersectionChart.hour, TestIntersectionChart.day, + ] : []), ]; diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index 9a53db1f38..d53a4235b8 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -1,5 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; -import pushSw from './push-notification.js'; +import { pushNotification } from '@/services/push-notification.js'; import { Notifications, Mutings, UserProfiles, Users } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { User } from '@/models/entities/user.js'; @@ -52,8 +52,8 @@ export async function createNotification( //#endregion publishMainStream(notifieeId, 'unreadNotification', packed); + pushNotification(notifieeId, 'notification', packed); - pushSw(notifieeId, 'notification', packed); if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); }, 2000); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 549b11c9fe..cfbcb60ddf 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -7,7 +7,7 @@ import { deleteFile } from './delete-file.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { GenerateVideoThumbnail } from './generate-video-thumbnail.js'; import { driveLogger } from './logger.js'; -import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor.js'; +import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { getFileInfo } from '@/misc/get-file-info.js'; import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js'; @@ -179,6 +179,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } let img: sharp.Sharp | null = null; + let satisfyWebpublic: boolean; try { img = sharp(path); @@ -192,6 +193,13 @@ export async function generateAlts(path: string, type: string, generateWeb: bool thumbnail: null, }; } + + satisfyWebpublic = !!( + type !== 'image/svg+xml' && type !== 'image/webp' && + !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) && + metadata.width && metadata.width <= 2048 && + metadata.height && metadata.height <= 2048 + ); } catch (err) { logger.warn(`sharp failed: ${err}`); return { @@ -203,15 +211,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool // #region webpublic let webpublic: IImage | null = null; - if (generateWeb) { + if (generateWeb && !satisfyWebpublic) { logger.info(`creating web image`); try { - if (['image/jpeg'].includes(type)) { + if (['image/jpeg', 'image/webp'].includes(type)) { webpublic = await convertSharpToJpeg(img, 2048, 2048); - } else if (['image/webp'].includes(type)) { - webpublic = await convertSharpToWebp(img, 2048, 2048); - } else if (['image/png', 'image/svg+xml'].includes(type)) { + } else if (['image/png'].includes(type)) { + webpublic = await convertSharpToPng(img, 2048, 2048); + } else if (['image/svg+xml'].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); } else { logger.debug(`web image not created (not an required image)`); @@ -220,7 +228,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool logger.warn(`web image not created (an error occured)`, err as Error); } } else { - logger.info(`web image not created (from remote)`); + if (satisfyWebpublic) logger.info(`web image not created (original satisfies webpublic)`); + else logger.info(`web image not created (from remote)`); } // #endregion webpublic @@ -228,10 +237,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool let thumbnail: IImage | null = null; try { - if (['image/jpeg', 'image/webp'].includes(type)) { - thumbnail = await convertSharpToJpeg(img, 498, 280); - } else if (['image/png', 'image/svg+xml'].includes(type)) { - thumbnail = await convertSharpToPngOrJpeg(img, 498, 280); + if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) { + thumbnail = await convertSharpToWebp(img, 498, 280); } else { logger.debug(`thumbnail not created (not an required file)`); } diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts index 04a7a83346..ca12ab8d3d 100644 --- a/packages/backend/src/services/drive/generate-video-thumbnail.ts +++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts @@ -1,37 +1,31 @@ import * as fs from 'node:fs'; -import * as tmp from 'tmp'; +import * as path from 'node:path'; +import { createTemp } from '@/misc/create-temp.js'; import { IImage, convertToJpeg } from './image-processor.js'; -import * as FFmpeg from 'fluent-ffmpeg'; +import FFmpeg from 'fluent-ffmpeg'; -export async function GenerateVideoThumbnail(path: string): Promise<IImage> { - const [outDir, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); +export async function GenerateVideoThumbnail(source: string): Promise<IImage> { + const [file, cleanup] = await createTemp(); + const parsed = path.parse(file); + + try { + await new Promise((res, rej) => { + FFmpeg({ + source, + }) + .on('end', res) + .on('error', rej) + .screenshot({ + folder: parsed.dir, + filename: parsed.base, + count: 1, + timestamps: ['5%'], + }); }); - }); - await new Promise((res, rej) => { - FFmpeg({ - source: path, - }) - .on('end', res) - .on('error', rej) - .screenshot({ - folder: outDir, - filename: 'output.png', - count: 1, - timestamps: ['5%'], - }); - }); - - const outPath = `${outDir}/output.png`; - - const thumbnail = await convertToJpeg(outPath, 498, 280); - - // cleanup - await fs.promises.unlink(outPath); - cleanup(); - - return thumbnail; + // JPEGใซๅคๆ (WebpใงใใใใใMastodonใฏWebpใใตใใผใใใ่กจ็คบใงใใชใใชใ) + return await convertToJpeg(498, 280); + } finally { + cleanup(); + } } diff --git a/packages/backend/src/services/drive/image-processor.ts b/packages/backend/src/services/drive/image-processor.ts index 146dcfb6ca..2c564ea595 100644 --- a/packages/backend/src/services/drive/image-processor.ts +++ b/packages/backend/src/services/drive/image-processor.ts @@ -38,11 +38,11 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ -export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToWebp(await sharp(path), width, height); +export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise<IImage> { + return convertSharpToWebp(await sharp(path), width, height, quality); } -export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { +export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise<IImage> { const data = await sharp .resize(width, height, { fit: 'inside', @@ -50,7 +50,7 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig }) .rotate() .webp({ - quality: 85, + quality, }) .toBuffer(); @@ -85,23 +85,3 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh type: 'image/png', }; } - -/** - * Convert to PNG or JPEG - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToPngOrJpeg(await sharp(path), width, height); -} - -export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const stats = await sharp.stats(); - const metadata = await sharp.metadata(); - - // ไธ้ๆใง300x300pxใฎ็ฏๅฒใ่ถ ใใฆใใใฐJPEG - if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) { - return await convertSharpToJpeg(sharp, width, height); - } else { - return await convertSharpToPng(sharp, width, height); - } -} diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 79b1b8c2e1..001fc49ee4 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -45,29 +45,20 @@ export async function uploadFromUrl({ // Create temp file const [path, cleanup] = await createTemp(); - // write content at URL to temp file - await downloadUrl(url, path); - - let driveFile: DriveFile; - let error; - try { - driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); + // write content at URL to temp file + await downloadUrl(url, path); + + const driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); logger.succ(`Got: ${driveFile.id}`); + return driveFile!; } catch (e) { - error = e; logger.error(`Failed to create drive file: ${e}`, { url: url, e: e, }); - } - - // clean-up - cleanup(); - - if (error) { - throw error; - } else { - return driveFile!; + throw e; + } finally { + cleanup(); } } diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index d5294c5fe8..029c388dc2 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -1,5 +1,6 @@ import { DOMWindow, JSDOM } from 'jsdom'; import fetch from 'node-fetch'; +import tinycolor from 'tinycolor2'; import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch.js'; import { Instance } from '@/models/entities/instance.js'; import { Instances } from '@/models/index.js'; @@ -208,16 +209,11 @@ async function fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | nul } async function getThemeColor(doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> { - if (doc) { - const themeColor = doc.querySelector('meta[name="theme-color"]')?.getAttribute('content'); + const themeColor = doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') || manifest?.theme_color; - if (themeColor) { - return themeColor; - } - } - - if (manifest) { - return manifest.theme_color; + if (themeColor) { + const color = new tinycolor(themeColor); + if (color.isValid()) return color.toHexString(); } return null; diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 7491c44f83..72c24676bb 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -67,8 +67,10 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ if (alreadyFollowed) return; //#region Increment counts - Users.increment({ id: follower.id }, 'followingCount', 1); - Users.increment({ id: followee.id }, 'followersCount', 1); + await Promise.all([ + Users.increment({ id: follower.id }, 'followingCount', 1), + Users.increment({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index 241f9606e5..91b5a3d61d 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -58,12 +58,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur } export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) { - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); + //#region Decrement following / followers counts + await Promise.all([ + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index e5cd5a30d2..e6b3204922 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -5,7 +5,7 @@ import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/i import { genId } from '@/misc/gen-id.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; -import pushNotification from '../push-notification.js'; +import { pushNotification } from '@/services/push-notification.js'; import { Not } from 'typeorm'; import { Note } from '@/models/entities/note.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index f14bc2059b..e2bf9d5b59 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -187,6 +187,8 @@ export default async (user: { id: User['id']; username: User['username']; host: if (data.text) { data.text = data.text.trim(); + } else { + data.text = null; } let tags = data.apHashtags; @@ -310,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host: endedPollNotificationQueue.add({ noteId: note.id, }, { - delay + delay, + removeOnComplete: true, }); } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index ffd609dd84..4963200161 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -1,3 +1,4 @@ +import { Brackets, In } from 'typeorm'; import { publishNoteStream } from '@/services/stream.js'; import renderDelete from '@/remote/activitypub/renderer/delete.js'; import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; @@ -5,15 +6,14 @@ import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderTombstone from '@/remote/activitypub/renderer/tombstone.js'; import config from '@/config/index.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { Notes, Users, Instances } from '@/models/index.js'; import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js'; import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js'; import { countSameRenotes } from '@/misc/count-same-renotes.js'; +import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { deliverToRelays } from '../relay.js'; -import { Brackets, In } from 'typeorm'; /** * ๆ็จฟใๅ้คใใพใใ @@ -40,7 +40,7 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us //#region ใญใผใซใซใฎๆ็จฟใชใๅ้คใขใฏใใฃใใใฃใ้ ้ if (Users.isLocalUser(user) && !note.localOnly) { - let renote: Note | null; + let renote: Note | null = null; // if deletd note is renote if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { @@ -113,7 +113,7 @@ async function getMentionedRemoteUsers(note: Note) { const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); if (uris.length > 0) { where.push( - { uri: In(uris) } + { uri: In(uris) }, ); } diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 5a0948bca9..83d302826a 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -27,6 +27,11 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, } } + // check visibility + if (!await Notes.isVisibleForMe(note, user.id)) { + throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.'); + } + // TODO: cache reaction = await toDbReaction(reaction, user.host); diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 41122c92e8..5c3bafbb34 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -5,8 +5,15 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { Packed } from '@/misc/schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -type notificationType = 'notification' | 'unreadMessagingMessage'; -type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>; +// Defined also packages/sw/types.ts#L14-L21 +type pushNotificationsTypes = { + 'notification': Packed<'Notification'>; + 'unreadMessagingMessage': Packed<'MessagingMessage'>; + 'readNotifications': { notificationIds: string[] }; + 'readAllNotifications': undefined; + 'readAllMessagingMessages': undefined; + 'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string }; +}; // ใใใทใฅใกใใปใผใธใตใผใใผใซใฏๆๅญๆฐๅถ้ใใใใใใๅ ๅฎนใๅๆธใใพใ function truncateNotification(notification: Packed<'Notification'>): any { @@ -17,12 +24,11 @@ function truncateNotification(notification: Packed<'Notification'>): any { ...notification.note, // textใgetNoteSummaryใใใใฎใซ็ฝฎใๆใใ text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note), - ...{ - cw: undefined, - reply: undefined, - renote: undefined, - user: undefined as any, // ้็ฅใๅใๅใฃใใฆใผใถใผใงใใๅ ดๅใๅคใใฎใงใใใๆจใฆใ - } + + cw: undefined, + reply: undefined, + renote: undefined, + user: undefined as any, // ้็ฅใๅใๅใฃใใฆใผใถใผใงใใๅ ดๅใๅคใใฎใงใใใๆจใฆใ } }; } @@ -30,7 +36,7 @@ function truncateNotification(notification: Packed<'Notification'>): any { return notification; } -export default async function(userId: string, type: notificationType, body: notificationBody) { +export async function pushNotification<T extends keyof pushNotificationsTypes>(userId: string, type: T, body: pushNotificationsTypes[T]) { const meta = await fetchMeta(); if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 1ab45588da..6bc4304436 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -1,4 +1,4 @@ -import { createSystemUser } from './create-system-user.js'; +import { IsNull } from 'typeorm'; import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js'; import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; @@ -8,7 +8,7 @@ import { Users, Relays } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { Cache } from '@/misc/cache.js'; import { Relay } from '@/models/entities/relay.js'; -import { IsNull } from 'typeorm'; +import { createSystemUser } from './create-system-user.js'; const ACTOR_USERNAME = 'relay.actor' as const; @@ -88,6 +88,8 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act })); if (relays.length === 0) return; + // TODO + //const copy = structuredClone(activity); const copy = JSON.parse(JSON.stringify(activity)); if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; diff --git a/packages/backend/test/.eslintrc b/packages/backend/test/.eslintrc deleted file mode 100644 index cea1b11388..0000000000 --- a/packages/backend/test/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true, - "commonjs": true - } -} diff --git a/packages/backend/test/.eslintrc.cjs b/packages/backend/test/.eslintrc.cjs new file mode 100644 index 0000000000..d83dc37d2f --- /dev/null +++ b/packages/backend/test/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + extends: ['../.eslintrc.cjs'], + env: { + node: true, + mocha: true, + }, +}; diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts index 70f35cafd8..f4ae27e5ec 100644 --- a/packages/backend/test/activitypub.ts +++ b/packages/backend/test/activitypub.ts @@ -1,12 +1,14 @@ process.env.NODE_ENV = 'test'; -import rndstr from 'rndstr'; import * as assert from 'assert'; +import rndstr from 'rndstr'; +import { initDb } from '../src/db/postgre.js'; import { initTestDb } from './utils.js'; describe('ActivityPub', () => { before(async () => { - await initTestDb(); + //await initTestDb(); + await initDb(); }); describe('Parse minimum object', () => { @@ -57,8 +59,8 @@ describe('ActivityPub', () => { const note = await createNote(post.id, resolver, true); assert.deepStrictEqual(note?.uri, post.id); - assert.deepStrictEqual(note?.visibility, 'public'); - assert.deepStrictEqual(note?.text, post.content); + assert.deepStrictEqual(note.visibility, 'public'); + assert.deepStrictEqual(note.text, post.content); }); }); diff --git a/packages/backend/test/ap-request.ts b/packages/backend/test/ap-request.ts index 48f4fceb51..da95c421f3 100644 --- a/packages/backend/test/ap-request.ts +++ b/packages/backend/test/ap-request.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; +import httpSignature from 'http-signature'; import { genRsaKeyPair } from '../src/misc/gen-key-pair.js'; import { createSignedPost, createSignedGet } from '../src/remote/activitypub/ap-request.js'; -import httpSignature from 'http-signature'; export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { return { @@ -13,7 +13,7 @@ export const buildParsedSignature = (signingString: string, signature: string, a signature: signature, }, signingString: signingString, - algorithm: algorithm?.toUpperCase(), + algorithm: algorithm.toUpperCase(), keyId: 'KeyID', // dummy, not used for verify }; }; @@ -26,7 +26,7 @@ describe('ap-request', () => { const activity = { a: 1 }; const body = JSON.stringify(activity); const headers = { - 'User-Agent': 'UA' + 'User-Agent': 'UA', }; const req = createSignedPost({ key, url, body, additionalHeaders: headers }); @@ -42,7 +42,7 @@ describe('ap-request', () => { const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey }; const url = 'https://example.com/outbox'; const headers = { - 'User-Agent': 'UA' + 'User-Agent': 'UA', }; const req = createSignedGet({ key, url, additionalHeaders: headers }); diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts index d946191be8..b155549f98 100644 --- a/packages/backend/test/api-visibility.ts +++ b/packages/backend/test/api-visibility.ts @@ -61,40 +61,40 @@ describe('API visibility', () => { const show = async (noteId: any, by: any) => { return await request('/notes/show', { - noteId + noteId, }, by); }; before(async () => { //#region prepare // signup - alice = await signup({ username: 'alice' }); + alice = await signup({ username: 'alice' }); follower = await signup({ username: 'follower' }); - other = await signup({ username: 'other' }); - target = await signup({ username: 'target' }); - target2 = await signup({ username: 'target2' }); + other = await signup({ username: 'other' }); + target = await signup({ username: 'target' }); + target2 = await signup({ username: 'target2' }); // follow alice <= follower await request('/following/create', { userId: alice.id }, follower); // normal posts - pub = await post(alice, { text: 'x', visibility: 'public' }); + pub = await post(alice, { text: 'x', visibility: 'public' }); home = await post(alice, { text: 'x', visibility: 'home' }); - fol = await post(alice, { text: 'x', visibility: 'followers' }); - spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] }); + fol = await post(alice, { text: 'x', visibility: 'followers' }); + spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] }); // replies tgt = await post(target, { text: 'y', visibility: 'public' }); - pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' }); + pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' }); homeR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'home' }); - folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' }); - speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' }); + folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' }); + speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' }); // mentions - pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' }); + pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' }); homeM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'home' }); - folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' }); - speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' }); + folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' }); + speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' }); //#endregion }); diff --git a/packages/backend/test/block.ts b/packages/backend/test/block.ts index 103eec991d..b3343813cd 100644 --- a/packages/backend/test/block.ts +++ b/packages/backend/test/block.ts @@ -25,7 +25,7 @@ describe('Block', () => { it('Blockไฝๆ', async(async () => { const res = await request('/blocking/create', { - userId: bob.id + userId: bob.id, }, alice); assert.strictEqual(res.status, 200); diff --git a/packages/backend/test/chart.ts b/packages/backend/test/chart.ts index c8cea874f0..ac0844679f 100644 --- a/packages/backend/test/chart.ts +++ b/packages/backend/test/chart.ts @@ -2,30 +2,21 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as lolex from '@sinonjs/fake-timers'; -import { async, initTestDb } from './utils.js'; import TestChart from '../src/services/chart/charts/test.js'; import TestGroupedChart from '../src/services/chart/charts/test-grouped.js'; import TestUniqueChart from '../src/services/chart/charts/test-unique.js'; import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js'; -import * as _TestChart from '../src/services/chart/charts/entities/test.js'; -import * as _TestGroupedChart from '../src/services/chart/charts/entities/test-grouped.js'; -import * as _TestUniqueChart from '../src/services/chart/charts/entities/test-unique.js'; -import * as _TestIntersectionChart from '../src/services/chart/charts/entities/test-intersection.js'; +import { initDb } from '../src/db/postgre.js'; describe('Chart', () => { let testChart: TestChart; let testGroupedChart: TestGroupedChart; let testUniqueChart: TestUniqueChart; let testIntersectionChart: TestIntersectionChart; - let clock: lolex.Clock; + let clock: lolex.InstalledClock; - beforeEach(async(async () => { - await initTestDb(false, [ - _TestChart.entity.hour, _TestChart.entity.day, - _TestGroupedChart.entity.hour, _TestGroupedChart.entity.day, - _TestUniqueChart.entity.hour, _TestUniqueChart.entity.day, - _TestIntersectionChart.entity.hour, _TestIntersectionChart.entity.day, - ]); + beforeEach(async () => { + await initDb(true); testChart = new TestChart(); testGroupedChart = new TestGroupedChart(); @@ -33,15 +24,16 @@ describe('Chart', () => { testIntersectionChart = new TestIntersectionChart(); clock = lolex.install({ - now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)) + now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)), + shouldClearNativeTimers: true, }); - })); + }); - afterEach(async(async () => { + afterEach(() => { clock.uninstall(); - })); + }); - it('Can updates', async(async () => { + it('Can updates', async () => { await testChart.increment(); await testChart.save(); @@ -52,7 +44,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -60,12 +52,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can updates (dec)', async(async () => { + it('Can updates (dec)', async () => { await testChart.decrement(); await testChart.save(); @@ -76,7 +68,7 @@ describe('Chart', () => { foo: { dec: [1, 0, 0], inc: [0, 0, 0], - total: [-1, 0, 0] + total: [-1, 0, 0], }, }); @@ -84,12 +76,12 @@ describe('Chart', () => { foo: { dec: [1, 0, 0], inc: [0, 0, 0], - total: [-1, 0, 0] + total: [-1, 0, 0], }, }); - })); + }); - it('Empty chart', async(async () => { + it('Empty chart', async () => { const chartHours = await testChart.getChart('hour', 3, null); const chartDays = await testChart.getChart('day', 3, null); @@ -97,7 +89,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); @@ -105,12 +97,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); - })); + }); - it('Can updates at multiple times at same time', async(async () => { + it('Can updates at multiple times at same time', async () => { await testChart.increment(); await testChart.increment(); await testChart.increment(); @@ -123,7 +115,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [3, 0, 0], - total: [3, 0, 0] + total: [3, 0, 0], }, }); @@ -131,12 +123,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [3, 0, 0], - total: [3, 0, 0] + total: [3, 0, 0], }, }); - })); + }); - it('่คๆฐๅsaveใใใฆใใใผใฟใฎๆดๆฐใฏไธๅบฆใ ใ', async(async () => { + it('่คๆฐๅsaveใใใฆใใใผใฟใฎๆดๆฐใฏไธๅบฆใ ใ', async () => { await testChart.increment(); await testChart.save(); await testChart.save(); @@ -149,7 +141,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -157,12 +149,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can updates at different times', async(async () => { + it('Can updates at different times', async () => { await testChart.increment(); await testChart.save(); @@ -178,7 +170,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 1, 0], - total: [2, 1, 0] + total: [2, 1, 0], }, }); @@ -186,14 +178,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); // ไปๆงไธใฏใใใชใฃใฆใปใใใใฉใๅฎ่ฃ ใฏ้ฃใใใใชใฎใงskip /* - it('Can updates at different times without save', async(async () => { + it('Can updates at different times without save', async () => { await testChart.increment(); clock.tick('01:00:00'); @@ -219,10 +211,10 @@ describe('Chart', () => { total: [2, 0, 0] }, }); - })); + }); */ - it('Can padding', async(async () => { + it('Can padding', async () => { await testChart.increment(); await testChart.save(); @@ -238,7 +230,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 1], - total: [2, 1, 1] + total: [2, 1, 1], }, }); @@ -246,13 +238,13 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); // ่ฆๆฑใใใ็ฏๅฒใซใญใฐใใฒใจใคใใชใๅ ดๅใงใใใใฃใณใฐใงใใ - it('Can padding from past range', async(async () => { + it('Can padding from past range', async () => { await testChart.increment(); await testChart.save(); @@ -265,7 +257,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 1, 1] + total: [1, 1, 1], }, }); @@ -273,14 +265,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); // ่ฆๆฑใใใ็ฏๅฒใฎๆใๅคใ็ฎๆใซไฝ็ฝฎใใใญใฐใๅญๅจใใชใๅ ดๅใงใใใใฃใณใฐใงใใ // Issue #3190 - it('Can padding from past range 2', async(async () => { + it('Can padding from past range 2', async () => { await testChart.increment(); await testChart.save(); @@ -296,7 +288,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [2, 1, 1] + total: [2, 1, 1], }, }); @@ -304,12 +296,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset', async(async () => { + it('Can specify offset', async () => { await testChart.increment(); await testChart.save(); @@ -325,7 +317,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -333,12 +325,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset (floor time)', async(async () => { + it('Can specify offset (floor time)', async () => { clock.tick('00:30:00'); await testChart.increment(); @@ -356,7 +348,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -364,13 +356,13 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); describe('Grouped', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testGroupedChart.increment('alice'); await testGroupedChart.save(); @@ -383,7 +375,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -391,7 +383,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -399,7 +391,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); @@ -407,14 +399,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); - })); + }); }); describe('Unique increment', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('bob'); @@ -430,10 +422,10 @@ describe('Chart', () => { assert.deepStrictEqual(chartDays, { foo: [2, 0, 0], }); - })); + }); describe('Intersection', () => { - it('ๆกไปถใๆบใใใใฆใใชใๅ ดๅใฏใซใฆใณใใใใชใ', async(async () => { + it('ๆกไปถใๆบใใใใฆใใชใๅ ดๅใฏใซใฆใณใใใใชใ', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -453,9 +445,9 @@ describe('Chart', () => { b: [1, 0, 0], aAndB: [0, 0, 0], }); - })); + }); - it('ๆกไปถใๆบใใใใฆใใๅ ดๅใซใซใฆใณใใใใ', async(async () => { + it('ๆกไปถใๆบใใใใฆใใๅ ดๅใซใซใฆใณใใใใ', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -476,12 +468,12 @@ describe('Chart', () => { b: [2, 0, 0], aAndB: [1, 0, 0], }); - })); + }); }); }); describe('Resync', () => { - it('Can resync', async(async () => { + it('Can resync', async () => { testChart.total = 1; await testChart.resync(); @@ -493,7 +485,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -501,12 +493,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can resync (2)', async(async () => { + it('Can resync (2)', async () => { await testChart.increment(); await testChart.save(); @@ -523,7 +515,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 1, 0], - total: [100, 1, 0] + total: [100, 1, 0], }, }); @@ -531,9 +523,9 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [100, 0, 0] + total: [100, 0, 0], }, }); - })); + }); }); }); diff --git a/packages/backend/test/extract-mentions.ts b/packages/backend/test/extract-mentions.ts index 9bfbc4192a..85afb098d8 100644 --- a/packages/backend/test/extract-mentions.ts +++ b/packages/backend/test/extract-mentions.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; -import { extractMentions } from '../src/misc/extract-mentions.js'; import { parse } from 'mfm-js'; +import { extractMentions } from '../src/misc/extract-mentions.js'; describe('Extract mentions', () => { it('simple', () => { @@ -10,15 +10,15 @@ describe('Extract mentions', () => { assert.deepStrictEqual(mentions, [{ username: 'foo', acct: '@foo', - host: null + host: null, }, { username: 'bar', acct: '@bar', - host: null + host: null, }, { username: 'baz', acct: '@baz', - host: null + host: null, }]); }); @@ -28,15 +28,15 @@ describe('Extract mentions', () => { assert.deepStrictEqual(mentions, [{ username: 'foo', acct: '@foo', - host: null + host: null, }, { username: 'bar', acct: '@bar', - host: null + host: null, }, { username: 'baz', acct: '@baz', - host: null + host: null, }]); }); }); diff --git a/packages/backend/test/fetch-resource.ts b/packages/backend/test/fetch-resource.ts index 4cb4b42562..ddb0e94b86 100644 --- a/packages/backend/test/fetch-resource.ts +++ b/packages/backend/test/fetch-resource.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js'; import * as openapi from '@redocly/openapi-core'; +import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js'; // Request Accept const ONLY_AP = 'application/activity+json'; @@ -26,7 +26,7 @@ describe('Fetch resource', () => { p = await startServer(); alice = await signup({ username: 'alice' }); alicesPost = await post(alice, { - text: 'test' + text: 'test', }); }); @@ -70,7 +70,7 @@ describe('Fetch resource', () => { const config = await openapi.loadConfig(); const result = await openapi.bundle({ config, - ref: `http://localhost:${port}/api.json` + ref: `http://localhost:${port}/api.json`, }); for (const problem of result.problems) { diff --git a/packages/backend/test/get-file-info.ts b/packages/backend/test/get-file-info.ts index 20061b8708..7ce98db50f 100644 --- a/packages/backend/test/get-file-info.ts +++ b/packages/backend/test/get-file-info.ts @@ -1,10 +1,15 @@ import * as assert from 'assert'; -import { async } from './utils.js'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; import { getFileInfo } from '../src/misc/get-file-info.js'; +import { async } from './utils.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); describe('Get file info', () => { it('Empty file', async (async () => { - const path = `${__dirname}/resources/emptyfile`; + const path = `${_dirname}/resources/emptyfile`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -13,7 +18,7 @@ describe('Get file info', () => { md5: 'd41d8cd98f00b204e9800998ecf8427e', type: { mime: 'application/octet-stream', - ext: null + ext: null, }, width: undefined, height: undefined, @@ -22,7 +27,7 @@ describe('Get file info', () => { })); it('Generic JPEG', async (async () => { - const path = `${__dirname}/resources/Lenna.jpg`; + const path = `${_dirname}/resources/Lenna.jpg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -31,7 +36,7 @@ describe('Get file info', () => { md5: '091b3f259662aa31e2ffef4519951168', type: { mime: 'image/jpeg', - ext: 'jpg' + ext: 'jpg', }, width: 512, height: 512, @@ -40,7 +45,7 @@ describe('Get file info', () => { })); it('Generic APNG', async (async () => { - const path = `${__dirname}/resources/anime.png`; + const path = `${_dirname}/resources/anime.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -49,7 +54,7 @@ describe('Get file info', () => { md5: '08189c607bea3b952704676bb3c979e0', type: { mime: 'image/apng', - ext: 'apng' + ext: 'apng', }, width: 256, height: 256, @@ -58,7 +63,7 @@ describe('Get file info', () => { })); it('Generic AGIF', async (async () => { - const path = `${__dirname}/resources/anime.gif`; + const path = `${_dirname}/resources/anime.gif`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -67,7 +72,7 @@ describe('Get file info', () => { md5: '32c47a11555675d9267aee1a86571e7e', type: { mime: 'image/gif', - ext: 'gif' + ext: 'gif', }, width: 256, height: 256, @@ -76,7 +81,7 @@ describe('Get file info', () => { })); it('PNG with alpha', async (async () => { - const path = `${__dirname}/resources/with-alpha.png`; + const path = `${_dirname}/resources/with-alpha.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -85,7 +90,7 @@ describe('Get file info', () => { md5: 'f73535c3e1e27508885b69b10cf6e991', type: { mime: 'image/png', - ext: 'png' + ext: 'png', }, width: 256, height: 256, @@ -94,7 +99,7 @@ describe('Get file info', () => { })); it('Generic SVG', async (async () => { - const path = `${__dirname}/resources/image.svg`; + const path = `${_dirname}/resources/image.svg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -103,7 +108,7 @@ describe('Get file info', () => { md5: 'b6f52b4b021e7b92cdd04509c7267965', type: { mime: 'image/svg+xml', - ext: 'svg' + ext: 'svg', }, width: 256, height: 256, @@ -113,7 +118,7 @@ describe('Get file info', () => { it('SVG with XML definition', async (async () => { // https://github.com/misskey-dev/misskey/issues/4413 - const path = `${__dirname}/resources/with-xml-def.svg`; + const path = `${_dirname}/resources/with-xml-def.svg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -122,7 +127,7 @@ describe('Get file info', () => { md5: '4b7a346cde9ccbeb267e812567e33397', type: { mime: 'image/svg+xml', - ext: 'svg' + ext: 'svg', }, width: 256, height: 256, @@ -131,7 +136,7 @@ describe('Get file info', () => { })); it('Dimension limit', async (async () => { - const path = `${__dirname}/resources/25000x25000.png`; + const path = `${_dirname}/resources/25000x25000.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -140,7 +145,7 @@ describe('Get file info', () => { md5: '268c5dde99e17cf8fe09f1ab3f97df56', type: { mime: 'application/octet-stream', // do not treat as image - ext: null + ext: null, }, width: 25000, height: 25000, @@ -149,7 +154,7 @@ describe('Get file info', () => { })); it('Rotate JPEG', async (async () => { - const path = `${__dirname}/resources/rotate.jpg`; + const path = `${_dirname}/resources/rotate.jpg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -158,7 +163,7 @@ describe('Get file info', () => { md5: '68d5b2d8d1d1acbbce99203e3ec3857e', type: { mime: 'image/jpeg', - ext: 'jpg' + ext: 'jpg', }, width: 512, height: 256, diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js index 016f32f1a8..6b21587e32 100644 --- a/packages/backend/test/loader.js +++ b/packages/backend/test/loader.js @@ -1,37 +1,34 @@ -import path from 'path' -import typescript from 'typescript' -import { createMatchPath } from 'tsconfig-paths' -import { resolve as BaseResolve, getFormat, transformSource } from 'ts-node/esm' +/** + * ts-node/esmใญใผใใผใซๆใใๅใซpath mappingใ่งฃๆฑบใใ + * ๅ่ + * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115 + * - https://nodejs.org/api/esm.html#loaders + * โป https://github.com/TypeStrong/ts-node/pull/1585 ใๅใ่พผใพใใใใใฎใซในใฟใ ใญใผใใผใฏๅฟ ่ฆใชใใชใ + */ -const { readConfigFile, parseJsonConfigFileContent, sys } = typescript +import { resolve as resolveTs, load } from 'ts-node/esm'; +import { loadConfig, createMatchPath } from 'tsconfig-paths'; +import { pathToFileURL } from 'url'; -const __dirname = path.dirname(new URL(import.meta.url).pathname) +const tsconfig = loadConfig(); +const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths); -const configFile = readConfigFile('./test/tsconfig.json', sys.readFile) -if (typeof configFile.error !== 'undefined') { - throw new Error(`Failed to load tsconfig: ${configFile.error}`) +export function resolve(specifier, ctx, defaultResolve) { + let resolvedSpecifier; + if (specifier.endsWith('.js')) { + // maybe transpiled + const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length); + const matchedSpecifier = matchPath(specifierWithoutExtension); + if (matchedSpecifier) { + resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href; + } + } else { + const matchedSpecifier = matchPath(specifier); + if (matchedSpecifier) { + resolvedSpecifier = pathToFileURL(matchedSpecifier).href; + } + } + return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve); } -const { options } = parseJsonConfigFileContent( - configFile.config, - { - fileExists: sys.fileExists, - readFile: sys.readFile, - readDirectory: sys.readDirectory, - useCaseSensitiveFileNames: true, - }, - __dirname -) - -export { getFormat, transformSource } // ใใใคใใฏใใฎใพใพไฝฟใฃใฆใปใใใฎใง re-export ใใ - -const matchPath = createMatchPath(options.baseUrl, options.paths) - -export async function resolve(specifier, context, defaultResolve) { - const matchedSpecifier = matchPath(specifier.replace('.js', '.ts')) - return BaseResolve( // ts-node/esm ใฎ resolve ใซ tsconfig-paths ใง่งฃๆฑบใใใในใๆธกใ - matchedSpecifier ? `${matchedSpecifier}.ts` : specifier, - context, - defaultResolve - ) -} +export { load }; diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 5a46daf49f..ba89ac329a 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -11,7 +11,7 @@ export class MockResolver extends Resolver { public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') { this._rs.set(uri, { type, - content: typeof content === 'string' ? content : JSON.stringify(content) + content: typeof content === 'string' ? content : JSON.stringify(content), }); } @@ -22,9 +22,9 @@ export class MockResolver extends Resolver { if (!r) { throw { - name: `StatusError`, + name: 'StatusError', statusCode: 404, - message: `Not registed for mock` + message: 'Not registed for mock', }; } diff --git a/packages/backend/test/mute.ts b/packages/backend/test/mute.ts index 288e8a8055..2be70f2b65 100644 --- a/packages/backend/test/mute.ts +++ b/packages/backend/test/mute.ts @@ -25,7 +25,7 @@ describe('Mute', () => { it('ใใฅใผใไฝๆ', async(async () => { const res = await request('/mute/create', { - userId: carol.id + userId: carol.id, }, alice); assert.strictEqual(res.status, 204); @@ -117,7 +117,7 @@ describe('Mute', () => { const aliceNote = await post(alice); const carolNote = await post(carol); const bobNote = await post(bob, { - renoteId: carolNote.id + renoteId: carolNote.id, }); const res = await request('/notes/local-timeline', {}, alice); diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts index 942b2709df..1183e9e4f1 100644 --- a/packages/backend/test/note.ts +++ b/packages/backend/test/note.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js'; import { Note } from '../src/models/entities/note.js'; +import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js'; describe('Note', () => { let p: childProcess.ChildProcess; @@ -26,7 +26,7 @@ describe('Note', () => { it('ๆ็จฟใงใใ', async(async () => { const post = { - text: 'test' + text: 'test', }; const res = await request('/notes/create', post, alice); @@ -40,7 +40,7 @@ describe('Note', () => { const file = await uploadFile(alice); const res = await request('/notes/create', { - fileIds: [file.id] + fileIds: [file.id], }, alice); assert.strictEqual(res.status, 200); @@ -53,7 +53,7 @@ describe('Note', () => { const res = await request('/notes/create', { text: 'test', - fileIds: [file.id] + fileIds: [file.id], }, alice); assert.strictEqual(res.status, 200); @@ -64,7 +64,7 @@ describe('Note', () => { it('ๅญๅจใใชใใใกใคใซใฏ็ก่ฆ', async(async () => { const res = await request('/notes/create', { text: 'test', - fileIds: ['000000000000000000000000'] + fileIds: ['000000000000000000000000'], }, alice); assert.strictEqual(res.status, 200); @@ -74,19 +74,19 @@ describe('Note', () => { it('ไธๆญฃใชใใกใคใซIDใงๆใใใ', async(async () => { const res = await request('/notes/create', { - fileIds: ['kyoppie'] + fileIds: ['kyoppie'], }, alice); assert.strictEqual(res.status, 400); })); it('่ฟไฟกใงใใ', async(async () => { const bobPost = await post(bob, { - text: 'foo' + text: 'foo', }); const alicePost = { text: 'bar', - replyId: bobPost.id + replyId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -100,11 +100,11 @@ describe('Note', () => { it('renoteใงใใ', async(async () => { const bobPost = await post(bob, { - text: 'test' + text: 'test', }); const alicePost = { - renoteId: bobPost.id + renoteId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -117,12 +117,12 @@ describe('Note', () => { it('ๅผ็จrenoteใงใใ', async(async () => { const bobPost = await post(bob, { - text: 'test' + text: 'test', }); const alicePost = { text: 'test', - renoteId: bobPost.id + renoteId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -136,7 +136,7 @@ describe('Note', () => { it('ๆๅญๆฐใใใใใงๆใใใชใ', async(async () => { const post = { - text: '!'.repeat(500) + text: '!'.repeat(500), }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 200); @@ -144,7 +144,7 @@ describe('Note', () => { it('ๆๅญๆฐใชใผใใผใงๆใใใ', async(async () => { const post = { - text: '!'.repeat(501) + text: '!'.repeat(501), }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -153,7 +153,7 @@ describe('Note', () => { it('ๅญๅจใใชใใชใใฉใคๅ ใงๆใใใ', async(async () => { const post = { text: 'test', - replyId: '000000000000000000000000' + replyId: '000000000000000000000000', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -161,7 +161,7 @@ describe('Note', () => { it('ๅญๅจใใชใrenoteๅฏพ่ฑกใงๆใใใ', async(async () => { const post = { - renoteId: '000000000000000000000000' + renoteId: '000000000000000000000000', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -170,7 +170,7 @@ describe('Note', () => { it('ไธๆญฃใชใชใใฉใคๅ IDใงๆใใใ', async(async () => { const post = { text: 'test', - replyId: 'foo' + replyId: 'foo', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -178,7 +178,7 @@ describe('Note', () => { it('ไธๆญฃใชrenoteๅฏพ่ฑกIDใงๆใใใ', async(async () => { const post = { - renoteId: 'foo' + renoteId: 'foo', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -186,7 +186,7 @@ describe('Note', () => { it('ๅญๅจใใชใใฆใผใถใผใซใกใณใทใงใณใงใใ', async(async () => { const post = { - text: '@ghost yo' + text: '@ghost yo', }; const res = await request('/notes/create', post, alice); @@ -198,7 +198,7 @@ describe('Note', () => { it('ๅใใฆใผใถใผใซ่คๆฐใกใณใทใงใณใใฆใๅ ้จ็ใซใพใจใใใใ', async(async () => { const post = { - text: '@bob @bob @bob yo' + text: '@bob @bob @bob yo', }; const res = await request('/notes/create', post, alice); @@ -216,8 +216,8 @@ describe('Note', () => { const res = await request('/notes/create', { text: 'test', poll: { - choices: ['foo', 'bar'] - } + choices: ['foo', 'bar'], + }, }, alice); assert.strictEqual(res.status, 200); @@ -227,7 +227,7 @@ describe('Note', () => { it('ๆ็ฅจใฎ้ธๆ่ขใ็กใใฆๆใใใ', async(async () => { const res = await request('/notes/create', { - poll: {} + poll: {}, }, alice); assert.strictEqual(res.status, 400); })); @@ -235,8 +235,8 @@ describe('Note', () => { it('ๆ็ฅจใฎ้ธๆ่ขใ็กใใฆๆใใใ (็ฉบใฎ้ ๅ)', async(async () => { const res = await request('/notes/create', { poll: { - choices: [] - } + choices: [], + }, }, alice); assert.strictEqual(res.status, 400); })); @@ -244,8 +244,8 @@ describe('Note', () => { it('ๆ็ฅจใฎ้ธๆ่ขใ1ใคใงๆใใใ', async(async () => { const res = await request('/notes/create', { poll: { - choices: ['Strawberry Pasta'] - } + choices: ['Strawberry Pasta'], + }, }, alice); assert.strictEqual(res.status, 400); })); @@ -254,13 +254,13 @@ describe('Note', () => { const { body } = await request('/notes/create', { text: 'test', poll: { - choices: ['sakura', 'izumi', 'ako'] - } + choices: ['sakura', 'izumi', 'ako'], + }, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); assert.strictEqual(res.status, 204); @@ -270,18 +270,18 @@ describe('Note', () => { const { body } = await request('/notes/create', { text: 'test', poll: { - choices: ['sakura', 'izumi', 'ako'] - } + choices: ['sakura', 'izumi', 'ako'], + }, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 0 + choice: 0, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 2 + choice: 2, }, alice); assert.strictEqual(res.status, 400); @@ -292,23 +292,23 @@ describe('Note', () => { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], - multiple: true - } + multiple: true, + }, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 0 + choice: 0, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 2 + choice: 2, }, alice); assert.strictEqual(res.status, 204); @@ -319,15 +319,15 @@ describe('Note', () => { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], - expiredAfter: 1 - } + expiredAfter: 1, + }, }, alice); await new Promise(x => setTimeout(x, 2)); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); assert.strictEqual(res.status, 400); @@ -341,11 +341,11 @@ describe('Note', () => { }, alice); const replyOneRes = await request('/notes/create', { text: 'reply one', - replyId: mainNoteRes.body.createdNote.id + replyId: mainNoteRes.body.createdNote.id, }, alice); const replyTwoRes = await request('/notes/create', { text: 'reply two', - replyId: mainNoteRes.body.createdNote.id + replyId: mainNoteRes.body.createdNote.id, }, alice); const deleteOneRes = await request('/notes/delete', { @@ -353,7 +353,7 @@ describe('Note', () => { }, alice); assert.strictEqual(deleteOneRes.status, 204); - let mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + let mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id }); assert.strictEqual(mainNote.repliesCount, 1); const deleteTwoRes = await request('/notes/delete', { @@ -361,7 +361,7 @@ describe('Note', () => { }, alice); assert.strictEqual(deleteTwoRes.status, 204); - mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id }); assert.strictEqual(mainNote.repliesCount, 0); })); }); diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.ts index 84e43d26c2..df102c8dfe 100644 --- a/packages/backend/test/prelude/url.ts +++ b/packages/backend/test/prelude/url.ts @@ -6,7 +6,7 @@ describe('url', () => { const s = query({ foo: 'ใตใ ', bar: 'b a r', - baz: undefined + baz: undefined, }); assert.deepStrictEqual(s, 'foo=%E3%81%B5%E3%81%85&bar=b%20a%20r'); }); diff --git a/packages/backend/test/streaming.ts b/packages/backend/test/streaming.ts index 8d22b6d3d3..f080b71dd4 100644 --- a/packages/backend/test/streaming.ts +++ b/packages/backend/test/streaming.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js'; import { Following } from '../src/models/entities/following.js'; +import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js'; describe('Streaming', () => { let p: childProcess.ChildProcess; @@ -30,7 +30,7 @@ describe('Streaming', () => { followerSharedInbox: null, followeeHost: followee.host, followeeInbox: null, - followeeSharedInbox: null + followeeSharedInbox: null, }); }; @@ -47,7 +47,7 @@ describe('Streaming', () => { }); post(alice, { - text: 'foo @bob bar' + text: 'foo @bob bar', }); })); @@ -55,7 +55,7 @@ describe('Streaming', () => { const alice = await signup({ username: 'alice' }); const bob = await signup({ username: 'bob' }); const bobNote = await post(bob, { - text: 'foo' + text: 'foo', }); const ws = await connectStream(bob, 'main', ({ type, body }) => { @@ -67,14 +67,14 @@ describe('Streaming', () => { }); post(alice, { - renoteId: bobNote.id + renoteId: bobNote.id, }); })); describe('Home Timeline', () => { it('่ชๅใฎๆ็จฟใๆตใใ', () => new Promise(async done => { const post = { - text: 'foo' + text: 'foo', }; const me = await signup(); @@ -96,7 +96,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { @@ -108,7 +108,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -125,7 +125,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -141,7 +141,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { @@ -157,7 +157,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -168,7 +168,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -183,7 +183,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [carol.id] + visibleUserIds: [carol.id], }); setTimeout(() => { @@ -207,7 +207,7 @@ describe('Streaming', () => { }); post(me, { - text: 'foo' + text: 'foo', }); })); @@ -224,7 +224,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -241,7 +241,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -257,7 +257,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -269,7 +269,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -294,7 +294,7 @@ describe('Streaming', () => { // ใใผใ ๆๅฎ post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -310,7 +310,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -325,7 +325,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); setTimeout(() => { @@ -350,7 +350,7 @@ describe('Streaming', () => { // ใใฉใญใฏใผๅฎใฆๆ็จฟ post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -374,7 +374,7 @@ describe('Streaming', () => { }); post(me, { - text: 'foo' + text: 'foo', }); })); @@ -391,7 +391,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -411,7 +411,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -428,7 +428,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -444,7 +444,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => { @@ -460,7 +460,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -470,7 +470,7 @@ describe('Streaming', () => { // Alice ใ Bob ใใใฉใญใผ await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => { @@ -485,7 +485,7 @@ describe('Streaming', () => { // ใใผใ ๆ็จฟ post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); })); @@ -504,7 +504,7 @@ describe('Streaming', () => { // ใใผใ ๆ็จฟ post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -529,7 +529,7 @@ describe('Streaming', () => { // ใใฉใญใฏใผๅฎใฆๆ็จฟ post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -554,7 +554,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -571,7 +571,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -590,7 +590,7 @@ describe('Streaming', () => { // ใใผใ ๆ็จฟ post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -608,13 +608,13 @@ describe('Streaming', () => { // ใชในใไฝๆ const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice ใ Bob ใใชในใคใณ await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'userList', ({ type, body }) => { @@ -624,11 +624,11 @@ describe('Streaming', () => { done(); } }, { - listId: list.id + listId: list.id, }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -638,7 +638,7 @@ describe('Streaming', () => { // ใชในใไฝๆ const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); let fired = false; @@ -648,11 +648,11 @@ describe('Streaming', () => { fired = true; } }, { - listId: list.id + listId: list.id, }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -669,13 +669,13 @@ describe('Streaming', () => { // ใชในใไฝๆ const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice ใ Bob ใใชในใคใณ await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'userList', ({ type, body }) => { @@ -686,14 +686,14 @@ describe('Streaming', () => { done(); } }, { - listId: list.id + listId: list.id, }); // Bob ใ Alice ๅฎใฆใฎใใคใฌใฏใๆ็จฟ post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -704,13 +704,13 @@ describe('Streaming', () => { // ใชในใไฝๆ const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice ใ Bob ใใชในใคใณ await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -720,13 +720,13 @@ describe('Streaming', () => { fired = true; } }, { - listId: list.id + listId: list.id, }); // ใใฉใญใฏใผๅฎใฆๆ็จฟ post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -749,12 +749,12 @@ describe('Streaming', () => { } }, { q: [ - ['foo'] - ] + ['foo'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); })); @@ -773,20 +773,20 @@ describe('Streaming', () => { } }, { q: [ - ['foo', 'bar'] - ] + ['foo', 'bar'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); setTimeout(() => { @@ -816,24 +816,24 @@ describe('Streaming', () => { }, { q: [ ['foo'], - ['bar'] - ] + ['bar'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); post(me, { - text: '#piyo' + text: '#piyo', }); setTimeout(() => { @@ -866,28 +866,28 @@ describe('Streaming', () => { }, { q: [ ['foo', 'bar'], - ['piyo'] - ] + ['piyo'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); post(me, { - text: '#piyo' + text: '#piyo', }); post(me, { - text: '#waaa' + text: '#waaa', }); setTimeout(() => { diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.ts index 25ffe04756..5b7933da67 100644 --- a/packages/backend/test/user-notes.ts +++ b/packages/backend/test/user-notes.ts @@ -2,8 +2,13 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { async, signup, request, post, uploadFile, startServer, shutdownServer } from './utils.js'; +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + describe('users/notes', () => { let p: childProcess.ChildProcess; @@ -15,16 +20,16 @@ describe('users/notes', () => { before(async () => { p = await startServer(); alice = await signup({ username: 'alice' }); - const jpg = await uploadFile(alice, __dirname + '/resources/Lenna.jpg'); - const png = await uploadFile(alice, __dirname + '/resources/Lenna.png'); + const jpg = await uploadFile(alice, _dirname + '/resources/Lenna.jpg'); + const png = await uploadFile(alice, _dirname + '/resources/Lenna.png'); jpgNote = await post(alice, { - fileIds: [jpg.id] + fileIds: [jpg.id], }); pngNote = await post(alice, { - fileIds: [png.id] + fileIds: [png.id], }); jpgPngNote = await post(alice, { - fileIds: [jpg.id, png.id] + fileIds: [jpg.id, png.id], }); }); @@ -35,7 +40,7 @@ describe('users/notes', () => { it('ใใกใคใซใฟใคใๆๅฎ (jpg)', async(async () => { const res = await request('/users/notes', { userId: alice.id, - fileType: ['image/jpeg'] + fileType: ['image/jpeg'], }, alice); assert.strictEqual(res.status, 200); @@ -48,7 +53,7 @@ describe('users/notes', () => { it('ใใกใคใซใฟใคใๆๅฎ (jpg or png)', async(async () => { const res = await request('/users/notes', { userId: alice.id, - fileType: ['image/jpeg', 'image/png'] + fileType: ['image/jpeg', 'image/png'], }, alice); assert.strictEqual(res.status, 200); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 32a030f933..5eb4ed3b01 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -1,14 +1,20 @@ import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import * as childProcess from 'child_process'; +import * as http from 'node:http'; +import { SIGKILL } from 'constants'; import * as WebSocket from 'ws'; import * as misskey from 'misskey-js'; import fetch from 'node-fetch'; import FormData from 'form-data'; -import * as childProcess from 'child_process'; -import * as http from 'http'; +import { DataSource } from 'typeorm'; import loadConfig from '../src/config/load.js'; -import { SIGKILL } from 'constants'; import { entities } from '../src/db/postgre.js'; +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + const config = loadConfig(); export const port = config.port; @@ -22,29 +28,29 @@ export const async = (fn: Function) => (done: Function) => { export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => { const auth = me ? { - i: me.token + i: me.token, } : {}; const res = await fetch(`http://localhost:${port}/api${endpoint}`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, - body: JSON.stringify(Object.assign(auth, params)) + body: JSON.stringify(Object.assign(auth, params)), }); const status = res.status; const body = res.status !== 204 ? await res.json().catch() : null; return { - body, status + body, status, }; }; export const signup = async (params?: any): Promise<any> => { const q = Object.assign({ username: 'test', - password: 'test' + password: 'test', }, params); const res = await request('/signup', q); @@ -54,7 +60,7 @@ export const signup = async (params?: any): Promise<any> => { export const post = async (user: any, params?: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => { const q = Object.assign({ - text: 'test' + text: 'test', }, params); const res = await request('/notes/create', q, user); @@ -65,26 +71,26 @@ export const post = async (user: any, params?: misskey.Endpoints['notes/create'] export const react = async (user: any, note: any, reaction: string): Promise<any> => { await request('/notes/reactions/create', { noteId: note.id, - reaction: reaction + reaction: reaction, }, user); }; export const uploadFile = (user: any, path?: string): Promise<any> => { - const formData = new FormData(); - formData.append('i', user.token); - formData.append('file', fs.createReadStream(path || __dirname + '/resources/Lenna.png')); + const formData = new FormData(); + formData.append('i', user.token); + formData.append('file', fs.createReadStream(path || _dirname + '/resources/Lenna.png')); - return fetch(`http://localhost:${port}/api/drive/files/create`, { - method: 'post', - body: formData, - timeout: 30 * 1000, - }).then(res => { - if (!res.ok) { - throw `${res.status} ${res.statusText}`; - } else { - return res.json(); - } - }); + return fetch(`http://localhost:${port}/api/drive/files/create`, { + method: 'post', + body: formData, + timeout: 30 * 1000, + }).then(res => { + if (!res.ok) { + throw `${res.status} ${res.statusText}`; + } else { + return res.json(); + } + }); }; export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> { @@ -94,9 +100,9 @@ export function connectStream(user: any, channel: string, listener: (message: Re ws.on('open', () => { ws.on('message', data => { const msg = JSON.parse(data.toString()); - if (msg.type == 'channel' && msg.body.id == 'a') { + if (msg.type === 'channel' && msg.body.id === 'a') { listener(msg.body); - } else if (msg.type == 'connected' && msg.body.id == 'a') { + } else if (msg.type === 'connected' && msg.body.id === 'a') { res(ws); } }); @@ -107,8 +113,8 @@ export function connectStream(user: any, channel: string, listener: (message: Re channel: channel, id: 'a', pong: true, - params: params - } + params: params, + }, })); }); }); @@ -119,8 +125,8 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? return await new Promise((resolve, reject) => { const req = http.request(`http://localhost:${port}${path}`, { headers: { - Accept: accept - } + Accept: accept, + }, }, res => { if (res.statusCode! >= 400) { reject(res); @@ -139,9 +145,9 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProcess) => void, moreProcess: () => Promise<void> = async () => {}) { return (done: (err?: Error) => any) => { - const p = childProcess.spawn('node', [__dirname + '/../index.js'], { + const p = childProcess.spawn('node', [_dirname + '/../index.js'], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env: { NODE_ENV: 'test', PATH: process.env.PATH } + env: { NODE_ENV: 'test', PATH: process.env.PATH }, }); callbackSpawnedProcess(p); p.on('message', message => { @@ -153,12 +159,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce export async function initTestDb(justBorrow = false, initEntities?: any[]) { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; - try { - const conn = await getConnection(); - await conn.close(); - } catch (e) {} - - return await createConnection({ + const db = new DataSource({ type: 'postgres', host: config.db.host, port: config.db.port, @@ -167,8 +168,12 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) { database: config.db.db, synchronize: true && !justBorrow, dropSchema: true && !justBorrow, - entities: initEntities || entities + entities: initEntities || entities, }); + + await db.initialize(); + + return db; } export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProcess> { @@ -178,9 +183,9 @@ export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProc rej('timeout to start'); }, timeout); - const p = childProcess.spawn('node', [__dirname + '/../built/index.js'], { + const p = childProcess.spawn('node', [_dirname + '/../built/index.js'], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env: { NODE_ENV: 'test', PATH: process.env.PATH } + env: { NODE_ENV: 'test', PATH: process.env.PATH }, }); p.on('error', e => rej(e)); diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 3120851aae..22338a4976 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -25,9 +25,14 @@ "rootDir": "./src", "baseUrl": "./", "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] }, "outDir": "./built", + "types": [ + "node" + ], "typeRoots": [ "./node_modules/@types", "./src/@types" diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 5cd71acf9c..5ce87c34eb 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -12,20 +12,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99" integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw== -"@babel/runtime@^7.16.0": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" - integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.6.2": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" - integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/types@^7.6.1", "@babel/types@^7.9.6": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" @@ -35,68 +21,63 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@bull-board/api@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.3.tgz#c6aad9f5cfb3acbe02c57e823ee81c1ae575849d" - integrity sha512-kV6EPwi9j71qBmozvDmtT01j986r4cFqNmBgq7HApYXW0G2U8Brmv0Ut0iMQZRc/X7aA5KYL3qXcEsriFnq+jw== +"@bull-board/api@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.11.1.tgz#98b2c9556f643718bb5bde4a1306e6706af8192e" + integrity sha512-ElwX7sM+Ng4ZL9KUsbDubRE+r2hu/gss85OsROeE9bmyfkW14jOJkgr5MKUyjTTgPEeMs1Mw55TgQs2vxoWBiA== dependencies: redis-info "^3.0.8" -"@bull-board/koa@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.3.tgz#b9f02629f96f056d6a038c3c58fc339d58e55abb" - integrity sha512-DK8m09MwcRwUR3tz3xI0iSK/Ih2huQ2MAWm8krYjO5deswP2yBaCWE4OtpiULLfVpf8z4zB3Oqa0xNJrKRHTOQ== +"@bull-board/koa@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.11.1.tgz#1872aba2c65d116d1183b3003e4a2cb2c1e2fbbf" + integrity sha512-F/thrTuC1JWpdBO7DPdKD/wr8c+d7MJGu0sr5ARsT1WXhng7sU7OqBEP/5Y7HhByurjDFXDxcgk/mc78Tmeb/Q== dependencies: - "@bull-board/api" "3.10.3" - "@bull-board/ui" "3.10.3" - ejs "^3.1.6" + "@bull-board/api" "3.11.1" + "@bull-board/ui" "3.11.1" + ejs "^3.1.7" koa "^2.13.1" koa-mount "^4.0.0" koa-router "^10.0.0" koa-static "^5.0.0" koa-views "^7.0.1" -"@bull-board/ui@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.3.tgz#b921199d42b32d8ddd9bbf0e35c25be0d64403e9" - integrity sha512-6zYW3FqySg+4IKEeM1jt/5ixNVBKQjtZLG9W81ADVcHk8YceQ++7URWzDb8nQEct3rEW4bjR6nicVWNXMSN7Lw== +"@bull-board/ui@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.11.1.tgz#17a2af5573f31811a543105b9a96249c95e93ce7" + integrity sha512-SRrfvxHF/WaBICiAFuWAoAlTvoBYUBmX94oRbSKzVILRFZMe3gs0hN071BFohrn4yOTFHAkWPN7cjMbaqHwCag== dependencies: - "@bull-board/api" "3.10.3" + "@bull-board/api" "3.11.1" -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: - "@cspotcode/source-map-consumer" "0.8.0" + "@jridgewell/trace-mapping" "0.3.9" "@cto.af/textdecoder@^0.0.0": version "0.0.0" resolved "https://registry.yarnpkg.com/@cto.af/textdecoder/-/textdecoder-0.0.0.tgz#e1e8d84c936c30a0f4619971f19ca41941af9fdc" integrity sha512-sJpx3F5xcVV/9jNYJQtvimo4Vfld/nD3ph+ZWtQzZ03Zo8rJC7QKQTRcIGS13Rcz80DwFNthCWMrd58vpY4ZAQ== -"@digitalbazaar/http-client@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-1.1.0.tgz#cac383b24ace04b18b919deab773462b03d3d7b0" - integrity sha512-ks7hqa6hm9NyULdbm9qL6TRS8rADzBw8R0lETvUgvdNXu9H62XG2YqoKRDThtfgWzWxLwRJ3Z2o4ev81dZZbyQ== +"@digitalbazaar/http-client@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-3.2.0.tgz#b85ea09028c7d0f288f976c852d0a8f3875f0fcf" + integrity sha512-NhYXcWE/JDE7AnJikNX7q0S6zNuUPA2NuIoRdUpmvHlarjmRqyr6hIO3Awu2FxlUzbdiI1uzuWrZyB9mD1tTvw== dependencies: - esm "^3.2.22" - ky "^0.25.1" - ky-universal "^0.8.2" + ky "^0.30.0" + ky-universal "^0.10.1" + undici "^5.2.0" -"@discordapp/twemoji@13.1.1": - version "13.1.1" - resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" - integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== +"@discordapp/twemoji@14.0.2": + version "14.0.2" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837" + integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA== dependencies: fs-extra "^8.0.1" jsonfile "^5.0.0" - twemoji-parser "13.1.0" + twemoji-parser "14.0.0" universalify "^0.1.2" "@elastic/elasticsearch@7.11.0": @@ -110,19 +91,19 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" - integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" + espree "^9.3.2" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@gar/promisify@^1.0.1": @@ -144,6 +125,24 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@koa/cors@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2" @@ -234,6 +233,15 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@peertube/http-signature@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@peertube/http-signature/-/http-signature-1.6.0.tgz#22bef028384e6437e8dbd94052ba7b8bd7f7f1ae" + integrity sha512-Bx780c7FPYtkV4LgCoaJcXYcKQqaMef2iQR2V2r5klkYkIQWFxbTOpyhKxvVXYIBIFpj5Cb8DGVDAmhkm7aavg== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.14.1" + "@redocly/ajv@^8.6.4": version "8.6.4" resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" @@ -244,10 +252,10 @@ require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.93": - version "1.0.0-beta.93" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.93.tgz#882db8684598217f621adc7349288229253a0038" - integrity sha512-xQj7UnjPj3mKtkyRrm+bjzEoyo0CVNjGP4pV6BzQ0vgKf0Jqq7apFC703psyBH+JscYr7NKK1hPQU76ylhFDdg== +"@redocly/openapi-core@1.0.0-beta.97": + version "1.0.0-beta.97" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz#324ed46e9a9aee4c615be22ee348c53f7bb5f180" + integrity sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ== dependencies: "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" @@ -255,7 +263,7 @@ js-levenshtein "^1.1.6" js-yaml "^4.1.0" lodash.isequal "^4.5.0" - minimatch "^3.0.4" + minimatch "^5.0.1" node-fetch "^2.6.1" pluralize "^8.0.0" yaml-ast-parser "0.0.43" @@ -277,10 +285,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.1.tgz#7b698e0b9d12d93611f06ee143c30ced848e2840" - integrity sha512-Wp5vwlZ0lOqpSYGKqr53INws9HLkt6JDc/pDZcPf7bchQnrXJMXPns8CXx0hFikMSGSWfvtvvpb2gtMVfkWagA== +"@sinonjs/fake-timers@9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== dependencies: "@sinonjs/commons" "^1.7.0" @@ -527,6 +535,11 @@ resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4" integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg== +"@types/jsrsasign@10.5.1": + version "10.5.1" + resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.1.tgz#6f9defd46dfcf324b1cff08a06be639858deee3b" + integrity sha512-QqM03IXHY6SX835mWdx7Vp8ZOxw/hcnMjGjapUQf+pgFPRyGdjg3jxFsr4p+rolKcdRhptm3mtVQNk4OMhCQcA== + "@types/keygrip@*": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" @@ -649,10 +662,10 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== -"@types/mocha@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5" - integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg== +"@types/mocha@9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node-fetch@3.0.3": version "3.0.3" @@ -666,10 +679,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@17.0.23": - version "17.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== +"@types/node@17.0.41": + version "17.0.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" + integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== "@types/node@^14.11.8": version "14.17.9" @@ -700,11 +713,6 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== -"@types/portscanner@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b" - integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q== - "@types/pug@2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" @@ -777,6 +785,11 @@ dependencies: htmlparser2 "^6.0.0" +"@types/semver@7.3.9": + version "7.3.9" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -785,10 +798,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.1.tgz#31bd128f2437e8fc31424eb23d8284aa127bfa8d" - integrity sha512-LxzQsKo2YtvA2DlqACNXmlbLGMVJCSU/HhV4N9RrStClUEf02iN+AakD/zUOpZkbo1OG+lHk2LeqoHedLwln2w== +"@types/sharp@0.30.2": + version "0.30.2" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.2.tgz#df5ff34140b3bad165482e6f3d26b08e42a0503a" + integrity sha512-uLCBwjDg/BTcQit0dpNGvkIjvH3wsb8zpaJePCjvONBBSfaKHoxXBIuq1MT8DMQEfk2fKYnpC9QExCgFhkGkMQ== dependencies: "@types/node" "*" @@ -845,85 +858,85 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d" - integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A== +"@typescript-eslint/eslint-plugin@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758" + integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/type-utils" "5.18.0" - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/type-utils" "5.27.1" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" -"@typescript-eslint/type-utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74" - integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA== +"@typescript-eslint/type-utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166" + integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw== dependencies: - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855" - integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA== +"@typescript-eslint/utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f" + integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" "@ungap/promise-all-settled@1.1.2": version "1.1.2" @@ -963,10 +976,10 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +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-walk@^7.1.1: version "7.1.1" @@ -988,11 +1001,16 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.5.0, acorn@^8.7.0: +acorn@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1074,7 +1092,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -1082,6 +1100,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +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" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1131,13 +1156,13 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" -archiver@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" - integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== +archiver@5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6" + integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w== dependencies: archiver-utils "^2.1.0" - async "^3.2.0" + async "^3.2.3" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" readdir-glob "^1.0.0" @@ -1227,27 +1252,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - -async@>=0.2.9: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== - -async@^2.6.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - -async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== +async@>=0.2.9, async@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== asynckit@^0.4.0: version "0.4.0" @@ -1266,10 +1274,10 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1111.0: - version "2.1111.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1111.0.tgz#02b1e5c530ef8140235ee7c48c710bb2dbd7dc84" - integrity sha512-WRyNcCckzmu1djTAWfR2r+BuI/PbuLrhG3oa+oH39v4NZ4EecYWFL1CoCPlC2kRUML4maSba5T4zlxjcNl7ELQ== +aws-sdk@2.1152.0: + version "2.1152.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1152.0.tgz#73e4fb81b3a9c289234b5d6848bcdb854f169bdf" + integrity sha512-Lqwk0bDhm3vzpYb3AAM9VgGHeDpbB8+o7UJnP9R+CO23kJfi/XRpKihAcbyKDD/AUQ+O1LJaUVpvaJYLS9Am7w== dependencies: buffer "4.9.2" events "1.1.1" @@ -1278,7 +1286,7 @@ aws-sdk@2.1111.0: querystring "0.2.0" sax "1.2.1" url "0.10.3" - uuid "3.3.2" + uuid "8.0.0" xml2js "0.4.19" axios@^0.24.0: @@ -1296,9 +1304,9 @@ babel-walk@3.0.0-canary-5: "@babel/types" "^7.9.6" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base32.js@0.0.1: version "0.0.1" @@ -1327,11 +1335,6 @@ bcryptjs@2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= -big-integer@^1.6.16: - version "1.6.48" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" - integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== - big-integer@^1.6.17: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -1397,27 +1400,20 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +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.1, 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" -broadcast-channel@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198" - integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ== - dependencies: - "@babel/runtime" "^7.16.0" - detect-node "^2.1.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - p-queue "6.6.2" - rimraf "3.0.2" - unload "2.3.1" - browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -1490,10 +1486,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.8.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.1.tgz#83daaefc3118876450b21d7a02bc11ea28a2440e" - integrity sha512-ojH5AfOchKQsQwwE+thViS1pMpvREGC+Ov1+3HXsQqn5Q27ZSGkgMriMqc6c9J9rvQ/+D732pZE+TN1+2LRWVg== +bull@4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.3.tgz#4ab67029fee1183dcb7185895b20dc08c02d6bf2" + integrity sha512-oOHr+KTLu3JM5V9TXsg18/1xyVQceoYCFiGrXZOpu9abZn3W3vXJtMBrwB6Yvl/RxSKVVBpoa25RF/ya3750qg== dependencies: cron-parser "^4.2.1" debuglog "^1.0.0" @@ -1586,11 +1582,6 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -cafy@15.2.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a" - integrity sha512-g2zOmFb63p6XcZ/zeMWKYP8YKQYNWnhJmi6K71Ql4EAFTAay31xF0PBPtdBCCfQ0fiETgWTMxKtySAVI/Od6aQ== - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1678,7 +1669,7 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2: +chalk@^4.0.2, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1720,7 +1711,7 @@ cheerio@0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" -chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.2: +chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.3: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -1859,10 +1850,10 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" - integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== +color@^4.0.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" color-string "^1.9.0" @@ -1884,10 +1875,10 @@ commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== compress-commons@^4.1.0: version "4.1.1" @@ -2079,11 +2070,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-uri-to-buffer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" - integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== - data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" @@ -2124,6 +2110,13 @@ debug@4.3.3: dependencies: ms "2.1.2" +debug@4.3.4, debug@^4.3.3, 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" + debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2145,13 +2138,6 @@ debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.3: - 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" - debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -2261,26 +2247,16 @@ destroy@^1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= detect-libc@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204" integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw== -detect-libc@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - -detect-node@2.1.0, detect-node@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - dicer@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" @@ -2470,12 +2446,12 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== +ejs@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: - jake "^10.6.1" + jake "^10.8.5" emoji-regex@^8.0.0: version "8.0.0" @@ -2700,22 +2676,17 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint-visitor-keys@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" - integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7" - integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ== +eslint@8.17.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21" + integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw== dependencies: - "@eslint/eslintrc" "^1.2.1" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2726,14 +2697,14 @@ eslint@8.13.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.1" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2742,7 +2713,7 @@ eslint@8.13.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -2751,18 +2722,13 @@ eslint@8.13.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -esm@^3.2.22: - version "3.2.25" - resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" - integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== - -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" + acorn "^8.7.1" + acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" esprima@^4.0.1: @@ -2809,7 +2775,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.4, eventemitter3@^4.0.7: +eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -2844,13 +2810,6 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2897,6 +2856,17 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + 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" @@ -2926,11 +2896,6 @@ feed@4.2.2: dependencies: xml-js "^1.6.11" -fetch-blob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" - integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" @@ -2946,21 +2911,21 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@17.1.1: - version "17.1.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.1.tgz#24c59bc663df0c0c181b31dfacde25e06431afbe" - integrity sha512-heRUMZHby2Qj6wZAA3YHeMlRmZNQTcb6VxctkGmM+mcM6ROQKvHpr7SS6EgdfEhH+s25LDshBjvPx/Ecm+bOVQ== +file-type@17.1.2: + version "17.1.2" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.2.tgz#9257437a64e0c3623f70d9f27430522d978b1384" + integrity sha512-3thBUSfa9YEUEGO/NAAiQGvjujZxZiJTF6xNwyDn6kB0NcEtwMn5ttkGG9jGwm/Nt/t8U1bpBNqyBNZCz4F4ig== dependencies: readable-web-to-node-stream "^3.0.2" strtok3 "^7.0.0-alpha.7" token-types "^5.0.0-alpha.2" filelist@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" - integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83" + integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q== dependencies: - minimatch "^3.0.4" + minimatch "^5.0.1" fill-range@^7.0.1: version "7.0.1" @@ -2969,14 +2934,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-node-modules@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" - integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== - dependencies: - findup-sync "^4.0.0" - merge "^2.1.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -3000,16 +2957,6 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3210,7 +3157,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3248,37 +3195,10 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.6.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" - integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== - dependencies: - type-fest "^0.20.2" - -globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -3294,6 +3214,18 @@ globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" +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" + got@11.5.1: version "11.5.1" resolved "https://registry.yarnpkg.com/got/-/got-11.5.1.tgz#bf098a270fe80b3fb88ffd5a043a59ebb0a391db" @@ -3311,10 +3243,10 @@ got@11.5.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/got/-/got-12.0.3.tgz#c7314daab26d42039e624adbf98f6d442e5de749" - integrity sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug== +got@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4" + integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig== dependencies: "@sindresorhus/is" "^4.6.0" "@szmarczak/http-timer" "^5.0.1" @@ -3345,11 +3277,6 @@ graceful-fs@^4.2.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3404,13 +3331,6 @@ highlight.js@^10.7.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - hpagent@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-0.1.2.tgz#cab39c66d4df2d4377dbd212295d878deb9bdaa9" @@ -3507,15 +3427,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-signature@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" - integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== - dependencies: - assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.14.1" - http2-wrapper@^1.0.0-beta.5.0: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -3600,11 +3511,6 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.1.8: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== - ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -3700,10 +3606,10 @@ ip-address@^7.1.0: jsbn "1.1.0" sprintf-js "1.1.2" -ip-cidr@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375" - integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ== +ip-cidr@3.0.10: + version "3.0.10" + resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.10.tgz#e1a039705196d84b43858f81a243fd70def9cefc" + integrity sha512-PXSsrRYirsuaCI1qBVyVXRLUIpNzxm76eHd3UvN5NXTMUG85GWGZpr6P+70mimc5e7Nfh/tShmjk0oSywErMWg== dependencies: ip-address "^7.1.0" jsbn "^1.1.0" @@ -3848,13 +3754,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-like@^1.0.3: - version "1.0.8" - resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" - integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== - dependencies: - lodash.isfinite "^3.3.2" - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -3962,11 +3861,6 @@ is-whitespace@^0.3.0: resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38= -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3982,13 +3876,13 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jake@^10.6.1: - version "10.8.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" - integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "0.9.x" - chalk "^2.4.2" + async "^3.2.3" + chalk "^4.0.2" filelist "^1.0.1" minimatch "^3.0.4" @@ -4117,7 +4011,7 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.1: +json5@2.2.1, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -4152,30 +4046,30 @@ jsonfile@^5.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonld@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-5.2.0.tgz#d1e8af38a334cb95edf0f2ae4e2b58baf8d2b5a9" - integrity sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw== +jsonld@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-6.0.0.tgz#560a8a871dce72aba5d4c6b08356438d863d62fb" + integrity sha512-1SkN2RXhMCTCSkX+bzHvr9ycM2HTmjWyV41hn2xG7k6BqlCgRjw0zHmuqfphjBRPqi1gKMIqgBCe/0RZMcWrAA== dependencies: - "@digitalbazaar/http-client" "^1.1.0" + "@digitalbazaar/http-client" "^3.2.0" canonicalize "^1.0.1" lru-cache "^6.0.0" rdf-canonize "^3.0.0" -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" json-schema "0.4.0" verror "1.10.0" -jsrsasign@8.0.20: - version "8.0.20" - resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-8.0.20.tgz#37d8029c9d8f794d8ac8d8998bce319921491f11" - integrity sha512-JTXt9+nqdynIB8wFsS6e8ffHhIjilhywXwdaEVHSj9OVmwldG2H0EoCqkQ+KXkm2tVqREfH/HEmklY4k1/6Rcg== +jsrsasign@10.5.24: + version "10.5.24" + resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.24.tgz#2d159e1756b2268682c6eb5e147184e33e946b1c" + integrity sha512-0i/UHRgJZifp/YmoXHyNQXUY4eKWiSd7YxuD7oKEw9mlqgr51hg9lZQw2nlEDvwHDh7pyj6ZjYlxldlW27xb/Q== jstransformer@1.0.0: version "1.0.0" @@ -4368,18 +4262,18 @@ koa@2.13.4, koa@^2.13.1: type-is "^1.6.16" vary "^1.1.2" -ky-universal@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.8.2.tgz#edc398d54cf495d7d6830aa1ab69559a3cc7f824" - integrity sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ== +ky-universal@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.10.1.tgz#778881e098f6e3c52a87b382d9acca54d22bb0d3" + integrity sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g== dependencies: abort-controller "^3.0.0" - node-fetch "3.0.0-beta.9" + node-fetch "^3.2.2" -ky@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc" - integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA== +ky@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/ky/-/ky-0.30.0.tgz#a3d293e4f6c4604a9a4694eceb6ce30e73d27d64" + integrity sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog== lazystream@^1.0.0: version "1.0.1" @@ -4485,11 +4379,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.isfinite@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" - integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M= - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -4535,7 +4424,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4573,11 +4462,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.4.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb" - integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== - luxon@^1.28.0: version "1.28.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" @@ -4625,27 +4509,22 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +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== -merge@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - methods@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mfm-js@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" - integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== +mfm-js@0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2" + integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ== dependencies: - twemoji-parser "13.1.x" + twemoji-parser "14.0.x" micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" @@ -4655,10 +4534,13 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== +micromatch@^4.0.4: + 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" mime-db@1.44.0: version "1.44.0" @@ -4704,21 +4586,14 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== +minimatch@5.0.1, minimatch@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: +minimatch@^3.0.4, 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== @@ -4815,40 +4690,38 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" - integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.3" - debug "4.3.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.2.0" - growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "4.2.1" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.1" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" moment@^2.22.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== ms@2.0.0: version "2.0.0" @@ -4899,10 +4772,10 @@ multer@1.4.4: type-is "^1.6.4" xtend "^4.0.0" -mylas@^2.1.4: - version "2.1.5" - resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.5.tgz#7ccf41ec5a93ab2d63fc3678abf1942c0e7bdeb1" - integrity sha512-7ZyrJux1lipSR45IxDvWz7zJOXWTazTFCqD4/p8XBF4O+mtJwf7QpMWTH+jE4lV9O2I38xcpS0KTIp7GwhUTmA== +mylas@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc" + integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg== mz@^2.4.0, mz@^2.7.0: version "2.7.0" @@ -4918,14 +4791,12 @@ nan@^2.14.2, nan@^2.15.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= - dependencies: - big-integer "^1.6.16" +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@3.3.1, nanoid@^3.1.30: +nanoid@^3.1.30: version "3.3.1" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== @@ -4976,7 +4847,7 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-addon-api@^4.3.0: +node-addon-api@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== @@ -4995,18 +4866,10 @@ node-fetch@*: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-fetch@3.0.0-beta.9: - version "3.0.0-beta.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b" - integrity sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg== - dependencies: - data-uri-to-buffer "^3.0.1" - fetch-blob "^2.1.1" - -node-fetch@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.3.tgz#a03c9cc2044d21d1a021566bd52f080f333719a6" - integrity sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA== +node-fetch@3.2.6, node-fetch@^3.2.2: + version "3.2.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9" + integrity sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" @@ -5045,10 +4908,10 @@ node-gyp@^8.4.1: tar "^6.1.2" which "^2.0.2" -nodemailer@6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018" - integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g== +nodemailer@6.7.5: + version "6.7.5" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.5.tgz#b30b1566f5fa2249f7bd49ced4c58bec6b25915e" + integrity sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg== nofilter@^2.0.3: version "2.0.3" @@ -5175,11 +5038,6 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - on-finished@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -5327,14 +5185,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-queue@6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -5364,11 +5214,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - parse-srcset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" @@ -5507,11 +5352,23 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +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@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +plimit-lit@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3" + integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw== + dependencies: + queue-lit "^1.2.7" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -5527,14 +5384,6 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== -portscanner@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" - integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== - dependencies: - async "^2.6.0" - is-number-like "^1.0.3" - postcss@^8.3.11: version "8.3.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" @@ -5566,10 +5415,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" - integrity sha512-QBSab31WqkyxpnMWQxubYAHR5S9B2+r81ucocew34Fkl98FhvKIF50jIJnNOBmAZfyNV7vE5T6gd3hTVWgY6tg== +prebuild-install@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.0.tgz#991b6ac16c81591ba40a6d5de93fb33673ac1370" + integrity sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -5828,6 +5677,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +queue-lit@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763" + integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -6006,11 +5860,6 @@ reflect-metadata@0.1.13, reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== - regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -6053,14 +5902,6 @@ resolve-alpn@^1.2.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -6115,7 +5956,7 @@ rimraf@2: dependencies: glob "^7.1.3" -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, 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== @@ -6213,12 +6054,12 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@7.3.6: - version "7.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" - integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== +semver@7.3.7, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: - lru-cache "^7.4.0" + lru-cache "^6.0.0" semver@^5.6.0: version "5.7.1" @@ -6274,17 +6115,17 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.30.3: - version "0.30.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37" - integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg== +sharp@0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: - color "^4.2.1" - detect-libc "^2.0.1" - node-addon-api "^4.3.0" - prebuild-install "^7.0.1" + color "^4.0.1" + detect-libc "^1.0.3" + node-addon-api "^4.2.0" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^4.0.1" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -6329,7 +6170,7 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0, simple-get@^4.0.1: +simple-get@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== @@ -6588,16 +6429,17 @@ style-loader@3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -summaly@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea" - integrity sha512-IzvO2s7yj/PUyH42qWjVjSPpIiPlgTRWGh33t4cIZKOqPQJ2INo7e83hXhHFr4hXTb3JRcIdCuM1ELjlrujiUQ== +summaly@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.1.tgz#742fe6631987f84ad2e95d2b0f7902ec57e0f6b3" + integrity sha512-WWvl7rLs3wm61Xc2JqgTbSuqtIOmGqKte+rkbnxe6ISy4089lQ+7F2ajooQNee6PWHl9kZ27SDd1ZMoL3/6R4A== dependencies: cheerio "0.22.0" debug "4.3.3" escape-regexp "0.0.1" got "11.5.1" html-entities "2.3.2" + iconv-lite "0.6.3" jschardet "3.0.0" koa "2.13.4" private-ip "2.3.3" @@ -6642,10 +6484,10 @@ syslog-pro@1.0.0: dependencies: moment "^2.22.2" -systeminformation@5.11.9: - version "5.11.9" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d" - integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA== +systeminformation@5.11.16: + version "5.11.16" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.16.tgz#5f6fda2447fafe204bd2ab543475f1ffa8c14a85" + integrity sha512-/a1VfP9WELKLT330yhAHJ4lWCXRYynel1kMMHKc/qdzCgDt3BIcMlo+3tKcTiRHFefjV3fz4AvqMx7dGO/72zw== tapable@^2.2.0: version "2.2.0" @@ -6803,22 +6645,22 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -ts-loader@9.2.8: - version "9.2.8" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48" - integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw== +ts-loader@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f" + integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" micromatch "^4.0.0" semver "^7.3.4" -ts-node@10.7.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== +ts-node@10.8.1: + version "10.8.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066" + integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g== dependencies: - "@cspotcode/source-map-support" "0.7.0" + "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" @@ -6829,22 +6671,31 @@ ts-node@10.7.0: create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsc-alias@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.4.1.tgz#6a6075dd94267d9befdad1431f410bd0b8819805" - integrity sha512-nHTR8qvM/LiYI8Fx6UrzAQXRngAuE2PEK+n9uXmQY6fN+oLZhweNFkCLbyxKDmlLfYnclSuaR+dSuvRd7FUu8Q== +tsc-alias@1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa" + integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ== dependencies: - chokidar "^3.5.2" - commander "^8.2.0" - find-node-modules "^2.1.2" + chokidar "^3.5.3" + commander "^9.0.0" globby "^11.0.4" - mylas "^2.1.4" + mylas "^2.1.9" normalize-path "^3.0.0" + plimit-lit "^1.2.6" -tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1: +tsconfig-paths@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== @@ -6888,12 +6739,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twemoji-parser@13.1.0, twemoji-parser@13.1.x: - version "13.1.0" - resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" - integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== - -twemoji-parser@14.0.0: +twemoji-parser@14.0.0, twemoji-parser@14.0.x: version "14.0.0" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== @@ -6952,10 +6798,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.5.tgz#8fe50d517de5ec6f4b38856ea0f180e4a60cf7e4" - integrity sha512-KL4c8nQqouHaXs4m1J3xh7oXWqX4+A9poExbceLxBRtlavpJQYqiSnqt3JYGpy7Tl9vD5DG5DrmZrSslTkkW5Q== +typeorm@0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.6.tgz#65203443a1b684bb746785913fe2b0877aa991c0" + integrity sha512-DRqgfqcelMiGgWSMbBmVoJNFN2nPNA3EeY2gC324ndr2DZoGRTb9ILtp2oGVGnlA+cu5zgQ6it5oqKFNkte7Aw== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -6975,10 +6821,10 @@ typeorm@0.3.5: xml2js "^0.4.23" yargs "^17.3.1" -typescript@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== ulid@2.3.0: version "2.3.0" @@ -6995,6 +6841,11 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +undici@^5.2.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.4.0.tgz#c474fae02743d4788b96118d46008a24195024d2" + integrity sha512-A1SRXysDg7J+mVP46jF+9cKANw0kptqSFZ8tGyL+HBiv0K1spjxPX8Z4EGu+Eu6pjClJUBdnUPlxrOafR668/g== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -7014,14 +6865,6 @@ universalify@^0.1.0, universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unload@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/unload/-/unload-2.3.1.tgz#9d16862d372a5ce5cb630ad1309c2fd6e35dacfe" - integrity sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "2.1.0" - unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -7075,25 +6918,25 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - uuid@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-compile-cache@^2.0.3: version "2.2.0" @@ -7133,10 +6976,10 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -web-push@3.4.5: - version "3.4.5" - resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.4.5.tgz#f94074ff150538872c7183e4d8881c8305920cf1" - integrity sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g== +web-push@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.5.0.tgz#4576533746052eda3bd50414b54a1b0a21eeaeae" + integrity sha512-JC0V9hzKTqlDYJ+LTZUXtW7B175qwwaqzbbMSWDxHWxZvd3xY0C2rcotMGDavub2nAAFw+sXTsqR65/KY2A5AQ== dependencies: asn1.js "^5.3.0" http_ece "1.1.0" @@ -7216,20 +7059,20 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2, which@^2.0.1, which@^2.0.2: - 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" - -which@^1.1.1, which@^1.2.14: +which@^1.1.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" +which@^2.0.1, which@^2.0.2: + 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" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -7259,10 +7102,10 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: version "6.2.0" @@ -7287,20 +7130,20 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== ws@^8.2.3: version "8.4.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== -xev@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/xev/-/xev-2.0.1.tgz#24484173a22115bc8a990ef5d4d5129695b827a7" - integrity sha512-icDf9M67bDge0F2qf02WKZq+s7mMO/SbPv67ZQPym6JThLEOdlWWLdB7VTVgRJp3ekgaiVItCAyH6aoKCPvfIA== +xev@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/xev/-/xev-3.0.2.tgz#3f4080bd8bed0d3479c674050e3696da98d22a4d" + integrity sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw== xml-js@^1.6.11: version "1.6.11" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index a6e23e5171..10f0e5a9cb 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -1,68 +1,85 @@ module.exports = { root: true, env: { - "node": false + 'node': false, }, - parser: "vue-eslint-parser", + parser: 'vue-eslint-parser', parserOptions: { - "parser": "@typescript-eslint/parser", + 'parser': '@typescript-eslint/parser', tsconfigRootDir: __dirname, - //project: ['./tsconfig.json'], + project: ['./tsconfig.json'], + extraFileExtensions: ['.vue'], }, extends: [ - //"../shared/.eslintrc.js", - "plugin:vue/vue3-recommended" + '../shared/.eslintrc.js', + 'plugin:vue/vue3-recommended', ], rules: { + '@typescript-eslint/no-empty-interface': [ + 'error', + { + 'allowSingleExtends': true, + }, + ], // window ใฎ็ฆๆญข็็ฑ: ใฐใญใผใใซในใณใผใใจ่ก็ชใใไบๆใใฌ็ตๆใๆใใใ // data ใฎ็ฆๆญข็็ฑ: ๆฝ่ฑก็ใใใใใ // e ใฎ็ฆๆญข็็ฑ: error ใ event ใชใฉใ่คๆฐใฎใญใผใฏใผใใฎ้ ญๆๅญใงใใๅใใใซใใใใ - "id-denylist": ["error", "window", "data", "e"], + 'id-denylist': ['error', 'window', 'data', 'e'], 'eqeqeq': ['error', 'always', { 'null': 'ignore' }], - "no-shadow": ["warn"], - "vue/attributes-order": ["error", { - "alphabetical": false + 'no-shadow': ['warn'], + 'vue/attributes-order': ['error', { + 'alphabetical': false, }], - "vue/no-use-v-if-with-v-for": ["error", { - "allowUsingIterationVar": false + 'vue/no-use-v-if-with-v-for': ['error', { + 'allowUsingIterationVar': false, }], - "vue/no-ref-as-operand": "error", - "vue/no-multi-spaces": ["error", { - "ignoreProperties": false + 'vue/no-ref-as-operand': 'error', + 'vue/no-multi-spaces': ['error', { + 'ignoreProperties': false, }], - "vue/no-v-html": "error", - "vue/order-in-components": "error", - "vue/html-indent": ["warn", "tab", { - "attribute": 1, - "baseIndent": 0, - "closeBracket": 0, - "alignAttributesVertically": true, - "ignores": [] + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/html-indent': ['warn', 'tab', { + 'attribute': 1, + 'baseIndent': 0, + 'closeBracket': 0, + 'alignAttributesVertically': true, + 'ignores': [], }], - "vue/html-closing-bracket-spacing": ["warn", { - "startTag": "never", - "endTag": "never", - "selfClosingTag": "never" + 'vue/html-closing-bracket-spacing': ['warn', { + 'startTag': 'never', + 'endTag': 'never', + 'selfClosingTag': 'never', }], - "vue/multi-word-component-names": "warn", - "vue/require-v-for-key": "warn", - "vue/no-unused-components": "warn", - "vue/valid-v-for": "warn", - "vue/return-in-computed-property": "warn", - "vue/no-setup-props-destructure": "warn", - "vue/max-attributes-per-line": "off", - "vue/html-self-closing": "off", - "vue/singleline-html-element-content-newline": "off", + 'vue/multi-word-component-names': 'warn', + 'vue/require-v-for-key': 'warn', + 'vue/no-unused-components': 'warn', + 'vue/valid-v-for': 'warn', + 'vue/return-in-computed-property': 'warn', + 'vue/no-setup-props-destructure': 'warn', + 'vue/max-attributes-per-line': 'off', + 'vue/html-self-closing': 'off', + 'vue/singleline-html-element-content-newline': 'off', }, globals: { - "require": false, - "_DEV_": false, - "_LANGS_": false, - "_VERSION_": false, - "_ENV_": false, - "_PERF_PREFIX_": false, - "_DATA_TRANSFER_DRIVE_FILE_": false, - "_DATA_TRANSFER_DRIVE_FOLDER_": false, - "_DATA_TRANSFER_DECK_COLUMN_": false - } -} + // Node.js + 'module': false, + 'require': false, + '__dirname': false, + + // Vue + '$$': false, + '$ref': false, + '$computed': false, + + // Misskey + '_DEV_': false, + '_LANGS_': false, + '_VERSION_': false, + '_ENV_': false, + '_PERF_PREFIX_': false, + '_DATA_TRANSFER_DRIVE_FILE_': false, + '_DATA_TRANSFER_DRIVE_FOLDER_': false, + '_DATA_TRANSFER_DECK_COLUMN_': false, + }, +}; diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts new file mode 100644 index 0000000000..67f724a9aa --- /dev/null +++ b/packages/client/@types/theme.d.ts @@ -0,0 +1,7 @@ +declare module '@/themes/*.json5' { + import { Theme } from "@/scripts/theme"; + + const theme: Theme; + + export default theme; +} diff --git a/packages/client/package.json b/packages/client/package.json index 9de500f3ab..83c8086e23 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,80 +1,52 @@ { "private": true, "scripts": { - "watch": "webpack --watch", - "build": "webpack", - "lint": "eslint --quiet 'src/**/*.{ts,vue}'" + "watch": "vite build --watch --mode development", + "build": "vite build", + "lint": "eslint --quiet \"src/**/*.{ts,vue}\"" }, "resolutions": { "chokidar": "^3.3.1", "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.1", + "@discordapp/twemoji": "14.0.2", "@fortawesome/fontawesome-free": "6.1.1", + "@rollup/plugin-alias": "3.1.9", + "@rollup/plugin-json": "4.1.0", "@syuilo/aiscript": "0.11.1", - "@types/escape-regexp": "0.0.1", - "@types/glob": "7.2.0", - "@types/gulp": "4.0.9", - "@types/gulp-rename": "2.0.1", - "@types/is-url": "1.2.30", - "@types/katex": "0.14.0", - "@types/matter-js": "0.17.7", - "@types/mocha": "9.1.0", - "@types/oauth": "0.9.1", - "@types/parse5": "6.0.3", - "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.2", - "@types/random-seed": "0.3.3", - "@types/seedrandom": "3.0.2", - "@types/throttle-debounce": "2.1.0", - "@types/tinycolor2": "1.4.3", - "@types/uuid": "8.3.4", - "@types/webpack": "5.28.0", - "@types/webpack-stream": "3.2.12", - "@types/websocket": "1.0.5", - "@types/ws": "8.5.3", - "@typescript-eslint/parser": "5.18.0", - "@vue/compiler-sfc": "3.2.31", + "@vitejs/plugin-vue": "2.3.3", + "@vue/compiler-sfc": "3.2.37", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", "autosize": "5.0.1", "autwh": "0.1.0", "blurhash": "1.1.5", - "broadcast-channel": "4.10.0", - "chart.js": "3.7.1", + "broadcast-channel": "4.13.0", + "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2", + "chart.js": "3.8.0", "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-gradient": "0.2.2", + "chartjs-plugin-gradient": "0.5.0", "chartjs-plugin-zoom": "1.2.1", "compare-versions": "4.1.3", "content-disposition": "0.5.4", - "css-loader": "6.7.1", - "cssnano": "5.1.7", + "cropperjs": "2.0.0-beta", "date-fns": "2.28.0", "escape-regexp": "0.0.1", - "eslint": "8.13.0", - "eslint-plugin-vue": "8.6.0", "eventemitter3": "4.0.7", "feed": "4.2.2", - "glob": "7.2.0", "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", - "ip-cidr": "3.0.4", "json5": "2.2.1", - "json5-loader": "4.0.1", - "katex": "0.15.3", + "katex": "0.15.6", "matter-js": "0.18.0", - "mfm-js": "0.21.0", + "mfm-js": "0.22.1", "misskey-js": "0.0.14", - "mocha": "9.2.2", + "mocha": "10.0.0", "ms": "2.1.3", "nested-property": "4.0.0", - "parse5": "6.0.1", - "photoswipe": "5.2.4", - "portscanner": "2.2.0", - "postcss": "8.4.12", - "postcss-loader": "6.2.1", - "prismjs": "1.27.0", + "photoswipe": "5.2.7", + "prismjs": "1.28.0", "private-ip": "2.3.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -84,43 +56,58 @@ "random-seed": "0.3.0", "reflect-metadata": "0.1.13", "rndstr": "1.0.0", + "rollup": "2.75.6", "s-age": "1.1.2", - "sass": "1.50.0", - "sass-loader": "12.6.0", + "sass": "1.52.3", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.139.2", - "throttle-debounce": "4.0.0", + "three": "0.141.0", + "throttle-debounce": "5.0.0", "tinycolor2": "1.4.2", - "ts-loader": "9.2.8", - "tsc-alias": "1.5.0", - "tsconfig-paths": "3.14.1", + "tsc-alias": "1.6.9", + "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", - "typescript": "4.6.3", + "typescript": "4.7.3", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.31", - "vue-loader": "17.0.0", + "vite": "2.9.10", + "vue": "3.2.37", "vue-prism-editor": "2.0.0-alpha.2", - "vue-router": "4.0.14", - "vue-style-loader": "4.1.3", - "vue-svg-loader": "0.17.0-beta.2", + "vue-router": "4.0.16", "vuedraggable": "4.0.1", - "webpack": "5.72.0", - "webpack-cli": "4.9.2", "websocket": "1.0.34", - "ws": "8.5.0" + "ws": "8.8.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "5.18.0", + "@types/escape-regexp": "0.0.1", + "@types/glob": "7.2.0", + "@types/gulp": "4.0.9", + "@types/gulp-rename": "2.0.1", + "@types/is-url": "1.2.30", + "@types/katex": "0.14.0", + "@types/matter-js": "0.17.7", + "@types/mocha": "9.1.1", + "@types/oauth": "0.9.1", + "@types/punycode": "2.1.0", + "@types/qrcode": "1.4.2", + "@types/random-seed": "0.3.3", + "@types/seedrandom": "3.0.2", + "@types/throttle-debounce": "5.0.0", + "@types/tinycolor2": "1.4.3", + "@types/uuid": "8.3.4", + "@types/websocket": "1.0.5", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "cypress": "9.5.3", + "cypress": "10.0.3", + "eslint": "8.17.0", "eslint-plugin-import": "2.26.0", + "eslint-plugin-vue": "9.1.0", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index f4dcab319c..ce4af61f18 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -1,5 +1,5 @@ import { del, get, set } from '@/scripts/idb-proxy'; -import { reactive } from 'vue'; +import { defineAsyncComponent, reactive } from 'vue'; import * as misskey from 'misskey-js'; import { apiUrl } from '@/config'; import { waiting, api, popup, popupMenu, success, alert } from '@/os'; @@ -11,10 +11,10 @@ import { i18n } from './i18n'; type Account = misskey.entities.MeDetailed; -const data = localStorage.getItem('account'); +const accountData = localStorage.getItem('account'); // TODO: ๅค้จใใใฏreadonlyใซ -export const $i = data ? reactive(JSON.parse(data) as Account) : null; +export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); @@ -52,7 +52,7 @@ export async function signout() { return Promise.all(registrations.map(registration => registration.unregister())); }); } - } catch (e) {} + } catch (err) {} //#endregion document.cookie = `igi=; path=/`; @@ -104,8 +104,8 @@ function fetchAccount(token: string): Promise<Account> { }); } -export function updateAccount(data) { - for (const [key, value] of Object.entries(data)) { +export function updateAccount(accountData) { + for (const [key, value] of Object.entries(accountData)) { $i[key] = value; } localStorage.setItem('account', JSON.stringify($i)); @@ -141,7 +141,7 @@ export async function openAccountMenu(opts: { onChoose?: (account: misskey.entities.UserDetailed) => void; }, ev: MouseEvent) { function showSigninDialog() { - popup(import('@/components/signin-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, { done: res => { addAccount(res.id, res.i); success(); @@ -150,7 +150,7 @@ export async function openAccountMenu(opts: { } function createAccount() { - popup(import('@/components/signup-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, { done: res => { addAccount(res.id, res.i); switchAccountWithToken(res.i); diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index f2cb369802..5114349620 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -37,7 +37,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const window = ref<InstanceType<typeof XWindow>>(); diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue index b67cef209b..a947406f88 100644 --- a/packages/client/src/components/abuse-report.vue +++ b/packages/client/src/components/abuse-report.vue @@ -2,7 +2,7 @@ <div class="bcekxzvu _card _gap"> <div class="_content target"> <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> - <MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId"> + <MkA v-user-preview="report.targetUserId" class="info" :to="userPage(report.targetUser)"> <MkUserName class="name" :user="report.targetUser"/> <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> </MkA> @@ -43,20 +43,20 @@ export default defineComponent({ MkSwitch, }, - emits: ['resolved'], - props: { report: { type: Object, required: true, } - } + }, + + emits: ['resolved'], data() { return { forward: this.report.forwarded, }; - } + }, methods: { acct, diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 59b8e97304..18dd1e3f41 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -42,7 +42,7 @@ <script lang="ts" setup> import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; withDefaults(defineProps<{ thickness: number; diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index adeac4e050..1e4a4506f7 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -131,8 +131,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'done', v: { type: string; value: any }): void; - (e: 'closed'): void; + (event: 'done', value: { type: string; value: any }): void; + (event: 'closed'): void; }>(); const suggests = ref<Element>(); @@ -152,7 +152,7 @@ function complete(type: string, value: any) { emit('closed'); if (type === 'emoji') { let recents = defaultStore.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== value); + recents = recents.filter((emoji: any) => emoji !== value); recents.unshift(value); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); } @@ -232,7 +232,7 @@ function exec() { } else if (props.type === 'emoji') { if (!props.q || props.q === '') { // ๆ่ฟไฝฟใฃใ็ตตๆๅญใใตใธใงในใ - emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji === emoji)).filter(x => x) as EmojiDef[]; + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; return; } @@ -269,17 +269,17 @@ function exec() { } } -function onMousedown(e: Event) { - if (!contains(rootEl.value, e.target) && (rootEl.value !== e.target)) props.close(); +function onMousedown(event: Event) { + if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close(); } -function onKeydown(e: KeyboardEvent) { +function onKeydown(event: KeyboardEvent) { const cancel = () => { - e.preventDefault(); - e.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); }; - switch (e.key) { + switch (event.key) { case 'Enter': if (select.value !== -1) { cancel(); @@ -310,7 +310,7 @@ function onKeydown(e: KeyboardEvent) { break; default: - e.stopPropagation(); + event.stopPropagation(); props.textarea.focus(); } } diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index ccd8880df8..183658471b 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -27,8 +27,7 @@ type CaptchaContainer = { }; declare global { - interface Window extends CaptchaContainer { - } + interface Window extends CaptchaContainer { } } const props = defineProps<{ diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index 7bbf5ae663..dff02beec0 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -48,8 +48,8 @@ async function onClick() { }); isFollowing.value = true; } - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); } finally { wait.value = false; } diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index cc1aa9c20a..4e9c4e587a 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -7,8 +7,13 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref, watch, PropType, onUnmounted, shallowRef } from 'vue'; +<script lang="ts" setup> +/* eslint-disable id-denylist -- + Chart.js has a `data` attribute in most chart definitions, which triggers the + id-denylist violation when setting it. This is causing about 60+ lint issues. + As this is part of Chart.js's API it makes sense to disable the check here. +*/ +import { defineProps, onMounted, ref, watch, PropType, onUnmounted } from 'vue'; import { Chart, ArcElement, @@ -29,11 +34,53 @@ import { import 'chartjs-adapter-date-fns'; import { enUS } from 'date-fns/locale'; import zoomPlugin from 'chartjs-plugin-zoom'; -import gradient from 'chartjs-plugin-gradient'; +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114242002 +// We can't use gradient because Vite throws a error. +//import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; import { defaultStore } from '@/store'; import MkChartTooltip from '@/components/chart-tooltip.vue'; +const props = defineProps({ + src: { + type: String, + required: true, + }, + args: { + type: Object, + required: false, + }, + limit: { + type: Number, + required: false, + default: 90 + }, + span: { + type: String as PropType<'hour' | 'day'>, + required: true, + }, + detailed: { + type: Boolean, + required: false, + default: false + }, + stacked: { + type: Boolean, + required: false, + default: false + }, + bar: { + type: Boolean, + required: false, + default: false + }, + aspectRatio: { + type: Number, + required: false, + default: null + }, +}); + Chart.register( ArcElement, LineElement, @@ -50,7 +97,7 @@ Chart.register( SubTitle, Filler, zoomPlugin, - gradient, + //gradient, ); const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); @@ -78,826 +125,777 @@ const getColor = (i) => { return colorSets[i % colorSets.length]; }; -export default defineComponent({ - props: { - src: { - type: String, - required: true, - }, - args: { - type: Object, - required: false, - }, - limit: { - type: Number, - required: false, - default: 90 - }, - span: { - type: String as PropType<'hour' | 'day'>, - required: true, - }, - detailed: { - type: Boolean, - required: false, - default: false - }, - stacked: { - type: Boolean, - required: false, - default: false - }, - bar: { - type: Boolean, - required: false, - default: false - }, - aspectRatio: { - type: Number, - required: false, - default: null - }, - }, +const now = new Date(); +let chartInstance: Chart = null; +let chartData: { + series: { + name: string; + type: 'line' | 'area'; + color?: string; + dashed?: boolean; + hidden?: boolean; + data: { + x: number; + y: number; + }[]; + }[]; +} = null; - setup(props) { - const now = new Date(); - let chartInstance: Chart = null; - let data: { - series: { - name: string; - type: 'line' | 'area'; - color?: string; - dashed?: boolean; - hidden?: boolean; - data: { - x: number; - y: number; - }[]; - }[]; - } = null; +const chartEl = ref<HTMLCanvasElement>(null); +const fetching = ref(true); - const chartEl = ref<HTMLCanvasElement>(null); - const fetching = ref(true); +const getDate = (ago: number) => { + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + const h = now.getHours(); - const getDate = (ago: number) => { - const y = now.getFullYear(); - const m = now.getMonth(); - const d = now.getDate(); - const h = now.getHours(); + return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago); +}; - return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago); - }; +const format = (arr) => { + return arr.map((v, i) => ({ + x: getDate(i).getTime(), + y: v + })); +}; - const format = (arr) => { - return arr.map((v, i) => ({ - x: getDate(i).getTime(), - y: v - })); - }; +const tooltipShowing = ref(false); +const tooltipX = ref(0); +const tooltipY = ref(0); +const tooltipTitle = ref(null); +const tooltipSeries = ref(null); +let disposeTooltipComponent; - const tooltipShowing = ref(false); - const tooltipX = ref(0); - const tooltipY = ref(0); - const tooltipTitle = ref(null); - const tooltipSeries = ref(null); - let disposeTooltipComponent; +os.popup(MkChartTooltip, { + showing: tooltipShowing, + x: tooltipX, + y: tooltipY, + title: tooltipTitle, + series: tooltipSeries, +}, {}).then(({ dispose }) => { + disposeTooltipComponent = dispose; +}); - os.popup(MkChartTooltip, { - showing: tooltipShowing, - x: tooltipX, - y: tooltipY, - title: tooltipTitle, - series: tooltipSeries, - }, {}).then(({ dispose }) => { - disposeTooltipComponent = dispose; - }); +function externalTooltipHandler(context) { + if (context.tooltip.opacity === 0) { + tooltipShowing.value = false; + return; + } - function externalTooltipHandler(context) { - if (context.tooltip.opacity === 0) { - tooltipShowing.value = false; - return; - } + tooltipTitle.value = context.tooltip.title[0]; + tooltipSeries.value = context.tooltip.body.map((b, i) => ({ + backgroundColor: context.tooltip.labelColors[i].backgroundColor, + borderColor: context.tooltip.labelColors[i].borderColor, + text: b.lines[0], + })); - tooltipTitle.value = context.tooltip.title[0]; - tooltipSeries.value = context.tooltip.body.map((b, i) => ({ - backgroundColor: context.tooltip.labelColors[i].backgroundColor, - borderColor: context.tooltip.labelColors[i].borderColor, - text: b.lines[0], - })); + const rect = context.chart.canvas.getBoundingClientRect(); - const rect = context.chart.canvas.getBoundingClientRect(); + tooltipShowing.value = true; + tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX; + tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY; +} - tooltipShowing.value = true; - tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX; - tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY; - } +const render = () => { + if (chartInstance) { + chartInstance.destroy(); + } - const render = () => { - if (chartInstance) { - chartInstance.destroy(); - } + const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; + const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + // ใใฉใณใใซใฉใผ + Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - // ใใฉใณใใซใฉใผ - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); + const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y))); - const maxes = data.series.map((x, i) => Math.max(...x.data.map(d => d.y))); - - chartInstance = new Chart(chartEl.value, { - type: props.bar ? 'bar' : 'line', - data: { - labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(), - datasets: data.series.map((x, i) => ({ - parsing: false, - label: x.name, - data: x.data.slice().reverse(), - tension: 0.3, - pointRadius: 0, - borderWidth: props.bar ? 0 : 2, - borderColor: x.color ? x.color : getColor(i), - borderDash: x.dashed ? [5, 5] : [], - borderJoinStyle: 'round', - borderRadius: props.bar ? 3 : undefined, - backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), - gradient: props.bar ? undefined : { - backgroundColor: { - axis: 'y', - colors: { - 0: alpha(x.color ? x.color : getColor(i), 0), - [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2), - }, - }, - }, - barPercentage: 0.9, - categoryPercentage: 0.9, - fill: x.type === 'area', - clip: 8, - hidden: !!x.hidden, - })), - }, - options: { - aspectRatio: props.aspectRatio || 2.5, - layout: { - padding: { - left: 0, - right: 8, - top: 0, - bottom: 0, + chartInstance = new Chart(chartEl.value, { + type: props.bar ? 'bar' : 'line', + data: { + labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(), + datasets: chartData.series.map((x, i) => ({ + parsing: false, + label: x.name, + data: x.data.slice().reverse(), + tension: 0.3, + pointRadius: 0, + borderWidth: props.bar ? 0 : 2, + borderColor: x.color ? x.color : getColor(i), + borderDash: x.dashed ? [5, 5] : [], + borderJoinStyle: 'round', + borderRadius: props.bar ? 3 : undefined, + backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), + /*gradient: props.bar ? undefined : { + backgroundColor: { + axis: 'y', + colors: { + 0: alpha(x.color ? x.color : getColor(i), 0), + [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2), }, }, - scales: { + },*/ + barPercentage: 0.9, + categoryPercentage: 0.9, + fill: x.type === 'area', + clip: 8, + hidden: !!x.hidden, + })), + }, + options: { + aspectRatio: props.aspectRatio || 2.5, + layout: { + padding: { + left: 0, + right: 8, + top: 0, + bottom: 0, + }, + }, + scales: { + x: { + type: 'time', + stacked: props.stacked, + offset: false, + time: { + stepSize: 1, + unit: props.span === 'day' ? 'month' : 'day', + }, + grid: { + color: gridColor, + borderColor: 'rgb(0, 0, 0, 0)', + }, + ticks: { + display: props.detailed, + maxRotation: 0, + autoSkipPadding: 16, + }, + adapters: { + date: { + locale: enUS, + }, + }, + min: getDate(props.limit).getTime(), + }, + y: { + position: 'left', + stacked: props.stacked, + suggestedMax: 50, + grid: { + color: gridColor, + borderColor: 'rgb(0, 0, 0, 0)', + }, + ticks: { + display: props.detailed, + //mirror: true, + }, + }, + }, + interaction: { + intersect: false, + mode: 'index', + }, + elements: { + point: { + hoverRadius: 5, + hoverBorderWidth: 2, + }, + }, + animation: false, + plugins: { + legend: { + display: props.detailed, + position: 'bottom', + labels: { + boxWidth: 16, + }, + }, + tooltip: { + enabled: false, + mode: 'index', + animation: { + duration: 0, + }, + external: externalTooltipHandler, + }, + zoom: props.detailed ? { + pan: { + enabled: true, + }, + zoom: { + wheel: { + enabled: true, + }, + pinch: { + enabled: true, + }, + drag: { + enabled: false, + }, + mode: 'x', + }, + limits: { x: { - type: 'time', - stacked: props.stacked, - offset: false, - time: { - stepSize: 1, - unit: props.span === 'day' ? 'month' : 'day', - }, - grid: { - color: gridColor, - borderColor: 'rgb(0, 0, 0, 0)', - }, - ticks: { - display: props.detailed, - maxRotation: 0, - autoSkipPadding: 16, - }, - adapters: { - date: { - locale: enUS, - }, - }, - min: getDate(props.limit).getTime(), + min: 'original', + max: 'original', }, y: { - position: 'left', - stacked: props.stacked, - suggestedMax: 50, - grid: { - color: gridColor, - borderColor: 'rgb(0, 0, 0, 0)', - }, - ticks: { - display: props.detailed, - //mirror: true, - }, + min: 'original', + max: 'original', }, - }, - interaction: { - intersect: false, - mode: 'index', - }, - elements: { - point: { - hoverRadius: 5, - hoverBorderWidth: 2, - }, - }, - animation: false, - plugins: { - legend: { - display: props.detailed, - position: 'bottom', - labels: { - boxWidth: 16, - }, - }, - tooltip: { - enabled: false, - mode: 'index', - animation: { - duration: 0, - }, - external: externalTooltipHandler, - }, - zoom: props.detailed ? { - pan: { - enabled: true, - }, - zoom: { - wheel: { - enabled: true, - }, - pinch: { - enabled: true, - }, - drag: { - enabled: false, - }, - mode: 'x', - }, - limits: { - x: { - min: 'original', - max: 'original', - }, - y: { - min: 'original', - max: 'original', - }, - } - } : undefined, - gradient, - }, - }, - plugins: [{ - id: 'vLine', - beforeDraw(chart, args, options) { - if (chart.tooltip._active && chart.tooltip._active.length) { - const activePoint = chart.tooltip._active[0]; - const ctx = chart.ctx; - const x = activePoint.element.x; - const topY = chart.scales.y.top; - const bottomY = chart.scales.y.bottom; - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(x, bottomY); - ctx.lineTo(x, topY); - ctx.lineWidth = 1; - ctx.strokeStyle = vLineColor; - ctx.stroke(); - ctx.restore(); - } } - }] - }); - }; + } : undefined, + //gradient, + }, + }, + plugins: [{ + id: 'vLine', + beforeDraw(chart, args, options) { + if (chart.tooltip._active && chart.tooltip._active.length) { + const activePoint = chart.tooltip._active[0]; + const ctx = chart.ctx; + const x = activePoint.element.x; + const topY = chart.scales.y.top; + const bottomY = chart.scales.y.bottom; - const exportData = () => { - // TODO - }; - - const fetchFederationChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Received', - type: 'area', - data: format(raw.inboxInstances), - color: colors.blue, - }, { - name: 'Delivered', - type: 'area', - data: format(raw.deliveredInstances), - color: colors.green, - }, { - name: 'Stalled', - type: 'area', - data: format(raw.stalled), - color: colors.red, - }, { - name: 'Pub Active', - type: 'line', - data: format(raw.pubActive), - color: colors.purple, - }, { - name: 'Sub Active', - type: 'line', - data: format(raw.subActive), - color: colors.orange, - }, { - name: 'Pub & Sub', - type: 'line', - data: format(raw.pubsub), - dashed: true, - color: colors.cyan, - }, { - name: 'Pub', - type: 'line', - data: format(raw.pub), - dashed: true, - color: colors.purple, - }, { - name: 'Sub', - type: 'line', - data: format(raw.sub), - dashed: true, - color: colors.orange, - }], - }; - }; - - const fetchApRequestChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'In', - type: 'area', - color: '#008FFB', - data: format(raw.inboxReceived) - }, { - name: 'Out (succ)', - type: 'area', - color: '#00E396', - data: format(raw.deliverSucceeded) - }, { - name: 'Out (fail)', - type: 'area', - color: '#FEB019', - data: format(raw.deliverFailed) - }] - }; - }; - - const fetchNotesChart = async (type: string): Promise<typeof data> => { - const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'All', - type: 'line', - data: format(type == 'combined' - ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) - : sum(raw[type].inc, negate(raw[type].dec)) - ), - color: '#888888', - }, { - name: 'Renotes', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.renote, raw.remote.diffs.renote) - : raw[type].diffs.renote - ), - color: colors.green, - }, { - name: 'Replies', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.reply, raw.remote.diffs.reply) - : raw[type].diffs.reply - ), - color: colors.yellow, - }, { - name: 'Normal', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.normal, raw.remote.diffs.normal) - : raw[type].diffs.normal - ), - color: colors.blue, - }, { - name: 'With file', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile) - : raw[type].diffs.withFile - ), - color: colors.purple, - }], - }; - }; - - const fetchNotesTotalChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Combined', - type: 'line', - data: format(sum(raw.local.total, raw.remote.total)), - }, { - name: 'Local', - type: 'area', - data: format(raw.local.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.total), - }], - }; - }; - - const fetchUsersChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/users', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Combined', - type: 'line', - data: format(total - ? sum(raw.local.total, raw.remote.total) - : sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) - ), - }, { - name: 'Local', - type: 'area', - data: format(total - ? raw.local.total - : sum(raw.local.inc, negate(raw.local.dec)) - ), - }, { - name: 'Remote', - type: 'area', - data: format(total - ? raw.remote.total - : sum(raw.remote.inc, negate(raw.remote.dec)) - ), - }], - }; - }; - - const fetchActiveUsersChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Read & Write', - type: 'area', - data: format(raw.readWrite), - color: colors.orange, - }, { - name: 'Write', - type: 'area', - data: format(raw.write), - color: colors.lime, - }, { - name: 'Read', - type: 'area', - data: format(raw.read), - color: colors.blue, - }, { - name: '< Week', - type: 'area', - data: format(raw.registeredWithinWeek), - color: colors.green, - }, { - name: '< Month', - type: 'area', - data: format(raw.registeredWithinMonth), - color: colors.yellow, - }, { - name: '< Year', - type: 'area', - data: format(raw.registeredWithinYear), - color: colors.red, - }, { - name: '> Week', - type: 'area', - data: format(raw.registeredOutsideWeek), - color: colors.yellow, - }, { - name: '> Month', - type: 'area', - data: format(raw.registeredOutsideMonth), - color: colors.red, - }, { - name: '> Year', - type: 'area', - data: format(raw.registeredOutsideYear), - color: colors.purple, - }], - }; - }; - - const fetchDriveChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); - return { - bytes: true, - series: [{ - name: 'All', - type: 'line', - dashed: true, - data: format( - sum( - raw.local.incSize, - negate(raw.local.decSize), - raw.remote.incSize, - negate(raw.remote.decSize) - ) - ), - }, { - name: 'Local +', - type: 'area', - data: format(raw.local.incSize), - }, { - name: 'Local -', - type: 'area', - data: format(negate(raw.local.decSize)), - }, { - name: 'Remote +', - type: 'area', - data: format(raw.remote.incSize), - }, { - name: 'Remote -', - type: 'area', - data: format(negate(raw.remote.decSize)), - }], - }; - }; - - const fetchDriveFilesChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'All', - type: 'line', - dashed: true, - data: format( - sum( - raw.local.incCount, - negate(raw.local.decCount), - raw.remote.incCount, - negate(raw.remote.decCount) - ) - ), - }, { - name: 'Local +', - type: 'area', - data: format(raw.local.incCount), - }, { - name: 'Local -', - type: 'area', - data: format(negate(raw.local.decCount)), - }, { - name: 'Remote +', - type: 'area', - data: format(raw.remote.incCount), - }, { - name: 'Remote -', - type: 'area', - data: format(negate(raw.remote.decCount)), - }], - }; - }; - - const fetchInstanceRequestsChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'In', - type: 'area', - color: '#008FFB', - data: format(raw.requests.received) - }, { - name: 'Out (succ)', - type: 'area', - color: '#00E396', - data: format(raw.requests.succeeded) - }, { - name: 'Out (fail)', - type: 'area', - color: '#FEB019', - data: format(raw.requests.failed) - }] - }; - }; - - const fetchInstanceUsersChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Users', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.users.total - : sum(raw.users.inc, negate(raw.users.dec)) - ) - }] - }; - }; - - const fetchInstanceNotesChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Notes', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.notes.total - : sum(raw.notes.inc, negate(raw.notes.dec)) - ) - }] - }; - }; - - const fetchInstanceFfChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Following', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.following.total - : sum(raw.following.inc, negate(raw.following.dec)) - ) - }, { - name: 'Followers', - type: 'area', - color: '#00E396', - data: format(total - ? raw.followers.total - : sum(raw.followers.inc, negate(raw.followers.dec)) - ) - }] - }; - }; - - const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - bytes: true, - series: [{ - name: 'Drive usage', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.drive.totalUsage - : sum(raw.drive.incUsage, negate(raw.drive.decUsage)) - ) - }] - }; - }; - - const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Drive files', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.drive.totalFiles - : sum(raw.drive.incFiles, negate(raw.drive.decFiles)) - ) - }] - }; - }; - - const fetchPerUserNotesChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [...(props.args.withoutAll ? [] : [{ - name: 'All', - type: 'line', - data: format(sum(raw.inc, negate(raw.dec))), - color: '#888888', - }]), { - name: 'With file', - type: 'area', - data: format(raw.diffs.withFile), - color: colors.purple, - }, { - name: 'Renotes', - type: 'area', - data: format(raw.diffs.renote), - color: colors.green, - }, { - name: 'Replies', - type: 'area', - data: format(raw.diffs.reply), - color: colors.yellow, - }, { - name: 'Normal', - type: 'area', - data: format(raw.diffs.normal), - color: colors.blue, - }], - }; - }; - - const fetchPerUserFollowingChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Local', - type: 'area', - data: format(raw.local.followings.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.followings.total), - }], - }; - }; - - const fetchPerUserFollowersChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Local', - type: 'area', - data: format(raw.local.followers.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.followers.total), - }], - }; - }; - - const fetchPerUserDriveChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Inc', - type: 'area', - data: format(raw.incSize), - }, { - name: 'Dec', - type: 'area', - data: format(raw.decSize), - }], - }; - }; - - const fetchAndRender = async () => { - const fetchData = () => { - switch (props.src) { - case 'federation': return fetchFederationChart(); - case 'ap-request': return fetchApRequestChart(); - case 'users': return fetchUsersChart(false); - case 'users-total': return fetchUsersChart(true); - case 'active-users': return fetchActiveUsersChart(); - case 'notes': return fetchNotesChart('combined'); - case 'local-notes': return fetchNotesChart('local'); - case 'remote-notes': return fetchNotesChart('remote'); - case 'notes-total': return fetchNotesTotalChart(); - case 'drive': return fetchDriveChart(); - case 'drive-files': return fetchDriveFilesChart(); - - case 'instance-requests': return fetchInstanceRequestsChart(); - case 'instance-users': return fetchInstanceUsersChart(false); - case 'instance-users-total': return fetchInstanceUsersChart(true); - case 'instance-notes': return fetchInstanceNotesChart(false); - case 'instance-notes-total': return fetchInstanceNotesChart(true); - case 'instance-ff': return fetchInstanceFfChart(false); - case 'instance-ff-total': return fetchInstanceFfChart(true); - case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false); - case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true); - case 'instance-drive-files': return fetchInstanceDriveFilesChart(false); - case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); - - case 'per-user-notes': return fetchPerUserNotesChart(); - case 'per-user-following': return fetchPerUserFollowingChart(); - case 'per-user-followers': return fetchPerUserFollowersChart(); - case 'per-user-drive': return fetchPerUserDriveChart(); + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x, bottomY); + ctx.lineTo(x, topY); + ctx.lineWidth = 1; + ctx.strokeStyle = vLineColor; + ctx.stroke(); + ctx.restore(); } - }; - fetching.value = true; - data = await fetchData(); - fetching.value = false; - render(); - }; + } + }] + }); +}; - watch(() => [props.src, props.span], fetchAndRender); +const exportData = () => { + // TODO +}; - onMounted(() => { - fetchAndRender(); - }); +const fetchFederationChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Received', + type: 'area', + data: format(raw.inboxInstances), + color: colors.blue, + }, { + name: 'Delivered', + type: 'area', + data: format(raw.deliveredInstances), + color: colors.green, + }, { + name: 'Stalled', + type: 'area', + data: format(raw.stalled), + color: colors.red, + }, { + name: 'Pub Active', + type: 'line', + data: format(raw.pubActive), + color: colors.purple, + }, { + name: 'Sub Active', + type: 'line', + data: format(raw.subActive), + color: colors.orange, + }, { + name: 'Pub & Sub', + type: 'line', + data: format(raw.pubsub), + dashed: true, + color: colors.cyan, + }, { + name: 'Pub', + type: 'line', + data: format(raw.pub), + dashed: true, + color: colors.purple, + }, { + name: 'Sub', + type: 'line', + data: format(raw.sub), + dashed: true, + color: colors.orange, + }], + }; +}; - onUnmounted(() => { - if (disposeTooltipComponent) disposeTooltipComponent(); - }); +const fetchApRequestChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'In', + type: 'area', + color: '#008FFB', + data: format(raw.inboxReceived) + }, { + name: 'Out (succ)', + type: 'area', + color: '#00E396', + data: format(raw.deliverSucceeded) + }, { + name: 'Out (fail)', + type: 'area', + color: '#FEB019', + data: format(raw.deliverFailed) + }] + }; +}; - return { - chartEl, - fetching, - }; - }, +const fetchNotesChart = async (type: string): Promise<typeof chartData> => { + const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'All', + type: 'line', + data: format(type === 'combined' + ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) + : sum(raw[type].inc, negate(raw[type].dec)) + ), + color: '#888888', + }, { + name: 'Renotes', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.renote, raw.remote.diffs.renote) + : raw[type].diffs.renote + ), + color: colors.green, + }, { + name: 'Replies', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.reply, raw.remote.diffs.reply) + : raw[type].diffs.reply + ), + color: colors.yellow, + }, { + name: 'Normal', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.normal, raw.remote.diffs.normal) + : raw[type].diffs.normal + ), + color: colors.blue, + }, { + name: 'With file', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile) + : raw[type].diffs.withFile + ), + color: colors.purple, + }], + }; +}; + +const fetchNotesTotalChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Combined', + type: 'line', + data: format(sum(raw.local.total, raw.remote.total)), + }, { + name: 'Local', + type: 'area', + data: format(raw.local.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.total), + }], + }; +}; + +const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/users', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Combined', + type: 'line', + data: format(total + ? sum(raw.local.total, raw.remote.total) + : sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) + ), + }, { + name: 'Local', + type: 'area', + data: format(total + ? raw.local.total + : sum(raw.local.inc, negate(raw.local.dec)) + ), + }, { + name: 'Remote', + type: 'area', + data: format(total + ? raw.remote.total + : sum(raw.remote.inc, negate(raw.remote.dec)) + ), + }], + }; +}; + +const fetchActiveUsersChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Read & Write', + type: 'area', + data: format(raw.readWrite), + color: colors.orange, + }, { + name: 'Write', + type: 'area', + data: format(raw.write), + color: colors.lime, + }, { + name: 'Read', + type: 'area', + data: format(raw.read), + color: colors.blue, + }, { + name: '< Week', + type: 'area', + data: format(raw.registeredWithinWeek), + color: colors.green, + }, { + name: '< Month', + type: 'area', + data: format(raw.registeredWithinMonth), + color: colors.yellow, + }, { + name: '< Year', + type: 'area', + data: format(raw.registeredWithinYear), + color: colors.red, + }, { + name: '> Week', + type: 'area', + data: format(raw.registeredOutsideWeek), + color: colors.yellow, + }, { + name: '> Month', + type: 'area', + data: format(raw.registeredOutsideMonth), + color: colors.red, + }, { + name: '> Year', + type: 'area', + data: format(raw.registeredOutsideYear), + color: colors.purple, + }], + }; +}; + +const fetchDriveChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); + return { + bytes: true, + series: [{ + name: 'All', + type: 'line', + dashed: true, + data: format( + sum( + raw.local.incSize, + negate(raw.local.decSize), + raw.remote.incSize, + negate(raw.remote.decSize) + ) + ), + }, { + name: 'Local +', + type: 'area', + data: format(raw.local.incSize), + }, { + name: 'Local -', + type: 'area', + data: format(negate(raw.local.decSize)), + }, { + name: 'Remote +', + type: 'area', + data: format(raw.remote.incSize), + }, { + name: 'Remote -', + type: 'area', + data: format(negate(raw.remote.decSize)), + }], + }; +}; + +const fetchDriveFilesChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'All', + type: 'line', + dashed: true, + data: format( + sum( + raw.local.incCount, + negate(raw.local.decCount), + raw.remote.incCount, + negate(raw.remote.decCount) + ) + ), + }, { + name: 'Local +', + type: 'area', + data: format(raw.local.incCount), + }, { + name: 'Local -', + type: 'area', + data: format(negate(raw.local.decCount)), + }, { + name: 'Remote +', + type: 'area', + data: format(raw.remote.incCount), + }, { + name: 'Remote -', + type: 'area', + data: format(negate(raw.remote.decCount)), + }], + }; +}; + +const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'In', + type: 'area', + color: '#008FFB', + data: format(raw.requests.received) + }, { + name: 'Out (succ)', + type: 'area', + color: '#00E396', + data: format(raw.requests.succeeded) + }, { + name: 'Out (fail)', + type: 'area', + color: '#FEB019', + data: format(raw.requests.failed) + }] + }; +}; + +const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Users', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.users.total + : sum(raw.users.inc, negate(raw.users.dec)) + ) + }] + }; +}; + +const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Notes', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.notes.total + : sum(raw.notes.inc, negate(raw.notes.dec)) + ) + }] + }; +}; + +const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Following', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.following.total + : sum(raw.following.inc, negate(raw.following.dec)) + ) + }, { + name: 'Followers', + type: 'area', + color: '#00E396', + data: format(total + ? raw.followers.total + : sum(raw.followers.inc, negate(raw.followers.dec)) + ) + }] + }; +}; + +const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + bytes: true, + series: [{ + name: 'Drive usage', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.drive.totalUsage + : sum(raw.drive.incUsage, negate(raw.drive.decUsage)) + ) + }] + }; +}; + +const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Drive files', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.drive.totalFiles + : sum(raw.drive.incFiles, negate(raw.drive.decFiles)) + ) + }] + }; +}; + +const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [...(props.args.withoutAll ? [] : [{ + name: 'All', + type: 'line', + data: format(sum(raw.inc, negate(raw.dec))), + color: '#888888', + }]), { + name: 'With file', + type: 'area', + data: format(raw.diffs.withFile), + color: colors.purple, + }, { + name: 'Renotes', + type: 'area', + data: format(raw.diffs.renote), + color: colors.green, + }, { + name: 'Replies', + type: 'area', + data: format(raw.diffs.reply), + color: colors.yellow, + }, { + name: 'Normal', + type: 'area', + data: format(raw.diffs.normal), + color: colors.blue, + }], + }; +}; + +const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followings.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followings.total), + }], + }; +}; + +const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followers.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followers.total), + }], + }; +}; + +const fetchPerUserDriveChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Inc', + type: 'area', + data: format(raw.incSize), + }, { + name: 'Dec', + type: 'area', + data: format(raw.decSize), + }], + }; +}; + +const fetchAndRender = async () => { + const fetchData = () => { + switch (props.src) { + case 'federation': return fetchFederationChart(); + case 'ap-request': return fetchApRequestChart(); + case 'users': return fetchUsersChart(false); + case 'users-total': return fetchUsersChart(true); + case 'active-users': return fetchActiveUsersChart(); + case 'notes': return fetchNotesChart('combined'); + case 'local-notes': return fetchNotesChart('local'); + case 'remote-notes': return fetchNotesChart('remote'); + case 'notes-total': return fetchNotesTotalChart(); + case 'drive': return fetchDriveChart(); + case 'drive-files': return fetchDriveFilesChart(); + case 'instance-requests': return fetchInstanceRequestsChart(); + case 'instance-users': return fetchInstanceUsersChart(false); + case 'instance-users-total': return fetchInstanceUsersChart(true); + case 'instance-notes': return fetchInstanceNotesChart(false); + case 'instance-notes-total': return fetchInstanceNotesChart(true); + case 'instance-ff': return fetchInstanceFfChart(false); + case 'instance-ff-total': return fetchInstanceFfChart(true); + case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false); + case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true); + case 'instance-drive-files': return fetchInstanceDriveFilesChart(false); + case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); + + case 'per-user-notes': return fetchPerUserNotesChart(); + case 'per-user-following': return fetchPerUserFollowingChart(); + case 'per-user-followers': return fetchPerUserFollowersChart(); + case 'per-user-drive': return fetchPerUserDriveChart(); + } + }; + fetching.value = true; + chartData = await fetchData(); + fetching.value = false; + render(); +}; + +watch(() => [props.src, props.span], fetchAndRender); + +onMounted(() => { + fetchAndRender(); }); + +onUnmounted(() => { + if (disposeTooltipComponent) disposeTooltipComponent(); +}); +/* eslint-enable id-denylist */ </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue new file mode 100644 index 0000000000..a8bde6ea05 --- /dev/null +++ b/packages/client/src/components/cropper-dialog.vue @@ -0,0 +1,175 @@ +<template> +<XModalWindow + ref="dialogEl" + :width="800" + :height="500" + :scroll="false" + :with-ok-button="true" + @close="cancel()" + @ok="ok()" + @closed="$emit('closed')" +> + <template #header>{{ $ts.cropImage }}</template> + <template #default="{ width, height }"> + <div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`"> + <Transition name="fade"> + <div v-if="loading" class="loading"> + <MkLoading/> + </div> + </Transition> + <div class="container"> + <img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad"> + </div> + </div> + </template> +</XModalWindow> +</template> + +<script lang="ts" setup> +import { nextTick, onMounted } from 'vue'; +import * as misskey from 'misskey-js'; +import Cropper from 'cropperjs'; +import tinycolor from 'tinycolor2'; +import XModalWindow from '@/components/ui/modal-window.vue'; +import * as os from '@/os'; +import { $i } from '@/account'; +import { defaultStore } from '@/store'; +import { apiUrl, url } from '@/config'; +import { query } from '@/scripts/url'; + +const emit = defineEmits<{ + (ev: 'ok', cropped: misskey.entities.DriveFile): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; +}>(); + +const props = defineProps<{ + file: misskey.entities.DriveFile; + aspectRatio: number; +}>(); + +const imgUrl = `${url}/proxy/image.webp?${query({ + url: props.file.url, +})}`; +let dialogEl = $ref<InstanceType<typeof XModalWindow>>(); +let imgEl = $ref<HTMLImageElement>(); +let cropper: Cropper | null = null; +let loading = $ref(true); + +const ok = async () => { + const promise = new Promise<misskey.entities.DriveFile>(async (res) => { + const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); + croppedCanvas.toBlob(blob => { + const formData = new FormData(); + formData.append('file', blob); + formData.append('i', $i.token); + if (defaultStore.state.uploadFolder) { + formData.append('folderId', defaultStore.state.uploadFolder); + } + + fetch(apiUrl + '/drive/files/create', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(f => { + res(f); + }); + }); + }); + + os.promiseDialog(promise); + + const f = await promise; + + emit('ok', f); + dialogEl.close(); +}; + +const cancel = () => { + emit('cancel'); + dialogEl.close(); +}; + +const onImageLoad = () => { + loading = false; + + if (cropper) { + cropper.getCropperImage()!.$center('contain'); + cropper.getCropperSelection()!.$center(); + } +}; + +onMounted(() => { + cropper = new Cropper(imgEl, { + }); + + const computedStyle = getComputedStyle(document.documentElement); + + const selection = cropper.getCropperSelection()!; + selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + selection.aspectRatio = props.aspectRatio; + selection.initialAspectRatio = props.aspectRatio; + selection.outlined = true; + + window.setTimeout(() => { + cropper.getCropperImage()!.$center('contain'); + selection.$center(); + }, 100); + + // ใขใผใใซใชใผใใณใขใใกใผใทใงใณใ็ตใใฃใใใจใงๅๅบฆ่ชฟๆด + window.setTimeout(() => { + cropper.getCropperImage()!.$center('contain'); + selection.$center(); + }, 500); +}); +</script> + +<style lang="scss" scoped> +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s ease 0.5s; +} +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.mk-cropper-dialog { + display: flex; + flex-direction: column; + width: var(--vw); + height: var(--vh); + position: relative; + + > .loading { + position: absolute; + z-index: 10; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + -webkit-backdrop-filter: var(--blur, blur(10px)); + backdrop-filter: var(--blur, blur(10px)); + background: rgba(0, 0, 0, 0.5); + } + + > .container { + flex: 1; + width: 100%; + height: 100%; + + > ::v-deep(cropper-canvas) { + width: 100%; + height: 100%; + + > cropper-selection > cropper-handle[action="move"] { + background: transparent; + } + } + } +} +</style> diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index e7c9aabe4e..dd906f9bf3 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -18,7 +18,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'update:modelValue', v: boolean): void; + (ev: 'update:modelValue', v: boolean): void; }>(); const label = computed(() => { diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index 3e106a4f0c..b090f3cb4e 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -90,8 +90,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done', v: { canceled: boolean; result: any }): void; - (e: 'closed'): void; + (ev: 'done', v: { canceled: boolean; result: any }): void; + (ev: 'closed'): void; }>(); const modal = ref<InstanceType<typeof MkModal>>(); @@ -122,14 +122,14 @@ function onBgClick() { if (props.cancelableByBgClick) cancel(); } */ -function onKeydown(e: KeyboardEvent) { - if (e.key === 'Escape') cancel(); +function onKeydown(evt: KeyboardEvent) { + if (evt.key === 'Escape') cancel(); } -function onInputKeydown(e: KeyboardEvent) { - if (e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); +function onInputKeydown(evt: KeyboardEvent) { + if (evt.key === 'Enter') { + evt.preventDefault(); + evt.stopPropagation(); ok(); } } diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index 81b80e7e8e..dd24440e82 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -42,7 +42,7 @@ const is = computed(() => { "application/x-tar", "application/gzip", "application/x-7z-compressed" - ].some(e => e === props.file.type)) return 'archive'; + ].some(archiveType => archiveType === props.file.type)) return 'archive'; return 'unknown'; }); diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index f6c59457d1..03974559d2 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -33,8 +33,8 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done', r?: Misskey.entities.DriveFile[]): void; - (e: 'closed'): void; + (ev: 'done', r?: Misskey.entities.DriveFile[]): void; + (ev: 'closed'): void; }>(); const dialog = ref<InstanceType<typeof XModalWindow>>(); diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index d08c5fb674..5bbfca83c9 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -24,6 +24,6 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); </script> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index 262eae0de1..aaf7ca3ca3 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -31,7 +31,7 @@ </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; @@ -50,9 +50,9 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', r: Misskey.entities.DriveFile): void; - (e: 'dragstart'): void; - (e: 'dragend'): void; + (ev: 'chosen', r: Misskey.entities.DriveFile): void; + (ev: 'dragstart'): void; + (ev: 'dragend'): void; }>(); const isDragging = ref(false); @@ -99,14 +99,14 @@ function onClick(ev: MouseEvent) { } } -function onContextmenu(e: MouseEvent) { - os.contextMenu(getMenu(), e); +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); } -function onDragstart(e: DragEvent) { - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); +function onDragstart(ev: DragEvent) { + if (ev.dataTransfer) { + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); } isDragging.value = true; @@ -133,11 +133,11 @@ function rename() { } function describe() { - os.popup(import('@/components/media-caption.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), { title: i18n.ts.describeFile, input: { placeholder: i18n.ts.inputNewDescription, - default: props.file.comment !== null ? props.file.comment : '', + default: props.file.comment != null ? props.file.comment : '', }, image: props.file }, { @@ -146,7 +146,7 @@ function describe() { let comment = result.result; os.api('drive/files/update', { fileId: props.file.id, - comment: comment.length == 0 ? null : comment + comment: comment.length === 0 ? null : comment }); } }, 'closed'); diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index 57621bf097..3ccb5d6219 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -27,7 +27,7 @@ </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -71,7 +71,7 @@ function onMouseover() { } function onMouseout() { - hover.value = false + hover.value = false; } function onDragover(ev: DragEvent) { @@ -84,12 +84,12 @@ function onDragover(ev: DragEvent) { return; } - const isFile = ev.dataTransfer.items[0].kind == 'file'; - const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { ev.dataTransfer.dropEffect = 'none'; } @@ -118,7 +118,7 @@ function onDrop(ev: DragEvent) { //#region ใใฉใคใใฎใใกใคใซ const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); emit('removeFile', file.id); os.api('drive/files/update', { @@ -130,11 +130,11 @@ function onDrop(ev: DragEvent) { //#region ใใฉใคใใฎใใฉใซใ const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + if (driveFolder != null && driveFolder !== '') { const folder = JSON.parse(driveFolder); // ็งปๅๅ ใ่ชๅ่ช่บซใชใreject - if (folder.id == props.folder.id) return; + if (folder.id === props.folder.id) return; emit('removeFolder', folder.id); os.api('drive/folders/update', { @@ -204,7 +204,7 @@ function deleteFolder() { defaultStore.set('uploadFolder', null); } }).catch(err => { - switch(err.id) { + switch (err.id) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', @@ -230,7 +230,7 @@ function onContextmenu(ev: MouseEvent) { text: i18n.ts.openInWindow, icon: 'fas fa-window-restore', action: () => { - os.popup(import('./drive-window.vue'), { + os.popup(defineAsyncComponent(() => import('./drive-window.vue')), { initialFolder: props.folder }, { }, 'closed'); diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 67223267c1..5482703317 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -24,10 +24,10 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'move', v?: Misskey.entities.DriveFolder): void; - (e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; - (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; - (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'move', v?: Misskey.entities.DriveFolder): void; + (ev: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; + (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; }>(); const hover = ref(false); @@ -45,22 +45,22 @@ function onMouseout() { hover.value = false; } -function onDragover(e: DragEvent) { - if (!e.dataTransfer) return; +function onDragover(ev: DragEvent) { + if (!ev.dataTransfer) return; // ใใฎใใฉใซใใใซใผใใใคใซใฌใณใใใฃใฌใฏใใชใชใใใญใใ็ฆๆญข if (props.folder == null && props.parentFolder == null) { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } return false; @@ -74,22 +74,22 @@ function onDragleave() { if (props.folder || props.parentFolder) draghover.value = false; } -function onDrop(e: DragEvent) { +function onDrop(ev: DragEvent) { draghover.value = false; - if (!e.dataTransfer) return; + if (!ev.dataTransfer) return; // ใใกใคใซใ ใฃใใ - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { emit('upload', file, props.folder); } return; } //#region ใใฉใคใใฎใใกใคใซ - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); emit('removeFile', file.id); os.api('drive/files/update', { @@ -100,11 +100,11 @@ function onDrop(e: DragEvent) { //#endregion //#region ใใฉใคใใฎใใฉใซใ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder !== '') { const folder = JSON.parse(driveFolder); // ็งปๅๅ ใ่ชๅ่ช่บซใชใreject - if (props.folder && folder.id == props.folder.id) return; + if (props.folder && folder.id === props.folder.id) return; emit('removeFolder', folder.id); os.api('drive/folders/update', { folderId: folder.id, diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index e044c67523..6c2c8acad0 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -97,6 +97,7 @@ import * as os from '@/os'; import { stream } from '@/stream'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import { uploadFile, uploads } from '@/scripts/upload'; const props = withDefaults(defineProps<{ initialFolder?: Misskey.entities.DriveFolder; @@ -109,11 +110,11 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; - (e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; - (e: 'move-root'): void; - (e: 'cd', v: Misskey.entities.DriveFolder | null): void; - (e: 'open-folder', v: Misskey.entities.DriveFolder): void; + (ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; + (ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; + (ev: 'move-root'): void; + (ev: 'cd', v: Misskey.entities.DriveFolder | null): void; + (ev: 'open-folder', v: Misskey.entities.DriveFolder): void; }>(); const loadMoreFiles = ref<InstanceType<typeof MkButton>>(); @@ -127,8 +128,9 @@ const moreFolders = ref(false); const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); -const uploadings = os.uploads; +const uploadings = uploads; const connection = stream.useChannel('drive'); +const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // ๅค้จๆธกใใๅคใใฎใง$refใฏไฝฟใใชใใปใใใใ // ใใญใใใใใใใจใใฆใใใ const draghover = ref(false); @@ -141,7 +143,7 @@ const fetching = ref(true); const ilFilesObserver = new IntersectionObserver( (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() -) +); watch(folder, () => emit('cd', folder.value)); @@ -151,7 +153,7 @@ function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) { const current = folder.value ? folder.value.id : null; - if (current != file.folderId) { + if (current !== file.folderId) { removeFile(file); } else { addFile(file, true); @@ -168,7 +170,7 @@ function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) { const current = folder.value ? folder.value.id : null; - if (current != updatedFolder.parentId) { + if (current !== updatedFolder.parentId) { removeFolder(updatedFolder); } else { addFolder(updatedFolder, true); @@ -179,23 +181,23 @@ function onStreamDriveFolderDeleted(folderId: string) { removeFolder(folderId); } -function onDragover(e: DragEvent): any { - if (!e.dataTransfer) return; +function onDragover(ev: DragEvent): any { + if (!ev.dataTransfer) return; // ใใฉใใฐๅ ใ่ชๅ่ช่บซใฎๆๆใใใขใคใใ ใ ใฃใใ if (isDragSource.value) { // ่ชๅ่ช่บซใซใฏใใญใใใใใชใ - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; return; } - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } return false; @@ -209,24 +211,24 @@ function onDragleave() { draghover.value = false; } -function onDrop(e: DragEvent): any { +function onDrop(ev: DragEvent): any { draghover.value = false; - if (!e.dataTransfer) return; + if (!ev.dataTransfer) return; // ใใญใใใใใฆใใใใฎใใใกใคใซใ ใฃใใ - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { upload(file, folder.value); } return; } //#region ใใฉใคใใฎใใกใคใซ - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); - if (files.value.some(f => f.id == file.id)) return; + if (files.value.some(f => f.id === file.id)) return; removeFile(file.id); os.api('drive/files/update', { fileId: file.id, @@ -236,13 +238,13 @@ function onDrop(e: DragEvent): any { //#endregion //#region ใใฉใคใใฎใใฉใซใ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder !== '') { const droppedFolder = JSON.parse(driveFolder); // ็งปๅๅ ใ่ชๅ่ช่บซใชใreject - if (folder.value && droppedFolder.id == folder.value.id) return false; - if (folders.value.some(f => f.id == droppedFolder.id)) return false; + if (folder.value && droppedFolder.id === folder.value.id) return false; + if (folders.value.some(f => f.id === droppedFolder.id)) return false; removeFolder(droppedFolder.id); os.api('drive/folders/update', { folderId: droppedFolder.id, @@ -330,7 +332,7 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { // ๅ้คๆใซ่ฆชใใฉใซใใซ็งปๅ move(folderToDelete.parentId); }).catch(err => { - switch(err.id) { + switch (err.id) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', @@ -355,16 +357,16 @@ function onChangeFileInput() { } function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { - os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => { + uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => { addFile(res, true); }); } function chooseFile(file: Misskey.entities.DriveFile) { - const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id); + const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id); if (props.multiple) { if (isAlreadySelected) { - selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id); + selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id); } else { selectedFiles.value.push(file); } @@ -380,10 +382,10 @@ function chooseFile(file: Misskey.entities.DriveFile) { } function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { - const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id); + const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id); if (props.multiple) { if (isAlreadySelected) { - selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id); + selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id); } else { selectedFolders.value.push(folderToChoose); } @@ -402,7 +404,7 @@ function move(target?: Misskey.entities.DriveFolder) { if (!target) { goRoot(); return; - } else if (typeof target == 'object') { + } else if (typeof target === 'object') { target = target.id; } @@ -428,9 +430,9 @@ function move(target?: Misskey.entities.DriveFolder) { function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { const current = folder.value ? folder.value.id : null; - if (current != folderToAdd.parentId) return; + if (current !== folderToAdd.parentId) return; - if (folders.value.some(f => f.id == folderToAdd.id)) { + if (folders.value.some(f => f.id === folderToAdd.id)) { const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id); folders.value[exist] = folderToAdd; return; @@ -445,9 +447,9 @@ function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { const current = folder.value ? folder.value.id : null; - if (current != fileToAdd.folderId) return; + if (current !== fileToAdd.folderId) return; - if (files.value.some(f => f.id == fileToAdd.id)) { + if (files.value.some(f => f.id === fileToAdd.id)) { const exist = files.value.map(f => f.id).indexOf(fileToAdd.id); files.value[exist] = fileToAdd; return; @@ -462,12 +464,12 @@ function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) { const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove; - folders.value = folders.value.filter(f => f.id != folderIdToRemove); + folders.value = folders.value.filter(f => f.id !== folderIdToRemove); } function removeFile(file: Misskey.entities.DriveFile | string) { const fileId = typeof file === 'object' ? file.id : file; - files.value = files.value.filter(f => f.id != fileId); + files.value = files.value.filter(f => f.id !== fileId); } function appendFile(file: Misskey.entities.DriveFile) { @@ -510,7 +512,7 @@ async function fetch() { folderId: folder.value ? folder.value.id : null, limit: foldersMax + 1 }).then(fetchedFolders => { - if (fetchedFolders.length == foldersMax + 1) { + if (fetchedFolders.length === foldersMax + 1) { moreFolders.value = true; fetchedFolders.pop(); } @@ -522,7 +524,7 @@ async function fetch() { type: props.type, limit: filesMax + 1 }).then(fetchedFiles => { - if (fetchedFiles.length == filesMax + 1) { + if (fetchedFiles.length === filesMax + 1) { moreFiles.value = true; fetchedFiles.pop(); } @@ -549,7 +551,7 @@ function fetchMoreFiles() { untilId: files.value[files.value.length - 1].id, limit: max + 1 }).then(files => { - if (files.length == max + 1) { + if (files.length === max + 1) { moreFiles.value = true; files.pop(); } else { @@ -562,6 +564,10 @@ function fetchMoreFiles() { function getMenu() { return [{ + type: 'switch', + text: i18n.ts.keepOriginalUploading, + ref: keepOriginal, + }, null, { text: i18n.ts.addFile, type: 'label' }, { @@ -601,7 +607,7 @@ function onContextmenu(ev: MouseEvent) { onMounted(() => { if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) { nextTick(() => { - ilFilesObserver.observe(loadMoreFiles.value?.$el) + ilFilesObserver.observe(loadMoreFiles.value?.$el); }); } @@ -622,7 +628,7 @@ onMounted(() => { onActivated(() => { if (defaultStore.state.enableInfiniteScroll) { nextTick(() => { - ilFilesObserver.observe(loadMoreFiles.value?.$el) + ilFilesObserver.observe(loadMoreFiles.value?.$el); }); } }); diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue index 4d27fb48ba..610690d701 100644 --- a/packages/client/src/components/emoji-picker-window.vue +++ b/packages/client/src/components/emoji-picker-window.vue @@ -25,8 +25,8 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', v: any): void; - (e: 'closed'): void; + (ev: 'chosen', v: any): void; + (ev: 'closed'): void; }>(); function chosen(emoji: any) { diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue index 1026e894d1..52f7047487 100644 --- a/packages/client/src/components/emoji-picker.section.vue +++ b/packages/client/src/components/emoji-picker.section.vue @@ -24,7 +24,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'chosen', v: string, ev: MouseEvent): void; + (ev: 'chosen', v: string, event: MouseEvent): void; }>(); const shown = ref(!!props.initialShown); diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index 8601ea121c..64732e7033 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,6 +1,6 @@ <template> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> @@ -61,7 +61,7 @@ </div> <div> <header class="_acrylic">{{ i18n.ts.emoji }}</header> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> + <XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> <div class="tabs"> @@ -97,7 +97,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', v: string): void; + (ev: 'chosen', v: string): void; }>(); const search = ref<HTMLInputElement>(); @@ -138,7 +138,7 @@ watch(q, () => { const emojis = customEmojis; const matches = new Set<Misskey.entities.CustomEmoji>(); - const exactMatch = emojis.find(e => e.name === newQ); + const exactMatch = emojis.find(emoji => emoji.name === newQ); if (exactMatch) matches.add(exactMatch); if (newQ.includes(' ')) { // ANDๆค็ดข @@ -201,7 +201,7 @@ watch(q, () => { const emojis = emojilist; const matches = new Set<UnicodeEmojiDef>(); - const exactMatch = emojis.find(e => e.name === newQ); + const exactMatch = emojis.find(emoji => emoji.name === newQ); if (exactMatch) matches.add(exactMatch); if (newQ.includes(' ')) { // ANDๆค็ดข @@ -295,7 +295,7 @@ function chosen(emoji: any, ev?: MouseEvent) { // ๆ่ฟไฝฟใฃใ็ตตๆๅญๆดๆฐ if (!pinned.value.includes(key)) { let recents = defaultStore.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== key); + recents = recents.filter((emoji: any) => emoji !== key); recents.unshift(key); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); } @@ -313,12 +313,12 @@ function done(query?: any): boolean | void { if (query == null || typeof query !== 'string') return; const q2 = query.replace(/:/g, ''); - const exactMatchCustom = customEmojis.find(e => e.name === q2); + const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2); if (exactMatchCustom) { chosen(exactMatchCustom); return true; } - const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2); + const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2); if (exactMatchUnicode) { chosen(exactMatchUnicode); return true; diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index 93c9e891c1..efee795e43 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -28,7 +28,7 @@ </template> <script lang="ts" setup> -import { onBeforeUnmount, onMounted, ref } from 'vue'; +import { onBeforeUnmount, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os'; import { stream } from '@/stream'; @@ -43,32 +43,30 @@ const props = withDefaults(defineProps<{ large: false, }); -const isFollowing = ref(props.user.isFollowing); -const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); -const wait = ref(false); +let isFollowing = $ref(props.user.isFollowing); +let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); +let wait = $ref(false); const connection = stream.useChannel('main'); if (props.user.isFollowing == null) { os.api('users/show', { userId: props.user.id - }).then(u => { - isFollowing.value = u.isFollowing; - hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou; - }); + }) + .then(onFollowChange); } function onFollowChange(user: Misskey.entities.UserDetailed) { - if (user.id == props.user.id) { - isFollowing.value = user.isFollowing; - hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; + if (user.id === props.user.id) { + isFollowing = user.isFollowing; + hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; } } async function onClick() { - wait.value = true; + wait = true; try { - if (isFollowing.value) { + if (isFollowing) { const { canceled } = await os.confirm({ type: 'warning', text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), @@ -80,26 +78,22 @@ async function onClick() { userId: props.user.id }); } else { - if (hasPendingFollowRequestFromYou.value) { + if (hasPendingFollowRequestFromYou) { await os.api('following/requests/cancel', { userId: props.user.id }); - } else if (props.user.isLocked) { - await os.api('following/create', { - userId: props.user.id - }); - hasPendingFollowRequestFromYou.value = true; + hasPendingFollowRequestFromYou = false; } else { await os.api('following/create', { userId: props.user.id }); - hasPendingFollowRequestFromYou.value = true; + hasPendingFollowRequestFromYou = true; } } - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); } finally { - wait.value = false; + wait = false; } } diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index 46cbf6bd70..19c1f23c85 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -41,8 +41,8 @@ import { instance } from '@/instance'; import { i18n } from '@/i18n'; const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; }>(); let dialog: InstanceType<typeof XModalWindow> = $ref(); diff --git a/packages/client/src/components/form-dialog.vue b/packages/client/src/components/form-dialog.vue index efd0da443d..11459f5937 100644 --- a/packages/client/src/components/form-dialog.vue +++ b/packages/client/src/components/form-dialog.vue @@ -44,7 +44,7 @@ <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template> </FormRange> - <MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)" class="_formBlock"> + <MkButton v-else-if="form[item].type === 'button'" class="_formBlock" @click="form[item].action($event, values)"> <span v-text="form[item].content || item"></span> </MkButton> </template> diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue index 571afe50c0..1b960657d7 100644 --- a/packages/client/src/components/form/folder.vue +++ b/packages/client/src/components/form/folder.vue @@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{ defaultOpen: boolean; }>(), { defaultOpen: false, -}) +}); let opened = $ref(props.defaultOpen); let openedAtLeastOnce = $ref(props.defaultOpen); diff --git a/packages/client/src/components/form/radios.vue b/packages/client/src/components/form/radios.vue index ff5d51f9c7..a52acae9e1 100644 --- a/packages/client/src/components/form/radios.vue +++ b/packages/client/src/components/form/radios.vue @@ -14,7 +14,7 @@ export default defineComponent({ data() { return { value: this.modelValue, - } + }; }, watch: { value() { diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue index a82348d317..07f2c23124 100644 --- a/packages/client/src/components/form/range.vue +++ b/packages/client/src/components/form/range.vue @@ -16,7 +16,7 @@ </template> <script lang="ts"> -import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; import * as os from '@/os'; export default defineComponent({ @@ -112,7 +112,7 @@ export default defineComponent({ ev.preventDefault(); const tooltipShowing = ref(true); - os.popup(import('@/components/ui/tooltip.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { showing: tooltipShowing, text: computed(() => { return props.textConverter(finalValue.value); diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index b5a30d635c..fadb770aee 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -31,7 +31,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'update:modelValue', v: boolean): void; + (ev: 'update:modelValue', v: boolean): void; }>(); let button = $ref<HTMLElement>(); diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 52fef50f9b..5287d59b3e 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -5,14 +5,13 @@ </template> <script lang="ts" setup> -import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { router } from '@/router'; import { url } from '@/config'; import { popout as popout_ } from '@/scripts/popout'; import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; +import { MisskeyNavigator } from '@/scripts/navigate'; const props = withDefaults(defineProps<{ to: string; @@ -23,9 +22,7 @@ const props = withDefaults(defineProps<{ behavior: null, }); -type Navigate = (path: string, record?: boolean) => void; -const navHook = inject<null | Navigate>('navHook', null); -const sideViewHook = inject<null | Navigate>('sideViewHook', null); +const mkNav = new MisskeyNavigator(); const active = $computed(() => { if (props.activeClass == null) return false; @@ -48,11 +45,11 @@ function onContextmenu(ev) { action: () => { os.pageWindow(props.to); } - }, sideViewHook ? { + }, mkNav.sideViewHook ? { icon: 'fas fa-columns', text: i18n.ts.openInSideView, action: () => { - sideViewHook(props.to); + if (mkNav.sideViewHook) mkNav.sideViewHook(props.to); } } : undefined, { icon: 'fas fa-expand-alt', @@ -101,18 +98,6 @@ function nav() { } } - if (navHook) { - navHook(props.to); - } else { - if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') { - return sideViewHook(props.to); - } - - if (router.currentRoute.value.path === props.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - router.push(props.to); - } - } + mkNav.push(props.to); } </script> diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 27cfb6e4d4..4868896c99 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'click', ev: MouseEvent): void; + (ev: 'click', v: MouseEvent): void; }>(); const url = $computed(() => defaultStore.state.disableShowingAnimatedImages diff --git a/packages/client/src/components/global/emoji.vue b/packages/client/src/components/global/emoji.vue index 92edb1caf9..0075e0867d 100644 --- a/packages/client/src/components/global/emoji.vue +++ b/packages/client/src/components/global/emoji.vue @@ -46,7 +46,7 @@ export default defineComponent({ const url = computed(() => { if (char.value) { let codes = Array.from(char.value).map(x => x.codePointAt(0).toString(16)); - if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); + if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); codes = codes.filter(x => x && x.length); return `${twemojiSvgBase}/${codes.join('-')}.svg`; } else { diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue index e558614c12..63db19a520 100644 --- a/packages/client/src/components/global/header.vue +++ b/packages/client/src/components/global/header.vue @@ -38,7 +38,7 @@ <script lang="ts"> import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; import { popupMenu } from '@/os'; import { url } from '@/config'; import { scrollToTop } from '@/scripts/scroll'; diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 43ea1395ed..5a7e362fcf 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -1,11 +1,24 @@ <template> -<div class="yxspomdl" :class="{ inline, colored, mini }"> - <div class="ring"></div> +<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini }]"> + <div :class="$style.container"> + <svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + <svg :class="[$style.spinner, $style.fg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + </div> </div> </template> <script lang="ts" setup> -import { } from 'vue'; +import { useCssModule } from 'vue'; + +useCssModule(); const props = withDefaults(defineProps<{ inline?: boolean; @@ -18,8 +31,8 @@ const props = withDefaults(defineProps<{ }); </script> -<style lang="scss" scoped> -@keyframes ring { +<style lang="scss" module> +@keyframes spinner { 0% { transform: rotate(0deg); } @@ -28,12 +41,12 @@ const props = withDefaults(defineProps<{ } } -.yxspomdl { +.root { padding: 32px; text-align: center; cursor: wait; - --size: 48px; + --size: 40px; &.colored { color: var(--accent); @@ -49,34 +62,33 @@ const props = withDefaults(defineProps<{ padding: 16px; --size: 32px; } +} - > .ring { - position: relative; - display: inline-block; - vertical-align: middle; +.container { + position: relative; + width: var(--size); + height: var(--size); + margin: 0 auto; +} - &:before, - &:after { - content: " "; - display: block; - box-sizing: border-box; - width: var(--size); - height: var(--size); - border-radius: 50%; - border: solid 4px; - } +.spinner { + position: absolute; + top: 0; + left: 0; + width: var(--size); + height: var(--size); + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; +} - &:before { - border-color: currentColor; - opacity: 0.3; - } +.bg { + opacity: 0.275; +} - &:after { - position: absolute; - top: 0; - border-color: currentColor transparent transparent transparent; - animation: ring 0.5s linear infinite; - } - } +.fg { + animation: spinner 0.5s linear infinite; } </style> diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index 243d8614ba..70d0108e9f 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -31,6 +31,32 @@ const props = withDefaults(defineProps<{ } } +.mfm-x2 { + --mfm-zoom-size: 200%; +} + +.mfm-x3 { + --mfm-zoom-size: 400%; +} + +.mfm-x4 { + --mfm-zoom-size: 600%; +} + +.mfm-x2, .mfm-x3, .mfm-x4 { + font-size: var(--mfm-zoom-size); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* only half effective */ + font-size: calc(var(--mfm-zoom-size) / 2 + 50%); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* disabled */ + font-size: 100%; + } + } +} + @keyframes mfm-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index 5748d9de61..a7f142f961 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{ mode: 'relative', }); -const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; +const _time = typeof props.time === 'string' ? new Date(props.time) : props.time; const absolute = _time.toLocaleString(); let now = $ref(new Date()); @@ -32,8 +32,7 @@ const relative = $computed(() => { ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : ago >= -1 ? i18n.ts._ago.justNow : - ago < -1 ? i18n.ts._ago.future : - i18n.ts._ago.unknown); + i18n.ts._ago.future); }); function tick() { diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 55f6c5d5f9..34ba9024cc 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -18,7 +18,7 @@ </template> <script lang="ts"> -import { defineComponent, ref } from 'vue'; +import { defineAsyncComponent, defineComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode/'; import { url as local } from '@/config'; import * as os from '@/os'; @@ -50,7 +50,7 @@ export default defineComponent({ const el = ref(); useTooltip(el, (showing) => { - os.popup(import('@/components/url-preview-popup.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), { showing, url: props.url, source: el.value, diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue index c39076df16..7bc88399ef 100644 --- a/packages/client/src/components/image-viewer.vue +++ b/packages/client/src/components/image-viewer.vue @@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const modal = $ref<InstanceType<typeof MkModal>>(); diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue index 9b0a18ec90..c32409ecf4 100644 --- a/packages/client/src/components/instance-ticker.vue +++ b/packages/client/src/components/instance-ticker.vue @@ -39,6 +39,19 @@ const bg = { border-radius: 4px 0 0 4px; overflow: hidden; color: #fff; + text-shadow: /* .866 โ sin(60deg) */ + 1px 0 1px #000, + .866px .5px 1px #000, + .5px .866px 1px #000, + 0 1px 1px #000, + -.5px .866px 1px #000, + -.866px .5px 1px #000, + -1px 0 1px #000, + -.866px -.5px 1px #000, + -.5px -.866px 1px #000, + 0 -1px 1px #000, + .5px -.866px 1px #000, + .866px -.5px 1px #000; > .icon { height: 100%; diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue index 317c931cec..846a9a3a76 100644 --- a/packages/client/src/components/link.vue +++ b/packages/client/src/components/link.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { } from 'vue'; +import { defineAsyncComponent } from 'vue'; import { url as local } from '@/config'; import { useTooltip } from '@/scripts/use-tooltip'; import * as os from '@/os'; @@ -26,7 +26,7 @@ const target = self ? null : '_blank'; const el = $ref(); useTooltip($$(el), (showing) => { - os.popup(import('@/components/url-preview-popup.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), { showing, url: props.url, source: el, diff --git a/packages/client/src/components/media-caption.vue b/packages/client/src/components/media-caption.vue index ef546f3f70..feed3854f9 100644 --- a/packages/client/src/components/media-caption.vue +++ b/packages/client/src/components/media-caption.vue @@ -77,7 +77,7 @@ export default defineComponent({ computed: { remainingLength(): number { - if (typeof this.inputValue != "string") return 512; + if (typeof this.inputValue !== "string") return 512; return 512 - length(this.inputValue); } }, @@ -116,17 +116,17 @@ export default defineComponent({ } }, - onKeydown(e) { - if (e.which === 27) { // ESC + onKeydown(evt) { + if (evt.which === 27) { // ESC this.cancel(); } }, - onInputKeydown(e) { - if (e.which === 13) { // Enter - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); + onInputKeydown(evt) { + if (evt.which === 13) { // Enter + if (evt.ctrlKey) { + evt.preventDefault(); + evt.stopPropagation(); this.ok(); } } diff --git a/packages/client/src/components/media-video.vue b/packages/client/src/components/media-video.vue index 680eb27e64..5c38691e69 100644 --- a/packages/client/src/components/media-video.vue +++ b/packages/client/src/components/media-video.vue @@ -8,7 +8,8 @@ <div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu"> <video :poster="video.thumbnailUrl" - :title="video.name" + :title="video.comment" + :alt="video.comment" preload="none" controls @contextmenu.stop diff --git a/packages/client/src/components/mention.vue b/packages/client/src/components/mention.vue index 479acfbc8f..70c2f49afa 100644 --- a/packages/client/src/components/mention.vue +++ b/packages/client/src/components/mention.vue @@ -1,22 +1,22 @@ <template> -<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="ldlomzub" :class="{ isMe }" :to="url" :style="{ background: bg }"> - <img class="icon" :src="`/avatar/@${username}@${host}`" alt=""> +<MkA v-if="url.startsWith('/')" v-user-preview="canonical" :class="[$style.root, { isMe }]" :to="url" :style="{ background: bg }"> + <img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt=""> <span class="main"> <span class="username">@{{ username }}</span> - <span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span> + <span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.mainHost">@{{ toUnicode(host) }}</span> </span> </MkA> -<a v-else class="ldlomzub" :href="url" target="_blank" rel="noopener" :style="{ background: bg }"> +<a v-else :class="$style.root" :href="url" target="_blank" rel="noopener" :style="{ background: bg }"> <span class="main"> <span class="username">@{{ username }}</span> - <span class="host">@{{ toUnicode(host) }}</span> + <span :class="$style.mainHost">@{{ toUnicode(host) }}</span> </span> </a> </template> <script lang="ts"> -import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import { defineComponent, useCssModule } from 'vue'; +import tinycolor from 'tinycolor2'; import { toUnicode } from 'punycode'; import { host as localHost } from '@/config'; import { $i } from '@/account'; @@ -45,6 +45,8 @@ export default defineComponent({ const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention')); bg.setAlpha(0.1); + useCssModule(); + return { localHost, isMe, @@ -57,8 +59,8 @@ export default defineComponent({ }); </script> -<style lang="scss" scoped> -.ldlomzub { +<style lang="scss" module> +.root { display: inline-block; padding: 4px 8px 4px 4px; border-radius: 999px; @@ -67,18 +69,18 @@ export default defineComponent({ &.isMe { color: var(--mentionMe); } +} - > .icon { - width: 1.5em; - margin: 0 0.2em 0 0; - vertical-align: bottom; - border-radius: 100%; - } +.icon { + width: 1.5em; + height: 1.5em; + object-fit: cover; + margin: 0 0.2em 0 0; + vertical-align: bottom; + border-radius: 100%; +} - > .main { - > .host { - opacity: 0.5; - } - } +.mainHost { + opacity: 0.5; } </style> diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 37076652fd..4556a82d55 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -91,7 +91,8 @@ export default defineComponent({ let style; switch (token.props.name) { case 'tada': { - style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : ''); + const speed = validTime(token.props.args.speed) || '1s'; + style = 'font-size: 150%;' + (this.$store.state.animatedMfm ? `animation: tada ${speed} linear infinite both;` : ''); break; } case 'jelly': { @@ -123,11 +124,13 @@ export default defineComponent({ break; } case 'jump': { - style = this.$store.state.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : ''; + const speed = validTime(token.props.args.speed) || '0.75s'; + style = this.$store.state.animatedMfm ? `animation: mfm-jump ${speed} linear infinite;` : ''; break; } case 'bounce': { - style = this.$store.state.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : ''; + const speed = validTime(token.props.args.speed) || '0.75s'; + style = this.$store.state.animatedMfm ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : ''; break; } case 'flip': { @@ -139,16 +142,19 @@ export default defineComponent({ break; } case 'x2': { - style = `font-size: 200%;`; - break; + return h('span', { + class: 'mfm-x2', + }, genEl(token.children)); } case 'x3': { - style = `font-size: 400%;`; - break; + return h('span', { + class: 'mfm-x3', + }, genEl(token.children)); } case 'x4': { - style = `font-size: 600%;`; - break; + return h('span', { + class: 'mfm-x4', + }, genEl(token.children)); } case 'font': { const family = @@ -168,7 +174,8 @@ export default defineComponent({ }, genEl(token.children)); } case 'rainbow': { - style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : ''; + const speed = validTime(token.props.args.speed) || '1s'; + style = this.$store.state.animatedMfm ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; break; } case 'sparkle': { diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue index 2e17d5d030..21bdb657b7 100644 --- a/packages/client/src/components/modal-page-window.vue +++ b/packages/client/src/components/modal-page-window.vue @@ -39,8 +39,8 @@ export default defineComponent({ inject: { sideViewHook: { - default: null - } + default: null, + }, }, provide() { @@ -94,31 +94,31 @@ export default defineComponent({ }, { icon: 'fas fa-expand-alt', text: this.$ts.showInPage, - action: this.expand + action: this.expand, }, this.sideViewHook ? { icon: 'fas fa-columns', text: this.$ts.openInSideView, action: () => { this.sideViewHook(this.path); this.$refs.window.close(); - } + }, } : undefined, { icon: 'fas fa-external-link-alt', text: this.$ts.popout, - action: this.popout + action: this.popout, }, null, { icon: 'fas fa-external-link-alt', text: this.$ts.openInNewTab, action: () => { window.open(this.url, '_blank'); this.$refs.window.close(); - } + }, }, { icon: 'fas fa-link', text: this.$ts.copyLink, action: () => { copyToClipboard(this.url); - } + }, }]; }, }, @@ -155,7 +155,7 @@ export default defineComponent({ onContextmenu(ev: MouseEvent) { os.contextMenu(this.contextmenu, ev); - } + }, }, }); </script> diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index d30284ca5f..6234b710d2 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -2,9 +2,9 @@ <div v-if="!muted" v-show="!isDeleted" + ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" - ref="el" class="lxwezrsl _block" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" @@ -197,7 +197,7 @@ const keymap = { 'q': () => renoteButton.value.renote(true), 'esc': blur, 'm|o': () => menu(true), - 's': () => showContent.value != showContent.value, + 's': () => showContent.value !== showContent.value, }; useNoteCapture({ @@ -222,7 +222,7 @@ function react(viaKeyboard = false): void { reactionPicker.show(reactButton.value, reaction => { os.api('notes/reactions/create', { noteId: appearNote.id, - reaction: reaction + reaction: reaction, }); }, () => { focus(); @@ -233,7 +233,7 @@ function undoReact(note): void { const oldReaction = note.myReaction; if (!oldReaction) return; os.api('notes/reactions/delete', { - noteId: note.id + noteId: note.id, }); } @@ -257,7 +257,7 @@ function onContextmenu(ev: MouseEvent): void { function menu(viaKeyboard = false): void { os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { - viaKeyboard + viaKeyboard, }).then(focus); } @@ -269,12 +269,12 @@ function showRenoteMenu(viaKeyboard = false): void { danger: true, action: () => { os.api('notes/delete', { - noteId: note.id + noteId: note.id, }); isDeleted.value = true; - } + }, }], renoteTime.value, { - viaKeyboard: viaKeyboard + viaKeyboard: viaKeyboard, }); } @@ -288,14 +288,14 @@ function blur() { os.api('notes/children', { noteId: appearNote.id, - limit: 30 + limit: 30, }).then(res => { replies.value = res; }); if (appearNote.replyId) { os.api('notes/conversation', { - noteId: appearNote.replyId + noteId: appearNote.replyId, }).then(res => { conversation.value = res.reverse(); }); diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index c6907787b5..b813b9a2b9 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -5,7 +5,7 @@ <XNoteHeader class="header" :note="note" :mini="true"/> <div class="body"> <p v-if="note.cw != null" class="cw"> - <span v-if="note.cw != ''" class="text">{{ note.cw }}</span> + <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index 3cd7a819d4..e5744d1ce9 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -185,7 +185,7 @@ const keymap = { 'down|j|tab': focusAfter, 'esc': blur, 'm|o': () => menu(true), - 's': () => showContent.value != showContent.value, + 's': () => showContent.value !== showContent.value, }; useNoteCapture({ @@ -210,7 +210,7 @@ function react(viaKeyboard = false): void { reactionPicker.show(reactButton.value, reaction => { os.api('notes/reactions/create', { noteId: appearNote.id, - reaction: reaction + reaction: reaction, }); }, () => { focus(); @@ -221,7 +221,7 @@ function undoReact(note): void { const oldReaction = note.myReaction; if (!oldReaction) return; os.api('notes/reactions/delete', { - noteId: note.id + noteId: note.id, }); } @@ -245,7 +245,7 @@ function onContextmenu(ev: MouseEvent): void { function menu(viaKeyboard = false): void { os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { - viaKeyboard + viaKeyboard, }).then(focus); } @@ -257,12 +257,12 @@ function showRenoteMenu(viaKeyboard = false): void { danger: true, action: () => { os.api('notes/delete', { - noteId: note.id + noteId: note.id, }); isDeleted.value = true; - } + }, }], renoteTime.value, { - viaKeyboard: viaKeyboard + viaKeyboard: viaKeyboard, }); } @@ -284,7 +284,7 @@ function focusAfter() { function readPromo() { os.api('promo/read', { - noteId: appearNote.id + noteId: appearNote.id, }); isDeleted.value = true; } diff --git a/packages/client/src/components/notification-setting-window.vue b/packages/client/src/components/notification-setting-window.vue index ec1efec261..64d828394b 100644 --- a/packages/client/src/components/notification-setting-window.vue +++ b/packages/client/src/components/notification-setting-window.vue @@ -1,5 +1,6 @@ <template> -<XModalWindow ref="dialog" +<XModalWindow + ref="dialog" :width="400" :height="450" :with-ok-button="true" @@ -28,18 +29,18 @@ <script lang="ts"> import { defineComponent, PropType } from 'vue'; -import XModalWindow from '@/components/ui/modal-window.vue'; +import { notificationTypes } from 'misskey-js'; import MkSwitch from './form/switch.vue'; import MkInfo from './ui/info.vue'; import MkButton from './ui/button.vue'; -import { notificationTypes } from 'misskey-js'; +import XModalWindow from '@/components/ui/modal-window.vue'; export default defineComponent({ components: { XModalWindow, MkSwitch, MkInfo, - MkButton + MkButton, }, props: { @@ -53,7 +54,7 @@ export default defineComponent({ type: Boolean, required: false, default: true, - } + }, }, emits: ['done', 'closed'], @@ -93,7 +94,7 @@ export default defineComponent({ for (const type in this.typesMap) { this.typesMap[type as typeof notificationTypes[number]] = true; } - } - } + }, + }, }); </script> diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 1a360f9905..cbfd809f37 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -16,7 +16,8 @@ <i v-else-if="notification.type === 'pollVote'" class="fas fa-poll-h"></i> <i v-else-if="notification.type === 'pollEnded'" class="fas fa-poll-h"></i> <!-- notification.reaction ใ null ใซใชใใใจใฏใพใใชใใใใใใงoptional chainingไฝฟใใจไธ้จใใฉใฆใถใงๅบใใใฎใงๅฟตใฎ็บ --> - <XReactionIcon v-else-if="notification.type === 'reaction'" + <XReactionIcon + v-else-if="notification.type === 'reaction'" ref="reactionRef" :reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" :custom-emojis="notification.note.emojis" @@ -72,12 +73,12 @@ </template> <script lang="ts"> -import { defineComponent, ref, onMounted, onUnmounted } from 'vue'; +import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue'; import * as misskey from 'misskey-js'; -import { getNoteSummary } from '@/scripts/get-note-summary'; import XReactionIcon from './reaction-icon.vue'; import MkFollowButton from './follow-button.vue'; import XReactionTooltip from './reaction-tooltip.vue'; +import { getNoteSummary } from '@/scripts/get-note-summary'; import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; @@ -87,7 +88,7 @@ import { useTooltip } from '@/scripts/use-tooltip'; export default defineComponent({ components: { - XReactionIcon, MkFollowButton + XReactionIcon, MkFollowButton, }, props: { @@ -116,7 +117,7 @@ export default defineComponent({ const readObserver = new IntersectionObserver((entries, observer) => { if (!entries.some(entry => entry.isIntersecting)) return; stream.send('readNotification', { - id: props.notification.id + id: props.notification.id, }); observer.disconnect(); }); @@ -126,6 +127,10 @@ export default defineComponent({ const connection = stream.useChannel('main'); connection.on('readAllNotifications', () => readObserver.disconnect()); + watch(props.notification.isRead, () => { + readObserver.disconnect(); + }); + onUnmounted(() => { readObserver.disconnect(); connection.dispose(); diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index d522503a14..8eb569c369 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -19,8 +19,7 @@ <script lang="ts" setup> import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; -import MkPagination from '@/components/ui/pagination.vue'; -import { Paging } from '@/components/ui/pagination.vue'; +import MkPagination, { Paging } from '@/components/ui/pagination.vue'; import XNotification from '@/components/notification.vue'; import XList from '@/components/date-separated-list.vue'; import XNote from '@/components/note.vue'; @@ -49,14 +48,14 @@ const onNotification = (notification) => { const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type); if (isMuted || document.visibilityState === 'visible') { stream.send('readNotification', { - id: notification.id + id: notification.id, }); } if (!isMuted) { pagingComponent.value.prepend({ ...notification, - isRead: document.visibilityState === 'visible' + isRead: document.visibilityState === 'visible', }); } }; @@ -64,6 +63,31 @@ const onNotification = (notification) => { onMounted(() => { const connection = stream.useChannel('main'); connection.on('notification', onNotification); + connection.on('readAllNotifications', () => { + if (pagingComponent.value) { + for (const item of pagingComponent.value.queue) { + item.isRead = true; + } + for (const item of pagingComponent.value.items) { + item.isRead = true; + } + } + }); + connection.on('readNotifications', notificationIds => { + if (pagingComponent.value) { + for (let i = 0; i < pagingComponent.value.queue.length; i++) { + if (notificationIds.includes(pagingComponent.value.queue[i].id)) { + pagingComponent.value.queue[i].isRead = true; + } + } + for (let i = 0; i < (pagingComponent.value.items || []).length; i++) { + if (notificationIds.includes(pagingComponent.value.items[i].id)) { + pagingComponent.value.items[i].isRead = true; + } + } + } + }); + onUnmounted(() => { connection.dispose(); }); diff --git a/packages/client/src/components/number-diff.vue b/packages/client/src/components/number-diff.vue index 9889c97ec3..e7d4a5472a 100644 --- a/packages/client/src/components/number-diff.vue +++ b/packages/client/src/components/number-diff.vue @@ -12,7 +12,7 @@ export default defineComponent({ props: { value: { type: Number, - required: true + required: true, }, }, @@ -26,7 +26,7 @@ export default defineComponent({ isZero, number, }; - } + }, }); </script> diff --git a/packages/client/src/components/page/page.image.vue b/packages/client/src/components/page/page.image.vue index 04ce74bd7c..6e38a9f424 100644 --- a/packages/client/src/components/page/page.image.vue +++ b/packages/client/src/components/page/page.image.vue @@ -1,34 +1,22 @@ <template> <div class="lzyxtsnt"> - <img v-if="image" :src="image.url"/> + <ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/> </div> </template> -<script lang="ts"> +<script lang="ts" setup> import { defineComponent, PropType } from 'vue'; +import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; import * as os from '@/os'; import { ImageBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; -export default defineComponent({ - props: { - block: { - type: Object as PropType<ImageBlock>, - required: true - }, - hpml: { - type: Object as PropType<Hpml>, - required: true - } - }, - setup(props, ctx) { - const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); +const props = defineProps<{ + block: PropType<ImageBlock>, + hpml: PropType<Hpml>, +}>(); - return { - image - }; - } -}); +const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue index 847da37c51..3401f945bd 100644 --- a/packages/client/src/components/page/page.post.vue +++ b/packages/client/src/components/page/page.post.vue @@ -52,21 +52,21 @@ export default defineComponent({ const promise = new Promise((ok) => { const canvas = this.hpml.canvases[this.block.canvasId]; canvas.toBlob(blob => { - const data = new FormData(); - data.append('file', blob); - data.append('i', this.$i.token); + const formData = new FormData(); + formData.append('file', blob); + formData.append('i', this.$i.token); if (this.$store.state.uploadFolder) { - data.append('folderId', this.$store.state.uploadFolder); + formData.append('folderId', this.$store.state.uploadFolder); } fetch(apiUrl + '/drive/files/create', { method: 'POST', - body: data + body: formData, }) .then(response => response.json()) .then(f => { ok(f); - }) + }); }); }); os.promiseDialog(promise); diff --git a/packages/client/src/components/page/page.vue b/packages/client/src/components/page/page.vue index e54147bbd0..a067762372 100644 --- a/packages/client/src/components/page/page.vue +++ b/packages/client/src/components/page/page.vue @@ -38,8 +38,8 @@ export default defineComponent({ let ast; try { ast = parse(props.page.script); - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); /*os.alert({ type: 'error', text: 'Syntax error :(' @@ -48,11 +48,11 @@ export default defineComponent({ } hpml.aiscript.exec(ast).then(() => { hpml.eval(); - }).catch(e => { - console.error(e); + }).catch(err => { + console.error(err); /*os.alert({ type: 'error', - text: e + text: err });*/ }); } else { diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue index 6f3f23a2d3..9aa5510c7f 100644 --- a/packages/client/src/components/poll-editor.vue +++ b/packages/client/src/components/poll-editor.vue @@ -104,7 +104,7 @@ function add() { } function remove(i) { - choices.value = choices.value.filter((_, _i) => _i != i); + choices.value = choices.value.filter((_, _i) => _i !== i); } function get() { diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 9dd69a0ee5..6b9827407b 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -16,7 +16,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; -import MkDriveFileThumbnail from './drive-file-thumbnail.vue' +import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import * as os from '@/os'; export default defineComponent({ @@ -88,7 +88,7 @@ export default defineComponent({ }, async describe(file) { - os.popup(import("@/components/media-caption.vue"), { + os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), { title: this.$ts.describeFile, input: { placeholder: this.$ts.inputNewDescription, @@ -98,7 +98,7 @@ export default defineComponent({ }, { done: result => { if (!result || result.canceled) return; - let comment = result.result.length == 0 ? null : result.result; + let comment = result.result.length === 0 ? null : result.result; os.api('drive/files/update', { fileId: file.id, comment: comment, @@ -114,19 +114,19 @@ export default defineComponent({ this.menu = os.popupMenu([{ text: this.$ts.renameFile, icon: 'fas fa-i-cursor', - action: () => { this.rename(file) } + action: () => { this.rename(file); } }, { text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye', - action: () => { this.toggleSensitive(file) } + action: () => { this.toggleSensitive(file); } }, { text: this.$ts.describeFile, icon: 'fas fa-i-cursor', - action: () => { this.describe(file) } + action: () => { this.describe(file); } }, { text: this.$ts.attachCancel, icon: 'fas fa-times-circle', - action: () => { this.detachMedia(file.id) } + action: () => { this.detachMedia(file.id); } }], ev.currentTarget ?? ev.target).then(() => this.menu = null); } } diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 656689ddcb..0197313e0e 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -62,7 +62,7 @@ </template> <script lang="ts" setup> -import { inject, watch, nextTick, onMounted } from 'vue'; +import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; @@ -87,6 +87,7 @@ import MkInfo from '@/components/ui/info.vue'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; +import { uploadFile } from '@/scripts/upload'; const modal = inject('modal'); @@ -106,7 +107,7 @@ const props = withDefaults(defineProps<{ fixed?: boolean; autofocus?: boolean; }>(), { - initialVisibleUsers: [], + initialVisibleUsers: () => [], autofocus: true, }); @@ -227,7 +228,7 @@ if (props.mention) { text += ' '; } -if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) { +if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) { text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; } @@ -238,16 +239,15 @@ if (props.reply && props.reply.text != null) { for (const x of extractMentions(ast)) { const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : - (otherHost == null || otherHost == host) ? + (otherHost == null || otherHost === host) ? `@${x.username}` : `@${x.username}@${toASCII(otherHost)}`; // ่ชๅใฏ้คๅค - if ($i.username == x.username && x.host == null) continue; - if ($i.username == x.username && x.host == host) continue; + if ($i.username === x.username && (x.host == null || x.host === host)) continue; // ้่คใฏ้คๅค - if (text.indexOf(`${mention} `) != -1) continue; + if (text.includes(`${mention} `)) continue; text += `${mention} `; } @@ -302,7 +302,7 @@ function checkMissingMention() { const ast = mfm.parse(text); for (const x of extractMentions(ast)) { - if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { hasNotSpecifiedMentions = true; return; } @@ -315,7 +315,7 @@ function addMissingMention() { const ast = mfm.parse(text); for (const x of extractMentions(ast)) { - if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { os.api('users/show', { username: x.username, host: x.host }).then(user => { visibleUsers.push(user); }); @@ -356,7 +356,7 @@ function chooseFileFrom(ev) { } function detachFile(id) { - files = files.filter(x => x.id != id); + files = files.filter(x => x.id !== id); } function updateFiles(_files) { @@ -372,7 +372,7 @@ function updateFileName(file, name) { } function upload(file: File, name?: string) { - os.upload(file, defaultStore.state.uploadFolder, name).then(res => { + uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { files.push(res); }); } @@ -383,7 +383,7 @@ function setVisibility() { return; } - os.popup(import('./visibility-picker.vue'), { + os.popup(defineAsyncComponent(() => import('./visibility-picker.vue')), { currentVisibility: visibility, currentLocalOnly: localOnly, src: visibilityButton, @@ -426,24 +426,24 @@ function clear() { quoteId = null; } -function onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post(); - if (e.which === 27) emit('esc'); +function onKeydown(ev: KeyboardEvent) { + if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post(); + if (ev.which === 27) emit('esc'); typing(); } -function onCompositionUpdate(e: CompositionEvent) { - imeText = e.data; +function onCompositionUpdate(ev: CompositionEvent) { + imeText = ev.data; typing(); } -function onCompositionEnd(e: CompositionEvent) { +function onCompositionEnd(ev: CompositionEvent) { imeText = ''; } -async function onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { +async function onPaste(ev: ClipboardEvent) { + for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) { + if (item.kind === 'file') { const file = item.getAsFile(); const lio = file.name.lastIndexOf('.'); const ext = lio >= 0 ? file.name.slice(lio) : ''; @@ -452,10 +452,10 @@ async function onPaste(e: ClipboardEvent) { } } - const paste = e.clipboardData.getData('text'); + const paste = ev.clipboardData.getData('text'); if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); + ev.preventDefault(); os.confirm({ type: 'info', @@ -471,49 +471,49 @@ async function onPaste(e: ClipboardEvent) { } } -function onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; +function onDragover(ev) { + if (!ev.dataTransfer.items[0]) return; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.preventDefault(); + ev.preventDefault(); draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } } -function onDragenter(e) { +function onDragenter(ev) { draghover = true; } -function onDragleave(e) { +function onDragleave(ev) { draghover = false; } -function onDrop(e): void { +function onDrop(ev): void { draghover = false; // ใใกใคใซใ ใฃใใ - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) upload(x); + if (ev.dataTransfer.files.length > 0) { + ev.preventDefault(); + for (const x of Array.from(ev.dataTransfer.files)) upload(x); return; } //#region ใใฉใคใใฎใใกใคใซ - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); files.push(file); - e.preventDefault(); + ev.preventDefault(); } //#endregion } function saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); - data[draftKey] = { + draftData[draftKey] = { updatedAt: new Date(), data: { text: text, @@ -526,20 +526,20 @@ function saveDraft() { } }; - localStorage.setItem('drafts', JSON.stringify(data)); + localStorage.setItem('drafts', JSON.stringify(draftData)); } function deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); - delete data[draftKey]; + delete draftData[draftKey]; - localStorage.setItem('drafts', JSON.stringify(data)); + localStorage.setItem('drafts', JSON.stringify(draftData)); } async function post() { - let data = { - text: text == '' ? undefined : text, + let postData = { + text: text === '' ? undefined : text, fileIds: files.length > 0 ? files.map(f => f.id) : undefined, replyId: props.reply ? props.reply.id : undefined, renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, @@ -548,18 +548,18 @@ async function post() { cw: useCw ? cw || '' : undefined, localOnly: localOnly, visibility: visibility, - visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined, + visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined, }; if (withHashtags && hashtags && hashtags.trim() !== '') { const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - data.text = data.text ? `${data.text} ${hashtags_}` : hashtags_; + postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; } // plugin if (notePostInterruptors.length > 0) { for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); + postData = await interruptor.handler(JSON.parse(JSON.stringify(postData))); } } @@ -571,13 +571,13 @@ async function post() { } posting = true; - os.api('notes/create', data, token).then(() => { + os.api('notes/create', postData, token).then(() => { clear(); nextTick(() => { deleteDraft(); emit('posted'); - if (data.text && data.text != '') { - const hashtags_ = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + if (postData.text && postData.text !== '') { + const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history)))); } @@ -661,7 +661,7 @@ onMounted(() => { cw = draft.data.cw; visibility = draft.data.visibility; localOnly = draft.data.localOnly; - files = (draft.data.files || []).filter(e => e); + files = (draft.data.files || []).filter(draftFile => draftFile); if (draft.data.poll) { poll = draft.data.poll; } diff --git a/packages/client/src/components/queue-chart.vue b/packages/client/src/components/queue-chart.vue index 7e0ed58cbd..7bb548cf06 100644 --- a/packages/client/src/components/queue-chart.vue +++ b/packages/client/src/components/queue-chart.vue @@ -222,7 +222,7 @@ export default defineComponent({ return { chartEl, - } + }; }, }); </script> diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue index 7dc079fde6..91a90a6996 100644 --- a/packages/client/src/components/reactions-viewer.reaction.vue +++ b/packages/client/src/components/reactions-viewer.reaction.vue @@ -7,8 +7,8 @@ :class="{ reacted: note.myReaction == reaction, canToggle }" @click="toggleReaction()" > - <XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/> - <span>{{ count }}</span> + <XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/> + <span class="count">{{ count }}</span> </button> </template> @@ -141,12 +141,16 @@ export default defineComponent({ background: var(--accent); } - > span { + > .count { color: var(--fgOnAccent); } + + > .icon { + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)); + } } - > span { + > .count { font-size: 0.9em; line-height: 32px; margin: 0 0 0 4px; diff --git a/packages/client/src/components/sample.vue b/packages/client/src/components/sample.vue index 65249ff7e9..f80b9c96b7 100644 --- a/packages/client/src/components/sample.vue +++ b/packages/client/src/components/sample.vue @@ -52,7 +52,7 @@ export default defineComponent({ flag: true, radio: 'misskey', mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.` - } + }; }, methods: { diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue index 5c2048e7b0..848b11fada 100644 --- a/packages/client/src/components/signin-dialog.vue +++ b/packages/client/src/components/signin-dialog.vue @@ -2,12 +2,12 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="dialog.close()" + @close="onClose" @closed="emit('closed')" > <template #header>{{ $ts.login }}</template> - <MkSignin :auto-set="autoSet" @login="onLogin"/> + <MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/> </XModalWindow> </template> @@ -18,17 +18,25 @@ import MkSignin from './signin.vue'; const props = withDefaults(defineProps<{ autoSet?: boolean; + message?: string, }>(), { autoSet: false, + message: '' }); const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; + (ev: 'cancelled'): void; }>(); const dialog = $ref<InstanceType<typeof XModalWindow>>(); +function onClose() { + emit('cancelled'); + dialog.close(); +} + function onLogin(res) { emit('done', res); dialog.close(); diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue index f640e948ad..b772d1479b 100644 --- a/packages/client/src/components/signin.vue +++ b/packages/client/src/components/signin.vue @@ -1,39 +1,42 @@ <template> <form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> <div class="auth _section _formRoot"> - <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }"></div> + <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> + <MkInfo v-if="message"> + {{ message }} + </MkInfo> <div v-if="!totpLogin" class="normal-signin"> - <MkInput v-model="username" class="_formBlock" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> + <MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> + <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> <template #prefix><i class="fas fa-lock"></i></template> - <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template> + <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> - <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> + <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> </div> <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }"> <div v-if="user && user.securityKeys" class="twofa-group tap-group"> - <p>{{ $ts.tapSecurityKey }}</p> + <p>{{ i18n.ts.tapSecurityKey }}</p> <MkButton v-if="!queryingKey" @click="queryKey"> - {{ $ts.retry }} + {{ i18n.ts.retry }} </MkButton> </div> <div v-if="user && user.securityKeys" class="or-hr"> - <p class="or-msg">{{ $ts.or }}</p> + <p class="or-msg">{{ i18n.ts.or }}</p> </div> <div class="twofa-group totp-group"> - <p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p> + <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required> - <template #label>{{ $ts.password }}</template> + <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="fas fa-lock"></i></template> </MkInput> <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> - <template #label>{{ $ts.token }}</template> + <template #label>{{ i18n.ts.token }}</template> <template #prefix><i class="fas fa-gavel"></i></template> </MkInput> - <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> + <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> </div> </div> </div> @@ -45,190 +48,198 @@ </form> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import { toUnicode } from 'punycode/'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; -import { apiUrl, host } from '@/config'; +import MkInfo from '@/components/ui/info.vue'; +import { apiUrl, host as configHost } from '@/config'; import { byteify, hexify } from '@/scripts/2fa'; import * as os from '@/os'; import { login } from '@/account'; import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, +let signing = $ref(false); +let user = $ref(null); +let username = $ref(''); +let password = $ref(''); +let token = $ref(''); +let host = $ref(toUnicode(configHost)); +let totpLogin = $ref(false); +let credential = $ref(null); +let challengeData = $ref(null); +let queryingKey = $ref(false); +let hCaptchaResponse = $ref(null); +let reCaptchaResponse = $ref(null); + +const meta = $computed(() => instance); + +const emit = defineEmits<{ + (ev: 'login', v: any): void; +}>(); + +const props = defineProps({ + withAvatar: { + type: Boolean, + required: false, + default: true }, - - props: { - withAvatar: { - type: Boolean, - required: false, - default: true - }, - autoSet: { - type: Boolean, - required: false, - default: false, - } + autoSet: { + type: Boolean, + required: false, + default: false, }, - - emits: ['login'], - - data() { - return { - signing: false, - user: null, - username: '', - password: '', - token: '', - apiUrl, - host: toUnicode(host), - totpLogin: false, - credential: null, - challengeData: null, - queryingKey: false, - }; - }, - - computed: { - meta() { - return this.$instance; - }, - }, - - methods: { - onUsernameChange() { - os.api('users/show', { - username: this.username - }).then(user => { - this.user = user; - }, () => { - this.user = null; - }); - }, - - onLogin(res) { - if (this.autoSet) { - return login(res.i); - } else { - return; - } - }, - - queryKey() { - this.queryingKey = true; - return navigator.credentials.get({ - publicKey: { - challenge: byteify(this.challengeData.challenge, 'base64'), - allowCredentials: this.challengeData.securityKeys.map(key => ({ - id: byteify(key.id, 'hex'), - type: 'public-key', - transports: ['usb', 'nfc', 'ble', 'internal'] - })), - timeout: 60 * 1000 - } - }).catch(() => { - this.queryingKey = false; - return Promise.reject(null); - }).then(credential => { - this.queryingKey = false; - this.signing = true; - return os.api('signin', { - username: this.username, - password: this.password, - signature: hexify(credential.response.signature), - authenticatorData: hexify(credential.response.authenticatorData), - clientDataJSON: hexify(credential.response.clientDataJSON), - credentialId: credential.id, - challengeId: this.challengeData.challengeId - }); - }).then(res => { - this.$emit('login', res); - return this.onLogin(res); - }).catch(err => { - if (err === null) return; - os.alert({ - type: 'error', - text: this.$ts.signinFailed - }); - this.signing = false; - }); - }, - - onSubmit() { - this.signing = true; - if (!this.totpLogin && this.user && this.user.twoFactorEnabled) { - if (window.PublicKeyCredential && this.user.securityKeys) { - os.api('signin', { - username: this.username, - password: this.password - }).then(res => { - this.totpLogin = true; - this.signing = false; - this.challengeData = res; - return this.queryKey(); - }).catch(this.loginFailed); - } else { - this.totpLogin = true; - this.signing = false; - } - } else { - os.api('signin', { - username: this.username, - password: this.password, - token: this.user && this.user.twoFactorEnabled ? this.token : undefined - }).then(res => { - this.$emit('login', res); - this.onLogin(res); - }).catch(this.loginFailed); - } - }, - - loginFailed(err) { - switch (err.id) { - case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: this.$ts.noSuchUser - }); - break; - } - case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: this.$ts.incorrectPassword, - }); - break; - } - case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { - showSuspendedDialog(); - break; - } - default: { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: JSON.stringify(err) - }); - } - } - - this.challengeData = null; - this.totpLogin = false; - this.signing = false; - }, - - resetPassword() { - os.popup(import('@/components/forgot-password.vue'), {}, { - }, 'closed'); - } + message: { + type: String, + required: false, + default: '' } }); + +function onUsernameChange() { + os.api('users/show', { + username: username + }).then(userResponse => { + user = userResponse; + }, () => { + user = null; + }); +} + +function onLogin(res) { + if (props.autoSet) { + return login(res.i); + } +} + +function queryKey() { + queryingKey = true; + return navigator.credentials.get({ + publicKey: { + challenge: byteify(challengeData.challenge, 'base64'), + allowCredentials: challengeData.securityKeys.map(key => ({ + id: byteify(key.id, 'hex'), + type: 'public-key', + transports: ['usb', 'nfc', 'ble', 'internal'] + })), + timeout: 60 * 1000 + } + }).catch(() => { + queryingKey = false; + return Promise.reject(null); + }).then(credential => { + queryingKey = false; + signing = true; + return os.api('signin', { + username, + password, + signature: hexify(credential.response.signature), + authenticatorData: hexify(credential.response.authenticatorData), + clientDataJSON: hexify(credential.response.clientDataJSON), + credentialId: credential.id, + challengeId: challengeData.challengeId, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }); + }).then(res => { + emit('login', res); + return onLogin(res); + }).catch(err => { + if (err === null) return; + os.alert({ + type: 'error', + text: i18n.ts.signinFailed + }); + signing = false; + }); +} + +function onSubmit() { + signing = true; + console.log('submit'); + if (!totpLogin && user && user.twoFactorEnabled) { + if (window.PublicKeyCredential && user.securityKeys) { + os.api('signin', { + username, + password, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }).then(res => { + totpLogin = true; + signing = false; + challengeData = res; + return queryKey(); + }).catch(loginFailed); + } else { + totpLogin = true; + signing = false; + } + } else { + os.api('signin', { + username, + password, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + token: user && user.twoFactorEnabled ? token : undefined + }).then(res => { + emit('login', res); + onLogin(res); + }).catch(loginFailed); + } +} + +function loginFailed(err) { + switch (err.id) { + case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.noSuchUser + }); + break; + } + case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.incorrectPassword, + }); + break; + } + case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { + showSuspendedDialog(); + break; + } + case '22d05606-fbcf-421a-a2db-b32610dcfd1b': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.rateLimitExceeded, + }); + break; + } + default: { + console.log(err); + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: JSON.stringify(err) + }); + } + } + + challengeData = null; + totpLogin = false; + signing = false; +} + +function resetPassword() { + os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, { + }, 'closed'); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue index bda2495ba7..6dad9257a4 100644 --- a/packages/client/src/components/signup-dialog.vue +++ b/packages/client/src/components/signup-dialog.vue @@ -27,8 +27,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; }>(); const dialog = $ref<InstanceType<typeof XModalWindow>>(); diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue index 38a9fd55f1..3f2af306e5 100644 --- a/packages/client/src/components/signup.vue +++ b/packages/client/src/components/signup.vue @@ -1,11 +1,11 @@ <template> -<form class="qlvuhzng _formRoot" :autocomplete="Math.random()" @submit.prevent="onSubmit"> +<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit"> <template v-if="meta"> - <MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :autocomplete="Math.random()" spellcheck="false" required> + <MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" spellcheck="false" required> <template #label>{{ $ts.invitationCode }}</template> <template #prefix><i class="fas fa-key"></i></template> </MkInput> - <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> + <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> <template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> <template #prefix>@</template> <template #suffix>@{{ host }}</template> @@ -19,7 +19,7 @@ <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> </template> </MkInput> - <MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> + <MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> <template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> <template #prefix><i class="fas fa-envelope"></i></template> <template #caption> @@ -34,7 +34,7 @@ <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> </template> </MkInput> - <MkInput v-model="password" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password @update:modelValue="onChangePassword"> + <MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> <template #label>{{ $ts.password }}</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -43,7 +43,7 @@ <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> </template> </MkInput> - <MkInput v-model="retypedPassword" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> + <MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> <template #label>{{ $ts.password }} ({{ $ts.retype }})</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -58,8 +58,8 @@ </template> </I18n> </MkSwitch> - <captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> - <captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> + <MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> + <MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> </template> </form> @@ -67,7 +67,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; -const getPasswordStrength = require('syuilo-password-strength'); +const getPasswordStrength = await import('syuilo-password-strength'); import { toUnicode } from 'punycode/'; import { host, url } from '@/config'; import MkButton from './ui/button.vue'; @@ -81,7 +81,7 @@ export default defineComponent({ MkButton, MkInput, MkSwitch, - captcha: defineAsyncComponent(() => import('./captcha.vue')), + MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')), }, props: { @@ -111,7 +111,7 @@ export default defineComponent({ ToSAgreement: false, hCaptchaResponse: null, reCaptchaResponse: null, - } + }; }, computed: { @@ -124,20 +124,20 @@ export default defineComponent({ this.meta.tosUrl && !this.ToSAgreement || this.meta.enableHcaptcha && !this.hCaptchaResponse || this.meta.enableRecaptcha && !this.reCaptchaResponse || - this.passwordRetypeState == 'not-match'; + this.passwordRetypeState === 'not-match'; }, shouldShowProfileUrl(): boolean { - return (this.username != '' && - this.usernameState != 'invalid-format' && - this.usernameState != 'min-range' && - this.usernameState != 'max-range'); + return (this.username !== '' && + this.usernameState !== 'invalid-format' && + this.usernameState !== 'min-range' && + this.usernameState !== 'max-range'); } }, methods: { onChangeUsername() { - if (this.username == '') { + if (this.username === '') { this.usernameState = null; return; } @@ -165,7 +165,7 @@ export default defineComponent({ }, onChangeEmail() { - if (this.email == '') { + if (this.email === '') { this.emailState = null; return; } @@ -188,7 +188,7 @@ export default defineComponent({ }, onChangePassword() { - if (this.password == '') { + if (this.password === '') { this.passwordStrength = ''; return; } @@ -198,12 +198,12 @@ export default defineComponent({ }, onChangePasswordRetype() { - if (this.retypedPassword == '') { + if (this.retypedPassword === '') { this.passwordRetypeState = null; return; } - this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match'; + this.passwordRetypeState = this.password === this.retypedPassword ? 'match' : 'not-match'; }, onSubmit() { diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index 59956b9526..a3fa27ab78 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -19,8 +19,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'note'): void; - (e: 'queue', count: number): void; + (ev: 'note'): void; + (ev: 'queue', count: number): void; }>(); provide('inChannel', computed(() => props.src === 'channel')); @@ -95,7 +95,7 @@ if (props.src === 'antenna') { visibility: 'specified' }; const onNote = note => { - if (note.visibility == 'specified') { + if (note.visibility === 'specified') { prepend(note); } }; diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 99933f3846..c9fad64eb6 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -19,7 +19,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const zIndex = os.claimZIndex('high'); diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index c7b6c8ba96..e6b20d9881 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -90,32 +90,32 @@ export default defineComponent({ } }, methods: { - onMousedown(e: MouseEvent) { + onMousedown(evt: MouseEvent) { function distance(p, q) { return Math.hypot(p.x - q.x, p.y - q.y); } function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) { - const origin = {x: circleCenterX, y: circleCenterY}; - const dist1 = distance({x: 0, y: 0}, origin); - const dist2 = distance({x: boxW, y: 0}, origin); - const dist3 = distance({x: 0, y: boxH}, origin); - const dist4 = distance({x: boxW, y: boxH }, origin); + const origin = { x: circleCenterX, y: circleCenterY }; + const dist1 = distance({ x: 0, y: 0 }, origin); + const dist2 = distance({ x: boxW, y: 0 }, origin); + const dist3 = distance({ x: 0, y: boxH }, origin); + const dist4 = distance({ x: boxW, y: boxH }, origin); return Math.max(dist1, dist2, dist3, dist4) * 2; } - const rect = e.target.getBoundingClientRect(); + const rect = evt.target.getBoundingClientRect(); const ripple = document.createElement('div'); - ripple.style.top = (e.clientY - rect.top - 1).toString() + 'px'; - ripple.style.left = (e.clientX - rect.left - 1).toString() + 'px'; + ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; + ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; this.$refs.ripples.appendChild(ripple); - const circleCenterX = e.clientX - rect.left; - const circleCenterY = e.clientY - rect.top; + const circleCenterX = evt.clientX - rect.left; + const circleCenterY = evt.clientY - rect.top; - const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY); + const scale = calcCircleScale(evt.target.clientWidth, evt.target.clientHeight, circleCenterX, circleCenterY); window.setTimeout(() => { ripple.style.transform = 'scale(' + (scale / 2) + ')'; diff --git a/packages/client/src/components/ui/context-menu.vue b/packages/client/src/components/ui/context-menu.vue index f491b43b46..e637d361cf 100644 --- a/packages/client/src/components/ui/context-menu.vue +++ b/packages/client/src/components/ui/context-menu.vue @@ -19,7 +19,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); let rootEl = $ref<HTMLDivElement>(); @@ -63,8 +63,8 @@ onBeforeUnmount(() => { } }); -function onMousedown(e: Event) { - if (!contains(rootEl, e.target) && (rootEl != e.target)) emit('closed'); +function onMousedown(evt: Event) { + if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed'); } </script> diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue index fe1602b2bb..7daa82cbd3 100644 --- a/packages/client/src/components/ui/folder.vue +++ b/packages/client/src/components/ui/folder.vue @@ -23,7 +23,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; const localStoragePrefix = 'ui:folder:'; diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index a93cc8cda8..dad5dfa8b0 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -1,5 +1,6 @@ <template> -<div ref="itemsEl" v-hotkey="keymap" +<div + ref="itemsEl" v-hotkey="keymap" class="rrevdjwt" :class="{ center: align === 'center', asDrawer }" :style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" @@ -60,7 +61,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'close'): void; + (ev: 'close'): void; }>(); let itemsEl = $ref<HTMLDivElement>(); @@ -162,6 +163,15 @@ function focusDown() { position: relative; } + &:not(:disabled):hover { + color: var(--accent); + text-decoration: none; + + &:before { + background: var(--accentedBg); + } + } + &.danger { color: #ff2a2a; @@ -191,15 +201,6 @@ function focusDown() { } } - &:not(:disabled):hover { - color: var(--accent); - text-decoration: none; - - &:before { - background: var(--accentedBg); - } - } - &:not(:active):focus-visible { box-shadow: 0 0 0 2px var(--focus) inset; } diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue index b4b8c2b965..d2b2ccff7a 100644 --- a/packages/client/src/components/ui/modal-window.vue +++ b/packages/client/src/components/ui/modal-window.vue @@ -1,7 +1,7 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')"> - <div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> - <div class="header"> +<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> + <div ref="rootEl" class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> + <div ref="headerEl" class="header"> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button> <span class="title"> <slot name="header"></slot> @@ -11,82 +11,82 @@ </div> <div v-if="padding" class="body"> <div class="_section"> - <slot></slot> + <slot :width="bodyWidth" :height="bodyHeight"></slot> </div> </div> <div v-else class="body"> - <slot></slot> + <slot :width="bodyWidth" :height="bodyHeight"></slot> </div> </div> </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted } from 'vue'; import MkModal from './modal.vue'; -export default defineComponent({ - components: { - MkModal - }, - props: { - withOkButton: { - type: Boolean, - required: false, - default: false - }, - okButtonDisabled: { - type: Boolean, - required: false, - default: false - }, - padding: { - type: Boolean, - required: false, - default: false - }, - width: { - type: Number, - required: false, - default: 400 - }, - height: { - type: Number, - required: false, - default: null - }, - canClose: { - type: Boolean, - required: false, - default: true, - }, - scroll: { - type: Boolean, - required: false, - default: true, - }, - }, +const props = withDefaults(defineProps<{ + withOkButton: boolean; + okButtonDisabled: boolean; + padding: boolean; + width: number; + height: number | null; + scroll: boolean; +}>(), { + withOkButton: false, + okButtonDisabled: false, + padding: false, + width: 400, + height: null, + scroll: true, +}); - emits: ['click', 'close', 'closed', 'ok'], +const emit = defineEmits<{ + (event: 'click'): void; + (event: 'close'): void; + (event: 'closed'): void; + (event: 'ok'): void; +}>(); - data() { - return { - }; - }, +let modal = $ref<InstanceType<typeof MkModal>>(); +let rootEl = $ref<HTMLElement>(); +let headerEl = $ref<HTMLElement>(); +let bodyWidth = $ref(0); +let bodyHeight = $ref(0); - methods: { - close() { - this.$refs.modal.close(); - }, +const close = () => { + modal.close(); +}; - onKeydown(e) { - if (e.which === 27) { // Esc - e.preventDefault(); - e.stopPropagation(); - this.close(); - } - }, +const onBgClick = () => { + emit('click'); +}; + +const onKeydown = (evt) => { + if (evt.which === 27) { // Esc + evt.preventDefault(); + evt.stopPropagation(); + close(); } +}; + +const ro = new ResizeObserver((entries, observer) => { + bodyWidth = rootEl.offsetWidth; + bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; +}); + +onMounted(() => { + bodyWidth = rootEl.offsetWidth; + bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; + ro.observe(rootEl); +}); + +onUnmounted(() => { + ro.disconnect(); +}); + +defineExpose({ + close, }); </script> diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index 1e4159055e..d6a29ec4b7 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -1,5 +1,5 @@ <template> -<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered"> +<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"> <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> <div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> @@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'opening'): void; + (ev: 'opened'): void; (ev: 'click'): void; (ev: 'esc'): void; (ev: 'close'): void; @@ -212,7 +213,9 @@ const align = () => { popover.style.top = top + 'px'; }; -const childRendered = () => { +const onOpened = () => { + emit('opened'); + // ใขใผใใซใณใณใใณใใซใใฆในใใฟใณใๆผใใใใณใณใใณใๅคใงใใฆในใใฟใณใ้ขใใใใจใใซใขใผใใซใใใฏใฐใฉใฆใณใใฏใชใใฏใจๅคๅฎใใใชใใใใซใใฆในใคใใณใใ็ฃ่ฆใใใฉใฐ็ฎก็ใใ const el = content.value!.children[0]; el.addEventListener('mousedown', ev => { @@ -234,10 +237,10 @@ onMounted(() => { } fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); - await nextTick() + await nextTick(); align(); - }, { immediate: true, }); + }, { immediate: true }); nextTick(() => { const popover = content.value; diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 13f3215671..c081e06acd 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -14,8 +14,14 @@ </div> <div v-else ref="rootEl"> + <div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> + <MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead"> + {{ $ts.loadMore }} + </MkButton> + <MkLoading v-else class="loading"/> + </div> <slot :items="items"></slot> - <div v-show="more" key="_more_" class="cxiknjgy _gap"> + <div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> {{ $ts.loadMore }} </MkButton> @@ -62,7 +68,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'queue', count: number): void; + (ev: 'queue', count: number): void; }>(); type Item = { id: string; [another: string]: unknown; }; @@ -106,7 +112,7 @@ const init = async (): Promise<void> => { offset.value = res.length; error.value = false; fetching.value = false; - }, e => { + }, err => { error.value = true; fetching.value = false; }); @@ -149,7 +155,7 @@ const fetchMore = async (): Promise<void> => { } offset.value += res.length; moreFetching.value = false; - }, e => { + }, err => { moreFetching.value = false; }); }; @@ -177,7 +183,7 @@ const fetchMoreAhead = async (): Promise<void> => { } offset.value += res.length; moreFetching.value = false; - }, e => { + }, err => { moreFetching.value = false; }); }; @@ -244,6 +250,11 @@ const append = (item: Item): void => { items.value.push(item); }; +const removeItem = (finder: (item: Item) => boolean) => { + const i = items.value.findIndex(finder); + items.value.splice(i, 1); +}; + const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => { const i = items.value.findIndex(item => item.id === id); items.value[i] = replacer(items.value[i]); @@ -270,11 +281,12 @@ onDeactivated(() => { defineExpose({ items, + queue, backed, reload, - fetchMoreAhead, prepend, append, + removeItem, updateItem, }); </script> diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue index 8d6c1b5695..2bc7030d77 100644 --- a/packages/client/src/components/ui/popup-menu.vue +++ b/packages/client/src/components/ui/popup-menu.vue @@ -19,7 +19,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); let modal = $ref<InstanceType<typeof MkModal>>(); diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index ee1909554e..571d11ba3b 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -63,7 +63,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenBottom = () => { let left: number; @@ -84,7 +84,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenLeft = () => { let left: number; @@ -105,7 +105,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenRight = () => { let left: number; @@ -126,7 +126,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calc = (): { left: number; @@ -172,7 +172,7 @@ const setPosition = () => { } return null as never; - } + }; const { left, top, transformOrigin } = calc(); el.value.style.transformOrigin = transformOrigin; diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index fa32ecfdef..2066cf579d 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -139,10 +139,10 @@ export default defineComponent({ this.showing = false; }, - onKeydown(e) { - if (e.which === 27) { // Esc - e.preventDefault(); - e.stopPropagation(); + onKeydown(evt) { + if (evt.which === 27) { // Esc + evt.preventDefault(); + evt.stopPropagation(); this.close(); } }, @@ -162,15 +162,15 @@ export default defineComponent({ this.top(); }, - onHeaderMousedown(e) { + onHeaderMousedown(evt) { const main = this.$el as any; if (!contains(main, document.activeElement)) main.focus(); const position = main.getBoundingClientRect(); - const clickX = e.touches && e.touches.length > 0 ? e.touches[0].clientX : e.clientX; - const clickY = e.touches && e.touches.length > 0 ? e.touches[0].clientY : e.clientY; + const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX; + const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY; const moveBaseX = clickX - position.left; const moveBaseY = clickY - position.top; const browserWidth = window.innerWidth; @@ -204,10 +204,10 @@ export default defineComponent({ }, // ไธใใณใใซๆดใฟๆ - onTopHandleMousedown(e) { + onTopHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientY; + const base = evt.clientY; const height = parseInt(getComputedStyle(main, '').height, 10); const top = parseInt(getComputedStyle(main, '').top, 10); @@ -230,10 +230,10 @@ export default defineComponent({ }, // ๅณใใณใใซๆดใฟๆ - onRightHandleMousedown(e) { + onRightHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientX; + const base = evt.clientX; const width = parseInt(getComputedStyle(main, '').width, 10); const left = parseInt(getComputedStyle(main, '').left, 10); const browserWidth = window.innerWidth; @@ -254,10 +254,10 @@ export default defineComponent({ }, // ไธใใณใใซๆดใฟๆ - onBottomHandleMousedown(e) { + onBottomHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientY; + const base = evt.clientY; const height = parseInt(getComputedStyle(main, '').height, 10); const top = parseInt(getComputedStyle(main, '').top, 10); const browserHeight = window.innerHeight; @@ -278,10 +278,10 @@ export default defineComponent({ }, // ๅทฆใใณใใซๆดใฟๆ - onLeftHandleMousedown(e) { + onLeftHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientX; + const base = evt.clientX; const width = parseInt(getComputedStyle(main, '').width, 10); const left = parseInt(getComputedStyle(main, '').left, 10); @@ -304,27 +304,27 @@ export default defineComponent({ }, // ๅทฆไธใใณใใซๆดใฟๆ - onTopLeftHandleMousedown(e) { - this.onTopHandleMousedown(e); - this.onLeftHandleMousedown(e); + onTopLeftHandleMousedown(evt) { + this.onTopHandleMousedown(evt); + this.onLeftHandleMousedown(evt); }, // ๅณไธใใณใใซๆดใฟๆ - onTopRightHandleMousedown(e) { - this.onTopHandleMousedown(e); - this.onRightHandleMousedown(e); + onTopRightHandleMousedown(evt) { + this.onTopHandleMousedown(evt); + this.onRightHandleMousedown(evt); }, // ๅณไธใใณใใซๆดใฟๆ - onBottomRightHandleMousedown(e) { - this.onBottomHandleMousedown(e); - this.onRightHandleMousedown(e); + onBottomRightHandleMousedown(evt) { + this.onBottomHandleMousedown(evt); + this.onRightHandleMousedown(evt); }, // ๅทฆไธใใณใใซๆดใฟๆ - onBottomLeftHandleMousedown(e) { - this.onBottomHandleMousedown(e); - this.onLeftHandleMousedown(e); + onBottomLeftHandleMousedown(evt) { + this.onBottomHandleMousedown(evt); + this.onLeftHandleMousedown(evt); }, // ้ซใใ้ฉ็จ diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index c7bbd1fbd1..6c593c7b41 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -90,7 +90,7 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the sitename = info.sitename; fetching = false; player = info.player; - }) + }); }); function adjustTweetHeight(message: any) { diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index 51c5330564..f80947f75a 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -70,7 +70,7 @@ export default defineComponent({ }, mounted() { - if (typeof this.q == 'object') { + if (typeof this.q === 'object') { this.user = this.q; this.fetched = true; } else { diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue index dbef34d547..b34d21af07 100644 --- a/packages/client/src/components/user-select-dialog.vue +++ b/packages/client/src/components/user-select-dialog.vue @@ -60,9 +60,9 @@ import * as os from '@/os'; import { defaultStore } from '@/store'; const emit = defineEmits<{ - (e: 'ok', selected: misskey.entities.UserDetailed): void; - (e: 'cancel'): void; - (e: 'closed'): void; + (ev: 'ok', selected: misskey.entities.UserDetailed): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; }>(); let username = $ref(''); diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 4b20063a51..c717c3a461 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -57,9 +57,9 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; - (e: 'changeLocalOnly', v: boolean): void; - (e: 'closed'): void; + (ev: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (ev: 'changeLocalOnly', v: boolean): void; + (ev: 'closed'): void; }>(); let v = $ref(props.currentVisibility); diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue index 7dfcc55695..9e631b55b1 100644 --- a/packages/client/src/components/waiting-dialog.vue +++ b/packages/client/src/components/waiting-dialog.vue @@ -21,8 +21,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'done'); - (e: 'closed'); + (ev: 'done'); + (ev: 'closed'); }>(); function done() { diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue index da9d935281..74dd79f733 100644 --- a/packages/client/src/components/widgets.vue +++ b/packages/client/src/components/widgets.vue @@ -2,11 +2,11 @@ <div class="vjoppmmu"> <template v-if="edit"> <header> - <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)"> + <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select"> <template #label>{{ $ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ $t(`_widgets.${widget}`) }}</option> </MkSelect> - <MkButton inline primary @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> + <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> </header> <XDraggable @@ -19,7 +19,7 @@ <div class="customize-container"> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> - <component class="handle" :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/> + <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="handle" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </template> </XDraggable> @@ -37,7 +37,7 @@ import { widgets as widgetDefs } from '@/widgets'; export default defineComponent({ components: { - XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), + XDraggable: defineAsyncComponent(() => import('vuedraggable')), MkSelect, MkButton, }, diff --git a/packages/client/src/directives/adaptive-border.ts b/packages/client/src/directives/adaptive-border.ts index fc426ca2cc..619c9f0b6d 100644 --- a/packages/client/src/directives/adaptive-border.ts +++ b/packages/client/src/directives/adaptive-border.ts @@ -9,7 +9,7 @@ export default { } else { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } - } + }; const parentBg = getBgColor(src.parentElement); diff --git a/packages/client/src/directives/get-size.ts b/packages/client/src/directives/get-size.ts index 1fcd0718dc..2c4e9c188d 100644 --- a/packages/client/src/directives/get-size.ts +++ b/packages/client/src/directives/get-size.ts @@ -25,12 +25,12 @@ function calc(src: Element) { return; } if (info.intersection) { - info.intersection.disconnect() + info.intersection.disconnect(); delete info.intersection; - }; + } info.fn(width, height); -}; +} export default { mounted(src, binding, vn) { diff --git a/packages/client/src/directives/panel.ts b/packages/client/src/directives/panel.ts index 5f9158db2e..d31dc41ed4 100644 --- a/packages/client/src/directives/panel.ts +++ b/packages/client/src/directives/panel.ts @@ -9,7 +9,7 @@ export default { } else { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } - } + }; const parentBg = getBgColor(src.parentElement); diff --git a/packages/client/src/directives/size.ts b/packages/client/src/directives/size.ts index 36f649f180..51855e0de5 100644 --- a/packages/client/src/directives/size.ts +++ b/packages/client/src/directives/size.ts @@ -60,9 +60,9 @@ function calc(el: Element) { return; } if (info.intersection) { - info.intersection.disconnect() + info.intersection.disconnect(); delete info.intersection; - }; + } mountings.set(el, Object.assign(info, { previousWidth: width })); diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index dd715227a4..0e69da954e 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -1,7 +1,7 @@ // TODO: useTooltip้ขๆฐไฝฟใใใใซใใใ // ใใ ใใฃใฌใฏใใฃใๅ ใงonUnmountedใชใฉใฎcomposition apiไฝฟใใใฎใไธๆ -import { Directive, ref } from 'vue'; +import { defineAsyncComponent, Directive, ref } from 'vue'; import { isTouchUsing } from '@/scripts/touch'; import { popup, alert } from '@/os'; @@ -45,7 +45,7 @@ export default { if (self.text == null) return; const showing = ref(true); - popup(import('@/components/ui/tooltip.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { showing, text: self.text, targetElement: el, diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts index cdd2afa194..9d18a69877 100644 --- a/packages/client/src/directives/user-preview.ts +++ b/packages/client/src/directives/user-preview.ts @@ -1,4 +1,4 @@ -import { Directive, ref } from 'vue'; +import { defineAsyncComponent, Directive, ref } from 'vue'; import autobind from 'autobind-decorator'; import { popup } from '@/os'; @@ -24,7 +24,7 @@ export class UserPreview { const showing = ref(true); - popup(import('@/components/user-preview.vue'), { + popup(defineAsyncComponent(() => import('@/components/user-preview.vue')), { showing, q: this.user, source: this.el diff --git a/packages/client/src/emojilist.json b/packages/client/src/emojilist.json index 75c424ab4b..402e82e33b 100644 --- a/packages/client/src/emojilist.json +++ b/packages/client/src/emojilist.json @@ -96,6 +96,13 @@ { "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] }, { "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] }, { "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] }, + { "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] }, + { "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] }, + { "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] }, + { "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] }, + { "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] }, + { "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] }, + { "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] }, { "category": "face", "char": "๐ฉ", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] }, { "category": "face", "char": "๐", "name": "smiling_imp", "keywords": ["devil", "horns"] }, { "category": "face", "char": "๐ฟ", "name": "imp", "keywords": ["devil", "angry", "horns"] }, @@ -149,11 +156,19 @@ { "category": "people", "char": "๐ค", "name": "crossed_fingers", "keywords": ["good", "lucky"] }, { "category": "people", "char": "๐", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] }, { "category": "people", "char": "โ", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] }, + { "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] }, { "category": "people", "char": "๐ค", "name": "pinching_hand", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "๐ค", "name": "pinched_fingers", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "๐คณ", "name": "selfie", "keywords": ["camera", "phone"] }, { "category": "people", "char": "๐ ", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] }, { "category": "people", "char": "๐", "name": "lips", "keywords": ["mouth", "kiss"] }, + { "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] }, { "category": "people", "char": "๐ฆท", "name": "tooth", "keywords": ["teeth", "dentist"] }, { "category": "people", "char": "๐ ", "name": "tongue", "keywords": ["mouth", "playful"] }, { "category": "people", "char": "๐", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] }, @@ -275,7 +290,11 @@ { "category": "people", "char": "๐งโโ๏ธ", "name": "woman_fairy", "keywords": ["woman", "female"] }, { "category": "people", "char": "๐งโโ๏ธ", "name": "man_fairy", "keywords": ["man", "male"] }, { "category": "people", "char": "๐ผ", "name": "angel", "keywords": ["heaven", "wings", "halo"] }, + { "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] }, { "category": "people", "char": "๐คฐ", "name": "pregnant_woman", "keywords": ["baby"] }, + { "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] }, { "category": "people", "char": "๐คฑ", "name": "breastfeeding", "keywords": ["nursing", "baby"] }, { "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] }, { "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] }, @@ -459,7 +478,7 @@ { "category": "animals_and_nature", "char": "๐", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] }, { "category": "animals_and_nature", "char": "๐ฆ", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] }, { "category": "animals_and_nature", "char": "๐", "name": "snail", "keywords": ["slow", "animal", "shell"] }, - { "category": "animals_and_nature", "char": "๐", "name": "beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, + { "category": "animals_and_nature", "char": "๐", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, { "category": "animals_and_nature", "char": "๐", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] }, { "category": "animals_and_nature", "char": "๐ฆ", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] }, { "category": "animals_and_nature", "char": "๐ท", "name": "spider", "keywords": ["animal", "arachnid"] }, @@ -615,6 +634,10 @@ { "category": "animals_and_nature", "char": "๐ง", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] }, { "category": "animals_and_nature", "char": "๐ฆ", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] }, { "category": "animals_and_nature", "char": "๐", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] }, { "category": "food_and_drink", "char": "๐", "name": "green_apple", "keywords": ["fruit", "nature"] }, { "category": "food_and_drink", "char": "๐", "name": "apple", "keywords": ["fruit", "mac", "school"] }, { "category": "food_and_drink", "char": "๐", "name": "pear", "keywords": ["fruit", "nature", "food"] }, @@ -737,6 +760,9 @@ { "category": "food_and_drink", "char": "๐ฅฃ", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] }, { "category": "food_and_drink", "char": "๐ฅก", "name": "takeout_box", "keywords": ["food", "leftovers"] }, { "category": "food_and_drink", "char": "๐ฅข", "name": "chopsticks", "keywords": ["food"] }, + { "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] }, { "category": "activity", "char": "โฝ", "name": "soccer", "keywords": ["sports", "football"] }, { "category": "activity", "char": "๐", "name": "basketball", "keywords": ["sports", "balls", "NBA"] }, { "category": "activity", "char": "๐", "name": "football", "keywords": ["sports", "balls", "NFL"] }, @@ -844,6 +870,8 @@ { "category": "activity", "char": "๐ช", "name": "magic_wand", "keywords": [] }, { "category": "activity", "char": "๐ช ", "name": "pinata", "keywords": [] }, { "category": "activity", "char": "๐ช", "name": "nesting_dolls", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] }, { "category": "travel_and_places", "char": "๐", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] }, { "category": "travel_and_places", "char": "๐", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] }, { "category": "travel_and_places", "char": "๐", "name": "blue_car", "keywords": ["transportation", "vehicle"] }, @@ -971,11 +999,12 @@ { "category": "travel_and_places", "char": "๐", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] }, { "category": "travel_and_places", "char": "โฉ", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] }, { "category": "travel_and_places", "char": "๐", "name": "hindu_temple", "keywords": ["temple"] }, - { "category": "travel_and_places", "char": "๐ชจ", "name": "rock", "keywords": [] }, { "category": "travel_and_places", "char": "๐ชต", "name": "wood", "keywords": [] }, { "category": "travel_and_places", "char": "๐", "name": "hut", "keywords": [] }, - + { "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] }, { "category": "objects", "char": "โ", "name": "watch", "keywords": ["time", "accessories"] }, { "category": "objects", "char": "๐ฑ", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] }, { "category": "objects", "char": "๐ฒ", "name": "calling", "keywords": ["iphone", "incoming"] }, @@ -1016,6 +1045,7 @@ { "category": "objects", "char": "โ", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] }, { "category": "objects", "char": "๐ก", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] }, { "category": "objects", "char": "๐", "name": "battery", "keywords": ["power", "energy", "sustain"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] }, { "category": "objects", "char": "๐", "name": "electric_plug", "keywords": ["charger", "power"] }, { "category": "objects", "char": "๐ก", "name": "bulb", "keywords": ["light", "electricity", "idea"] }, { "category": "objects", "char": "๐ฆ", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] }, @@ -1031,6 +1061,7 @@ { "category": "objects", "char": "๐ฐ", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "๐ช", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "๐ณ", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] }, { "category": "objects", "char": "๐", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] }, { "category": "objects", "char": "โ", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] }, { "category": "objects", "char": "๐งฐ", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] }, @@ -1077,6 +1108,8 @@ { "category": "objects", "char": "๐ฉน", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "๐ฉบ", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "๐ช", "name": "razor", "keywords": ["health"] }, + { "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] }, + { "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] }, { "category": "objects", "char": "๐งฌ", "name": "dna", "keywords": ["biologist", "genetics", "life"] }, { "category": "objects", "char": "๐งซ", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] }, { "category": "objects", "char": "๐งช", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] }, @@ -1111,6 +1144,7 @@ { "category": "objects", "char": "๐ชค", "name": "mouse_trap", "keywords": ["household"] }, { "category": "objects", "char": "๐ชฃ", "name": "bucket", "keywords": ["household"] }, { "category": "objects", "char": "๐ชฅ", "name": "toothbrush", "keywords": ["household"] }, + { "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] }, { "category": "objects", "char": "โฑ", "name": "parasol_on_ground", "keywords": ["weather", "summer"] }, { "category": "objects", "char": "๐ฟ", "name": "moyai", "keywords": ["rock", "easter island", "moai"] }, { "category": "objects", "char": "๐", "name": "shopping", "keywords": ["mall", "buy", "purchase"] }, @@ -1404,6 +1438,7 @@ { "category": "symbols", "char": "โ", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] }, { "category": "symbols", "char": "โ", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] }, { "category": "symbols", "char": "โ๏ธ", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] }, + { "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] }, { "category": "symbols", "char": "โพ", "name": "infinity", "keywords": ["forever"] }, { "category": "symbols", "char": "๐ฒ", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] }, { "category": "symbols", "char": "๐ฑ", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] }, @@ -1747,3 +1782,4 @@ { "category": "flags", "char": "๐บ๐ณ", "name": "united_nations", "keywords": ["un", "flag", "banner"] }, { "category": "flags", "char": "๐ดโโ ๏ธ", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] } ] + diff --git a/packages/client/src/filters/bytes.ts b/packages/client/src/filters/bytes.ts index 50e63534b6..c80f2f0ed2 100644 --- a/packages/client/src/filters/bytes.ts +++ b/packages/client/src/filters/bytes.ts @@ -1,7 +1,7 @@ export default (v, digits = 0) => { if (v == null) return '?'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - if (v == 0) return '0'; + if (v === 0) return '0'; const isMinus = v < 0; if (isMinus) v = -v; const i = Math.floor(Math.log(v) / Math.log(1024)); diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index ab3299d22b..bb6176e409 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -13,9 +13,9 @@ if (localStorage.getItem('accounts') != null) { } //#endregion -import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue'; +import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; import compareVersions from 'compare-versions'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; import widgets from '@/widgets'; import directives from '@/directives'; @@ -146,8 +146,7 @@ if ($i && $i.token) { try { document.body.innerHTML = '<div>Please wait...</div>'; await login(i); - location.reload(); - } catch (e) { + } catch (err) { // Render the error screen // TODO: ใกใใใจใใใณใณใใผใใณใใใฌใณใใชใณใฐใใ(v10ใจใใฎใใฉใใซใทใฅใผใใฃใณใฐใฒใผใ ไปใใฎใใคใฟใใใช) document.body.innerHTML = '<div id="err">Oops!</div>'; @@ -169,14 +168,14 @@ fetchInstanceMetaPromise.then(() => { initializeSw(); }); -const app = createApp(await ( - window.location.search === '?zen' ? import('@/ui/zen.vue') : - !$i ? import('@/ui/visitor.vue') : - ui === 'deck' ? import('@/ui/deck.vue') : - ui === 'desktop' ? import('@/ui/desktop.vue') : - ui === 'classic' ? import('@/ui/classic.vue') : - import('@/ui/universal.vue') -).then(x => x.default)); +const app = createApp( + window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : + !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : + ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : + ui === 'desktop' ? defineAsyncComponent(() => import('@/ui/desktop.vue')) : + ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : + defineAsyncComponent(() => import('@/ui/universal.vue')) +); if (_DEV_) { app.config.performance = true; @@ -204,8 +203,24 @@ if (splash) splash.addEventListener('transitionend', () => { splash.remove(); }); -const rootEl = document.createElement('div'); -document.body.appendChild(rootEl); +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 +// ใชใใinit.tsใฎๅ ๅฎนใ2ๅๅฎ่กใใใใใจใใใใใใmountใใdivใ1ใคใซๅถ้ใใ +const rootEl = (() => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentEl) { + console.warn('multiple import detected'); + return currentEl; + } + + const rootEl = document.createElement('div'); + rootEl.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(rootEl); + return rootEl; +})(); + app.mount(rootEl); // boot.jsใฎใใคใ่งฃ้ค @@ -231,10 +246,10 @@ if (lastVersion !== version) { if (lastVersion != null && compareVersions(version, lastVersion) === 1) { // ใญใฐใคใณใใฆใๅ ดๅใ ใ if ($i) { - popup(import('@/components/updated.vue'), {}, {}, 'closed'); + popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); } } - } catch (e) { + } catch (err) { } } @@ -319,7 +334,7 @@ stream.on('_disconnected_', async () => { } }); -stream.on('emojiAdded', data => { +stream.on('emojiAdded', emojiData => { // TODO //store.commit('instance/set', ); }); diff --git a/packages/client/src/instance.ts b/packages/client/src/instance.ts index 6e912aa2e5..d24eb2419a 100644 --- a/packages/client/src/instance.ts +++ b/packages/client/src/instance.ts @@ -4,11 +4,11 @@ import { api } from './os'; // TODO: ไปใฎใฟใใจๆฐธ็ถๅใใใstateใๅๆ -const data = localStorage.getItem('instance'); +const instanceData = localStorage.getItem('instance'); // TODO: instanceใใชใขใฏใใฃใใซใใใใฏๅ่ใฎไฝๅฐใใ -export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : { +export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : { // TODO: set default values }); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 43c110555f..14860465fa 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -1,6 +1,6 @@ // TODO: ใชใใงใใใใงใos.tsใซ็ชใฃ่พผใใฎใใใใใฎใงใใใชใซๅๅฒใใ -import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue'; +import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue'; import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as Misskey from 'misskey-js'; @@ -10,7 +10,6 @@ import MkWaitingDialog from '@/components/waiting-dialog.vue'; import { MenuItem } from '@/types/menu'; import { resolve } from '@/router'; import { $i } from '@/account'; -import { defaultStore } from '@/store'; export const pendingApiRequestsCount = ref(0); @@ -35,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s method: 'POST', body: JSON.stringify(data), credentials: 'omit', - cache: 'no-cache' + cache: 'no-cache', }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); @@ -60,10 +59,10 @@ export const apiWithDialog = (( token?: string | null | undefined, ) => { const promise = api(endpoint, data, token); - promiseDialog(promise, null, (e) => { + promiseDialog(promise, null, (err) => { alert({ type: 'error', - text: e.message + '\n' + (e as any).id, + text: err.message + '\n' + (err as any).id, }); }); @@ -73,7 +72,7 @@ export const apiWithDialog = (( export function promiseDialog<T extends Promise<any>>( promise: T, onSuccess?: ((res: any) => void) | null, - onFailure?: ((e: Error) => void) | null, + onFailure?: ((err: Error) => void) | null, text?: string, ): T { const showing = ref(true); @@ -89,14 +88,14 @@ export function promiseDialog<T extends Promise<any>>( showing.value = false; }, 1000); } - }).catch(e => { + }).catch(err => { showing.value = false; if (onFailure) { - onFailure(e); + onFailure(err); } else { alert({ type: 'error', - text: e + text: err, }); } }); @@ -111,10 +110,6 @@ export function promiseDialog<T extends Promise<any>>( return promise; } -function isModule(x: any): x is typeof import('*.vue') { - return x.default != null; -} - let popupIdCount = 0; export const popups = ref([]) as Ref<{ id: any; @@ -132,10 +127,7 @@ export function claimZIndex(priority: 'low' | 'middle' | 'high' = 'low'): number return zIndexes[priority]; } -export async function popup(component: Component | typeof import('*.vue') | Promise<Component | typeof import('*.vue')>, props: Record<string, any>, events = {}, disposeEvent?: string) { - if (component.then) component = await component; - - if (isModule(component)) component = component.default; +export async function popup(component: Component, props: Record<string, any>, events = {}, disposeEvent?: string) { markRaw(component); const id = ++popupIdCount; @@ -150,7 +142,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom props, events: disposeEvent ? { ...events, - [disposeEvent]: dispose + [disposeEvent]: dispose, } : events, id, }; @@ -164,7 +156,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom export function pageWindow(path: string) { const { component, props } = resolve(path); - popup(import('@/components/page-window.vue'), { + popup(defineAsyncComponent(() => import('@/components/page-window.vue')), { initialPath: path, initialComponent: markRaw(component), initialProps: props, @@ -173,7 +165,7 @@ export function pageWindow(path: string) { export function modalPageWindow(path: string) { const { component, props } = resolve(path); - popup(import('@/components/modal-page-window.vue'), { + popup(defineAsyncComponent(() => import('@/components/modal-page-window.vue')), { initialPath: path, initialComponent: markRaw(component), initialProps: props, @@ -181,8 +173,8 @@ export function modalPageWindow(path: string) { } export function toast(message: string) { - popup(import('@/components/toast.vue'), { - message + popup(defineAsyncComponent(() => import('@/components/toast.vue')), { + message, }, {}, 'closed'); } @@ -192,7 +184,7 @@ export function alert(props: { text?: string | null; }): Promise<void> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), props, { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, { done: result => { resolve(); }, @@ -206,7 +198,7 @@ export function confirm(props: { text?: string | null; }): Promise<{ canceled: boolean }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { ...props, showCancelButton: true, }, { @@ -227,14 +219,14 @@ export function inputText(props: { canceled: false; result: string; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: props.type, placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -252,14 +244,14 @@ export function inputNumber(props: { canceled: false; result: number; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: 'number', placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -277,14 +269,14 @@ export function inputDate(props: { canceled: false; result: Date; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: 'date', placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true }); @@ -293,7 +285,7 @@ export function inputDate(props: { }); } -export function select<C extends any = any>(props: { +export function select<C = any>(props: { title?: string | null; text?: string | null; default?: string | null; @@ -314,14 +306,14 @@ export function select<C extends any = any>(props: { canceled: false; result: C; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, select: { items: props.items, groupedItems: props.groupedItems, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -336,9 +328,9 @@ export function success() { window.setTimeout(() => { showing.value = false; }, 1000); - popup(import('@/components/waiting-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { success: true, - showing: showing + showing: showing, }, { done: () => resolve(), }, 'closed'); @@ -348,9 +340,9 @@ export function success() { export function waiting() { return new Promise((resolve, reject) => { const showing = ref(true); - popup(import('@/components/waiting-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { success: false, - showing: showing + showing: showing, }, { done: () => resolve(), }, 'closed'); @@ -359,7 +351,7 @@ export function waiting() { export function form(title, form) { return new Promise((resolve, reject) => { - popup(import('@/components/form-dialog.vue'), { title, form }, { + popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, { done: result => { resolve(result); }, @@ -369,7 +361,7 @@ export function form(title, form) { export async function selectUser() { return new Promise((resolve, reject) => { - popup(import('@/components/user-select-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, { ok: user => { resolve(user); }, @@ -379,9 +371,9 @@ export async function selectUser() { export async function selectDriveFile(multiple: boolean) { return new Promise((resolve, reject) => { - popup(import('@/components/drive-select-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { type: 'file', - multiple + multiple, }, { done: files => { if (files) { @@ -394,9 +386,9 @@ export async function selectDriveFile(multiple: boolean) { export async function selectDriveFolder(multiple: boolean) { return new Promise((resolve, reject) => { - popup(import('@/components/drive-select-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { type: 'folder', - multiple + multiple, }, { done: folders => { if (folders) { @@ -409,9 +401,9 @@ export async function selectDriveFolder(multiple: boolean) { export async function pickEmoji(src: HTMLElement | null, opts) { return new Promise((resolve, reject) => { - popup(import('@/components/emoji-picker-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { src, - ...opts + ...opts, }, { done: emoji => { resolve(emoji); @@ -420,6 +412,21 @@ export async function pickEmoji(src: HTMLElement | null, opts) { }); } +export async function cropImage(image: Misskey.entities.DriveFile, options: { + aspectRatio: number; +}): Promise<Misskey.entities.DriveFile> { + return new Promise((resolve, reject) => { + popup(defineAsyncComponent(() => import('@/components/cropper-dialog.vue')), { + file: image, + aspectRatio: options.aspectRatio, + }, { + ok: x => { + resolve(x); + }, + }, 'closed'); + }); +} + type AwaitType<T> = T extends Promise<infer U> ? U : T extends (...args: any[]) => Promise<infer V> ? V : @@ -459,9 +466,9 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: characterData: false, }); - openingEmojiPicker = await popup(import('@/components/emoji-picker-window.vue'), { + openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), { src, - ...opts + ...opts, }, { chosen: emoji => { insertTextAtCursor(activeTextarea, emoji); @@ -470,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: openingEmojiPicker!.dispose(); openingEmojiPicker = null; observer.disconnect(); - } + }, }); } @@ -481,12 +488,12 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement }) { return new Promise((resolve, reject) => { let dispose; - popup(import('@/components/ui/popup-menu.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/popup-menu.vue')), { items, src, width: options?.width, align: options?.align, - viaKeyboard: options?.viaKeyboard + viaKeyboard: options?.viaKeyboard, }, { closed: () => { resolve(); @@ -502,7 +509,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent) ev.preventDefault(); return new Promise((resolve, reject) => { let dispose; - popup(import('@/components/ui/context-menu.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), { items, ev, }, { @@ -537,78 +544,6 @@ export function post(props: Record<string, any> = {}) { export const deckGlobalEvents = new EventEmitter(); -export const uploads = ref<{ - id: string; - name: string; - progressMax: number | undefined; - progressValue: number | undefined; - img: string; -}[]>([]); - -export function upload(file: File, folder?: any, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading): Promise<Misskey.entities.DriveFile> { - if (folder && typeof folder === 'object') folder = folder.id; - - return new Promise((resolve, reject) => { - const id = Math.random().toString(); - - const reader = new FileReader(); - reader.onload = (e) => { - const ctx = reactive({ - id: id, - name: name || file.name || 'untitled', - progressMax: undefined, - progressValue: undefined, - img: window.URL.createObjectURL(file) - }); - - uploads.value.push(ctx); - - console.log(keepOriginal); - - const data = new FormData(); - data.append('i', $i.token); - data.append('force', 'true'); - data.append('file', file); - - if (folder) data.append('folderId', folder); - if (name) data.append('name', name); - - const xhr = new XMLHttpRequest(); - xhr.open('POST', apiUrl + '/drive/files/create', true); - xhr.onload = (ev) => { - if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { - // TODO: ๆถใใฎใงใฏใชใใฆๅ้ใงใใใใใซใใใ - uploads.value = uploads.value.filter(x => x.id != id); - - alert({ - type: 'error', - text: 'upload failed' - }); - - reject(); - return; - } - - const driveFile = JSON.parse(ev.target.response); - - resolve(driveFile); - - uploads.value = uploads.value.filter(x => x.id != id); - }; - - xhr.upload.onprogress = e => { - if (e.lengthComputable) { - ctx.progressMax = e.total; - ctx.progressValue = e.loaded; - } - }; - - xhr.send(data); - }; - reader.readAsArrayBuffer(file); - }); -} - /* export function checkExistence(fileData: ArrayBuffer): Promise<any> { return new Promise((resolve, reject) => { diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index ff04ed84f2..691bc4f07b 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -150,6 +150,7 @@ const patrons = [ 'Weeble', '่ๆฎใใใ', 'ThatOneCalculator', + 'pixeldesu', ]; let easterEggReady = false; diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 92f93797ce..e1d0361c0b 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -24,10 +24,10 @@ </div> <!-- TODO <div class="inputs" style="display: flex; padding-top: 1.2em;"> - <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()"> + <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false"> <span>{{ $ts.username }}</span> </MkInput> - <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'"> <span>{{ $ts.host }}</span> </MkInput> </div> @@ -41,8 +41,8 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -50,45 +50,35 @@ import MkPagination from '@/components/ui/pagination.vue'; import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkPagination, - XAbuseReport, - }, +let reports = $ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +let state = $ref('unresolved'); +let reporterOrigin = $ref('combined'); +let targetUserOrigin = $ref('combined'); +let searchUsername = $ref(''); +let searchHost = $ref(''); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.abuseReports, - icon: 'fas fa-exclamation-circle', - bg: 'var(--bg)', - }, - searchUsername: '', - searchHost: '', - state: 'unresolved', - reporterOrigin: 'combined', - targetUserOrigin: 'combined', - pagination: { - endpoint: 'admin/abuse-user-reports' as const, - limit: 10, - params: computed(() => ({ - state: this.state, - reporterOrigin: this.reporterOrigin, - targetUserOrigin: this.targetUserOrigin, - })), - }, - } - }, +const pagination = { + endpoint: 'admin/abuse-user-reports' as const, + limit: 10, + params: computed(() => ({ + state, + reporterOrigin, + targetUserOrigin, + })), +}; - methods: { - resolved(reportId) { - this.$refs.reports.removeItem(item => item.id === reportId); - }, +function resolved(reportId) { + reports.removeItem(item => item.id === reportId); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.abuseReports, + icon: 'fas fa-exclamation-circle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index 8f164caa99..b18e08db96 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -7,7 +7,7 @@ <template #label>URL</template> </MkInput> <MkInput v-model="ad.imageUrl" class="_formBlock"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> <FormRadios v-model="ad.place" class="_formBlock"> <template #label>Form</template> @@ -17,34 +17,34 @@ </FormRadios> <!-- <div style="margin: 32px 0;"> - {{ $ts.priority }} - <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> - <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> - <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> + {{ i18n.ts.priority }} + <MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio> + <MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio> + <MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio> </div> --> <FormSplit> <MkInput v-model="ad.ratio" type="number"> - <template #label>{{ $ts.ratio }}</template> + <template #label>{{ i18n.ts.ratio }}</template> </MkInput> <MkInput v-model="ad.expiresAt" type="date"> - <template #label>{{ $ts.expiration }}</template> + <template #label>{{ i18n.ts.expiration }}</template> </MkInput> </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> - <template #label>{{ $ts.memo }}</template> + <template #label>{{ i18n.ts.memo }}</template> </MkTextarea> <div class="buttons _formBlock"> - <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; @@ -52,81 +52,65 @@ import FormRadios from '@/components/form/radios.vue'; import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - FormRadios, - FormSplit, - }, +let ads: any[] = $ref([]); - emits: ['info'], +os.api('admin/ad/list').then(adsResponse => { + ads = adsResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.ads, - icon: 'fas fa-audio-description', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - ads: [], - } - }, +function add() { + ads.unshift({ + id: null, + memo: '', + place: 'square', + priority: 'middle', + ratio: 1, + url: '', + imageUrl: null, + expiresAt: null, + }); +} - created() { - os.api('admin/ad/list').then(ads => { - this.ads = ads; +function remove(ad) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: ad.url }), + }).then(({ canceled }) => { + if (canceled) return; + ads = ads.filter(x => x !== ad); + os.apiWithDialog('admin/ad/delete', { + id: ad.id }); - }, + }); +} - methods: { - add() { - this.ads.unshift({ - id: null, - memo: '', - place: 'square', - priority: 'middle', - ratio: 1, - url: '', - imageUrl: null, - expiresAt: null, - }); - }, +function save(ad) { + if (ad.id == null) { + os.apiWithDialog('admin/ad/create', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } else { + os.apiWithDialog('admin/ad/update', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } +} - remove(ad) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: ad.url }), - }).then(({ canceled }) => { - if (canceled) return; - this.ads = this.ads.filter(x => x != ad); - os.apiWithDialog('admin/ad/delete', { - id: ad.id - }); - }); - }, - - save(ad) { - if (ad.id == null) { - os.apiWithDialog('admin/ad/create', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } else { - os.apiWithDialog('admin/ad/update', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.ads, + icon: 'fas fa-audio-description', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index a0d720bb29..97774975de 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -3,112 +3,98 @@ <section v-for="announcement in announcements" class="_card _gap announcements"> <div class="_content announcement"> <MkInput v-model="announcement.title"> - <template #label>{{ $ts.title }}</template> + <template #label>{{ i18n.ts.title }}</template> </MkInput> <MkTextarea v-model="announcement.text"> - <template #label>{{ $ts.text }}</template> + <template #label>{{ i18n.ts.text }}</template> </MkTextarea> <MkInput v-model="announcement.imageUrl"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> - <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> + <p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p> <div class="buttons"> - <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </section> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - }, +let announcements: any[] = $ref([]); - emits: ['info'], +os.api('admin/announcements/list').then(announcementResponse => { + announcements = announcementResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.announcements, - icon: 'fas fa-broadcast-tower', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - announcements: [], - } - }, +function add() { + announcements.unshift({ + id: null, + title: '', + text: '', + imageUrl: null + }); +} - created() { - os.api('admin/announcements/list').then(announcements => { - this.announcements = announcements; +function remove(announcement) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: announcement.title }), + }).then(({ canceled }) => { + if (canceled) return; + announcements = announcements.filter(x => x !== announcement); + os.api('admin/announcements/delete', announcement); + }); +} + +function save(announcement) { + if (announcement.id == null) { + os.api('admin/announcements/create', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err + }); }); - }, - - methods: { - add() { - this.announcements.unshift({ - id: null, - title: '', - text: '', - imageUrl: null + } else { + os.api('admin/announcements/update', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved }); - }, - - remove(announcement) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: announcement.title }), - }).then(({ canceled }) => { - if (canceled) return; - this.announcements = this.announcements.filter(x => x != announcement); - os.api('admin/announcements/delete', announcement); + }).catch(err => { + os.alert({ + type: 'error', + text: err }); - }, + }); + } +} - save(announcement) { - if (announcement.id == null) { - os.api('admin/announcements/create', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } else { - os.api('admin/announcements/update', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.announcements, + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5e0cdd96a5..30fee5015a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -43,8 +43,8 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import FormRadios from '@/components/form/radios.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; @@ -54,64 +54,39 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormRadios, - FormInput, - FormButton, - FormSuspense, - FormSlot, - MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), - }, +const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue')); - emits: ['info'], +let provider = $ref(null); +let hcaptchaSiteKey: string | null = $ref(null); +let hcaptchaSecretKey: string | null = $ref(null); +let recaptchaSiteKey: string | null = $ref(null); +let recaptchaSecretKey: string | null = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.botProtection, - icon: 'fas fa-shield-alt' - }, - provider: null, - enableHcaptcha: false, - hcaptchaSiteKey: null, - hcaptchaSecretKey: null, - enableRecaptcha: false, - recaptchaSiteKey: null, - recaptchaSecretKey: null, - } - }, +const enableHcaptcha = $computed(() => provider === 'hcaptcha'); +const enableRecaptcha = $computed(() => provider === 'recaptcha'); - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableHcaptcha = meta.enableHcaptcha; - this.hcaptchaSiteKey = meta.hcaptchaSiteKey; - this.hcaptchaSecretKey = meta.hcaptchaSecretKey; - this.enableRecaptcha = meta.enableRecaptcha; - this.recaptchaSiteKey = meta.recaptchaSiteKey; - this.recaptchaSecretKey = meta.recaptchaSecretKey; +async function init() { + const meta = await os.api('admin/meta'); + enableHcaptcha = meta.enableHcaptcha; + hcaptchaSiteKey = meta.hcaptchaSiteKey; + hcaptchaSecretKey = meta.hcaptchaSecretKey; + enableRecaptcha = meta.enableRecaptcha; + recaptchaSiteKey = meta.recaptchaSiteKey; + recaptchaSecretKey = meta.recaptchaSecretKey; - this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null; + provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null; +} - this.$watch(() => this.provider, () => { - this.enableHcaptcha = this.provider === 'hcaptcha'; - this.enableRecaptcha = this.provider === 'recaptcha'; - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableHcaptcha: this.enableHcaptcha, - hcaptchaSiteKey: this.hcaptchaSiteKey, - hcaptchaSecretKey: this.hcaptchaSecretKey, - enableRecaptcha: this.enableRecaptcha, - recaptchaSiteKey: this.recaptchaSiteKey, - recaptchaSecretKey: this.recaptchaSecretKey, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableHcaptcha, + hcaptchaSiteKey, + hcaptchaSecretKey, + enableRecaptcha, + recaptchaSiteKey, + recaptchaSecretKey, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index 3a835eeafa..d3519922b1 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -9,36 +9,23 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - MkKeyValue, - }, +const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.database, - icon: 'fas fa-database', - bg: 'var(--bg)', - }, - databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)), - } - }, - - methods: { - bytes, number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.database, + icon: 'fas fa-database', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 7df0b6db1c..aa13043193 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -3,37 +3,37 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormSwitch v-model="enableEmail" class="_formBlock"> - <template #label>{{ $ts.enableEmail }}</template> - <template #caption>{{ $ts.emailConfigInfo }}</template> + <template #label>{{ i18n.ts.enableEmail }}</template> + <template #caption>{{ i18n.ts.emailConfigInfo }}</template> </FormSwitch> <template v-if="enableEmail"> <FormInput v-model="email" type="email" class="_formBlock"> - <template #label>{{ $ts.emailAddress }}</template> + <template #label>{{ i18n.ts.emailAddress }}</template> </FormInput> <FormSection> - <template #label>{{ $ts.smtpConfig }}</template> + <template #label>{{ i18n.ts.smtpConfig }}</template> <FormSplit :min-width="280"> <FormInput v-model="smtpHost" class="_formBlock"> - <template #label>{{ $ts.smtpHost }}</template> + <template #label>{{ i18n.ts.smtpHost }}</template> </FormInput> <FormInput v-model="smtpPort" type="number" class="_formBlock"> - <template #label>{{ $ts.smtpPort }}</template> + <template #label>{{ i18n.ts.smtpPort }}</template> </FormInput> </FormSplit> <FormSplit :min-width="280"> <FormInput v-model="smtpUser" class="_formBlock"> - <template #label>{{ $ts.smtpUser }}</template> + <template #label>{{ i18n.ts.smtpUser }}</template> </FormInput> <FormInput v-model="smtpPass" type="password" class="_formBlock"> - <template #label>{{ $ts.smtpPass }}</template> + <template #label>{{ i18n.ts.smtpPass }}</template> </FormInput> </FormSplit> - <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo> <FormSwitch v-model="smtpSecure" class="_formBlock"> - <template #label>{{ $ts.smtpSecure }}</template> - <template #caption>{{ $ts.smtpSecureInfo }}</template> + <template #label>{{ i18n.ts.smtpSecure }}</template> + <template #caption>{{ i18n.ts.smtpSecureInfo }}</template> </FormSwitch> </FormSection> </template> @@ -42,8 +42,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -52,86 +52,71 @@ import FormSplit from '@/components/form/split.vue'; import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; +import { fetchInstance, instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSplit, - FormSection, - FormInfo, - FormSuspense, - }, +let enableEmail: boolean = $ref(false); +let email: any = $ref(null); +let smtpSecure: boolean = $ref(false); +let smtpHost: string = $ref(''); +let smtpPort: number = $ref(0); +let smtpUser: string = $ref(''); +let smtpPass: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + enableEmail = meta.enableEmail; + email = meta.email; + smtpSecure = meta.smtpSecure; + smtpHost = meta.smtpHost; + smtpPort = meta.smtpPort; + smtpUser = meta.smtpUser; + smtpPass = meta.smtpPass; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.emailServer, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - text: this.$ts.testEmail, - handler: this.testEmail, - }, { - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - enableEmail: false, - email: null, - smtpSecure: false, - smtpHost: '', - smtpPort: 0, - smtpUser: '', - smtpPass: '', - } - }, +async function testEmail() { + const { canceled, result: destination } = await os.inputText({ + title: i18n.ts.destination, + type: 'email', + placeholder: instance.maintainerEmail + }); + if (canceled) return; + os.apiWithDialog('admin/send-email', { + to: destination, + subject: 'Test email', + text: 'Yo' + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableEmail = meta.enableEmail; - this.email = meta.email; - this.smtpSecure = meta.smtpSecure; - this.smtpHost = meta.smtpHost; - this.smtpPort = meta.smtpPort; - this.smtpUser = meta.smtpUser; - this.smtpPass = meta.smtpPass; - }, +function save() { + os.apiWithDialog('admin/update-meta', { + enableEmail, + email, + smtpSecure, + smtpHost, + smtpPort, + smtpUser, + smtpPass, + }).then(() => { + fetchInstance(); + }); +} - async testEmail() { - const { canceled, result: destination } = await os.inputText({ - title: this.$ts.destination, - type: 'email', - placeholder: this.$instance.maintainerEmail - }); - if (canceled) return; - os.apiWithDialog('admin/send-email', { - to: destination, - subject: 'Test email', - text: 'Yo' - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableEmail: this.enableEmail, - email: this.email, - smtpSecure: this.smtpSecure, - smtpHost: this.smtpHost, - smtpPort: this.smtpPort, - smtpUser: this.smtpUser, - smtpPass: this.smtpPass, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.emailServer, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: i18n.ts.testEmail, + handler: testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index 2e3903426e..d482fa49e6 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -27,85 +27,71 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; import { unique } from '@/scripts/array'; +import { i18n } from '@/i18n'; +import { emojiCategories } from '@/instance'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const props = defineProps<{ + emoji: any, +}>(); - props: { - emoji: { - required: true, +let dialog = $ref(null); +let name: string = $ref(props.emoji.name); +let category: string = $ref(props.emoji.category); +let aliases: string = $ref(props.emoji.aliases.join(' ')); +let categories: string[] = $ref(emojiCategories); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean, updated?: any }): void, + (ev: 'closed'): void +}>(); + +function ok() { + update(); +} + +async function update() { + await os.apiWithDialog('admin/emoji/update', { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), + }); + + emit('done', { + updated: { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), } - }, + }); - emits: ['done', 'closed'], + dialog.close(); +} - data() { - return { - name: this.emoji.name, - category: this.emoji.category, - aliases: this.emoji.aliases?.join(' '), - categories: [], - } - }, +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: name }), + }); + if (canceled) return; - created() { - os.api('meta', { detail: false }).then(({ emojis }) => { - this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== '')); + os.api('admin/emoji/delete', { + id: props.emoji.id + }).then(() => { + emit('done', { + deleted: true }); - }, - - methods: { - ok() { - this.update(); - }, - - async update() { - await os.apiWithDialog('admin/emoji/update', { - id: this.emoji.id, - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - }); - - this.$emit('done', { - updated: { - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - } - }); - this.$refs.dialog.close(); - }, - - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.emoji.name }), - }); - if (canceled) return; - - os.api('admin/emoji/delete', { - id: this.emoji.id - }).then(() => { - this.$emit('done', { - deleted: true - }); - this.$refs.dialog.close(); - }); - }, - } -}); + dialog.close(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index a080ee9c23..8ca5b3d65c 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -63,7 +63,7 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, ref, toRef } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -130,17 +130,17 @@ const add = async (ev: MouseEvent) => { }; const edit = (emoji) => { - os.popup(import('./emoji-edit-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { emoji: emoji }, { done: result => { if (result.updated) { - emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { - ...emoji, + emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + ...oldEmoji, ...result.updated - }); + })); } else if (result.deleted) { - emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id); } }, }, 'closed'); @@ -159,7 +159,7 @@ const remoteMenu = (emoji, ev: MouseEvent) => { }, { text: i18n.ts.import, icon: 'fas fa-plus', - action: () => { im(emoji) } + action: () => { im(emoji); } }], ev.currentTarget ?? ev.target); }; @@ -175,10 +175,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } @@ -195,10 +195,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.importRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/admin/file-dialog.vue b/packages/client/src/pages/admin/file-dialog.vue index 4c33f62399..0765548aab 100644 --- a/packages/client/src/pages/admin/file-dialog.vue +++ b/packages/client/src/pages/admin/file-dialog.vue @@ -34,74 +34,52 @@ </XModalWindow> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkSwitch from '@/components/form/switch.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkSwitch, - XModalWindow, - MkDriveFileThumbnail, - }, +let file: any = $ref(null); +let info: any = $ref(null); +let isSensitive: boolean = $ref(false); - props: { - fileId: { - required: true, - } - }, +const props = defineProps<{ + fileId: string, +}>(); - emits: ['closed'], +async function fetch() { + file = await os.api('drive/files/show', { fileId: props.fileId }); + info = await os.api('admin/drive/show-file', { fileId: props.fileId }); + isSensitive = file.isSensitive; +} - data() { - return { - file: null, - info: null, - isSensitive: false, - }; - }, +fetch(); - created() { - this.fetch(); - }, +function showUser() { + os.pageWindow(`/user-info/${file.userId}`); +} - methods: { - async fetch() { - this.file = await os.api('drive/files/show', { fileId: this.fileId }); - this.info = await os.api('admin/drive/show-file', { fileId: this.fileId }); - this.isSensitive = this.file.isSensitive; - }, +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: file.name }), + }); + if (canceled) return; - showUser() { - os.pageWindow(`/user-info/${this.file.userId}`); - }, + os.apiWithDialog('drive/files/delete', { + fileId: file.id + }); +} - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.file.name }), - }); - if (canceled) return; - - os.apiWithDialog('drive/files/delete', { - fileId: this.file.id - }); - }, - - async toggleIsSensitive(v) { - await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v }); - this.isSensitive = v; - }, - - bytes - } -}); +async function toggleIsSensitive(v) { + await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v }); + isSensitive = v; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index c62f053092..3cda688698 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -55,7 +55,7 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, defineAsyncComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -93,7 +93,7 @@ function clear() { } function show(file) { - os.popup(import('./file-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), { fileId: file.id }, {}, 'closed'); } diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 6b11650f48..9b7fa5678e 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -1,6 +1,6 @@ <template> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <MkHeader :info="header"></MkHeader> <MkSpacer :content-max="700" :margin-min="16"> @@ -12,21 +12,21 @@ <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </MkSpacer> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </MkStickyContainer> </div> </div> </template> -<script lang="ts"> -import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, nextTick, onMounted, onUnmounted, provide, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -35,292 +35,277 @@ import { instance } from '@/instance'; import * as symbols from '@/symbols'; import * as os from '@/os'; import { lookupUser } from '@/scripts/lookup-user'; +import { MisskeyNavigator } from '@/scripts/navigate'; -export default defineComponent({ - components: { - MkSuperMenu, - MkInfo, - }, +const isEmpty = (x: string | null) => x == null || x === ''; - provide: { - shouldOmitHeaderTitle: false, - }, +const nav = new MisskeyNavigator(); - props: { - initialPage: { - type: String, - required: false +const indexInfo = { + title: i18n.ts.controlPanel, + icon: 'fas fa-cog', + bg: 'var(--bg)', + hideHeader: true, +}; + +const props = defineProps<{ + initialPage?: string, +}>(); + +provide('shouldOmitHeaderTitle', false); + +let INFO = $ref(indexInfo); +let childInfo = $ref(null); +let page = $ref(props.initialPage); +let narrow = $ref(false); +let view = $ref(null); +let el = $ref(null); +let pageProps = $ref({}); +let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); +let noBotProtection = !instance.enableHcaptcha && !instance.enableRecaptcha; + +const NARROW_THRESHOLD = 600; +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); + +const menuDef = $computed(() => [{ + title: i18n.ts.quickAction, + items: [{ + type: 'button', + icon: 'fas fa-search', + text: i18n.ts.lookup, + action: lookup, + }, ...(instance.disableRegistration ? [{ + type: 'button', + icon: 'fas fa-user', + text: i18n.ts.invite, + action: invite, + }] : [])], +}, { + title: i18n.ts.administration, + items: [{ + icon: 'fas fa-tachometer-alt', + text: i18n.ts.dashboard, + to: '/admin/overview', + active: props.initialPage === 'overview', + }, { + icon: 'fas fa-users', + text: i18n.ts.users, + to: '/admin/users', + active: props.initialPage === 'users', + }, { + icon: 'fas fa-laugh', + text: i18n.ts.customEmojis, + to: '/admin/emojis', + active: props.initialPage === 'emojis', + }, { + icon: 'fas fa-globe', + text: i18n.ts.federation, + to: '/admin/federation', + active: props.initialPage === 'federation', + }, { + icon: 'fas fa-clipboard-list', + text: i18n.ts.jobQueue, + to: '/admin/queue', + active: props.initialPage === 'queue', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.files, + to: '/admin/files', + active: props.initialPage === 'files', + }, { + icon: 'fas fa-broadcast-tower', + text: i18n.ts.announcements, + to: '/admin/announcements', + active: props.initialPage === 'announcements', + }, { + icon: 'fas fa-audio-description', + text: i18n.ts.ads, + to: '/admin/ads', + active: props.initialPage === 'ads', + }, { + icon: 'fas fa-exclamation-circle', + text: i18n.ts.abuseReports, + to: '/admin/abuses', + active: props.initialPage === 'abuses', + }], +}, { + title: i18n.ts.settings, + items: [{ + icon: 'fas fa-cog', + text: i18n.ts.general, + to: '/admin/settings', + active: props.initialPage === 'settings', + }, { + icon: 'fas fa-envelope', + text: i18n.ts.emailServer, + to: '/admin/email-settings', + active: props.initialPage === 'email-settings', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.objectStorage, + to: '/admin/object-storage', + active: props.initialPage === 'object-storage', + }, { + icon: 'fas fa-lock', + text: i18n.ts.security, + to: '/admin/security', + active: props.initialPage === 'security', + }, { + icon: 'fas fa-globe', + text: i18n.ts.relays, + to: '/admin/relays', + active: props.initialPage === 'relays', + }, { + icon: 'fas fa-share-alt', + text: i18n.ts.integration, + to: '/admin/integrations', + active: props.initialPage === 'integrations', + }, { + icon: 'fas fa-ban', + text: i18n.ts.instanceBlocking, + to: '/admin/instance-block', + active: props.initialPage === 'instance-block', + }, { + icon: 'fas fa-ghost', + text: i18n.ts.proxyAccount, + to: '/admin/proxy-account', + active: props.initialPage === 'proxy-account', + }, { + icon: 'fas fa-cogs', + text: i18n.ts.other, + to: '/admin/other-settings', + active: props.initialPage === 'other-settings', + }], +}, { + title: i18n.ts.info, + items: [{ + icon: 'fas fa-database', + text: i18n.ts.database, + to: '/admin/database', + active: props.initialPage === 'database', + }], +}]); + +const component = $computed(() => { + if (props.initialPage == null) return null; + switch (props.initialPage) { + case 'overview': return defineAsyncComponent(() => import('./overview.vue')); + case 'users': return defineAsyncComponent(() => import('./users.vue')); + case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); + case 'federation': return defineAsyncComponent(() => import('../federation.vue')); + case 'queue': return defineAsyncComponent(() => import('./queue.vue')); + case 'files': return defineAsyncComponent(() => import('./files.vue')); + case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); + case 'ads': return defineAsyncComponent(() => import('./ads.vue')); + case 'database': return defineAsyncComponent(() => import('./database.vue')); + case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); + case 'settings': return defineAsyncComponent(() => import('./settings.vue')); + case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); + case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); + case 'security': return defineAsyncComponent(() => import('./security.vue')); + case 'relays': return defineAsyncComponent(() => import('./relays.vue')); + case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); + case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); + case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); + case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); + } +}); + +watch(component, () => { + pageProps = {}; + + nextTick(() => { + scroll(el, { top: 0 }); + }); +}, { immediate: true }); + +watch(() => props.initialPage, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } else { + if (props.initialPage == null) { + INFO = indexInfo; } - }, + } +}); - setup(props, context) { - const indexInfo = { - title: i18n.ts.controlPanel, - icon: 'fas fa-cog', - bg: 'var(--bg)', - hideHeader: true, - }; - const INFO = ref(indexInfo); - const childInfo = ref(null); - const page = ref(props.initialPage); - const narrow = ref(false); - const view = ref(null); - const el = ref(null); - const pageChanged = (page) => { - if (page == null) return; - const viewInfo = page[symbols.PAGE_INFO]; - if (isRef(viewInfo)) { - watch(viewInfo, () => { - childInfo.value = viewInfo.value; - }, { immediate: true }); - } else { - childInfo.value = viewInfo; - } - }; - const pageProps = ref({}); +watch(narrow, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); - const isEmpty = (x: any) => x == null || x == ''; +onMounted(() => { + ro.observe(el); - const noMaintainerInformation = ref(false); - const noBotProtection = ref(false); + narrow = el.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); - os.api('meta', { detail: true }).then(meta => { - // TODO: ่จญๅฎใๅฎไบใใฆใๆฎใฃใใพใพใซใชใใฎใงใในใใชใผใใณใฐใงmetaๆดๆฐใคใใณใใๅใๅใฃใฆใใใชใซๆดๆฐใใ - noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail); - noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha; +onUnmounted(() => { + ro.disconnect(); +}); + +const pageChanged = (page) => { + if (page == null) { + childInfo = null; + } else { + childInfo = page[symbols.PAGE_INFO]; + } +}; + +const invite = () => { + os.api('admin/invite').then(x => { + os.alert({ + type: 'info', + text: x.code }); - - const menuDef = computed(() => [{ - title: i18n.ts.quickAction, - items: [{ - type: 'button', - icon: 'fas fa-search', - text: i18n.ts.lookup, - action: lookup, - }, ...(instance.disableRegistration ? [{ - type: 'button', - icon: 'fas fa-user', - text: i18n.ts.invite, - action: invite, - }] : [])], - }, { - title: i18n.ts.administration, - items: [{ - icon: 'fas fa-tachometer-alt', - text: i18n.ts.dashboard, - to: '/admin/overview', - active: page.value === 'overview', - }, { - icon: 'fas fa-users', - text: i18n.ts.users, - to: '/admin/users', - active: page.value === 'users', - }, { - icon: 'fas fa-laugh', - text: i18n.ts.customEmojis, - to: '/admin/emojis', - active: page.value === 'emojis', - }, { - icon: 'fas fa-globe', - text: i18n.ts.federation, - to: '/admin/federation', - active: page.value === 'federation', - }, { - icon: 'fas fa-clipboard-list', - text: i18n.ts.jobQueue, - to: '/admin/queue', - active: page.value === 'queue', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.files, - to: '/admin/files', - active: page.value === 'files', - }, { - icon: 'fas fa-broadcast-tower', - text: i18n.ts.announcements, - to: '/admin/announcements', - active: page.value === 'announcements', - }, { - icon: 'fas fa-audio-description', - text: i18n.ts.ads, - to: '/admin/ads', - active: page.value === 'ads', - }, { - icon: 'fas fa-exclamation-circle', - text: i18n.ts.abuseReports, - to: '/admin/abuses', - active: page.value === 'abuses', - }], - }, { - title: i18n.ts.settings, - items: [{ - icon: 'fas fa-cog', - text: i18n.ts.general, - to: '/admin/settings', - active: page.value === 'settings', - }, { - icon: 'fas fa-envelope', - text: i18n.ts.emailServer, - to: '/admin/email-settings', - active: page.value === 'email-settings', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.objectStorage, - to: '/admin/object-storage', - active: page.value === 'object-storage', - }, { - icon: 'fas fa-lock', - text: i18n.ts.security, - to: '/admin/security', - active: page.value === 'security', - }, { - icon: 'fas fa-globe', - text: i18n.ts.relays, - to: '/admin/relays', - active: page.value === 'relays', - }, { - icon: 'fas fa-share-alt', - text: i18n.ts.integration, - to: '/admin/integrations', - active: page.value === 'integrations', - }, { - icon: 'fas fa-ban', - text: i18n.ts.instanceBlocking, - to: '/admin/instance-block', - active: page.value === 'instance-block', - }, { - icon: 'fas fa-ghost', - text: i18n.ts.proxyAccount, - to: '/admin/proxy-account', - active: page.value === 'proxy-account', - }, { - icon: 'fas fa-cogs', - text: i18n.ts.other, - to: '/admin/other-settings', - active: page.value === 'other-settings', - }], - }, { - title: i18n.ts.info, - items: [{ - icon: 'fas fa-database', - text: i18n.ts.database, - to: '/admin/database', - active: page.value === 'database', - }], - }]); - const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { - case 'overview': return defineAsyncComponent(() => import('./overview.vue')); - case 'users': return defineAsyncComponent(() => import('./users.vue')); - case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); - case 'federation': return defineAsyncComponent(() => import('../federation.vue')); - case 'queue': return defineAsyncComponent(() => import('./queue.vue')); - case 'files': return defineAsyncComponent(() => import('./files.vue')); - case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); - case 'ads': return defineAsyncComponent(() => import('./ads.vue')); - case 'database': return defineAsyncComponent(() => import('./database.vue')); - case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); - case 'settings': return defineAsyncComponent(() => import('./settings.vue')); - case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); - case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); - case 'security': return defineAsyncComponent(() => import('./security.vue')); - case 'relays': return defineAsyncComponent(() => import('./relays.vue')); - case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); - case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); - case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); - case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); - } + }).catch(err => { + os.alert({ + type: 'error', + text: err, }); + }); +}; - watch(component, () => { - pageProps.value = {}; +const lookup = (ev) => { + os.popupMenu([{ + text: i18n.ts.user, + icon: 'fas fa-user', + action: () => { + lookupUser(); + } + }, { + text: i18n.ts.note, + icon: 'fas fa-pencil-alt', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.file, + icon: 'fas fa-cloud', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.instance, + icon: 'fas fa-globe', + action: () => { + alert('TODO'); + } + }], ev.currentTarget ?? ev.target); +}; - nextTick(() => { - scroll(el.value, { top: 0 }); - }); - }, { immediate: true }); - - watch(() => props.initialPage, () => { - if (props.initialPage == null && !narrow.value) { - page.value = 'overview'; - } else { - page.value = props.initialPage; - if (props.initialPage == null) { - INFO.value = indexInfo; - } - } - }); - - onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'overview'; - } - }); - - const invite = () => { - os.api('admin/invite').then(x => { - os.alert({ - type: 'info', - text: x.code - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }; - - const lookup = (ev) => { - os.popupMenu([{ - text: i18n.ts.user, - icon: 'fas fa-user', - action: () => { - lookupUser(); - } - }, { - text: i18n.ts.note, - icon: 'fas fa-pencil-alt', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.file, - icon: 'fas fa-cloud', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.instance, - icon: 'fas fa-globe', - action: () => { - alert('TODO'); - } - }], ev.currentTarget ?? ev.target); - }; - - return { - [symbols.PAGE_INFO]: INFO, - menuDef, - header: { - title: i18n.ts.controlPanel, - }, - noMaintainerInformation, - noBotProtection, - page, - narrow, - view, - el, - pageChanged, - childInfo, - pageProps, - component, - invite, - lookup, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: INFO, + header: { + title: i18n.ts.controlPanel, + } }); </script> diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 4cb8dc604e..3347846a80 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -2,57 +2,45 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <FormTextarea v-model="blockedHosts" class="_formBlock"> - <span>{{ $ts.blockedInstances }}</span> - <template #caption>{{ $ts.blockedInstancesDescription }}</template> + <span>{{ i18n.ts.blockedInstances }}</span> + <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormSuspense, - }, +let blockedHosts: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + blockedHosts = meta.blockedHosts.join('\n'); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceBlocking, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - blockedHosts: '', - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + blockedHosts: blockedHosts.split('\n') || [], + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.blockedHosts = meta.blockedHosts.join('\n'); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - blockedHosts: this.blockedHosts.split('\n') || [], - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceBlocking, + icon: 'fas fa-ban', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 6b50f1b0a9..9fdc51a6ca 100644 --- a/packages/client/src/pages/admin/integrations.discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableDiscordIntegration: boolean = $ref(false); +let discordClientId: string | null = $ref(null); +let discordClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableDiscordIntegration = meta.enableDiscordIntegration; + discordClientId = meta.discordClientId; + discordClientSecret = meta.discordClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Discord', - icon: 'fab fa-discord' - }, - enableDiscordIntegration: false, - discordClientId: null, - discordClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - this.discordClientId = meta.discordClientId; - this.discordClientSecret = meta.discordClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableDiscordIntegration: this.enableDiscordIntegration, - discordClientId: this.discordClientId, - discordClientSecret: this.discordClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableDiscordIntegration, + discordClientId, + discordClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue index 67f299e1bc..b10ccb8394 100644 --- a/packages/client/src/pages/admin/integrations.github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableGithubIntegration: boolean = $ref(false); +let githubClientId: string | null = $ref(null); +let githubClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableGithubIntegration = meta.enableGithubIntegration; + githubClientId = meta.githubClientId; + githubClientSecret = meta.githubClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'GitHub', - icon: 'fab fa-github' - }, - enableGithubIntegration: false, - githubClientId: null, - githubClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.githubClientId = meta.githubClientId; - this.githubClientSecret = meta.githubClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableGithubIntegration: this.enableGithubIntegration, - githubClientId: this.githubClientId, - githubClientSecret: this.githubClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableGithubIntegration, + githubClientId, + githubClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index a389c71506..11b5fd86b2 100644 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -24,7 +24,7 @@ </FormSuspense> </template> -<script lang="ts"> +<script lang="ts" setup> import { defineComponent } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; @@ -32,49 +32,28 @@ import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableTwitterIntegration: boolean = $ref(false); +let twitterConsumerKey: string | null = $ref(null); +let twitterConsumerSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableTwitterIntegration = meta.enableTwitterIntegration; + twitterConsumerKey = meta.twitterConsumerKey; + twitterConsumerSecret = meta.twitterConsumerSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Twitter', - icon: 'fab fa-twitter' - }, - enableTwitterIntegration: false, - twitterConsumerKey: null, - twitterConsumerSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.twitterConsumerKey = meta.twitterConsumerKey; - this.twitterConsumerSecret = meta.twitterConsumerSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableTwitterIntegration: this.enableTwitterIntegration, - twitterConsumerKey: this.twitterConsumerKey, - twitterConsumerSecret: this.twitterConsumerSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableTwitterIntegration, + twitterConsumerKey, + twitterConsumerSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 4db8a9e0a9..d6061d0e51 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -4,69 +4,52 @@ <FormFolder class="_formBlock"> <template #icon><i class="fab fa-twitter"></i></template> <template #label>Twitter</template> - <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XTwitter/> </FormFolder> - <FormFolder to="/admin/integrations/github" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-github"></i></template> <template #label>GitHub</template> - <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XGithub/> </FormFolder> - <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-discord"></i></template> <template #label>Discord</template> - <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XDiscord/> </FormFolder> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; -import FormSecion from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; import XTwitter from './integrations.twitter.vue'; import XGithub from './integrations.github.vue'; import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormFolder, - FormSecion, - FormSuspense, - XTwitter, - XGithub, - XDiscord, - }, +let enableTwitterIntegration: boolean = $ref(false); +let enableGithubIntegration: boolean = $ref(false); +let enableDiscordIntegration: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + enableTwitterIntegration = meta.enableTwitterIntegration; + enableGithubIntegration = meta.enableGithubIntegration; + enableDiscordIntegration = meta.enableDiscordIntegration; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - enableTwitterIntegration: false, - enableGithubIntegration: false, - enableDiscordIntegration: false, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 1de297fd93..7e5f5bb094 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -132,7 +132,7 @@ export default defineComponent({ overviewHeight: '1fr', queueHeight: '1fr', paused: false, - } + }; }, computed: { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index a1ee0761c8..d109db9c38 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -2,32 +2,32 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <div class="_formRoot"> - <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ i18n.ts.useObjectStorage }}</FormSwitch> <template v-if="useObjectStorage"> <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> - <template #label>{{ $ts.objectStorageBaseUrl }}</template> - <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBaseUrl }}</template> + <template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template> </FormInput> <FormInput v-model="objectStorageBucket" class="_formBlock"> - <template #label>{{ $ts.objectStorageBucket }}</template> - <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBucket }}</template> + <template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template> </FormInput> <FormInput v-model="objectStoragePrefix" class="_formBlock"> - <template #label>{{ $ts.objectStoragePrefix }}</template> - <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + <template #label>{{ i18n.ts.objectStoragePrefix }}</template> + <template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template> </FormInput> <FormInput v-model="objectStorageEndpoint" class="_formBlock"> - <template #label>{{ $ts.objectStorageEndpoint }}</template> - <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + <template #label>{{ i18n.ts.objectStorageEndpoint }}</template> + <template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template> </FormInput> <FormInput v-model="objectStorageRegion" class="_formBlock"> - <template #label>{{ $ts.objectStorageRegion }}</template> - <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + <template #label>{{ i18n.ts.objectStorageRegion }}</template> + <template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template> </FormInput> <FormSplit :min-width="280"> @@ -43,17 +43,17 @@ </FormSplit> <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseSSL }}</template> - <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseSSL }}</template> + <template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseProxy }}</template> - <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseProxy }}</template> + <template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> - <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + <template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template> </FormSwitch> <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> @@ -65,8 +65,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormGroup from '@/components/form/group.vue'; @@ -76,84 +76,70 @@ import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormGroup, - FormSuspense, - FormSplit, - FormSection, - }, +let useObjectStorage: boolean = $ref(false); +let objectStorageBaseUrl: string | null = $ref(null); +let objectStorageBucket: string | null = $ref(null); +let objectStoragePrefix: string | null = $ref(null); +let objectStorageEndpoint: string | null = $ref(null); +let objectStorageRegion: string | null = $ref(null); +let objectStoragePort: number | null = $ref(null); +let objectStorageAccessKey: string | null = $ref(null); +let objectStorageSecretKey: string | null = $ref(null); +let objectStorageUseSSL: boolean = $ref(false); +let objectStorageUseProxy: boolean = $ref(false); +let objectStorageSetPublicRead: boolean = $ref(false); +let objectStorageS3ForcePathStyle: boolean = $ref(true); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + useObjectStorage = meta.useObjectStorage; + objectStorageBaseUrl = meta.objectStorageBaseUrl; + objectStorageBucket = meta.objectStorageBucket; + objectStoragePrefix = meta.objectStoragePrefix; + objectStorageEndpoint = meta.objectStorageEndpoint; + objectStorageRegion = meta.objectStorageRegion; + objectStoragePort = meta.objectStoragePort; + objectStorageAccessKey = meta.objectStorageAccessKey; + objectStorageSecretKey = meta.objectStorageSecretKey; + objectStorageUseSSL = meta.objectStorageUseSSL; + objectStorageUseProxy = meta.objectStorageUseProxy; + objectStorageSetPublicRead = meta.objectStorageSetPublicRead; + objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.objectStorage, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - useObjectStorage: false, - objectStorageBaseUrl: null, - objectStorageBucket: null, - objectStoragePrefix: null, - objectStorageEndpoint: null, - objectStorageRegion: null, - objectStoragePort: null, - objectStorageAccessKey: null, - objectStorageSecretKey: null, - objectStorageUseSSL: false, - objectStorageUseProxy: false, - objectStorageSetPublicRead: false, - objectStorageS3ForcePathStyle: true, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + useObjectStorage, + objectStorageBaseUrl, + objectStorageBucket, + objectStoragePrefix, + objectStorageEndpoint, + objectStorageRegion, + objectStoragePort, + objectStorageAccessKey, + objectStorageSecretKey, + objectStorageUseSSL, + objectStorageUseProxy, + objectStorageSetPublicRead, + objectStorageS3ForcePathStyle, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.useObjectStorage = meta.useObjectStorage; - this.objectStorageBaseUrl = meta.objectStorageBaseUrl; - this.objectStorageBucket = meta.objectStorageBucket; - this.objectStoragePrefix = meta.objectStoragePrefix; - this.objectStorageEndpoint = meta.objectStorageEndpoint; - this.objectStorageRegion = meta.objectStorageRegion; - this.objectStoragePort = meta.objectStoragePort; - this.objectStorageAccessKey = meta.objectStorageAccessKey; - this.objectStorageSecretKey = meta.objectStorageSecretKey; - this.objectStorageUseSSL = meta.objectStorageUseSSL; - this.objectStorageUseProxy = meta.objectStorageUseProxy; - this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead; - this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; - }, - save() { - os.apiWithDialog('admin/update-meta', { - useObjectStorage: this.useObjectStorage, - objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null, - objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null, - objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null, - objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null, - objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null, - objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null, - objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null, - objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null, - objectStorageUseSSL: this.objectStorageUseSSL, - objectStorageUseProxy: this.objectStorageUseProxy, - objectStorageSetPublicRead: this.objectStorageSetPublicRead, - objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.objectStorage, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index 99ea6a5f32..552b05f347 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -6,52 +6,35 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormInput from '@/components/form/input.vue'; -import FormSection from '@/components/form/section.vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSection, - FormSuspense, - }, +async function init() { + await os.api('admin/meta'); +} - emits: ['info'], +function save() { + os.apiWithDialog('admin/update-meta').then(() => { + fetchInstance(); + }); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - }, - save() { - os.apiWithDialog('admin/update-meta', { - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-cogs', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index b8ae8ad9e1..cc69424c3b 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -5,20 +5,20 @@ <div class="label">Users</div> <div class="value _monospace"> {{ number(stats.originalUsersCount) }} - <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> <div class="number _panel"> <div class="label">Notes</div> <div class="value _monospace"> {{ number(stats.originalNotesCount) }} - <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> </div> <MkContainer :foldable="true" class="charts"> - <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> + <template #header><i class="fas fa-chart-bar"></i>{{ i18n.ts.charts }}</template> <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> @@ -38,7 +38,7 @@ <!--<XMetrics/>--> <MkFolder style="margin: var(--margin)"> - <template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template> + <template #header><i class="fas fa-info-circle"></i> {{ i18n.ts.info }}</template> <div class="cfcdecdf"> <div class="number _panel"> <div class="label">Misskey</div> @@ -65,103 +65,61 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; +<script lang="ts" setup> +import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkInstanceStats from '@/components/instance-stats.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSelect from '@/components/form/select.vue'; import MkNumberDiff from '@/components/number-diff.vue'; import MkContainer from '@/components/ui/container.vue'; import MkFolder from '@/components/ui/folder.vue'; import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; -import bytes from '@/filters/bytes'; import number from '@/filters/number'; import XMetrics from './metrics.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkNumberDiff, - MkInstanceStats, - MkContainer, - MkFolder, - MkQueueChart, - XMetrics, - }, +let stats: any = $ref(null); +let serverInfo: any = $ref(null); +let usersComparedToThePrevDay: any = $ref(null); +let notesComparedToThePrevDay: any = $ref(null); +const queueStatsConnection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +onMounted(async () => { + os.api('stats', {}).then(statsResponse => { + stats = statsResponse; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.dashboard, - icon: 'fas fa-tachometer-alt', - bg: 'var(--bg)', - }, - version, - vueVersion, - url, - stats: null, - meta: null, - serverInfo: null, - usersComparedToThePrevDay: null, - notesComparedToThePrevDay: null, - fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), - fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(stream.useChannel('queueStats')), - } - }, - - async mounted() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - - os.api('stats', {}).then(stats => { - this.stats = stats; - - os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { - this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1]; - }); - - os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { - this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1]; - }); + os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { + usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1]; }); - os.api('admin/server-info', {}).then(serverInfo => { - this.serverInfo = serverInfo; + os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { + notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1]; }); + }); - this.$nextTick(() => { - this.queueStatsConnection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); + os.api('admin/server-info').then(serverInfoResponse => { + serverInfo = serverInfoResponse; + }); + + nextTick(() => { + queueStatsConnection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, + }); +}); - beforeUnmount() { - this.queueStatsConnection.dispose(); - }, +onBeforeUnmount(() => { + queueStatsConnection.dispose(); +}); - methods: { - async showInstanceInfo(q) { - let instance = q; - if (typeof q === 'string') { - instance = await os.api('federation/show-instance', { - host: q - }); - } - // TODO - }, - - bytes, - - number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.dashboard, + icon: 'fas fa-tachometer-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 00f14a176f..727e20e7e5 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,19 +1,19 @@ <template> <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts.proxyAccountDescription }}</MkInfo> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + <template #key>{{ i18n.ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template> </MkKeyValue> - <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkKeyValue from '@/components/key-value.vue'; import FormButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -21,53 +21,40 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkKeyValue, - FormButton, - MkInfo, - FormSuspense, - }, +let proxyAccount: any = $ref(null); +let proxyAccountId: any = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + proxyAccountId = meta.proxyAccountId; + if (proxyAccountId) { + proxyAccount = await os.api('users/show', { userId: proxyAccountId }); + } +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.proxyAccount, - icon: 'fas fa-ghost', - bg: 'var(--bg)', - }, - proxyAccount: null, - proxyAccountId: null, - } - }, +function chooseProxyAccount() { + os.selectUser().then(user => { + proxyAccount = user; + proxyAccountId = user.id; + save(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.proxyAccountId = meta.proxyAccountId; - if (this.proxyAccountId) { - this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId }); - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + proxyAccountId: proxyAccountId, + }).then(() => { + fetchInstance(); + }); +} - chooseProxyAccount() { - os.selectUser().then(user => { - this.proxyAccount = user; - this.proxyAccountId = user.id; - this.save(); - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - proxyAccountId: this.proxyAccountId, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.proxyAccount, + icon: 'fas fa-ghost', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/queue.chart.vue b/packages/client/src/pages/admin/queue.chart.vue index 136fb63bb6..be63830bdd 100644 --- a/packages/client/src/pages/admin/queue.chart.vue +++ b/packages/client/src/pages/admin/queue.chart.vue @@ -26,62 +26,40 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; import number from '@/filters/number'; import MkQueueChart from '@/components/queue-chart.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - MkQueueChart - }, +const activeSincePrevTick = ref(0); +const active = ref(0); +const waiting = ref(0); +const delayed = ref(0); +const jobs = ref([]); - props: { - domain: { - type: String, - required: true, - }, - connection: { - required: true, - }, - }, +const props = defineProps<{ + domain: string, + connection: any, +}>(); - setup(props) { - const activeSincePrevTick = ref(0); - const active = ref(0); - const waiting = ref(0); - const delayed = ref(0); - const jobs = ref([]); +onMounted(() => { + os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { + jobs.value = result; + }); - onMounted(() => { - os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { - jobs.value = result; - }); + const onStats = (stats) => { + activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; + active.value = stats[props.domain].active; + waiting.value = stats[props.domain].waiting; + delayed.value = stats[props.domain].delayed; + }; - const onStats = (stats) => { - activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; - active.value = stats[props.domain].active; - waiting.value = stats[props.domain].waiting; - delayed.value = stats[props.domain].delayed; - }; + props.connection.on('stats', onStats); - props.connection.on('stats', onStats); - - onUnmounted(() => { - props.connection.off('stats', onStats); - }); - }); - - return { - jobs, - activeSincePrevTick, - active, - waiting, - delayed, - number, - }; - }, + onUnmounted(() => { + props.connection.off('stats', onStats); + }); }); </script> diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 35fd618c82..656b18199f 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -6,71 +6,60 @@ <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.clearQueue }}</MkButton> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; import * as config from '@/config'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XQueue, - }, +const connection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +function clear() { + os.confirm({ + type: 'warning', + title: i18n.ts.clearQueueConfirmTitle, + text: i18n.ts.clearQueueConfirmText, + }).then(({ canceled }) => { + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.jobQueue, - icon: 'fas fa-clipboard-list', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-up-right-from-square', - text: this.$ts.dashboard, - handler: () => { - window.open(config.url + '/queue', '_blank'); - }, - }], - }, - connection: markRaw(stream.useChannel('queueStats')), - } - }, + os.apiWithDialog('admin/queue/clear'); + }); +} - mounted() { - this.$nextTick(() => { - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); +onMounted(() => { + nextTick(() => { + connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, + }); +}); - beforeUnmount() { - this.connection.dispose(); - }, +onBeforeUnmount(() => { + connection.dispose(); +}); - methods: { - clear() { - os.confirm({ - type: 'warning', - title: this.$ts.clearQueueConfirmTitle, - text: this.$ts.clearQueueConfirmText, - }).then(({ canceled }) => { - if (canceled) return; - - os.apiWithDialog('admin/queue/clear', {}); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.jobQueue, + icon: 'fas fa-clipboard-list', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-up-right-from-square', + text: i18n.ts.dashboard, + handler: () => { + window.open(config.url + '/queue', '_blank'); + }, + }], } }); </script> diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index bb840db0a2..1a36bb4753 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -8,84 +8,71 @@ <i v-else class="fas fa-clock icon requesting"></i> <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> - <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, +let relays: any[] = $ref([]); - emits: ['info'], +async function addRelay() { + const { canceled, result: inbox } = await os.inputText({ + title: i18n.ts.addRelay, + type: 'url', + placeholder: i18n.ts.inboxUrl + }); + if (canceled) return; + os.api('admin/relays/add', { + inbox + }).then((relay: any) => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.relays, - icon: 'fas fa-globe', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addRelay, - handler: this.addRelay, - }], - }, - relays: [], - inbox: '', - } - }, +function remove(inbox: string) { + os.api('admin/relays/remove', { + inbox + }).then(() => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - created() { - this.refresh(); - }, +function refresh() { + os.api('admin/relays/list').then((relayList: any) => { + relays = relayList; + }); +} - methods: { - async addRelay() { - const { canceled, result: inbox } = await os.inputText({ - title: this.$ts.addRelay, - type: 'url', - placeholder: this.$ts.inboxUrl - }); - if (canceled) return; - os.api('admin/relays/add', { - inbox - }).then((relay: any) => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, +refresh(); - remove(inbox: string) { - os.api('admin/relays/remove', { - inbox - }).then(() => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, - - refresh() { - os.api('admin/relays/list').then((relays: any) => { - this.relays = relays; - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.relays, + icon: 'fas fa-globe', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.addRelay, + handler: addRelay, + }], } }); </script> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index d1c979b3e0..6b8f70cca5 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -4,10 +4,10 @@ <div class="_formRoot"> <FormFolder class="_formBlock"> <template #icon><i class="fas fa-shield-alt"></i></template> - <template #label>{{ $ts.botProtection }}</template> + <template #label>{{ i18n.ts.botProtection }}</template> <template v-if="enableHcaptcha" #suffix>hCaptcha</template> <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> + <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> <XBotProtection/> </FormFolder> @@ -21,7 +21,7 @@ <template #label>Summaly Proxy URL</template> </FormInput> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </div> </FormFolder> </div> @@ -29,8 +29,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -42,49 +42,32 @@ import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormFolder, - FormSwitch, - FormInfo, - FormSection, - FormSuspense, - FormButton, - FormInput, - XBotProtection, - }, +let summalyProxy: string = $ref(''); +let enableHcaptcha: boolean = $ref(false); +let enableRecaptcha: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + summalyProxy = meta.summalyProxy; + enableHcaptcha = meta.enableHcaptcha; + enableRecaptcha = meta.enableRecaptcha; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - summalyProxy: '', - enableHcaptcha: false, - enableRecaptcha: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + summalyProxy, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.summalyProxy = meta.summalyProxy; - this.enableHcaptcha = meta.enableHcaptcha; - this.enableRecaptcha = meta.enableRecaptcha; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - summalyProxy: this.summalyProxy, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index f2970d0459..6dc30fe50b 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -3,104 +3,104 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormInput v-model="name" class="_formBlock"> - <template #label>{{ $ts.instanceName }}</template> + <template #label>{{ i18n.ts.instanceName }}</template> </FormInput> <FormTextarea v-model="description" class="_formBlock"> - <template #label>{{ $ts.instanceDescription }}</template> + <template #label>{{ i18n.ts.instanceDescription }}</template> </FormTextarea> <FormInput v-model="tosUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.tosUrl }}</template> + <template #label>{{ i18n.ts.tosUrl }}</template> </FormInput> <FormSplit :min-width="300"> <FormInput v-model="maintainerName" class="_formBlock"> - <template #label>{{ $ts.maintainerName }}</template> + <template #label>{{ i18n.ts.maintainerName }}</template> </FormInput> <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> <template #prefix><i class="fas fa-envelope"></i></template> - <template #label>{{ $ts.maintainerEmail }}</template> + <template #label>{{ i18n.ts.maintainerEmail }}</template> </FormInput> </FormSplit> <FormTextarea v-model="pinnedUsers" class="_formBlock"> - <template #label>{{ $ts.pinnedUsers }}</template> - <template #caption>{{ $ts.pinnedUsersDescription }}</template> + <template #label>{{ i18n.ts.pinnedUsers }}</template> + <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </FormTextarea> <FormSection> <FormSwitch v-model="enableRegistration" class="_formBlock"> - <template #label>{{ $ts.enableRegistration }}</template> + <template #label>{{ i18n.ts.enableRegistration }}</template> </FormSwitch> <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> - <template #label>{{ $ts.emailRequiredForSignup }}</template> + <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> </FormSwitch> </FormSection> <FormSection> - <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ i18n.ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ i18n.ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ i18n.ts.disablingTimelinesInfo }}</FormInfo> </FormSection> <FormSection> - <template #label>{{ $ts.theme }}</template> + <template #label>{{ i18n.ts.theme }}</template> <FormInput v-model="iconUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.iconUrl }}</template> + <template #label>{{ i18n.ts.iconUrl }}</template> </FormInput> <FormInput v-model="bannerUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.bannerUrl }}</template> + <template #label>{{ i18n.ts.bannerUrl }}</template> </FormInput> <FormInput v-model="backgroundImageUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.backgroundImageUrl }}</template> + <template #label>{{ i18n.ts.backgroundImageUrl }}</template> </FormInput> <FormInput v-model="themeColor" class="_formBlock"> <template #prefix><i class="fas fa-palette"></i></template> - <template #label>{{ $ts.themeColor }}</template> + <template #label>{{ i18n.ts.themeColor }}</template> <template #caption>#RRGGBB</template> </FormInput> <FormTextarea v-model="defaultLightTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultLightTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> <FormTextarea v-model="defaultDarkTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultDarkTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> </FormSection> <FormSection> - <template #label>{{ $ts.files }}</template> + <template #label>{{ i18n.ts.files }}</template> <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.cacheRemoteFiles }}</template> - <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + <template #label>{{ i18n.ts.cacheRemoteFiles }}</template> + <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template> </FormSwitch> <FormSplit :min-width="280"> <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerLocalAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerRemoteAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> </FormSplit> </FormSection> @@ -109,8 +109,8 @@ <template #label>ServiceWorker</template> <FormSwitch v-model="enableServiceWorker" class="_formBlock"> - <template #label>{{ $ts.enableServiceworker }}</template> - <template #caption>{{ $ts.serviceworkerInfo }}</template> + <template #label>{{ i18n.ts.enableServiceworker }}</template> + <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> </FormSwitch> <template v-if="enableServiceWorker"> @@ -142,8 +142,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -154,119 +154,103 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSuspense, - FormTextarea, - FormInfo, - FormSection, - FormSplit, - }, +let name: string | null = $ref(null); +let description: string | null = $ref(null); +let tosUrl: string | null = $ref(null); +let maintainerName: string | null = $ref(null); +let maintainerEmail: string | null = $ref(null); +let iconUrl: string | null = $ref(null); +let bannerUrl: string | null = $ref(null); +let backgroundImageUrl: string | null = $ref(null); +let themeColor: any = $ref(null); +let defaultLightTheme: any = $ref(null); +let defaultDarkTheme: any = $ref(null); +let enableLocalTimeline: boolean = $ref(false); +let enableGlobalTimeline: boolean = $ref(false); +let pinnedUsers: string = $ref(''); +let cacheRemoteFiles: boolean = $ref(false); +let localDriveCapacityMb: any = $ref(0); +let remoteDriveCapacityMb: any = $ref(0); +let enableRegistration: boolean = $ref(false); +let emailRequiredForSignup: boolean = $ref(false); +let enableServiceWorker: boolean = $ref(false); +let swPublicKey: any = $ref(null); +let swPrivateKey: any = $ref(null); +let deeplAuthKey: string = $ref(''); +let deeplIsPro: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + name = meta.name; + description = meta.description; + tosUrl = meta.tosUrl; + iconUrl = meta.iconUrl; + bannerUrl = meta.bannerUrl; + backgroundImageUrl = meta.backgroundImageUrl; + themeColor = meta.themeColor; + defaultLightTheme = meta.defaultLightTheme; + defaultDarkTheme = meta.defaultDarkTheme; + maintainerName = meta.maintainerName; + maintainerEmail = meta.maintainerEmail; + enableLocalTimeline = !meta.disableLocalTimeline; + enableGlobalTimeline = !meta.disableGlobalTimeline; + pinnedUsers = meta.pinnedUsers.join('\n'); + cacheRemoteFiles = meta.cacheRemoteFiles; + localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + enableRegistration = !meta.disableRegistration; + emailRequiredForSignup = meta.emailRequiredForSignup; + enableServiceWorker = meta.enableServiceWorker; + swPublicKey = meta.swPublickey; + swPrivateKey = meta.swPrivateKey; + deeplAuthKey = meta.deeplAuthKey; + deeplIsPro = meta.deeplIsPro; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cog', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - name: null, - description: null, - tosUrl: null as string | null, - maintainerName: null, - maintainerEmail: null, - iconUrl: null, - bannerUrl: null, - backgroundImageUrl: null, - themeColor: null, - defaultLightTheme: null, - defaultDarkTheme: null, - enableLocalTimeline: false, - enableGlobalTimeline: false, - pinnedUsers: '', - cacheRemoteFiles: false, - localDriveCapacityMb: 0, - remoteDriveCapacityMb: 0, - enableRegistration: false, - emailRequiredForSignup: false, - enableServiceWorker: false, - swPublicKey: null, - swPrivateKey: null, - deeplAuthKey: '', - deeplIsPro: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + name, + description, + tosUrl, + iconUrl, + bannerUrl, + backgroundImageUrl, + themeColor: themeColor === '' ? null : themeColor, + defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, + defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, + maintainerName, + maintainerEmail, + disableLocalTimeline: !enableLocalTimeline, + disableGlobalTimeline: !enableGlobalTimeline, + pinnedUsers: pinnedUsers.split('\n'), + cacheRemoteFiles, + localDriveCapacityMb: parseInt(localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(remoteDriveCapacityMb, 10), + disableRegistration: !enableRegistration, + emailRequiredForSignup, + enableServiceWorker, + swPublicKey, + swPrivateKey, + deeplAuthKey, + deeplIsPro, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.name = meta.name; - this.description = meta.description; - this.tosUrl = meta.tosUrl; - this.iconUrl = meta.iconUrl; - this.bannerUrl = meta.bannerUrl; - this.backgroundImageUrl = meta.backgroundImageUrl; - this.themeColor = meta.themeColor; - this.defaultLightTheme = meta.defaultLightTheme; - this.defaultDarkTheme = meta.defaultDarkTheme; - this.maintainerName = meta.maintainerName; - this.maintainerEmail = meta.maintainerEmail; - this.enableLocalTimeline = !meta.disableLocalTimeline; - this.enableGlobalTimeline = !meta.disableGlobalTimeline; - this.pinnedUsers = meta.pinnedUsers.join('\n'); - this.cacheRemoteFiles = meta.cacheRemoteFiles; - this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; - this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; - this.enableRegistration = !meta.disableRegistration; - this.emailRequiredForSignup = meta.emailRequiredForSignup; - this.enableServiceWorker = meta.enableServiceWorker; - this.swPublicKey = meta.swPublickey; - this.swPrivateKey = meta.swPrivateKey; - this.deeplAuthKey = meta.deeplAuthKey; - this.deeplIsPro = meta.deeplIsPro; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - name: this.name, - description: this.description, - tosUrl: this.tosUrl, - iconUrl: this.iconUrl, - bannerUrl: this.bannerUrl, - backgroundImageUrl: this.backgroundImageUrl, - themeColor: this.themeColor === '' ? null : this.themeColor, - defaultLightTheme: this.defaultLightTheme === '' ? null : this.defaultLightTheme, - defaultDarkTheme: this.defaultDarkTheme === '' ? null : this.defaultDarkTheme, - maintainerName: this.maintainerName, - maintainerEmail: this.maintainerEmail, - disableLocalTimeline: !this.enableLocalTimeline, - disableGlobalTimeline: !this.enableGlobalTimeline, - pinnedUsers: this.pinnedUsers.split('\n'), - cacheRemoteFiles: this.cacheRemoteFiles, - localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), - remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - disableRegistration: !this.enableRegistration, - emailRequiredForSignup: this.emailRequiredForSignup, - enableServiceWorker: this.enableServiceWorker, - swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey, - deeplAuthKey: this.deeplAuthKey, - deeplIsPro: this.deeplIsPro, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cog', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue index 142a3bee2e..88acbcd3a3 100644 --- a/packages/client/src/pages/api-console.vue +++ b/packages/client/src/pages/api-console.vue @@ -25,72 +25,60 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { ref } from 'vue'; +import JSON5 from 'json5'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { Endpoints } from 'misskey-js'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSwitch, - }, +const body = ref('{}'); +const endpoint = ref(''); +const endpoints = ref<any[]>([]); +const sending = ref(false); +const res = ref(''); +const withCredential = ref(true); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API console', - icon: 'fas fa-terminal' - }, +os.api('endpoints').then(endpointResponse => { + endpoints.value = endpointResponse; +}); - endpoint: '', - body: '{}', - res: null, - sending: false, - endpoints: [], - withCredential: true, +function send() { + sending.value = true; + const requestBody = JSON5.parse(body.value); + os.api(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => { + sending.value = false; + res.value = JSON5.stringify(resp, null, 2); + }, err => { + sending.value = false; + res.value = JSON5.stringify(err, null, 2); + }); +} - }; - }, - - created() { - os.api('endpoints').then(endpoints => { - this.endpoints = endpoints; - }); - }, - - methods: { - send() { - this.sending = true; - const body = JSON5.parse(this.body); - os.api(this.endpoint, body, body.i || (this.withCredential ? undefined : null)).then(res => { - this.sending = false; - this.res = JSON5.stringify(res, null, 2); - }, err => { - this.sending = false; - this.res = JSON5.stringify(err, null, 2); - }); - }, - - onEndpointChange() { - os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => { - const body = {}; - for (const p of endpoint.params) { - body[p.name] = - p.type === 'String' ? '' : - p.type === 'Number' ? 0 : - p.type === 'Boolean' ? false : - p.type === 'Array' ? [] : - p.type === 'Object' ? {} : - null; - } - this.body = JSON5.stringify(body, null, 2); - }); +function onEndpointChange() { + os.api('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => { + const endpointBody = {}; + for (const p of resp.params) { + endpointBody[p.name] = + p.type === 'String' ? '' : + p.type === 'Number' ? 0 : + p.type === 'Boolean' ? false : + p.type === 'Array' ? [] : + p.type === 'Object' ? {} : + null; } - } + body.value = JSON5.stringify(endpointBody, null, 2); + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API console', + icon: 'fas fa-terminal' + }, }); </script> diff --git a/packages/client/src/pages/auth.form.vue b/packages/client/src/pages/auth.form.vue index bc719aebd3..5feff0149a 100644 --- a/packages/client/src/pages/auth.form.vue +++ b/packages/client/src/pages/auth.form.vue @@ -32,7 +32,7 @@ export default defineComponent({ computed: { name(): string { const el = document.createElement('div'); - el.textContent = this.app.name + el.textContent = this.app.name; return el.innerHTML; }, app(): any { diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue index 3818c7481a..ea3a5dab76 100644 --- a/packages/client/src/pages/channel-editor.vue +++ b/packages/client/src/pages/channel-editor.vue @@ -111,8 +111,8 @@ export default defineComponent({ } }, - setBannerImage(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { + setBannerImage(evt) { + selectFile(evt.currentTarget ?? evt.target, null).then(file => { this.bannerId = file.id; }); }, diff --git a/packages/client/src/pages/emojis.category.vue b/packages/client/src/pages/emojis.category.vue index 9a317418be..c47870f4d4 100644 --- a/packages/client/src/pages/emojis.category.vue +++ b/packages/client/src/pages/emojis.category.vue @@ -58,7 +58,7 @@ export default defineComponent({ tags: emojiTags, selectedTags: new Set(), searchEmojis: null, - } + }; }, watch: { @@ -79,9 +79,9 @@ export default defineComponent({ } if (this.selectedTags.size === 0) { - this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q)); + this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q)); } else { - this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t))); + this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t))); } }, diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 886b5f7119..f44b29df04 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -25,10 +25,10 @@ function menu(ev) { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 5add2b5324..447918905b 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -127,7 +127,7 @@ function getStatus(instance) { if (instance.isSuspended) return 'suspended'; if (instance.isNotResponding) return 'error'; return 'alive'; -}; +} defineExpose({ [symbols.PAGE_INFO]: { diff --git a/packages/client/src/pages/follow.vue b/packages/client/src/pages/follow.vue index d8a6824dca..e69e0481e0 100644 --- a/packages/client/src/pages/follow.vue +++ b/packages/client/src/pages/follow.vue @@ -20,7 +20,7 @@ export default defineComponent({ uri: acct }); promise.then(res => { - if (res.type == 'User') { + if (res.type === 'User') { this.follow(res.object); } else if (res.type === 'Note') { this.$router.push(`/notes/${res.object.id}`); diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index 25ee513186..bc87160c44 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -71,7 +71,7 @@ export default defineComponent({ description: null, title: null, isSensitive: false, - } + }; }, watch: { @@ -91,8 +91,8 @@ export default defineComponent({ }, methods: { - selectFile(e) { - selectFiles(e.currentTarget ?? e.target, null).then(files => { + selectFile(evt) { + selectFiles(evt.currentTarget ?? evt.target, null).then(files => { this.files = this.files.concat(files); }); }, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 1755c23286..1ca3443e56 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -119,8 +119,8 @@ export default defineComponent({ postId: this.postId }).then(post => { this.post = post; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 88a1e07afc..7c1d3e3cbe 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -90,14 +90,14 @@ export default defineComponent({ getAcct: Acct.toString, isMe(message) { - return message.userId == this.$i.id; + return message.userId === this.$i.id; }, onMessage(message) { if (message.recipientId) { this.messages = this.messages.filter(m => !( - (m.recipientId == message.recipientId && m.userId == message.userId) || - (m.recipientId == message.userId && m.userId == message.recipientId))); + (m.recipientId === message.recipientId && m.userId === message.userId) || + (m.recipientId === message.userId && m.userId === message.recipientId))); this.messages.unshift(message); } else if (message.groupId) { @@ -108,7 +108,7 @@ export default defineComponent({ onRead(ids) { for (const id of ids) { - const found = this.messages.find(m => m.id == id); + const found = this.messages.find(m => m.id === id); if (found) { if (found.recipientId) { found.isRead = true; @@ -123,11 +123,11 @@ export default defineComponent({ os.popupMenu([{ text: this.$ts.messagingWithUser, icon: 'fas fa-user', - action: () => { this.startUser() } + action: () => { this.startUser(); } }, { text: this.$ts.messagingWithGroup, icon: 'fas fa-users', - action: () => { this.startGroup() } + action: () => { this.startGroup(); } }], ev.currentTarget ?? ev.target); }, diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index 3863c8f82b..8e779c4f39 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -31,6 +31,7 @@ import * as os from '@/os'; import { stream } from '@/stream'; import { Autocomplete } from '@/scripts/autocomplete'; import { throttle } from 'throttle-debounce'; +import { uploadFile } from '@/scripts/upload'; export default defineComponent({ props: { @@ -58,7 +59,7 @@ export default defineComponent({ return this.user ? 'user:' + this.user.id : 'group:' + this.group.id; }, canSend(): boolean { - return (this.text != null && this.text != '') || this.file != null; + return (this.text != null && this.text !== '') || this.file != null; }, room(): any { return this.$parent; @@ -87,12 +88,11 @@ export default defineComponent({ } }, methods: { - async onPaste(e: ClipboardEvent) { - const data = e.clipboardData; - const items = data.items; + async onPaste(evt: ClipboardEvent) { + const items = evt.clipboardData.items; - if (items.length == 1) { - if (items[0].kind == 'file') { + if (items.length === 1) { + if (items[0].kind === 'file') { const file = items[0].getAsFile(); const lio = file.name.lastIndexOf('.'); const ext = lio >= 0 ? file.name.slice(lio) : ''; @@ -100,7 +100,7 @@ export default defineComponent({ if (formatted) this.upload(file, formatted); } } else { - if (items[0].kind == 'file') { + if (items[0].kind === 'file') { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -109,23 +109,23 @@ export default defineComponent({ } }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.preventDefault(); - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.preventDefault(); + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } }, - onDrop(e): void { + onDrop(evt): void { // ใใกใคใซใ ใฃใใ - if (e.dataTransfer.files.length == 1) { - e.preventDefault(); - this.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + evt.preventDefault(); + this.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { - e.preventDefault(); + } else if (evt.dataTransfer.files.length > 1) { + evt.preventDefault(); os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -134,17 +134,17 @@ export default defineComponent({ } //#region ใใฉใคใใฎใใกใคใซ - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { this.file = JSON.parse(driveFile); - e.preventDefault(); + evt.preventDefault(); } //#endregion }, - onKeydown(e) { + onKeydown(evt) { this.typing(); - if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { + if ((evt.which === 10 || evt.which === 13) && (evt.ctrlKey || evt.metaKey) && this.canSend) { this.send(); } }, @@ -153,8 +153,8 @@ export default defineComponent({ this.typing(); }, - chooseFile(e) { - selectFile(e.currentTarget ?? e.target, this.$ts.selectFile).then(file => { + chooseFile(evt) { + selectFile(evt.currentTarget ?? evt.target, this.$ts.selectFile).then(file => { this.file = file; }); }, @@ -164,7 +164,7 @@ export default defineComponent({ }, upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { + uploadFile(file, this.$store.state.uploadFolder, name).then(res => { this.file = res; }); }, @@ -192,25 +192,25 @@ export default defineComponent({ }, saveDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - data[this.draftKey] = { + drafts[this.draftKey] = { updatedAt: new Date(), data: { text: this.text, file: this.file } - } + }; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, deleteDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - delete data[this.draftKey]; + delete drafts[this.draftKey]; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, async insertEmoji(ev) { diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index 2ecc68eb54..fd1962218a 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -166,23 +166,23 @@ const Component = defineComponent({ }); }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + evt.dataTransfer.dropEffect = 'none'; } }, - onDrop(e): void { + onDrop(evt): void { // ใใกใคใซใ ใฃใใ - if (e.dataTransfer.files.length == 1) { - this.form.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + this.form.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { + } else if (evt.dataTransfer.files.length > 1) { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -191,8 +191,8 @@ const Component = defineComponent({ } //#region ใใฉใคใใฎใใกใคใซ - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); this.form.file = file; } @@ -209,7 +209,7 @@ const Component = defineComponent({ limit: max + 1, untilId: this.existMoreMessages ? this.messages[0].id : undefined }).then(messages => { - if (messages.length == max + 1) { + if (messages.length === max + 1) { this.existMoreMessages = true; messages.pop(); } else { @@ -235,7 +235,7 @@ const Component = defineComponent({ const _isBottom = isBottom(this.$el, 64); this.messages.push(message); - if (message.userId != this.$i.id && !document.hidden) { + if (message.userId !== this.$i.id && !document.hidden) { this.connection.send('read', { id: message.id }); @@ -246,7 +246,7 @@ const Component = defineComponent({ this.$nextTick(() => { this.scrollToBottom(); }); - } else if (message.userId != this.$i.id) { + } else if (message.userId !== this.$i.id) { // Notify this.notifyNewMessage(); } @@ -256,7 +256,7 @@ const Component = defineComponent({ if (this.user) { if (!Array.isArray(x)) x = [x]; for (const id of x) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], @@ -266,7 +266,7 @@ const Component = defineComponent({ } } else if (this.group) { for (const id of x.ids) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue index 83ae5741c3..2c10494ede 100644 --- a/packages/client/src/pages/mfm-cheat-sheet.vue +++ b/packages/client/src/pages/mfm-cheat-sheet.vue @@ -325,23 +325,23 @@ export default defineComponent({ preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)', preview_quote: `> ${this.$ts._mfm.dummy}`, preview_search: `${this.$ts._mfm.dummy} ๆค็ดข`, - preview_jelly: `$[jelly ๐ฎ]`, - preview_tada: `$[tada ๐ฎ]`, - preview_jump: `$[jump ๐ฎ]`, - preview_bounce: `$[bounce ๐ฎ]`, - preview_shake: `$[shake ๐ฎ]`, - preview_twitch: `$[twitch ๐ฎ]`, - preview_spin: `$[spin ๐ฎ] $[spin.left ๐ฎ] $[spin.alternate ๐ฎ]\n$[spin.x ๐ฎ] $[spin.x,left ๐ฎ] $[spin.x,alternate ๐ฎ]\n$[spin.y ๐ฎ] $[spin.y,left ๐ฎ] $[spin.y,alternate ๐ฎ]`, + preview_jelly: `$[jelly ๐ฎ] $[jelly.speed=5s ๐ฎ]`, + preview_tada: `$[tada ๐ฎ] $[tada.speed=5s ๐ฎ]`, + preview_jump: `$[jump ๐ฎ] $[jump.speed=5s ๐ฎ]`, + preview_bounce: `$[bounce ๐ฎ] $[bounce.speed=5s ๐ฎ]`, + preview_shake: `$[shake ๐ฎ] $[shake.speed=5s ๐ฎ]`, + preview_twitch: `$[twitch ๐ฎ] $[twitch.speed=5s ๐ฎ]`, + preview_spin: `$[spin ๐ฎ] $[spin.left ๐ฎ] $[spin.alternate ๐ฎ]\n$[spin.x ๐ฎ] $[spin.x,left ๐ฎ] $[spin.x,alternate ๐ฎ]\n$[spin.y ๐ฎ] $[spin.y,left ๐ฎ] $[spin.y,alternate ๐ฎ]\n\n$[spin.speed=5s ๐ฎ]`, preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`, preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`, preview_x2: `$[x2 ๐ฎ]`, preview_x3: `$[x3 ๐ฎ]`, preview_x4: `$[x4 ๐ฎ]`, preview_blur: `$[blur ${this.$ts._mfm.dummy}]`, - preview_rainbow: `$[rainbow ๐ฎ]`, + preview_rainbow: `$[rainbow ๐ฎ] $[rainbow.speed=5s ๐ฎ]`, preview_sparkle: `$[sparkle ๐ฎ]`, preview_rotate: `$[rotate ๐ฎ]`, - } + }; }, }); </script> diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue index 6e85b784ff..4032d7723e 100644 --- a/packages/client/src/pages/miauth.vue +++ b/packages/client/src/pages/miauth.vue @@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { login } from '@/account'; +import { appendQuery, query } from '@/scripts/url'; export default defineComponent({ components: { @@ -82,7 +83,9 @@ export default defineComponent({ this.state = 'accepted'; if (this.callback) { - location.href = `${this.callback}?session=${this.session}`; + location.href = appendQuery(this.callback, query({ + session: this.session + })); } }, deny() { diff --git a/packages/client/src/pages/my-antennas/edit.vue b/packages/client/src/pages/my-antennas/edit.vue index 04928c81a3..38e56ce35d 100644 --- a/packages/client/src/pages/my-antennas/edit.vue +++ b/packages/client/src/pages/my-antennas/edit.vue @@ -4,49 +4,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; import * as os from '@/os'; +import { MisskeyNavigator } from '@/scripts/navigate'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XAntenna, - }, +const nav = new MisskeyNavigator(); - props: { - antennaId: { - type: String, - required: true, - } - }, +let antenna: any = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - antenna: null, - }; - }, +const props = defineProps<{ + antennaId: string +}>(); - watch: { - antennaId: { - async handler() { - this.antenna = await os.api('antennas/show', { antennaId: this.antennaId }); - }, - immediate: true, - } - }, +function onAntennaUpdated() { + nav.push('/my/antennas'); +} - methods: { - onAntennaUpdated() { - this.$router.push('/my/antennas'); - }, +os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => { + antenna = antennaResponse; +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', } }); </script> diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue index 8c1d6148fe..6f3c4afbfe 100644 --- a/packages/client/src/pages/my-antennas/editor.vue +++ b/packages/client/src/pages/my-antennas/editor.vue @@ -44,135 +44,100 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSelect from '@/components/form/select.vue'; import MkSwitch from '@/components/form/switch.vue'; -import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSelect, MkSwitch - }, +const props = defineProps<{ + antenna: any +}>(); - props: { - antenna: { - type: Object, - required: true - } - }, +const emit = defineEmits<{ + (ev: 'created'): void, + (ev: 'updated'): void, + (ev: 'deleted'): void, +}>(); - data() { - return { - name: '', - src: '', - userListId: null, - userGroupId: null, - users: '', - keywords: '', - excludeKeywords: '', - caseSensitive: false, - withReplies: false, - withFile: false, - notify: false, - userLists: null, - userGroups: null, - }; - }, +let name: string = $ref(props.antenna.name); +let src: string = $ref(props.antenna.src); +let userListId: any = $ref(props.antenna.userListId); +let userGroupId: any = $ref(props.antenna.userGroupId); +let users: string = $ref(props.antenna.users.join('\n')); +let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n')); +let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n')); +let caseSensitive: boolean = $ref(props.antenna.caseSensitive); +let withReplies: boolean = $ref(props.antenna.withReplies); +let withFile: boolean = $ref(props.antenna.withFile); +let notify: boolean = $ref(props.antenna.notify); +let userLists: any = $ref(null); +let userGroups: any = $ref(null); - watch: { - async src() { - if (this.src === 'list' && this.userLists === null) { - this.userLists = await os.api('users/lists/list'); - } +watch(() => src, async () => { + if (src === 'list' && userLists === null) { + userLists = await os.api('users/lists/list'); + } - if (this.src === 'group' && this.userGroups === null) { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); + if (src === 'group' && userGroups === null) { + const groups1 = await os.api('users/groups/owned'); + const groups2 = await os.api('users/groups/joined'); - this.userGroups = [...groups1, ...groups2]; - } - } - }, - - created() { - this.name = this.antenna.name; - this.src = this.antenna.src; - this.userListId = this.antenna.userListId; - this.userGroupId = this.antenna.userGroupId; - this.users = this.antenna.users.join('\n'); - this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n'); - this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'); - this.caseSensitive = this.antenna.caseSensitive; - this.withReplies = this.antenna.withReplies; - this.withFile = this.antenna.withFile; - this.notify = this.antenna.notify; - }, - - methods: { - async saveAntenna() { - if (this.antenna.id == null) { - await os.apiWithDialog('antennas/create', { - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('created'); - } else { - await os.apiWithDialog('antennas/update', { - antennaId: this.antenna.id, - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('updated'); - } - }, - - async deleteAntenna() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.antenna.name }), - }); - if (canceled) return; - - await os.api('antennas/delete', { - antennaId: this.antenna.id, - }); - - os.success(); - this.$emit('deleted'); - }, - - addUser() { - os.selectUser().then(user => { - this.users = this.users.trim(); - this.users += '\n@' + Acct.toString(user); - this.users = this.users.trim(); - }); - } + userGroups = [...groups1, ...groups2]; } }); + +async function saveAntenna() { + const antennaData = { + name, + src, + userListId, + userGroupId, + withReplies, + withFile, + notify, + caseSensitive, + users: users.trim().split('\n').map(x => x.trim()), + keywords: keywords.trim().split('\n').map(x => x.trim().split(' ')), + excludeKeywords: excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), + }; + + if (props.antenna.id == null) { + await os.apiWithDialog('antennas/create', antennaData); + emit('created'); + } else { + antennaData['antennaId'] = props.antenna.id; + await os.apiWithDialog('antennas/update', antennaData); + emit('updated'); + } +} + +async function deleteAntenna() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: props.antenna.name }), + }); + if (canceled) return; + + await os.api('antennas/delete', { + antennaId: props.antenna.id, + }); + + os.success(); + emit('deleted'); +} + +function addUser() { + os.selectUser().then(user => { + users = users.trim(); + users += '\n@' + Acct.toString(user as any); + users = users.trim(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index 7138d269a9..a568f64c52 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="ieepwinx"> - <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> + <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ i18n.ts.add }}</MkButton> <div class=""> <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> @@ -14,35 +14,24 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, +const pagination = { + endpoint: 'antennas/list' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'antennas/list' as const, - limit: 10, - }, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)' + } }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 29261ec484..f0a18ecc36 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -108,6 +108,10 @@ export default defineComponent({ }, methods: { fetch() { + this.hasPrev = false; + this.hasNext = false; + this.showPrev = false; + this.showNext = false; this.note = null; os.api('notes/show', { noteId: this.noteId @@ -132,8 +136,8 @@ export default defineComponent({ this.hasPrev = prev.length !== 0; this.hasNext = next.length !== 0; }); - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); } } diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue index f302ac4f90..9566592618 100644 --- a/packages/client/src/pages/page-editor/page-editor.vue +++ b/packages/client/src/pages/page-editor/page-editor.vue @@ -114,7 +114,7 @@ export default defineComponent({ readonly: this.readonly, getScriptBlockList: this.getScriptBlockList, getPageBlockList: this.getPageBlockList - } + }; }, props: { diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index b2c039a269..5bca971438 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -139,8 +139,8 @@ export default defineComponent({ username: this.username, }).then(page => { this.page = page; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index 7d008ae75c..b3e2ca8d6f 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -12,7 +12,7 @@ </template> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { defineAsyncComponent, onMounted } from 'vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; @@ -36,7 +36,7 @@ async function save() { onMounted(() => { if (props.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {}, 'closed'); router.push('/'); } }); diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue index f871dc48e8..34a41b81a5 100644 --- a/packages/client/src/pages/scratchpad.vue +++ b/packages/client/src/pages/scratchpad.vue @@ -6,20 +6,20 @@ </div> <MkContainer :foldable="true" class="_gap"> - <template #header>{{ $ts.output }}</template> + <template #header>{{ i18n.ts.output }}</template> <div class="bepmlvbi"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> </div> </MkContainer> <div class="_gap"> - {{ $ts.scratchpadDescription }} + {{ i18n.ts.scratchpadDescription }} </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import 'prismjs'; import { highlight, languages } from 'prismjs/components/prism-core'; import 'prismjs/components/prism-clike'; @@ -27,103 +27,90 @@ import 'prismjs/components/prism-javascript'; import 'prismjs/themes/prism-okaidia.css'; import { PrismEditor } from 'vue-prism-editor'; import 'vue-prism-editor/dist/prismeditor.min.css'; -import { AiScript, parse, utils, values } from '@syuilo/aiscript'; +import { AiScript, parse, utils } from '@syuilo/aiscript'; import MkContainer from '@/components/ui/container.vue'; import MkButton from '@/components/ui/button.vue'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkContainer, - MkButton, - PrismEditor, - }, +const code = ref(''); +const logs = ref<any[]>([]); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.scratchpad, - icon: 'fas fa-terminal', - }, - code: '', - logs: [], - } - }, +const saved = localStorage.getItem('scratchpad'); +if (saved) { + code.value = saved; +} - watch: { - code() { - localStorage.setItem('scratchpad', this.code); - } - }, +watch(code, () => { + localStorage.setItem('scratchpad', code.value); +}); - created() { - const saved = localStorage.getItem('scratchpad'); - if (saved) { - this.code = saved; - } - }, - - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'scratchpad', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } +async function run() { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'scratchpad', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); + }); }); - - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e - }); - } }, - - highlighter(code) { - return highlight(code, languages.js, 'javascript'); + out: (value) => { + logs.value.push({ + id: Math.random(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true + }); }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random(), + text: utils.valToString(params.val, true), + print: false + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(code.value); + } catch (error) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; } + try { + await aiscript.exec(ast); + } catch (error: any) { + os.alert({ + type: 'error', + text: error.message + }); + } +} + +function highlighter(code) { + return highlight(code, languages.js, 'javascript'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.scratchpad, + icon: 'fas fa-terminal', + }, }); </script> diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue index 10599d99ff..fb3a7a17f3 100644 --- a/packages/client/src/pages/settings/2fa.vue +++ b/packages/client/src/pages/settings/2fa.vue @@ -1,49 +1,49 @@ <template> <div> - <MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton> + <MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton> <template v-if="$i.twoFactorEnabled"> - <p>{{ $ts._2fa.alreadyRegistered }}</p> - <MkButton @click="unregister">{{ $ts.unregister }}</MkButton> + <p>{{ i18n.ts._2fa.alreadyRegistered }}</p> + <MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton> <template v-if="supportsCredentials"> <hr class="totp-method-sep"> - <h2 class="heading">{{ $ts.securityKey }}</h2> - <p>{{ $ts._2fa.securityKeyInfo }}</p> + <h2 class="heading">{{ i18n.ts.securityKey }}</h2> + <p>{{ i18n.ts._2fa.securityKeyInfo }}</p> <div class="key-list"> <div v-for="key in $i.securityKeysList" class="key"> <h3>{{ key.name }}</h3> - <div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> - <MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton> + <div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> + <MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton> </div> </div> - <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ $ts.passwordLessLogin }}</MkSwitch> + <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch> - <MkInfo v-if="registration && registration.error" warn>{{ $ts.error }} {{ registration.error }}</MkInfo> - <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton> + <MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo> + <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton> <ol v-if="registration && !registration.error"> <li v-if="registration.stage >= 0"> - {{ $ts.tapSecurityKey }} + {{ i18n.ts.tapSecurityKey }} <i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i> </li> <li v-if="registration.stage >= 1"> <MkForm :disabled="registration.stage != 1 || registration.saving"> <MkInput v-model="keyName" :max="30"> - <template #label>{{ $ts.securityKeyName }}</template> + <template #label>{{ i18n.ts.securityKeyName }}</template> </MkInput> - <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ $ts.registerSecurityKey }}</MkButton> + <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton> <i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i> </MkForm> </li> </ol> </template> </template> - <div v-if="data && !$i.twoFactorEnabled"> + <div v-if="twoFactorData && !$i.twoFactorEnabled"> <ol style="margin: 0; padding: 0 0 0 1em;"> <li> - <I18n :src="$ts._2fa.step1" tag="span"> + <I18n :src="i18n.ts._2fa.step1" tag="span"> <template #a> <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a> </template> @@ -52,19 +52,20 @@ </template> </I18n> </li> - <li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li> - <li>{{ $ts._2fa.step3 }}<br> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput> - <MkButton primary @click="submit">{{ $ts.done }}</MkButton> + <li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li> + <li> + {{ i18n.ts._2fa.step3 }}<br> + <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput> + <MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton> </li> </ol> - <MkInfo>{{ $ts._2fa.step4 }}</MkInfo> + <MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { hostname } from '@/config'; import { byteify, hexify, stringify } from '@/scripts/2fa'; import MkButton from '@/components/ui/button.vue'; @@ -72,155 +73,144 @@ import MkInfo from '@/components/ui/info.vue'; import MkInput from '@/components/form/input.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInfo, MkInput, MkSwitch - }, +const twoFactorData = ref<any>(null); +const supportsCredentials = ref(!!navigator.credentials); +const usePasswordLessLogin = ref($i!.usePasswordLessLogin); +const registration = ref<any>(null); +const keyName = ref(''); +const token = ref(null); - data() { - return { - data: null, - supportsCredentials: !!navigator.credentials, - usePasswordLessLogin: this.$i.usePasswordLessLogin, - registration: null, - keyName: '', - token: null, - }; - }, +function register() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register', { + password: password + }).then(data => { + twoFactorData.value = data; + }); + }); +} - methods: { - register() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register', { - password: password - }).then(data => { - this.data = data; - }); +function unregister() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/unregister', { + password: password + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + $i!.twoFactorEnabled = false; + }); + }); +} + +function submit() { + os.api('i/2fa/done', { + token: token.value + }).then(() => { + os.success(); + $i!.twoFactorEnabled = true; + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); +} + +function registerKey() { + registration.value.saving = true; + os.api('i/2fa/key-done', { + password: registration.value.password, + name: keyName.value, + challengeId: registration.value.challengeId, + // we convert each 16 bits to a string to serialise + clientDataJSON: stringify(registration.value.credential.response.clientDataJSON), + attestationObject: hexify(registration.value.credential.response.attestationObject) + }).then(key => { + registration.value = null; + key.lastUsed = new Date(); + os.success(); + }); +} + +function unregisterKey(key) { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + return os.api('i/2fa/remove-key', { + password, + credentialId: key.id + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + }); + }); +} + +function addSecurityKey() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register-key', { + password + }).then(reg => { + registration.value = { + password, + challengeId: reg!.challengeId, + stage: 0, + publicKeyOptions: { + challenge: byteify(reg!.challenge, 'base64'), + rp: { + id: hostname, + name: 'Misskey' + }, + user: { + id: byteify($i!.id, 'ascii'), + name: $i!.username, + displayName: $i!.name, + }, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + timeout: 60000, + attestation: 'direct' + }, + saving: true + }; + return navigator.credentials.create({ + publicKey: registration.value.publicKeyOptions }); - }, + }).then(credential => { + registration.value.credential = credential; + registration.value.saving = false; + registration.value.stage = 1; + }).catch(err => { + console.warn('Error while registering?', err); + registration.value.error = err.message; + registration.value.stage = -1; + }); + }); +} - unregister() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/unregister', { - password: password - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = false; - }); - }); - }, - - submit() { - os.api('i/2fa/done', { - token: this.token - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = true; - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }, - - registerKey() { - this.registration.saving = true; - os.api('i/2fa/key-done', { - password: this.registration.password, - name: this.keyName, - challengeId: this.registration.challengeId, - // we convert each 16 bits to a string to serialise - clientDataJSON: stringify(this.registration.credential.response.clientDataJSON), - attestationObject: hexify(this.registration.credential.response.attestationObject) - }).then(key => { - this.registration = null; - key.lastUsed = new Date(); - os.success(); - }) - }, - - unregisterKey(key) { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - return os.api('i/2fa/remove-key', { - password, - credentialId: key.id - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - }); - }); - }, - - addSecurityKey() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register-key', { - password - }).then(registration => { - this.registration = { - password, - challengeId: registration.challengeId, - stage: 0, - publicKeyOptions: { - challenge: byteify(registration.challenge, 'base64'), - rp: { - id: hostname, - name: 'Misskey' - }, - user: { - id: byteify(this.$i.id, 'ascii'), - name: this.$i.username, - displayName: this.$i.name, - }, - pubKeyCredParams: [{ alg: -7, type: 'public-key' }], - timeout: 60000, - attestation: 'direct' - }, - saving: true - }; - return navigator.credentials.create({ - publicKey: this.registration.publicKeyOptions - }); - }).then(credential => { - this.registration.credential = credential; - this.registration.saving = false; - this.registration.stage = 1; - }).catch(err => { - console.warn('Error while registering?', err); - this.registration.error = err.message; - this.registration.stage = -1; - }); - }); - }, - - updatePasswordLessLogin() { - os.api('i/2fa/password-less', { - value: !!this.usePasswordLessLogin - }); - } - } -}); +async function updatePasswordLessLogin() { + await os.api('i/2fa/password-less', { + value: !!usePasswordLessLogin.value + }); +} </script> diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index c98ad056f6..12142b4dc1 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -7,163 +7,150 @@ <FormSection> <MkKeyValue> - <template #key>{{ $ts.registeredDate }}</template> + <template #key>{{ i18n.ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> </MkKeyValue> </FormSection> <FormSection v-if="stats"> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.notesCount }}</template> + <template #key>{{ i18n.ts.notesCount }}</template> <template #value>{{ number(stats.notesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliesCount }}</template> + <template #key>{{ i18n.ts.repliesCount }}</template> <template #value>{{ number(stats.repliesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotesCount }}</template> + <template #key>{{ i18n.ts.renotesCount }}</template> <template #value>{{ number(stats.renotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliedCount }}</template> + <template #key>{{ i18n.ts.repliedCount }}</template> <template #value>{{ number(stats.repliedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotedCount }}</template> + <template #key>{{ i18n.ts.renotedCount }}</template> <template #value>{{ number(stats.renotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotesCount }}</template> + <template #key>{{ i18n.ts.pollVotesCount }}</template> <template #value>{{ number(stats.pollVotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotedCount }}</template> + <template #key>{{ i18n.ts.pollVotedCount }}</template> <template #value>{{ number(stats.pollVotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.sentReactionsCount }}</template> + <template #key>{{ i18n.ts.sentReactionsCount }}</template> <template #value>{{ number(stats.sentReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.receivedReactionsCount }}</template> + <template #key>{{ i18n.ts.receivedReactionsCount }}</template> <template #value>{{ number(stats.receivedReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.noteFavoritesCount }}</template> + <template #key>{{ i18n.ts.noteFavoritesCount }}</template> <template #value>{{ number(stats.noteFavoritesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }}</template> + <template #key>{{ i18n.ts.followingCount }}</template> <template #value>{{ number(stats.followingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }}</template> + <template #key>{{ i18n.ts.followersCount }}</template> <template #value>{{ number(stats.followersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikesCount }}</template> + <template #key>{{ i18n.ts.pageLikesCount }}</template> <template #value>{{ number(stats.pageLikesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikedCount }}</template> + <template #key>{{ i18n.ts.pageLikedCount }}</template> <template #value>{{ number(stats.pageLikedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveFilesCount }}</template> + <template #key>{{ i18n.ts.driveFilesCount }}</template> <template #value>{{ number(stats.driveFilesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveUsage }}</template> + <template #key>{{ i18n.ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> </MkKeyValue> </FormSection> <FormSection> - <template #label>{{ $ts.other }}</template> + <template #label>{{ i18n.ts.other }}</template> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>emailVerified</template> - <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>twoFactorEnabled</template> - <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>securityKeys</template> - <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>usePasswordLessLogin</template> - <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isModerator</template> - <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isAdmin</template> - <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> </FormSection> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref } from 'vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkKeyValue, - }, +const stats = ref<any>({}); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accountInfo, - icon: 'fas fa-info-circle' - }, - stats: null - } - }, +onMounted(() => { + os.api('users/stats', { + userId: $i!.id + }).then(response => { + stats.value = response; + }); +}); - mounted() { - os.api('users/stats', { - userId: this.$i.id - }).then(stats => { - this.stats = stats; - }); - }, - - methods: { - number, - bytes, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accountInfo, + icon: 'fas fa-info-circle' } }); </script> diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index a744a031d4..5e75639c55 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -1,7 +1,7 @@ <template> <div class="_formRoot"> <FormSuspense :p="init"> - <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> + <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ i18n.ts.addAccount }}</FormButton> <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)"> <div class="avatar"> @@ -20,90 +20,89 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { getAccounts, addAccount, login } from '@/account'; +import { getAccounts, addAccount as addAccounts, login, $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - FormButton, - }, +const storedAccounts = ref<any>(null); +const accounts = ref<any>(null); - emits: ['info'], +const init = async () => { + getAccounts().then(accounts => { + storedAccounts.value = accounts.filter(x => x.id !== $i!.id); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accounts, - icon: 'fas fa-users', - bg: 'var(--bg)', - }, - storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), - accounts: null, - init: async () => os.api('users/show', { - userIds: (await this.storedAccounts).map(x => x.id) - }).then(accounts => { - this.accounts = accounts; - }), - }; - }, + console.log(storedAccounts.value); - methods: { - menu(account, ev) { - os.popupMenu([{ - text: this.$ts.switch, - icon: 'fas fa-exchange-alt', - action: () => this.switchAccount(account), - }, { - text: this.$ts.remove, - icon: 'fas fa-trash-alt', - danger: true, - action: () => this.removeAccount(account), - }], ev.currentTarget ?? ev.target); + return os.api('users/show', { + userIds: storedAccounts.value.map(x => x.id) + }); + }).then(response => { + accounts.value = response; + console.log(accounts.value); + }); +}; + +function menu(account, ev) { + os.popupMenu([{ + text: i18n.ts.switch, + icon: 'fas fa-exchange-alt', + action: () => switchAccount(account), + }, { + text: i18n.ts.remove, + icon: 'fas fa-trash-alt', + danger: true, + action: () => removeAccount(account), + }], ev.currentTarget ?? ev.target); +} + +function addAccount(ev) { + os.popupMenu([{ + text: i18n.ts.existingAccount, + action: () => { addExistingAccount(); }, + }, { + text: i18n.ts.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget ?? ev.target); +} + +function addExistingAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + os.success(); }, + }, 'closed'); +} - addAccount(ev) { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addExistingAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget ?? ev.target); +function createAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + switchAccountWithToken(res.i); }, + }, 'closed'); +} - addExistingAccount() { - os.popup(import('@/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, +async function switchAccount(account: any) { + const fetchedAccounts: any[] = await getAccounts(); + const token = fetchedAccounts.find(x => x.id === account.id).token; + switchAccountWithToken(token); +} - createAccount() { - os.popup(import('@/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); - }, +function switchAccountWithToken(token: string) { + login(token); +} - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); - }, - - switchAccountWithToken(token: string) { - login(token); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accounts, + icon: 'fas fa-users', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 20ff2a8d96..e6375763f1 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -1,56 +1,45 @@ <template> <div class="_formRoot"> - <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> - <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink> + <FormButton primary class="_formBlock" @click="generateToken">{{ i18n.ts.generateAccessToken }}</FormButton> + <FormLink to="/settings/apps" class="_formBlock">{{ i18n.ts.manageAccessTokens }}</FormLink> <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormLink, - }, +const isDesktop = ref(window.innerWidth >= 1100); - emits: ['info'], +function generateToken() { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, + }); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API', - icon: 'fas fa-key', - bg: 'var(--bg)', - }, - isDesktop: window.innerWidth >= 1100, - }; - }, - - methods: { - generateToken() { - os.popup(import('@/components/token-generate-window.vue'), {}, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - os.alert({ - type: 'success', - title: this.$ts.token, - text: token - }); - }, - }, 'closed'); + os.alert({ + type: 'success', + title: i18n.ts.token, + text: token + }); }, + }, 'closed'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API', + icon: 'fas fa-key', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index 9c0fa8a54d..7b0b5548d5 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -4,7 +4,7 @@ <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.nothing }}</div> + <div>{{ i18n.ts.nothing }}</div> </div> </template> <template v-slot="{items}"> @@ -14,18 +14,18 @@ <div class="name">{{ token.name }}</div> <div class="description">{{ token.description }}</div> <div class="_keyValue"> - <div>{{ $ts.installedDate }}:</div> + <div>{{ i18n.ts.installedDate }}:</div> <div><MkTime :time="token.createdAt"/></div> </div> <div class="_keyValue"> - <div>{{ $ts.lastUsedDate }}:</div> + <div>{{ i18n.ts.lastUsedDate }}:</div> <div><MkTime :time="token.lastUsedAt"/></div> </div> <div class="actions"> <button class="_button" @click="revoke(token)"><i class="fas fa-trash-alt"></i></button> </div> <details> - <summary>{{ $ts.details }}</summary> + <summary>{{ i18n.ts.details }}</summary> <ul> <li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li> </ul> @@ -37,42 +37,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import FormPagination from '@/components/ui/pagination.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormPagination, - }, +const list = ref<any>(null); - emits: ['info'], +const pagination = { + endpoint: 'i/apps' as const, + limit: 100, + params: { + sort: '+lastUsedAt' + } +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.installedApps, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/apps' as const, - limit: 100, - params: { - sort: '+lastUsedAt' - } - }, - }; - }, +function revoke(token) { + os.api('i/revoke-token', { tokenId: token.id }).then(() => { + list.value.reload(); + }); +} - methods: { - revoke(token) { - os.api('i/revoke-token', { tokenId: token.id }).then(() => { - this.$refs.list.reload(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.installedApps, + icon: 'fas fa-plug', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 556ee30c1d..20db077ceb 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -1,6 +1,6 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts.customCssWarn }}</FormInfo> <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;"> <template #label>CSS</template> @@ -8,50 +8,38 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormInfo, - }, +const localCustomCss = ref(localStorage.getItem('customCss') ?? ''); - emits: ['info'], +async function apply() { + localStorage.setItem('customCss', localCustomCss.value); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.customCss, - icon: 'fas fa-code', - bg: 'var(--bg)', - }, - localCustomCss: localStorage.getItem('customCss') - } - }, + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - mounted() { - this.$watch('localCustomCss', this.apply); - }, + unisonReload(); +} - methods: { - async apply() { - localStorage.setItem('customCss', this.localCustomCss); +watch(localCustomCss, async () => { + await apply(); +}); - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.customCss, + icon: 'fas fa-code', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index 46b90d3d1a..2d868aa0a7 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -1,36 +1,36 @@ <template> <div class="_formRoot"> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="navWindow">{{ i18n.ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch> <FormRadios v-model="columnAlign" class="_formBlock"> - <template #label>{{ $ts._deck.columnAlign }}</template> - <option value="left">{{ $ts.left }}</option> - <option value="center">{{ $ts.center }}</option> + <template #label>{{ i18n.ts._deck.columnAlign }}</template> + <option value="left">{{ i18n.ts.left }}</option> + <option value="center">{{ i18n.ts.center }}</option> </FormRadios> <FormRadios v-model="columnHeaderHeight" class="_formBlock"> - <template #label>{{ $ts._deck.columnHeaderHeight }}</template> - <option :value="42">{{ $ts.narrow }}</option> - <option :value="45">{{ $ts.medium }}</option> - <option :value="48">{{ $ts.wide }}</option> + <template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template> + <option :value="42">{{ i18n.ts.narrow }}</option> + <option :value="45">{{ i18n.ts.medium }}</option> + <option :value="48">{{ i18n.ts.wide }}</option> </FormRadios> <FormInput v-model="columnMargin" type="number" class="_formBlock"> - <template #label>{{ $ts._deck.columnMargin }}</template> + <template #label>{{ i18n.ts._deck.columnMargin }}</template> <template #suffix>px</template> </FormInput> - <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> + <FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormLink from '@/components/form/link.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -40,59 +40,41 @@ import { deckStore } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormLink, - FormInput, - FormRadios, - FormGroup, - }, +const navWindow = computed(deckStore.makeGetterSetter('navWindow')); +const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn')); +const columnAlign = computed(deckStore.makeGetterSetter('columnAlign')); +const columnMargin = computed(deckStore.makeGetterSetter('columnMargin')); +const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight')); +const profile = computed(deckStore.makeGetterSetter('profile')); - emits: ['info'], +watch(navWindow, async () => { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.deck, - icon: 'fas fa-columns', - bg: 'var(--bg)', - }, - } - }, + unisonReload(); +}); - computed: { - navWindow: deckStore.makeGetterSetter('navWindow'), - alwaysShowMainColumn: deckStore.makeGetterSetter('alwaysShowMainColumn'), - columnAlign: deckStore.makeGetterSetter('columnAlign'), - columnMargin: deckStore.makeGetterSetter('columnMargin'), - columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'), - profile: deckStore.makeGetterSetter('profile'), - }, +async function setProfile() { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts._deck.profile, + allowEmpty: false + }); + if (canceled) return; + + profile.value = name; + unisonReload(); +} - watch: { - async navWindow() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); - } - }, - - methods: { - async setProfile() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts._deck.profile, - allowEmpty: false - }); - if (canceled) return; - this.profile = name; - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.deck, + icon: 'fas fa-columns', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 7edc81a309..e9f19aaf0b 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -1,64 +1,52 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo> - <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> - <FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton> + <FormInfo warn class="_formBlock">{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</FormButton> + <FormButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</FormButton> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormInfo from '@/components/ui/info.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { signout } from '@/account'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormInfo, - }, +async function deleteAccount() { + { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (canceled) return; + } - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._accountDelete.accountDelete, - icon: 'fas fa-exclamation-triangle', - bg: 'var(--bg)', - }, - } - }, + const { canceled, result: password } = await os.inputText({ + title: i18n.ts.password, + type: 'password' + }); + if (canceled) return; - methods: { - async deleteAccount() { - { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.deleteAccountConfirm, - }); - if (canceled) return; - } + await os.apiWithDialog('i/delete-account', { + password: password + }); - const { canceled, result: password } = await os.inputText({ - title: this.$ts.password, - type: 'password' - }); - if (canceled) return; + await os.alert({ + title: i18n.ts._accountDelete.started, + }); - await os.apiWithDialog('i/delete-account', { - password: password - }); + await signout(); +} - await os.alert({ - title: this.$ts._accountDelete.started, - }); - - signout(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._accountDelete.accountDelete, + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index 9309eb5ec7..09a2537ed5 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -1,41 +1,41 @@ <template> <div class="_formRoot"> <FormSection v-if="!fetching"> - <template #label>{{ $ts.usageAmount }}</template> + <template #label>{{ i18n.ts.usageAmount }}</template> <div class="_formBlock uawsfosz"> <div class="meter"><div :style="meterStyle"></div></div> </div> <FormSplit> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.capacity }}</template> + <template #key>{{ i18n.ts.capacity }}</template> <template #value>{{ bytes(capacity, 1) }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.inUse }}</template> + <template #key>{{ i18n.ts.inUse }}</template> <template #value>{{ bytes(usage, 1) }}</template> </MkKeyValue> </FormSplit> </FormSection> <FormSection> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/> </FormSection> <FormSection> <FormLink @click="chooseUploadFolder()"> - {{ $ts.uploadFolder }} + {{ i18n.ts.uploadFolder }} <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> <template #suffixIcon><i class="fas fa-folder-open"></i></template> </FormLink> - <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ $ts.keepOriginalUploading }}<template #caption>{{ $ts.keepOriginalUploadingDescription }}</template></FormSwitch> + <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ i18n.ts.keepOriginalUploading }}<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template></FormSwitch> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -46,80 +46,59 @@ import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; import { defaultStore } from '@/store'; import MkChart from '@/components/chart.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkKeyValue, - FormSplit, - MkChart, - }, +const fetching = ref(true); +const usage = ref<any>(null); +const capacity = ref<any>(null); +const uploadFolder = ref<any>(null); - emits: ['info'], +const meterStyle = computed(() => { + return { + width: `${usage.value / capacity.value * 100}%`, + background: tinycolor({ + h: 180 - (usage.value / capacity.value * 180), + s: 0.7, + l: 0.5 + }) + }; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.drive, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - }, - fetching: true, - usage: null, - capacity: null, - uploadFolder: null, +const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); + +os.api('drive').then(info => { + capacity.value = info.capacity; + usage.value = info.usage; + fetching.value = false; +}); + +if (defaultStore.state.uploadFolder) { + os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder + }).then(response => { + uploadFolder.value = response; + }); +} + +function chooseUploadFolder() { + os.selectDriveFolder(false).then(async folder => { + defaultStore.set('uploadFolder', folder ? folder.id : null); + os.success(); + if (defaultStore.state.uploadFolder) { + uploadFolder.value = await os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder + }); + } else { + uploadFolder.value = null; } - }, + }); +} - computed: { - meterStyle(): any { - return { - width: `${this.usage / this.capacity * 100}%`, - background: tinycolor({ - h: 180 - (this.usage / this.capacity * 180), - s: 0.7, - l: 0.5 - }) - }; - }, - keepOriginalUploading: defaultStore.makeGetterSetter('keepOriginalUploading'), - }, - - async created() { - os.api('drive').then(info => { - this.capacity = info.capacity; - this.usage = info.usage; - this.fetching = false; - this.$nextTick(() => { - this.renderChart(); - }); - }); - - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder - }); - } - }, - - methods: { - chooseUploadFolder() { - os.selectDriveFolder(false).then(async folder => { - this.$store.set('uploadFolder', folder ? folder.id : null); - os.success(); - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder - }); - } else { - this.uploadFolder = null; - } - }); - }, - - bytes +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index 4697fec9b7..37f14068e2 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -39,8 +39,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref, watch } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormInput from '@/components/form/input.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -49,79 +49,62 @@ import * as symbols from '@/symbols'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormInput, - }, +const emailAddress = ref($i!.email); - emits: ['info'], +const onChangeReceiveAnnouncementEmail = (v) => { + os.api('i/update', { + receiveAnnouncementEmail: v + }); +}; - setup(props, context) { - const emailAddress = ref($i.email); - - const INFO = { - title: i18n.ts.email, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - }; - - const onChangeReceiveAnnouncementEmail = (v) => { - os.api('i/update', { - receiveAnnouncementEmail: v - }); - }; - - const saveEmailAddress = () => { - os.inputText({ - title: i18n.ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.apiWithDialog('i/update-email', { - password: password, - email: emailAddress.value, - }); - }); - }; - - const emailNotification_mention = ref($i.emailNotificationTypes.includes('mention')); - const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply')); - const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote')); - const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow')); - const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest')); - const emailNotification_groupInvited = ref($i.emailNotificationTypes.includes('groupInvited')); - - const saveNotificationSettings = () => { - os.api('i/update', { - emailNotificationTypes: [ - ...[emailNotification_mention.value ? 'mention' : null], - ...[emailNotification_reply.value ? 'reply' : null], - ...[emailNotification_quote.value ? 'quote' : null], - ...[emailNotification_follow.value ? 'follow' : null], - ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], - ...[emailNotification_groupInvited.value ? 'groupInvited' : null], - ].filter(x => x != null) - }); - }; - - watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { - saveNotificationSettings(); +const saveEmailAddress = () => { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.apiWithDialog('i/update-email', { + password: password, + email: emailAddress.value, }); + }); +}; - onMounted(() => { - watch(emailAddress, () => { - saveEmailAddress(); - }); - }); +const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention')); +const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply')); +const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote')); +const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow')); +const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest')); +const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited')); - return { - [symbols.PAGE_INFO]: INFO, - emailAddress, - onChangeReceiveAnnouncementEmail, - emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited, - }; - }, +const saveNotificationSettings = () => { + os.api('i/update', { + emailNotificationTypes: [ + ...[emailNotification_mention.value ? 'mention' : null], + ...[emailNotification_reply.value ? 'reply' : null], + ...[emailNotification_quote.value ? 'quote' : null], + ...[emailNotification_follow.value ? 'follow' : null], + ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], + ...[emailNotification_groupInvited.value ? 'groupInvited' : null], + ].filter(x => x != null) + }); +}; + +watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { + saveNotificationSettings(); +}); + +onMounted(() => { + watch(emailAddress, () => { + saveEmailAddress(); + }); +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.email, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index c8f6f58322..64b8cc3106 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -1,10 +1,10 @@ <template> <div class="_formRoot"> <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.uiLanguage }}</template> + <template #label>{{ i18n.ts.uiLanguage }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> <template #caption> - <I18n :src="$ts.i18nInfo" tag="span"> + <I18n :src="i18n.ts.i18nInfo" tag="span"> <template #link> <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> </template> @@ -13,48 +13,48 @@ </FormSelect> <FormRadios v-model="overridedDeviceKind" class="_formBlock"> - <template #label>{{ $ts.overridedDeviceKind }}</template> - <option :value="null">{{ $ts.auto }}</option> - <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ $ts.smartphone }}</option> - <option value="tablet"><i class="fas fa-tablet-alt"/> {{ $ts.tablet }}</option> - <option value="desktop"><i class="fas fa-desktop"/> {{ $ts.desktop }}</option> + <template #label>{{ i18n.ts.overridedDeviceKind }}</template> + <option :value="null">{{ i18n.ts.auto }}</option> + <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ i18n.ts.smartphone }}</option> + <option value="tablet"><i class="fas fa-tablet-alt"/> {{ i18n.ts.tablet }}</option> + <option value="desktop"><i class="fas fa-desktop"/> {{ i18n.ts.desktop }}</option> </FormRadios> - <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ $ts.showFixedPostForm }}</FormSwitch> + <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ i18n.ts.showFixedPostForm }}</FormSwitch> <FormSection> - <template #label>{{ $ts.behavior }}</template> - <FormSwitch v-model="imageNewTab" class="_formBlock">{{ $ts.openImageInNewTab }}</FormSwitch> - <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ $ts.enableInfiniteScroll }}</FormSwitch> - <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch> - <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ $ts.disablePagesScript }}</FormSwitch> + <template #label>{{ i18n.ts.behavior }}</template> + <FormSwitch v-model="imageNewTab" class="_formBlock">{{ i18n.ts.openImageInNewTab }}</FormSwitch> + <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ i18n.ts.enableInfiniteScroll }}</FormSwitch> + <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch> + <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ i18n.ts.disablePagesScript }}</FormSwitch> <FormSelect v-model="serverDisconnectedBehavior" class="_formBlock"> - <template #label>{{ $ts.whenServerDisconnected }}</template> - <option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ $ts._serverDisconnectedBehavior.quiet }}</option> + <template #label>{{ i18n.ts.whenServerDisconnected }}</template> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> </FormSelect> </FormSection> <FormSection> - <template #label>{{ $ts.appearance }}</template> - <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ $ts.disableAnimatedMfm }}</FormSwitch> - <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ $ts.reduceUiAnimation }}</FormSwitch> - <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ $ts.useBlurEffect }}</FormSwitch> - <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ $ts.useBlurEffectForModal }}</FormSwitch> - <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch> - <FormSwitch v-model="loadRawImages" class="_formBlock">{{ $ts.loadRawImages }}</FormSwitch> - <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ $ts.disableShowingAnimatedImages }}</FormSwitch> - <FormSwitch v-model="squareAvatars" class="_formBlock">{{ $ts.squareAvatars }}</FormSwitch> - <FormSwitch v-model="useSystemFont" class="_formBlock">{{ $ts.useSystemFont }}</FormSwitch> - <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ $ts.useOsNativeEmojis }} + <template #label>{{ i18n.ts.appearance }}</template> + <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ i18n.ts.disableAnimatedMfm }}</FormSwitch> + <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ i18n.ts.reduceUiAnimation }}</FormSwitch> + <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ i18n.ts.useBlurEffect }}</FormSwitch> + <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ i18n.ts.useBlurEffectForModal }}</FormSwitch> + <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch> + <FormSwitch v-model="loadRawImages" class="_formBlock">{{ i18n.ts.loadRawImages }}</FormSwitch> + <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch> + <FormSwitch v-model="squareAvatars" class="_formBlock">{{ i18n.ts.squareAvatars }}</FormSwitch> + <FormSwitch v-model="useSystemFont" class="_formBlock">{{ i18n.ts.useSystemFont }}</FormSwitch> + <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ i18n.ts.useOsNativeEmojis }} <div><Mfm :key="useOsNativeEmojis" text="๐ฎ๐ฆ๐ญ๐ฉ๐ฐ๐ซ๐ฌ๐ฅ๐ช"/></div> </FormSwitch> - <FormSwitch v-model="disableDrawer" class="_formBlock">{{ $ts.disableDrawer }}</FormSwitch> + <FormSwitch v-model="disableDrawer" class="_formBlock">{{ i18n.ts.disableDrawer }}</FormSwitch> <FormRadios v-model="fontSize" class="_formBlock"> - <template #label>{{ $ts.fontSize }}</template> + <template #label>{{ i18n.ts.fontSize }}</template> <option value="small"><span style="font-size: 14px;">Aa</span></option> <option :value="null"><span style="font-size: 16px;">Aa</span></option> <option value="large"><span style="font-size: 18px;">Aa</span></option> @@ -63,36 +63,36 @@ </FormSection> <FormSection> - <FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch> + <FormSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</FormSwitch> </FormSection> <FormSelect v-model="instanceTicker" class="_formBlock"> - <template #label>{{ $ts.instanceTicker }}</template> - <option value="none">{{ $ts._instanceTicker.none }}</option> - <option value="remote">{{ $ts._instanceTicker.remote }}</option> - <option value="always">{{ $ts._instanceTicker.always }}</option> + <template #label>{{ i18n.ts.instanceTicker }}</template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> </FormSelect> <FormSelect v-model="nsfw" class="_formBlock"> - <template #label>{{ $ts.nsfw }}</template> - <option value="respect">{{ $ts._nsfw.respect }}</option> - <option value="ignore">{{ $ts._nsfw.ignore }}</option> - <option value="force">{{ $ts._nsfw.force }}</option> + <template #label>{{ i18n.ts.nsfw }}</template> + <option value="respect">{{ i18n.ts._nsfw.respect }}</option> + <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> + <option value="force">{{ i18n.ts._nsfw.force }}</option> </FormSelect> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="defaultSideView">{{ i18n.ts.openInSideView }}</FormSwitch> </FormGroup> - <FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink> + <FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink> - <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink> + <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -102,122 +102,87 @@ import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/link.vue'; import { langs } from '@/config'; import { defaultStore } from '@/store'; -import { ColdDeviceStorage } from '@/store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkLink, - FormSwitch, - FormSelect, - FormRadios, - FormGroup, - FormLink, - FormSection, - }, +const lang = ref(localStorage.getItem('lang')); +const fontSize = ref(localStorage.getItem('fontSize')); +const useSystemFont = ref(localStorage.getItem('useSystemFont') != null); - emits: ['info'], +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cogs', - bg: 'var(--bg)' - }, - langs, - lang: localStorage.getItem('lang'), - fontSize: localStorage.getItem('fontSize'), - useSystemFont: localStorage.getItem('useSystemFont') != null, - } - }, + unisonReload(); +} - computed: { - overridedDeviceKind: defaultStore.makeGetterSetter('overridedDeviceKind'), - serverDisconnectedBehavior: defaultStore.makeGetterSetter('serverDisconnectedBehavior'), - reduceAnimation: defaultStore.makeGetterSetter('animation', v => !v, v => !v), - useBlurEffectForModal: defaultStore.makeGetterSetter('useBlurEffectForModal'), - useBlurEffect: defaultStore.makeGetterSetter('useBlurEffect'), - showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'), - disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v), - useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'), - disableDrawer: defaultStore.makeGetterSetter('disableDrawer'), - disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'), - loadRawImages: defaultStore.makeGetterSetter('loadRawImages'), - imageNewTab: defaultStore.makeGetterSetter('imageNewTab'), - nsfw: defaultStore.makeGetterSetter('nsfw'), - disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'), - showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'), - defaultSideView: defaultStore.makeGetterSetter('defaultSideView'), - instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), - enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), - useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'), - squareAvatars: defaultStore.makeGetterSetter('squareAvatars'), - aiChanMode: defaultStore.makeGetterSetter('aiChanMode'), - }, +const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); +const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); +const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); +const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); +const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect')); +const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline')); +const disableAnimatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v)); +const useOsNativeEmojis = computed(defaultStore.makeGetterSetter('useOsNativeEmojis')); +const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); +const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); +const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); +const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); +const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); +const disablePagesScript = computed(defaultStore.makeGetterSetter('disablePagesScript')); +const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); +const defaultSideView = computed(defaultStore.makeGetterSetter('defaultSideView')); +const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker')); +const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll')); +const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); +const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); +const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); - watch: { - lang() { - localStorage.setItem('lang', this.lang); - localStorage.removeItem('locale'); - this.reloadAsk(); - }, +watch(lang, () => { + localStorage.setItem('lang', lang.value as string); + localStorage.removeItem('locale'); +}); - fontSize() { - if (this.fontSize == null) { - localStorage.removeItem('fontSize'); - } else { - localStorage.setItem('fontSize', this.fontSize); - } - this.reloadAsk(); - }, +watch(fontSize, () => { + if (fontSize.value == null) { + localStorage.removeItem('fontSize'); + } else { + localStorage.setItem('fontSize', fontSize.value); + } +}); - useSystemFont() { - if (this.useSystemFont) { - localStorage.setItem('useSystemFont', 't'); - } else { - localStorage.removeItem('useSystemFont'); - } - this.reloadAsk(); - }, +watch(useSystemFont, () => { + if (useSystemFont.value) { + localStorage.setItem('useSystemFont', 't'); + } else { + localStorage.removeItem('useSystemFont'); + } +}); - enableInfiniteScroll() { - this.reloadAsk(); - }, +watch([ + lang, + fontSize, + useSystemFont, + enableInfiniteScroll, + squareAvatars, + aiChanMode, + showGapBetweenNotesInTimeline, + instanceTicker, + overridedDeviceKind +], async () => { + await reloadAsk(); +}); - squareAvatars() { - this.reloadAsk(); - }, - - aiChanMode() { - this.reloadAsk(); - }, - - showGapBetweenNotesInTimeline() { - this.reloadAsk(); - }, - - instanceTicker() { - this.reloadAsk(); - }, - - overridedDeviceKind() { - this.reloadAsk(); - }, - }, - - methods: { - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cogs', + bg: 'var(--bg)' } }); </script> diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index c153b4d28c..127cbcd4c1 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -37,8 +37,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormSection from '@/components/form/section.vue'; import FormGroup from '@/components/form/group.vue'; @@ -48,108 +48,80 @@ import { selectFile } from '@/scripts/select-file'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormGroup, - FormSwitch, - MkButton, - }, +const excludeMutingUsers = ref(false); +const excludeInactiveUsers = ref(false); - emits: ['info'], +const onExportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.exportRequested, + }); +}; - setup(props, context) { - const INFO = { - title: i18n.ts.importAndExport, - icon: 'fas fa-boxes', - bg: 'var(--bg)', - }; +const onImportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.importRequested, + }); +}; - const excludeMutingUsers = ref(false); - const excludeInactiveUsers = ref(false); +const onError = (ev) => { + os.alert({ + type: 'error', + text: ev.message, + }); +}; - const onExportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.exportRequested, - }); - }; +const exportNotes = () => { + os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); +}; - const onImportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.importRequested, - }); - }; +const exportFollowing = () => { + os.api('i/export-following', { + excludeMuting: excludeMutingUsers.value, + excludeInactive: excludeInactiveUsers.value, + }) + .then(onExportSuccess).catch(onError); +}; - const onError = (e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }; +const exportBlocking = () => { + os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); +}; - const exportNotes = () => { - os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); - }; +const exportUserLists = () => { + os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); +}; - const exportFollowing = () => { - os.api('i/export-following', { - excludeMuting: excludeMutingUsers.value, - excludeInactive: excludeInactiveUsers.value, - }) - .then(onExportSuccess).catch(onError); - }; +const exportMuting = () => { + os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); +}; - const exportBlocking = () => { - os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); - }; +const importFollowing = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportUserLists = () => { - os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); - }; +const importUserLists = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportMuting = () => { - os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); - }; +const importMuting = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importFollowing = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; +const importBlocking = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importUserLists = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - const importMuting = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - const importBlocking = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - return { - [symbols.PAGE_INFO]: INFO, - excludeMutingUsers, - excludeInactiveUsers, - - exportNotes, - exportFollowing, - exportBlocking, - exportUserLists, - exportMuting, - - importFollowing, - importUserLists, - importMuting, - importBlocking, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.importAndExport, + icon: 'fas fa-boxes', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 44c3be62fe..e6670ea930 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -2,19 +2,22 @@ <MkSpacer :content-max="900" :margin-min="20" :margin-max="32"> <div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div class="header"> - <div class="title">{{ $ts.settings }}</div> + <div class="title"> + <MkA v-if="narrow" to="/settings">{{ $ts.settings }}</MkA> + <template v-else>{{ $ts.settings }}</template> + </div> <div v-if="childInfo" class="subtitle">{{ childInfo.title }}</div> </div> <div class="body"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <div class="baaadecd"> <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <div class="bkzroven"> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </div> </div> </div> @@ -23,7 +26,7 @@ </template> <script setup lang="ts"> -import { computed, defineAsyncComponent, nextTick, onMounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkInfo from '@/components/ui/info.vue'; import MkSuperMenu from '@/components/ui/super-menu.vue'; @@ -33,6 +36,7 @@ import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; import { instance } from '@/instance'; import { $i } from '@/account'; +import { MisskeyNavigator } from '@/scripts/navigate'; const props = defineProps<{ initialPage?: string @@ -45,53 +49,61 @@ const indexInfo = { hideHeader: true, }; const INFO = ref(indexInfo); -const page = ref(props.initialPage); -const narrow = ref(false); -const view = ref(null); const el = ref<HTMLElement | null>(null); const childInfo = ref(null); + +const nav = new MisskeyNavigator(); + +const narrow = ref(false); +const NARROW_THRESHOLD = 600; + +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); + const menuDef = computed(() => [{ title: i18n.ts.basicSettings, items: [{ icon: 'fas fa-user', text: i18n.ts.profile, to: '/settings/profile', - active: page.value === 'profile', + active: props.initialPage === 'profile', }, { icon: 'fas fa-lock-open', text: i18n.ts.privacy, to: '/settings/privacy', - active: page.value === 'privacy', + active: props.initialPage === 'privacy', }, { icon: 'fas fa-laugh', text: i18n.ts.reaction, to: '/settings/reaction', - active: page.value === 'reaction', + active: props.initialPage === 'reaction', }, { icon: 'fas fa-cloud', text: i18n.ts.drive, to: '/settings/drive', - active: page.value === 'drive', + active: props.initialPage === 'drive', }, { icon: 'fas fa-bell', text: i18n.ts.notifications, to: '/settings/notifications', - active: page.value === 'notifications', + active: props.initialPage === 'notifications', }, { icon: 'fas fa-envelope', text: i18n.ts.email, to: '/settings/email', - active: page.value === 'email', + active: props.initialPage === 'email', }, { icon: 'fas fa-share-alt', text: i18n.ts.integration, to: '/settings/integration', - active: page.value === 'integration', + active: props.initialPage === 'integration', }, { icon: 'fas fa-lock', text: i18n.ts.security, to: '/settings/security', - active: page.value === 'security', + active: props.initialPage === 'security', }], }, { title: i18n.ts.clientSettings, @@ -99,27 +111,27 @@ const menuDef = computed(() => [{ icon: 'fas fa-cogs', text: i18n.ts.general, to: '/settings/general', - active: page.value === 'general', + active: props.initialPage === 'general', }, { icon: 'fas fa-palette', text: i18n.ts.theme, to: '/settings/theme', - active: page.value === 'theme', + active: props.initialPage === 'theme', }, { icon: 'fas fa-list-ul', text: i18n.ts.menu, to: '/settings/menu', - active: page.value === 'menu', + active: props.initialPage === 'menu', }, { icon: 'fas fa-music', text: i18n.ts.sounds, to: '/settings/sounds', - active: page.value === 'sounds', + active: props.initialPage === 'sounds', }, { icon: 'fas fa-plug', text: i18n.ts.plugins, to: '/settings/plugin', - active: page.value === 'plugin', + active: props.initialPage === 'plugin', }], }, { title: i18n.ts.otherSettings, @@ -127,37 +139,37 @@ const menuDef = computed(() => [{ icon: 'fas fa-boxes', text: i18n.ts.importAndExport, to: '/settings/import-export', - active: page.value === 'import-export', + active: props.initialPage === 'import-export', }, { icon: 'fas fa-volume-mute', text: i18n.ts.instanceMute, to: '/settings/instance-mute', - active: page.value === 'instance-mute', + active: props.initialPage === 'instance-mute', }, { icon: 'fas fa-ban', text: i18n.ts.muteAndBlock, to: '/settings/mute-block', - active: page.value === 'mute-block', + active: props.initialPage === 'mute-block', }, { icon: 'fas fa-comment-slash', text: i18n.ts.wordMute, to: '/settings/word-mute', - active: page.value === 'word-mute', + active: props.initialPage === 'word-mute', }, { icon: 'fas fa-key', text: 'API', to: '/settings/api', - active: page.value === 'api', + active: props.initialPage === 'api', }, { icon: 'fas fa-bolt', text: 'Webhook', to: '/settings/webhook', - active: page.value === 'webhook', + active: props.initialPage === 'webhook', }, { icon: 'fas fa-ellipsis-h', text: i18n.ts.other, to: '/settings/other', - active: page.value === 'other', + active: props.initialPage === 'other', }], }, { items: [{ @@ -182,8 +194,8 @@ const menuDef = computed(() => [{ const pageProps = ref({}); const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { + if (props.initialPage == null) return null; + switch (props.initialPage) { case 'accounts': return defineAsyncComponent(() => import('./accounts.vue')); case 'profile': return defineAsyncComponent(() => import('./profile.vue')); case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); @@ -230,27 +242,41 @@ watch(component, () => { watch(() => props.initialPage, () => { if (props.initialPage == null && !narrow.value) { - page.value = 'profile'; + nav.push('/settings/profile'); } else { - page.value = props.initialPage; if (props.initialPage == null) { INFO.value = indexInfo; } } }); -onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'profile'; +watch(narrow, () => { + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); } }); +onMounted(() => { + ro.observe(el.value); + + narrow.value = el.value.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); + } +}); + +onUnmounted(() => { + ro.disconnect(); +}); + const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); const pageChanged = (page) => { - if (page == null) return; - childInfo.value = page[symbols.PAGE_INFO]; + if (page == null) { + childInfo.value = null; + } else { + childInfo.value = page[symbols.PAGE_INFO]; + } }; defineExpose({ @@ -267,6 +293,7 @@ defineExpose({ font-weight: bold; > .title { + display: block; width: 34%; } diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index f84a209b60..bcc2ee85ad 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -1,67 +1,51 @@ <template> <div class="_formRoot"> - <MkInfo>{{ $ts._instanceMute.title }}</MkInfo> + <MkInfo>{{ i18n.ts._instanceMute.title }}</MkInfo> <FormTextarea v-model="instanceMutes" class="_formBlock"> - <template #label>{{ $ts._instanceMute.heading }}</template> - <template #caption>{{ $ts._instanceMute.instanceMuteDescription }}<br>{{ $ts._instanceMute.instanceMuteDescription2 }}</template> + <template #label>{{ i18n.ts._instanceMute.heading }}</template> + <template #caption>{{ i18n.ts._instanceMute.instanceMuteDescription }}<br>{{ i18n.ts._instanceMute.instanceMuteDescription2 }}</template> </FormTextarea> - <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkInfo from '@/components/ui/info.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkInfo, - }, +const instanceMutes = ref($i!.mutedInstances.join('\n')); +const changed = ref(false); - emits: ['info'], +async function save() { + let mutes = instanceMutes.value + .trim().split('\n') + .map(el => el.trim()) + .filter(el => el); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceMute, - icon: 'fas fa-volume-mute' - }, - tab: 'soft', - instanceMutes: '', - changed: false, - } - }, + await os.api('i/update', { + mutedInstances: mutes, + }); - watch: { - instanceMutes: { - handler() { - this.changed = true; - }, - deep: true - }, - }, + changed.value = false; - async created() { - this.instanceMutes = this.$i.mutedInstances.join('\n'); - }, + // Refresh filtered list to signal to the user how they've been saved + instanceMutes.value = mutes.join('\n'); +} - methods: { - async save() { - let mutes = this.instanceMutes.trim().split('\n').map(el => el.trim()).filter(el => el); - await os.api('i/update', { - mutedInstances: mutes, - }); - this.changed = false; +watch(instanceMutes, () => { + changed.value = true; +}); - // Refresh filtered list to signal to the user how they've been saved - this.instanceMutes = mutes.join('\n'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceMute, + icon: 'fas fa-volume-mute' } -}) +}); </script> diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index ca36c91665..75c6200944 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -1,133 +1,98 @@ <template> <div class="_formRoot"> - <FormSection v-if="enableTwitterIntegration"> + <FormSection v-if="instance.enableTwitterIntegration"> <template #label><i class="fab fa-twitter"></i> Twitter</template> - <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> + <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableDiscordIntegration"> + <FormSection v-if="instance.enableDiscordIntegration"> <template #label><i class="fab fa-discord"></i> Discord</template> - <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.discord">{{ i18n.ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> + <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectDiscord">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableGithubIntegration"> + <FormSection v-if="instance.enableGithubIntegration"> <template #label><i class="fab fa-github"></i> GitHub</template> - <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.github">{{ i18n.ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> + <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, onMounted, ref, watch } from 'vue'; import { apiUrl } from '@/config'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/ui/button.vue'; -import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkButton - }, +const twitterForm = ref<Window | null>(null); +const discordForm = ref<Window | null>(null); +const githubForm = ref<Window | null>(null); - emits: ['info'], +const integrations = computed(() => $i!.integrations); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - apiUrl, - twitterForm: null, - discordForm: null, - githubForm: null, - enableTwitterIntegration: false, - enableDiscordIntegration: false, - enableGithubIntegration: false, - }; - }, +function openWindow(service: string, type: string) { + return window.open(`${apiUrl}/${type}/${service}`, + `${service}_${type}_window`, + 'height=570, width=520' + ); +} - computed: { - integrations() { - return this.$i.integrations; - }, - - meta() { - return this.$instance; - }, - }, +function connectTwitter() { + twitterForm.value = openWindow('twitter', 'connect'); +} - created() { - this.enableTwitterIntegration = this.meta.enableTwitterIntegration; - this.enableDiscordIntegration = this.meta.enableDiscordIntegration; - this.enableGithubIntegration = this.meta.enableGithubIntegration; - }, +function disconnectTwitter() { + openWindow('twitter', 'disconnect'); +} - mounted() { - document.cookie = `igi=${this.$i.token}; path=/;` + - ` max-age=31536000;` + - (document.location.protocol.startsWith('https') ? ' secure' : ''); +function connectDiscord() { + discordForm.value = openWindow('discord', 'connect'); +} - this.$watch('integrations', () => { - if (this.integrations.twitter) { - if (this.twitterForm) this.twitterForm.close(); - } - if (this.integrations.discord) { - if (this.discordForm) this.discordForm.close(); - } - if (this.integrations.github) { - if (this.githubForm) this.githubForm.close(); - } - }, { - deep: true - }); - }, +function disconnectDiscord() { + openWindow('discord', 'disconnect'); +} - methods: { - connectTwitter() { - this.twitterForm = window.open(apiUrl + '/connect/twitter', - 'twitter_connect_window', - 'height=570, width=520'); - }, +function connectGithub() { + githubForm.value = openWindow('github', 'connect'); +} - disconnectTwitter() { - window.open(apiUrl + '/disconnect/twitter', - 'twitter_disconnect_window', - 'height=570, width=520'); - }, +function disconnectGithub() { + openWindow('github', 'disconnect'); +} - connectDiscord() { - this.discordForm = window.open(apiUrl + '/connect/discord', - 'discord_connect_window', - 'height=570, width=520'); - }, +onMounted(() => { + document.cookie = `igi=${$i!.token}; path=/;` + + ` max-age=31536000;` + + (document.location.protocol.startsWith('https') ? ' secure' : ''); - disconnectDiscord() { - window.open(apiUrl + '/disconnect/discord', - 'discord_disconnect_window', - 'height=570, width=520'); - }, + watch(integrations, () => { + if (integrations.value.twitter) { + if (twitterForm.value) twitterForm.value.close(); + } + if (integrations.value.discord) { + if (discordForm.value) discordForm.value.close(); + } + if (integrations.value.github) { + if (githubForm.value) githubForm.value.close(); + } + }); +}); - connectGithub() { - this.githubForm = window.open(apiUrl + '/connect/github', - 'github_connect_window', - 'height=570, width=520'); - }, - - disconnectGithub() { - window.open(apiUrl + '/disconnect/github', - 'github_disconnect_window', - 'height=570, width=520'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 6e38cd5dfe..2288c3f718 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormTextarea v-model="items" tall manual-save class="_formBlock"> - <template #label>{{ $ts.menu }}</template> - <template #caption><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template> + <template #label>{{ i18n.ts.menu }}</template> + <template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template> </FormTextarea> <FormRadios v-model="menuDisplay" class="_formBlock"> - <template #label>{{ $ts.display }}</template> - <option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option> - <option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option> - <option value="top">{{ $ts._menuDisplay.top }}</option> - <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: ใตใคใใใผใๅฎๅ จใซ้ ใใใใใซใใใจใๅฅ้ใใณใใผใฌใผใใฟใณใฎใใใชใใฎใUIใซ่กจ็คบใใๅฟ ่ฆใใใ้ขๅ --> + <template #label>{{ i18n.ts.display }}</template> + <option value="sideFull">{{ i18n.ts._menuDisplay.sideFull }}</option> + <option value="sideIcon">{{ i18n.ts._menuDisplay.sideIcon }}</option> + <option value="top">{{ i18n.ts._menuDisplay.top }}</option> + <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: ใตใคใใใผใๅฎๅ จใซ้ ใใใใใซใใใจใๅฅ้ใใณใใผใฌใผใใฟใณใฎใใใชใใฎใUIใซ่กจ็คบใใๅฟ ่ฆใใใ้ขๅ --> </FormRadios> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; import FormButton from '@/components/ui/button.vue'; @@ -27,81 +27,60 @@ import { menuDef } from '@/menu'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormRadios, - }, +const items = ref(defaultStore.state.menu.join('\n')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.menu, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - }, - menuDef: menuDef, - items: defaultStore.state.menu.join('\n'), - } - }, +const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== '')); +const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); - computed: { - splited(): string[] { - return this.items.trim().split('\n').filter(x => x.trim() !== ''); - }, +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting + }); + if (canceled) return; - menuDisplay: defaultStore.makeGetterSetter('menuDisplay') - }, + unisonReload(); +} - watch: { - menuDisplay() { - this.reloadAsk(); - }, +async function addItem() { + const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k)); + const { canceled, result: item } = await os.select({ + title: i18n.ts.addItem, + items: [...menu.map(k => ({ + value: k, text: i18n.ts[menuDef[k].title] + })), { + value: '-', text: i18n.ts.divider + }] + }); + if (canceled) return; + items.value = [...split.value, item].join('\n'); +} - items() { - this.save(); - }, - }, +async function save() { + defaultStore.set('menu', split.value); + await reloadAsk(); +} - methods: { - async addItem() { - const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); - const { canceled, result: item } = await os.select({ - title: this.$ts.addItem, - items: [...menu.map(k => ({ - value: k, text: this.$ts[this.menuDef[k].title] - })), ...[{ - value: '-', text: this.$ts.divider - }]] - }); - if (canceled) return; - this.items = [...this.splited, item].join('\n'); - }, +function reset() { + defaultStore.reset('menu'); + items.value = defaultStore.state.menu.join('\n'); +} - save() { - this.$store.set('menu', this.splited); - this.reloadAsk(); - }, +watch(items, async () => { + await save(); +}); - reset() { - this.$store.reset('menu'); - this.items = this.$store.state.menu.join('\n'); - }, +watch(menuDisplay, async () => { + await reloadAsk(); +}); - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - showCancelButton: true - }); - if (canceled) return; - - unisonReload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.menu, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index 12171530bb..b8fff95a8d 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -1,71 +1,59 @@ <template> <div class="_formRoot"> - <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ $ts.notificationSetting }}</FormLink> + <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ i18n.ts.notificationSetting }}</FormLink> <FormSection> - <FormLink class="_formBlock" @click="readAllNotifications">{{ $ts.markAsReadAllNotifications }}</FormLink> - <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ $ts.markAsReadAllUnreadNotes }}</FormLink> - <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ $ts.markAsReadAllTalkMessages }}</FormLink> + <FormLink class="_formBlock" @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> + <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink> + <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import { notificationTypes } from 'misskey-js'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormSection, - }, +async function readAllUnreadNotes() { + await os.api('i/read-all-unread-notes'); +} - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - }, +async function readAllMessagingMessages() { + await os.api('i/read-all-messaging-messages'); +} + +async function readAllNotifications() { + await os.api('notifications/mark-all-as-read'); +} + +function configure() { + const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x)); + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { + includingTypes, + showGlobalToggle: false, + }, { + done: async (res) => { + const { includingTypes: value } = res; + await os.apiWithDialog('i/update', { + mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), + }).then(i => { + $i!.mutingNotificationTypes = i.mutingNotificationTypes; + }); } - }, + }, 'closed'); +} - methods: { - readAllUnreadNotes() { - os.api('i/read-all-unread-notes'); - }, - - readAllMessagingMessages() { - os.api('i/read-all-messaging-messages'); - }, - - readAllNotifications() { - os.api('notifications/mark-all-as-read'); - }, - - configure() { - const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes, - showGlobalToggle: false, - }, { - done: async (res) => { - const { includingTypes: value } = res; - await os.apiWithDialog('i/update', { - mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), - }).then(i => { - this.$i.mutingNotificationTypes = i.mutingNotificationTypes; - }); - } - }, 'closed'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index a9903acc7e..82e174a5b4 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -1,66 +1,44 @@ <template> <div class="_formRoot"> - <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> - {{ $ts.showFeaturedNotesInTimeline }} + <FormSwitch v-model="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> + {{ i18n.ts.showFeaturedNotesInTimeline }} </FormSwitch> <!-- - <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError" class="_formBlock">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></FormSwitch> --> - <FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink> + <FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink> - <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> + <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; -import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import * as os from '@/os'; -import { debug } from '@/config'; import { defaultStore } from '@/store'; -import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormLink, - }, +const reportError = computed(defaultStore.makeGetterSetter('reportError')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-ellipsis-h', - bg: 'var(--bg)', - }, - debug, - } - }, +function onChangeInjectFeaturedNote(v) { + os.api('i/update', { + injectFeaturedNote: v + }).then((i) => { + $i!.injectFeaturedNote = i.injectFeaturedNote; + }); +} - computed: { - reportError: defaultStore.makeGetterSetter('reportError'), - }, - - methods: { - changeDebug(v) { - console.log(v); - localStorage.setItem('debug', v.toString()); - unisonReload(); - }, - - onChangeInjectFeaturedNote(v) { - os.api('i/update', { - injectFeaturedNote: v - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-ellipsis-h', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index d35d20d17a..96c0abfd99 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -1,19 +1,19 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts._plugin.installWarn }}</FormInfo> <FormTextarea v-model="code" tall class="_formBlock"> - <template #label>{{ $ts.code }}</template> + <template #label>{{ i18n.ts.code }}</template> </FormTextarea> <div class="_formBlock"> - <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ i18n.ts.install }}</FormButton> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, defineAsyncComponent, nextTick, ref } from 'vue'; import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; @@ -23,111 +23,101 @@ import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormTextarea, - FormButton, - FormInfo, - }, +const code = ref(null); - emits: ['info'], +function installPlugin({ id, meta, ast, token }) { + ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ + ...meta, + id, + active: true, + configData: {}, + token: token, + ast: ast + })); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._plugin.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - code: null, - } - }, +async function install() { + let ast; + try { + ast = parse(code.value); + } catch (err) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; + } - methods: { - installPlugin({ id, meta, ast, token }) { - ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ - ...meta, - id, - active: true, - configData: {}, - token: token, - ast: ast - })); + const meta = AiScript.collectMetadata(ast); + if (meta == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } + + const metadata = meta.get(null); + if (metadata == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } + + const { name, version, author, description, permissions, config } = metadata; + if (name == null || version == null || author == null) { + os.alert({ + type: 'error', + text: 'Required property not found :(' + }); + return; + } + + const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), { + title: i18n.ts.tokenRequested, + information: i18n.ts.pluginTokenRequestedDescription, + initialName: name, + initialPermissions: permissions + }, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, + }); + res(token); + } + }, 'closed'); + }); + + installPlugin({ + id: uuid(), + meta: { + name, version, author, description, permissions, config }, + token, + ast: serialize(ast) + }); - async install() { - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - const meta = AiScript.collectMetadata(ast); - if (meta == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const data = meta.get(null); - if (data == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const { name, version, author, description, permissions, config } = data; - if (name == null || version == null || author == null) { - os.alert({ - type: 'error', - text: 'Required property not found :(' - }); - return; - } + os.success(); - const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { - os.popup(import('@/components/token-generate-window.vue'), { - title: this.$ts.tokenRequested, - information: this.$ts.pluginTokenRequestedDescription, - initialName: name, - initialPermissions: permissions - }, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); + nextTick(() => { + unisonReload(); + }); +} - res(token); - } - }, 'closed'); - }); - - this.installPlugin({ - id: uuid(), - meta: { - name, version, author, description, permissions, config - }, - token, - ast: serialize(ast) - }); - - os.success(); - - this.$nextTick(() => { - unisonReload(); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._plugin.install, + icon: 'fas fa-download', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 7a3ab9d152..873a022cbc 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,38 +1,38 @@ <template> <div class="_formRoot"> - <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink> + <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> <FormSection> - <template #label>{{ $ts.manage }}</template> + <template #label>{{ i18n.ts.manage }}</template> <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;"> <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> - <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</FormSwitch> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.author }}</template> + <template #key>{{ i18n.ts.author }}</template> <template #value>{{ plugin.author }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.description }}</template> + <template #key>{{ i18n.ts.description }}</template> <template #value>{{ plugin.description }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.permission }}</template> + <template #key>{{ i18n.ts.permission }}</template> <template #value>{{ plugin.permission }}</template> </MkKeyValue> <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ i18n.ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</MkButton> </div> </div> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, nextTick, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -41,67 +41,54 @@ import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import * as symbols from '@/symbols'; +import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkButton, - MkKeyValue, - }, +const plugins = ref(ColdDeviceStorage.get('plugins')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.plugins, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - plugins: ColdDeviceStorage.get('plugins'), - } - }, +function uninstall(plugin) { + ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); + os.success(); + nextTick(() => { + unisonReload(); + }); +} - methods: { - uninstall(plugin) { - ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); - os.success(); - this.$nextTick(() => { - unisonReload(); - }); - }, +// TODO: ใใฎๅฆ็ใstoreๅดใซactionใจใใฆ็งปๅใใ่จญๅฎ็ป้ขใ้ใAiScriptAPIใๅฎ่ฃ ใงใใใใใซใใ +async function config(plugin) { + const config = plugin.config; + for (const key in plugin.configData) { + config[key].default = plugin.configData[key]; + } - // TODO: ใใฎๅฆ็ใstoreๅดใซactionใจใใฆ็งปๅใใ่จญๅฎ็ป้ขใ้ใAiScriptAPIใๅฎ่ฃ ใงใใใใใซใใ - async config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } + const { canceled, result } = await os.form(plugin.name, config); + if (canceled) return; - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.configData = result; + ColdDeviceStorage.set('plugins', coldPlugins); - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).configData = result; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - }, +function changeActive(plugin, active) { + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.active = active; + ColdDeviceStorage.set('plugins', coldPlugins); - changeActive(plugin, active) { - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).active = active; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.plugins, + icon: 'fas fa-plug', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index e991d725b6..b64dc93cc7 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -62,7 +62,7 @@ </template> <script lang="ts" setup> -import { defineComponent, reactive, watch } from 'vue'; +import { reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -132,8 +132,21 @@ function save() { function changeAvatar(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 1, + }); + } + const i = await os.apiWithDialog('i/update', { - avatarId: file.id, + avatarId: originalOrCropped.id, }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; @@ -142,8 +155,21 @@ function changeAvatar(ev) { function changeBanner(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 2, + }); + } + const i = await os.apiWithDialog('i/update', { - bannerId: file.id, + bannerId: originalOrCropped.id, }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index a188ba353d..963ac81dfa 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -54,7 +54,7 @@ </template> <script lang="ts" setup> -import { watch } from 'vue'; +import { defineAsyncComponent, watch } from 'vue'; import XDraggable from 'vuedraggable'; import FormInput from '@/components/form/input.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -88,7 +88,7 @@ function remove(reaction, ev: MouseEvent) { } function preview(ev: MouseEvent) { - os.popup(import('@/components/emoji-picker-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { asReactionPicker: true, src: ev.currentTarget ?? ev.target, }, {}, 'closed'); diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 6fb3f1c413..401648790a 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -1,17 +1,17 @@ <template> <div class="_formRoot"> <FormSection> - <template #label>{{ $ts.password }}</template> - <FormButton primary @click="change()">{{ $ts.changePassword }}</FormButton> + <template #label>{{ i18n.ts.password }}</template> + <FormButton primary @click="change()">{{ i18n.ts.changePassword }}</FormButton> </FormSection> <FormSection> - <template #label>{{ $ts.twoStepAuthentication }}</template> + <template #label>{{ i18n.ts.twoStepAuthentication }}</template> <X2fa/> </FormSection> <FormSection> - <template #label>{{ $ts.signinHistory }}</template> + <template #label>{{ i18n.ts.signinHistory }}</template> <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> @@ -30,15 +30,15 @@ <FormSection> <FormSlot> - <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ $ts.regenerateLoginToken }}</FormButton> - <template #caption>{{ $ts.regenerateLoginTokenDescription }}</template> + <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ i18n.ts.regenerateLoginToken }}</FormButton> + <template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template> </FormSlot> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; @@ -46,77 +46,63 @@ import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormButton, - MkPagination, - FormSlot, - X2fa, - }, +const pagination = { + endpoint: 'i/signin-history' as const, + limit: 5, +}; + +async function change() { + const { canceled: canceled1, result: currentPassword } = await os.inputText({ + title: i18n.ts.currentPassword, + type: 'password' + }); + if (canceled1) return; + + const { canceled: canceled2, result: newPassword } = await os.inputText({ + title: i18n.ts.newPassword, + type: 'password' + }); + if (canceled2) return; + + const { canceled: canceled3, result: newPassword2 } = await os.inputText({ + title: i18n.ts.newPasswordRetype, + type: 'password' + }); + if (canceled3) return; + + if (newPassword !== newPassword2) { + os.alert({ + type: 'error', + text: i18n.ts.retypedNotMatch + }); + return; + } - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/signin-history' as const, - limit: 5, - }, - } - }, + os.apiWithDialog('i/change-password', { + currentPassword, + newPassword + }); +} - methods: { - async change() { - const { canceled: canceled1, result: currentPassword } = await os.inputText({ - title: this.$ts.currentPassword, - type: 'password' - }); - if (canceled1) return; +function regenerateToken() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/regenerate_token', { + password: password + }); + }); +} - const { canceled: canceled2, result: newPassword } = await os.inputText({ - title: this.$ts.newPassword, - type: 'password' - }); - if (canceled2) return; - - const { canceled: canceled3, result: newPassword2 } = await os.inputText({ - title: this.$ts.newPasswordRetype, - type: 'password' - }); - if (canceled3) return; - - if (newPassword !== newPassword2) { - os.alert({ - type: 'error', - text: this.$ts.retypedNotMatch - }); - return; - } - - os.apiWithDialog('i/change-password', { - currentPassword, - newPassword - }); - }, - - regenerateToken() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/regenerate_token', { - password: password - }); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 490a1b5514..d01e87c1f8 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`" class="_formBlock"> - <template #label>{{ $ts.masterVolume }}</template> + <template #label>{{ i18n.ts.masterVolume }}</template> </FormRange> <FormSection> - <template #label>{{ $ts.sounds }}</template> + <template #label>{{ i18n.ts.sounds }}</template> <FormLink v-for="type in Object.keys(sounds)" :key="type" style="margin-bottom: 8px;" @click="edit(type)"> {{ $t('_sfx.' + type) }} - <template #suffix>{{ sounds[type].type || $ts.none }}</template> + <template #suffix>{{ sounds[type].type || i18n.ts.none }}</template> <template #suffixIcon><i class="fas fa-chevron-down"></i></template> </FormLink> </FormSection> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; import FormRange from '@/components/form/range.vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; @@ -27,6 +27,28 @@ import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { playFile } from '@/scripts/sound'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +const masterVolume = computed({ + get: () => { + return ColdDeviceStorage.get('sound_masterVolume'); + }, + set: (value) => { + ColdDeviceStorage.set('sound_masterVolume', value); + } +}); + +const volumeIcon = computed(() => masterVolume.value === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'); + +const sounds = ref({ + note: ColdDeviceStorage.get('sound_note'), + noteMy: ColdDeviceStorage.get('sound_noteMy'), + notification: ColdDeviceStorage.get('sound_notification'), + chat: ColdDeviceStorage.get('sound_chat'), + chatBg: ColdDeviceStorage.get('sound_chatBg'), + antenna: ColdDeviceStorage.get('sound_antenna'), + channel: ColdDeviceStorage.get('sound_channel'), +}); const soundsTypes = [ null, @@ -55,94 +77,58 @@ const soundsTypes = [ 'noizenecio/kick_gaba2', ]; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormRange, - FormSection, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.sounds, - icon: 'fas fa-music', - bg: 'var(--bg)', - }, - sounds: {}, - } - }, - - computed: { - masterVolume: { // TODO: (ๅค้จ)้ขๆฐใซcomputedใไฝฟใใฎใฏใขใฌใชใฎใง็ดใ - get() { return ColdDeviceStorage.get('sound_masterVolume'); }, - set(value) { ColdDeviceStorage.set('sound_masterVolume', value); } +async function edit(type) { + const { canceled, result } = await os.form(i18n.t('_sfx.' + type), { + type: { + type: 'enum', + enum: soundsTypes.map(x => ({ + value: x, + label: x == null ? i18n.ts.none : x, + })), + label: i18n.ts.sound, + default: sounds.value[type].type, }, - volumeIcon() { - return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'; - } - }, - - created() { - this.sounds.note = ColdDeviceStorage.get('sound_note'); - this.sounds.noteMy = ColdDeviceStorage.get('sound_noteMy'); - this.sounds.notification = ColdDeviceStorage.get('sound_notification'); - this.sounds.chat = ColdDeviceStorage.get('sound_chat'); - this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); - this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); - this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - }, - - methods: { - async edit(type) { - const { canceled, result } = await os.form(this.$t('_sfx.' + type), { - type: { - type: 'enum', - enum: soundsTypes.map(x => ({ - value: x, - label: x == null ? this.$ts.none : x, - })), - label: this.$ts.sound, - default: this.sounds[type].type, - }, - volume: { - type: 'range', - mim: 0, - max: 1, - step: 0.05, - textConverter: (v) => `${Math.floor(v * 100)}%`, - label: this.$ts.volume, - default: this.sounds[type].volume - }, - listen: { - type: 'button', - content: this.$ts.listen, - action: (_, values) => { - playFile(values.type, values.volume); - } - } - }); - if (canceled) return; - - const v = { - type: result.type, - volume: result.volume, - }; - - ColdDeviceStorage.set('sound_' + type, v); - this.sounds[type] = v; + volume: { + type: 'range', + mim: 0, + max: 1, + step: 0.05, + textConverter: (v) => `${Math.floor(v * 100)}%`, + label: i18n.ts.volume, + default: sounds.value[type].volume }, - - reset() { - for (const sound of Object.keys(this.sounds)) { - const v = ColdDeviceStorage.default['sound_' + sound]; - ColdDeviceStorage.set('sound_' + sound, v); - this.sounds[sound] = v; + listen: { + type: 'button', + content: i18n.ts.listen, + action: (_, values) => { + playFile(values.type, values.volume); } } + }); + if (canceled) return; + + const v = { + type: result.type, + volume: result.volume, + }; + + ColdDeviceStorage.set('sound_' + type, v); + sounds.value[type] = v; +} + +function reset() { + for (const sound of Object.keys(sounds.value)) { + const v = ColdDeviceStorage.default['sound_' + sound]; + ColdDeviceStorage.set('sound_' + sound, v); + sounds.value[sound] = v; + } +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.sounds, + icon: 'fas fa-music', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index 2d3514342e..25fa6c012b 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -13,7 +13,7 @@ <script lang="ts" setup> import { } from 'vue'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormButton from '@/components/ui/button.vue'; import { applyTheme, validateTheme } from '@/scripts/theme'; @@ -29,7 +29,7 @@ function parseThemeCode(code: string) { try { theme = JSON5.parse(code); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: i18n.ts._theme.invalid diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index a1e849b540..94b2d24455 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -1,95 +1,77 @@ <template> <div class="_formRoot"> <FormSelect v-model="selectedThemeId" class="_formBlock"> - <template #label>{{ $ts.theme }}</template> - <optgroup :label="$ts._theme.installedThemes"> + <template #label>{{ i18n.ts.theme }}</template> + <optgroup :label="i18n.ts._theme.installedThemes"> <option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> - <optgroup :label="$ts._theme.builtinThemes"> + <optgroup :label="i18n.ts._theme.builtinThemes"> <option v-for="x in builtinThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> </FormSelect> <template v-if="selectedTheme"> - <FormInput readonly :modelValue="selectedTheme.author" class="_formBlock"> - <template #label>{{ $ts.author }}</template> + <FormInput readonly :model-value="selectedTheme.author" class="_formBlock"> + <template #label>{{ i18n.ts.author }}</template> </FormInput> - <FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock"> - <template #label>{{ $ts._theme.description }}</template> + <FormTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc" class="_formBlock"> + <template #label>{{ i18n.ts._theme.description }}</template> </FormTextarea> - <FormTextarea readonly tall :modelValue="selectedThemeCode" class="_formBlock"> - <template #label>{{ $ts._theme.code }}</template> - <template #caption><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template> + <FormTextarea readonly tall :model-value="selectedThemeCode" class="_formBlock"> + <template #label>{{ i18n.ts._theme.code }}</template> + <template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template> </FormTextarea> - <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton> + <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</FormButton> </template> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormSelect from '@/components/form/select.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; -import { Theme, builtinThemes } from '@/scripts/theme'; +import { Theme, getBuiltinThemesRef } from '@/scripts/theme'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { getThemes, removeTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormSelect, - FormInput, - FormButton, - }, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const selectedThemeId = ref(null); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.manage, - icon: 'fas fa-folder-open', - bg: 'var(--bg)', - }, - installedThemes: getThemes(), - builtinThemes, - selectedThemeId: null, - } - }, +const themes = computed(() => [ ...installedThemes.value, ...builtinThemes.value ]); - computed: { - themes(): Theme[] { - return this.builtinThemes.concat(this.installedThemes); - }, - - selectedTheme() { - if (this.selectedThemeId == null) return null; - return this.themes.find(x => x.id === this.selectedThemeId); - }, +const selectedTheme = computed(() => { + if (selectedThemeId.value == null) return null; + return themes.value.find(x => x.id === selectedThemeId.value); +}); - selectedThemeCode() { - if (this.selectedTheme == null) return null; - return JSON5.stringify(this.selectedTheme, null, '\t'); - }, - }, +const selectedThemeCode = computed(() => { + if (selectedTheme.value == null) return null; + return JSON5.stringify(selectedTheme.value, null, '\t'); +}); - methods: { - copyThemeCode() { - copyToClipboard(this.selectedThemeCode); - os.success(); - }, +function copyThemeCode() { + copyToClipboard(selectedThemeCode.value); + os.success(); +} - uninstall() { - removeTheme(this.selectedTheme); - this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId); - this.selectedThemeId = null; - os.success(); - }, +function uninstall() { + removeTheme(selectedTheme.value as Theme); + installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value); + selectedThemeId.value = null; + os.success(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._theme.manage, + icon: 'fas fa-folder-open', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index d134a092b6..5e7ffcff4b 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -85,116 +85,94 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, onActivated, ref, watch } from 'vue'; +import JSON5 from 'json5'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; -import FormGroup from '@/components/form/group.vue'; import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; -import { builtinThemes } from '@/scripts/theme'; +import { getBuiltinThemesRef } from '@/scripts/theme'; import { selectFile } from '@/scripts/select-file'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { ColdDeviceStorage } from '@/store'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { instance } from '@/instance'; -import { concat, uniqueBy } from '@/scripts/array'; +import { uniqueBy } from '@/scripts/array'; import { fetchThemes, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormSwitch, - FormSelect, - FormGroup, - FormSection, - FormLink, - FormButton, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const instanceThemes = []; + +if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); +if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); + +const themes = computed(() => uniqueBy([ ...instanceThemes, ...builtinThemes.value, ...installedThemes.value ], theme => theme.id)); +const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); +const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); +const darkTheme = ColdDeviceStorage.ref('darkTheme'); +const darkThemeId = computed({ + get() { + return darkTheme.value.id; }, + set(id) { + ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)); + } +}); +const lightTheme = ColdDeviceStorage.ref('lightTheme'); +const lightThemeId = computed({ + get() { + return lightTheme.value.id; + }, + set(id) { + ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)); + } +}); +const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); +const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); +const wallpaper = ref(localStorage.getItem('wallpaper')); +const themesCount = installedThemes.value.length; - emits: ['info'], +watch(syncDeviceDarkMode, () => { + if (syncDeviceDarkMode.value) { + defaultStore.set('darkMode', isDeviceDarkmode()); + } +}); - setup(props, { emit }) { - const INFO = { - title: i18n.ts.theme, - icon: 'fas fa-palette', - bg: 'var(--bg)', - }; +watch(wallpaper, () => { + if (wallpaper.value == null) { + localStorage.removeItem('wallpaper'); + } else { + localStorage.setItem('wallpaper', wallpaper.value); + } + location.reload(); +}); - const installedThemes = ref(getThemes()); - const instanceThemes = []; - if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); - const themes = computed(() => uniqueBy(instanceThemes.concat(builtinThemes.concat(installedThemes.value)), theme => theme.id)); - const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); - const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); - const darkTheme = ColdDeviceStorage.ref('darkTheme'); - const darkThemeId = computed({ - get() { - return darkTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)) - } - }); - const lightTheme = ColdDeviceStorage.ref('lightTheme'); - const lightThemeId = computed({ - get() { - return lightTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)) - } - }); - const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); - const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); - const wallpaper = ref(localStorage.getItem('wallpaper')); - const themesCount = installedThemes.value.length; +onActivated(() => { + fetchThemes().then(() => { + installedThemes.value = getThemes(); + }); +}); - watch(syncDeviceDarkMode, () => { - if (syncDeviceDarkMode.value) { - defaultStore.set('darkMode', isDeviceDarkmode()); - } - }); +fetchThemes().then(() => { + installedThemes.value = getThemes(); +}); - watch(wallpaper, () => { - if (wallpaper.value == null) { - localStorage.removeItem('wallpaper'); - } else { - localStorage.setItem('wallpaper', wallpaper.value); - } - location.reload(); - }); +function setWallpaper(event) { + selectFile(event.currentTarget ?? event.target, null).then(file => { + wallpaper.value = file.url; + }); +} - onActivated(() => { - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); - }); - - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); - - return { - [symbols.PAGE_INFO]: INFO, - darkThemes, - lightThemes, - darkThemeId, - lightThemeId, - darkMode, - syncDeviceDarkMode, - themesCount, - wallpaper, - setWallpaper(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { - wallpaper.value = file.url; - }); - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.theme, + icon: 'fas fa-palette', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue index bb3a25407e..3690526b41 100644 --- a/packages/client/src/pages/settings/webhook.edit.vue +++ b/packages/client/src/pages/settings/webhook.edit.vue @@ -43,6 +43,14 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Edit webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); + const webhook = await os.api('i/webhooks/show', { webhookId: new URLSearchParams(window.location.search).get('id') }); @@ -78,12 +86,4 @@ async function save(): Promise<void> { active, }); } - -defineExpose({ - [symbols.PAGE_INFO]: { - title: 'Edit webhook', - icon: 'fas fa-bolt', - bg: 'var(--bg)', - }, -}); </script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index c11707b6cf..6e1a4b2ccb 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -1,35 +1,35 @@ <template> <div class="_formRoot"> <MkTab v-model="tab" class="_formBlock"> - <option value="soft">{{ $ts._wordMute.soft }}</option> - <option value="hard">{{ $ts._wordMute.hard }}</option> + <option value="soft">{{ i18n.ts._wordMute.soft }}</option> + <option value="hard">{{ i18n.ts._wordMute.hard }}</option> </MkTab> <div class="_formBlock"> <div v-show="tab === 'soft'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.softDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.softDescription }}</MkInfo> <FormTextarea v-model="softMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> </div> <div v-show="tab === 'hard'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo> <FormTextarea v-model="hardMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> <MkKeyValue v-if="hardWordMutedNotesCount != null" class="_formBlock"> - <template #key>{{ $ts._wordMute.mutedNotes }}</template> + <template #key>{{ i18n.ts._wordMute.mutedNotes }}</template> <template #value>{{ number(hardWordMutedNotesCount) }}</template> </MkKeyValue> </div> </div> - <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkButton from '@/components/ui/button.vue'; @@ -38,114 +38,90 @@ import MkTab from '@/components/tab.vue'; import * as os from '@/os'; import number from '@/filters/number'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkKeyValue, - MkTab, - MkInfo, - }, +const render = (mutedWords) => mutedWords.map(x => { + if (Array.isArray(x)) { + return x.join(' '); + } else { + return x; + } +}).join('\n'); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.wordMute, - icon: 'fas fa-comment-slash', - bg: 'var(--bg)', - }, - tab: 'soft', - softMutedWords: '', - hardMutedWords: '', - hardWordMutedNotesCount: null, - changed: false, - } - }, +const tab = ref('soft'); +const softMutedWords = ref(render(defaultStore.state.mutedWords)); +const hardMutedWords = ref(render($i!.mutedWords)); +const hardWordMutedNotesCount = ref(null); +const changed = ref(false); - watch: { - softMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - hardMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - }, +os.api('i/get-word-muted-notes-count', {}).then(response => { + hardWordMutedNotesCount.value = response?.count; +}); - async created() { - const render = (mutedWords) => mutedWords.map(x => { - if (Array.isArray(x)) { - return x.join(' '); - } else { - return x; - } - }).join('\n'); +watch(softMutedWords, () => { + changed.value = true; +}); - this.softMutedWords = render(this.$store.state.mutedWords); - this.hardMutedWords = render(this.$i.mutedWords); +watch(hardMutedWords, () => { + changed.value = true; +}); - this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; - }, +async function save() { + const parseMutes = (mutes, tab) => { + // split into lines, remove empty lines and unnecessary whitespace + let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== ''); - methods: { - async save() { - const parseMutes = (mutes, tab) => { - // split into lines, remove empty lines and unnecessary whitespace - let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line != ''); - - // check each line if it is a RegExp or not - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - const regexp = line.match(/^\/(.+)\/(.*)$/); - if (regexp) { - // check that the RegExp is valid - try { - new RegExp(regexp[1], regexp[2]); - // note that regex lines will not be split by spaces! - } catch (err) { - // invalid syntax: do not save, do not reset changed flag - os.alert({ - type: 'error', - title: this.$ts.regexpError, - text: this.$t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() - }); - // re-throw error so these invalid settings are not saved - throw err; - } - } else { - lines[i] = line.split(' '); - } + // check each line if it is a RegExp or not + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const regexp = line.match(/^\/(.+)\/(.*)$/); + if (regexp) { + // check that the RegExp is valid + try { + new RegExp(regexp[1], regexp[2]); + // note that regex lines will not be split by spaces! + } catch (err: any) { + // invalid syntax: do not save, do not reset changed flag + os.alert({ + type: 'error', + title: i18n.ts.regexpError, + text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() + }); + // re-throw error so these invalid settings are not saved + throw err; } - - return lines; - }; - - let softMutes, hardMutes; - try { - softMutes = parseMutes(this.softMutedWords, this.$ts._wordMute.soft); - hardMutes = parseMutes(this.hardMutedWords, this.$ts._wordMute.hard); - } catch (err) { - // already displayed error message in parseMutes - return; + } else { + lines[i] = line.split(' '); } + } - this.$store.set('mutedWords', softMutes); - await os.api('i/update', { - mutedWords: hardMutes, - }); + return lines; + }; - this.changed = false; - }, + let softMutes, hardMutes; + try { + softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft); + hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard); + } catch (err) { + // already displayed error message in parseMutes + return; + } - number + defaultStore.set('mutedWords', softMutes); + await os.api('i/update', { + mutedWords: hardMutes, + }); + + changed.value = false; +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.wordMute, + icon: 'fas fa-comment-slash', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index 4d77de5819..1700944f82 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -56,7 +56,7 @@ export default defineComponent({ localOnly: null as boolean | null, files: [] as Misskey.entities.DriveFile[], visibleUsers: [] as Misskey.entities.User[], - } + }; }, async created() { @@ -153,11 +153,11 @@ export default defineComponent({ ); } //#endregion - } catch (e) { + } catch (err) { os.alert({ type: 'error', - title: e.message, - text: e.name + title: err.message, + text: err.name }); } diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index a53e23c1c5..2a11c07fd2 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -67,15 +67,17 @@ <script lang="ts" setup> import { watch } from 'vue'; import { toUnicode } from 'punycode/'; -import * as tinycolor from 'tinycolor2'; -import { v4 as uuid} from 'uuid'; -import * as JSON5 from 'json5'; +import tinycolor from 'tinycolor2'; +import { v4 as uuid } from 'uuid'; +import JSON5 from 'json5'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormFolder from '@/components/form/folder.vue'; -import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; +import { Theme, applyTheme } from '@/scripts/theme'; +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; import { host } from '@/config'; import * as os from '@/os'; import { ColdDeviceStorage, defaultStore } from '@/store'; @@ -124,11 +126,11 @@ let changed = $ref(false); useLeaveGuard($$(changed)); function showPreview() { - os.pageWindow('preview'); + os.pageWindow('/preview'); } function setBgColor(color: typeof bgColors[number]) { - if (theme.base != color.kind) { + if (theme.base !== color.kind) { const base = color.kind === 'dark' ? darkTheme : lightTheme; for (const prop of Object.keys(base.props)) { if (prop === 'accent') continue; diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 79f00c4b44..fe3dbc3cff 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -20,7 +20,7 @@ <script lang="ts"> export default { name: 'MkTimelinePage', -} +}; </script> <script lang="ts" setup> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 516ab4d440..54e1f13021 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -54,6 +54,9 @@ <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> </FormSection> + <MkObjectView v-if="info && $i.isAdmin" tall :value="info"> + </MkObjectView> + <MkObjectView tall :value="user"> </MkObjectView> </div> @@ -232,10 +235,10 @@ export default defineComponent({ await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id }); os.success(); }; - await process().catch(e => { + await process().catch(err => { os.alert({ type: 'error', - text: e.toString() + text: err.toString(), }); }); await this.refreshUser(); diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 10a86243f9..a024dd28bc 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -125,7 +125,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent, computed } from 'vue'; -import * as age from 's-age'; +import age from 's-age'; import XUserTimeline from './index.timeline.vue'; import XNote from '@/components/note.vue'; import MkFollowButton from '@/components/follow-button.vue'; @@ -141,6 +141,7 @@ import number from '@/filters/number'; import { userPage, acct as getAcct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { MisskeyNavigator } from '@/scripts/navigate'; export default defineComponent({ components: { @@ -190,33 +191,34 @@ export default defineComponent({ active: this.page === 'index', title: this.$ts.overview, icon: 'fas fa-home', - onClick: () => { this.$router.push('/@' + getAcct(this.user)); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user)); }, }, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{ active: this.page === 'reactions', title: this.$ts.reaction, icon: 'fas fa-laugh', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/reactions'); }, }] : [], { active: this.page === 'clips', title: this.$ts.clips, icon: 'fas fa-paperclip', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/clips'); }, }, { active: this.page === 'pages', title: this.$ts.pages, icon: 'fas fa-file-alt', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/pages'); }, }, { active: this.page === 'gallery', title: this.$ts.gallery, icon: 'fas fa-icons', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/gallery'); }, }], } : null), user: null, error: null, parallaxAnimationId: null, narrow: null, + mkNav: new MisskeyNavigator(), }; }, @@ -258,8 +260,8 @@ export default defineComponent({ this.user = null; os.api('users/show', Acct.parse(this.acct)).then(user => { this.user = user; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/welcome.setup.vue b/packages/client/src/pages/welcome.setup.vue index ec23b76e29..1a2f460283 100644 --- a/packages/client/src/pages/welcome.setup.vue +++ b/packages/client/src/pages/welcome.setup.vue @@ -41,7 +41,7 @@ export default defineComponent({ password: '', submitting: false, host, - } + }; }, methods: { diff --git a/packages/client/src/pages/welcome.timeline.vue b/packages/client/src/pages/welcome.timeline.vue index 38a85f67b1..bec9481ffd 100644 --- a/packages/client/src/pages/welcome.timeline.vue +++ b/packages/client/src/pages/welcome.timeline.vue @@ -39,7 +39,7 @@ export default defineComponent({ return { notes: [], isScrolling: false, - } + }; }, created() { diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 839841f0fe..db39dd741c 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -1,4 +1,4 @@ -import { defineAsyncComponent, markRaw } from 'vue'; +import { AsyncComponentLoader, defineAsyncComponent, markRaw } from 'vue'; import { createRouter, createWebHistory } from 'vue-router'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; @@ -6,8 +6,9 @@ import MkTimeline from '@/pages/timeline.vue'; import { $i, iAmModerator } from './account'; import { ui } from '@/config'; -const page = (path: string, ui?: string) => defineAsyncComponent({ - loader: ui ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`), +// pathใซ/ใๅ ฅใใจrollupใ่งฃๆฑบใใฆใใใชใใฎใงใ() => import('*.vue')ใๆๅฎใใใใจ +const page = (path: string | AsyncComponentLoader<any>, uiName?: string) => defineAsyncComponent({ + loader: typeof path === 'string' ? uiName ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`) : path, loadingComponent: MkLoading, errorComponent: MkError, }); @@ -17,10 +18,10 @@ let indexScrollPos = 0; const defaultRoutes = [ // NOTE: MkTimelineใdynamic importใใใจAsyncComponentWrapperใ้ใซๅ ฅใใใใงkeep-aliveใฎใณใณใใผใใณใๆๅฎใๅนใใชใใชใ { path: '/', name: 'index', component: $i ? MkTimeline : page('welcome') }, - { path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, + { path: '/@:acct/:page?', name: 'user', component: page(() => import('./pages/user/index.vue')), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, { path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, - { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, - { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, + { path: '/@:user/pages/:pageName/view-source', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, + { path: '/settings/:page(.*)?', name: 'settings', component: page(() => import('./pages/settings/index.vue')), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, { path: '/announcements', component: page('announcements') }, @@ -35,12 +36,12 @@ const defaultRoutes = [ { path: '/emojis', component: page('emojis') }, { path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) }, { path: '/pages', name: 'pages', component: page('pages') }, - { path: '/pages/new', component: page('page-editor/page-editor') }, - { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, - { path: '/gallery', component: page('gallery/index') }, - { path: '/gallery/new', component: page('gallery/edit') }, - { path: '/gallery/:postId/edit', component: page('gallery/edit'), props: route => ({ postId: route.params.postId }) }, - { path: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) }, + { path: '/pages/new', component: page(() => import('./pages/page-editor/page-editor.vue')) }, + { path: '/pages/edit/:pageId', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initPageId: route.params.pageId }) }, + { path: '/gallery', component: page(() => import('./pages/gallery/index.vue')) }, + { path: '/gallery/new', component: page(() => import('./pages/gallery/edit.vue')) }, + { path: '/gallery/:postId/edit', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) }, + { path: '/gallery/:postId', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) }, { path: '/channels', component: page('channels') }, { path: '/channels/new', component: page('channel-editor') }, { path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, @@ -52,23 +53,23 @@ const defaultRoutes = [ { path: '/my/favorites', component: page('favorites') }, { path: '/my/messages', component: page('messages') }, { path: '/my/mentions', component: page('mentions') }, - { path: '/my/messaging', name: 'messaging', component: page('messaging/index') }, - { path: '/my/messaging/:user', component: page('messaging/messaging-room'), props: route => ({ userAcct: route.params.user }) }, - { path: '/my/messaging/group/:group', component: page('messaging/messaging-room'), props: route => ({ groupId: route.params.group }) }, + { path: '/my/messaging', name: 'messaging', component: page(() => import('./pages/messaging/index.vue')) }, + { path: '/my/messaging/:user', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ userAcct: route.params.user }) }, + { path: '/my/messaging/group/:group', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ groupId: route.params.group }) }, { path: '/my/drive', name: 'drive', component: page('drive') }, { path: '/my/drive/folder/:folder', component: page('drive') }, { path: '/my/follow-requests', component: page('follow-requests') }, - { path: '/my/lists', component: page('my-lists/index') }, - { path: '/my/lists/:list', component: page('my-lists/list') }, - { path: '/my/groups', component: page('my-groups/index') }, - { path: '/my/groups/:group', component: page('my-groups/group'), props: route => ({ groupId: route.params.group }) }, - { path: '/my/antennas', component: page('my-antennas/index') }, - { path: '/my/antennas/create', component: page('my-antennas/create') }, - { path: '/my/antennas/:antennaId', component: page('my-antennas/edit'), props: true }, - { path: '/my/clips', component: page('my-clips/index') }, + { path: '/my/lists', component: page(() => import('./pages/my-lists/index.vue')) }, + { path: '/my/lists/:list', component: page(() => import('./pages/my-lists/list.vue')) }, + { path: '/my/groups', component: page(() => import('./pages/my-groups/index.vue')) }, + { path: '/my/groups/:group', component: page(() => import('./pages/my-groups/group.vue')), props: route => ({ groupId: route.params.group }) }, + { path: '/my/antennas', component: page(() => import('./pages/my-antennas/index.vue')) }, + { path: '/my/antennas/create', component: page(() => import('./pages/my-antennas/create.vue')) }, + { path: '/my/antennas/:antennaId', component: page(() => import('./pages/my-antennas/edit.vue')), props: true }, + { path: '/my/clips', component: page(() => import('./pages/my-clips/index.vue')) }, { path: '/scratchpad', component: page('scratchpad') }, - { path: '/admin/:page(.*)?', component: iAmModerator ? page('admin/index') : page('not-found'), props: route => ({ initialPage: route.params.page || null }) }, - { path: '/admin', component: iAmModerator ? page('admin/index') : page('not-found') }, + { path: '/admin/:page(.*)?', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found'), props: route => ({ initialPage: route.params.page || null }) }, + { path: '/admin', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found') }, { path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) }, { path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, { path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, diff --git a/packages/client/src/scripts/2fa.ts b/packages/client/src/scripts/2fa.ts index 00363cffa6..d1b9581e72 100644 --- a/packages/client/src/scripts/2fa.ts +++ b/packages/client/src/scripts/2fa.ts @@ -1,11 +1,11 @@ -export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { +export function byteify(string: string, encoding: 'ascii' | 'base64' | 'hex') { switch (encoding) { case 'ascii': - return Uint8Array.from(data, c => c.charCodeAt(0)); + return Uint8Array.from(string, c => c.charCodeAt(0)); case 'base64': return Uint8Array.from( atob( - data + string .replace(/-/g, '+') .replace(/_/g, '/') ), @@ -13,7 +13,7 @@ export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { ); case 'hex': return new Uint8Array( - data + string .match(/.{1,2}/g) .map(byte => parseInt(byte, 16)) ); diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f4a3a4c0fc..8d9bdee8f5 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,5 +1,5 @@ -import { nextTick, Ref, ref } from 'vue'; -import * as getCaretCoordinates from 'textarea-caret'; +import { nextTick, Ref, ref, defineAsyncComponent } from 'vue'; +import getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -74,21 +74,21 @@ export class Autocomplete { emojiIndex, mfmTagIndex); - if (max == -1) { + if (max === -1) { this.close(); return; } - const isMention = mentionIndex != -1; - const isHashtag = hashtagIndex != -1; - const isMfmTag = mfmTagIndex != -1; - const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isMention = mentionIndex !== -1; + const isHashtag = hashtagIndex !== -1; + const isMfmTag = mfmTagIndex !== -1; + const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; if (isMention) { const username = text.substr(mentionIndex + 1); - if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) { + if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) { this.open('user', username); opened = true; } else if (username === '') { @@ -130,7 +130,7 @@ export class Autocomplete { * ใตใธใงในใใๆ็คบใใพใใ */ private async open(type: string, q: string | null) { - if (type != this.currentType) { + if (type !== this.currentType) { this.close(); } if (this.opening) return; @@ -157,7 +157,7 @@ export class Autocomplete { const _y = ref(y); const _q = ref(q); - const { dispose } = await popup(import('@/components/autocomplete.vue'), { + const { dispose } = await popup(defineAsyncComponent(() => import('@/components/autocomplete.vue')), { textarea: this.textarea, close: this.close, type: type, @@ -201,7 +201,7 @@ export class Autocomplete { const caret = this.textarea.selectionStart; - if (type == 'user') { + if (type === 'user') { const source = this.text; const before = source.substr(0, caret); @@ -219,7 +219,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'hashtag') { + } else if (type === 'hashtag') { const source = this.text; const before = source.substr(0, caret); @@ -235,7 +235,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'emoji') { + } else if (type === 'emoji') { const source = this.text; const before = source.substr(0, caret); @@ -251,7 +251,7 @@ export class Autocomplete { const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'mfmTag') { + } else if (type === 'mfmTag') { const source = this.text; const before = source.substr(0, caret); diff --git a/packages/client/src/scripts/contains.ts b/packages/client/src/scripts/contains.ts index 770bda63bb..256e09d293 100644 --- a/packages/client/src/scripts/contains.ts +++ b/packages/client/src/scripts/contains.ts @@ -2,7 +2,7 @@ export default (parent, child, checkSame = true) => { if (checkSame && parent === child) return true; let node = child.parentNode; while (node) { - if (node == parent) return true; + if (node === parent) return true; node = node.parentNode; } return false; diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index bd8689e4f8..4196170d24 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -8,4 +8,4 @@ export type UnicodeEmojiDef = { } // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; +export const emojilist = (await import('../emojilist.json')).default as UnicodeEmojiDef[]; diff --git a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts index 123ab7a06d..af517f2672 100644 --- a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts +++ b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts @@ -1,5 +1,5 @@ export function extractAvgColorFromBlurhash(hash: string) { - return typeof hash == 'string' + return typeof hash === 'string' ? '#' + [...hash.slice(2, 6)] .map(x => '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'.indexOf(x)) .reduce((a, c) => a * 83 + c, 0) diff --git a/packages/client/src/scripts/format-time-string.ts b/packages/client/src/scripts/format-time-string.ts index bfb2c397ae..fb4718c007 100644 --- a/packages/client/src/scripts/format-time-string.ts +++ b/packages/client/src/scripts/format-time-string.ts @@ -13,7 +13,7 @@ const defaultLocaleStringFormats: {[index: string]: string} = { function formatLocaleString(date: Date, format: string): string { return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => { if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) { - return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]}); + return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] }); } else { return match; } @@ -24,8 +24,8 @@ export function formatDateTimeString(date: Date, format: string): string { return format .replace(/yyyy/g, date.getFullYear().toString()) .replace(/yy/g, date.getFullYear().toString().slice(-2)) - .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'})) - .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'})) + .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long' })) + .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short' })) .replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2)) .replace(/M/g, (date.getMonth() + 1).toString()) .replace(/dd/g, (`0${date.getDate()}`).slice(-2)) diff --git a/packages/client/src/scripts/get-account-from-id.ts b/packages/client/src/scripts/get-account-from-id.ts index ba3adceecc..1da897f176 100644 --- a/packages/client/src/scripts/get-account-from-id.ts +++ b/packages/client/src/scripts/get-account-from-id.ts @@ -3,5 +3,5 @@ import { get } from '@/scripts/idb-proxy'; export async function getAccountFromId(id: string) { const accounts = await get('accounts') as { token: string; id: string; }[]; if (!accounts) console.log('Accounts are not recorded'); - return accounts.find(e => e.id === id); + return accounts.find(account => account.id === id); } diff --git a/packages/client/src/scripts/get-md5.ts b/packages/client/src/scripts/get-md5.ts deleted file mode 100644 index b002d762b1..0000000000 --- a/packages/client/src/scripts/get-md5.ts +++ /dev/null @@ -1,10 +0,0 @@ -// ในใฏใชใใใตใคใบใใใซใ -//import * as crypto from 'crypto'; - -export default (data: ArrayBuffer) => { - //const buf = new Buffer(data); - //const hash = crypto.createHash('md5'); - //hash.update(buf); - //return hash.digest('hex'); - return ''; -}; diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index b19656d3cc..78749ad6bb 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -1,4 +1,4 @@ -import { Ref } from 'vue'; +import { defineAsyncComponent, Ref } from 'vue'; import * as misskey from 'misskey-js'; import { $i } from '@/account'; import { i18n } from '@/i18n'; @@ -22,7 +22,7 @@ export function getNoteMenu(props: { props.note.poll == null ); - let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; function del(): void { os.confirm({ @@ -83,8 +83,8 @@ export function getNoteMenu(props: { function togglePin(pin: boolean): void { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + }, undefined, null, res => { + if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { os.alert({ type: 'error', text: i18n.ts.pinLimitExceeded @@ -209,7 +209,7 @@ export function getNoteMenu(props: { text: i18n.ts.clip, action: () => clip() }, - (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? { icon: 'fas fa-eye-slash', text: i18n.ts.unwatch, action: () => toggleWatch(false) @@ -227,7 +227,7 @@ export function getNoteMenu(props: { text: i18n.ts.muteThread, action: () => toggleThreadMute(true) }), - appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { icon: 'fas fa-thumbtack', text: i18n.ts.unpin, action: () => togglePin(false) @@ -246,14 +246,14 @@ export function getNoteMenu(props: { }] : [] ),*/ - ...(appearNote.userId != $i.id ? [ + ...(appearNote.userId !== $i.id ? [ null, { icon: 'fas fa-exclamation-circle', text: i18n.ts.reportAbuse, action: () => { const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: appearNote.user, initialComment: `Note: ${u}\n-----\n` }, {}, 'closed'); @@ -261,9 +261,9 @@ export function getNoteMenu(props: { }] : [] ), - ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ null, - appearNote.userId == $i.id ? { + appearNote.userId === $i.id ? { icon: 'fas fa-edit', text: i18n.ts.deleteAndEdit, action: delEdit diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts index 54b8d109d6..d57e1c3029 100644 --- a/packages/client/src/scripts/get-note-summary.ts +++ b/packages/client/src/scripts/get-note-summary.ts @@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => { } // ใใกใคใซใๆทปไปใใใฆใใใจใ - if ((note.files || []).length != 0) { + if ((note.files || []).length !== 0) { summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`; } diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index 192d14b83e..091338efd6 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -6,6 +6,7 @@ import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; import { $i, iAmModerator } from '@/account'; +import { defineAsyncComponent } from 'vue'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -127,7 +128,7 @@ export function getUserMenu(user) { } function reportAbuse() { - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: user, }, {}, 'closed'); } @@ -147,7 +148,7 @@ export function getUserMenu(user) { userId: user.id }).then(() => { user.isFollowed = !user.isFollowed; - }) + }); } let menu = [{ @@ -168,7 +169,7 @@ export function getUserMenu(user) { action: () => { os.post({ specified: user }); } - }, meId != user.id ? { + }, meId !== user.id ? { type: 'link', icon: 'fas fa-comments', text: i18n.ts.startMessaging, @@ -177,13 +178,13 @@ export function getUserMenu(user) { icon: 'fas fa-list-ul', text: i18n.ts.addToList, action: pushList - }, meId != user.id ? { + }, meId !== user.id ? { icon: 'fas fa-users', text: i18n.ts.inviteToGroup, action: inviteGroup } : undefined] as any; - if ($i && meId != user.id) { + if ($i && meId !== user.id) { menu = menu.concat([null, { icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash', text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, diff --git a/packages/client/src/scripts/get-user-name.ts b/packages/client/src/scripts/get-user-name.ts new file mode 100644 index 0000000000..d499ea0203 --- /dev/null +++ b/packages/client/src/scripts/get-user-name.ts @@ -0,0 +1,3 @@ +export default function(user: { name?: string | null, username: string }): string { + return user.name || user.username; +} diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts index 2b3f491fd8..fd9c74f6c8 100644 --- a/packages/client/src/scripts/hotkey.ts +++ b/packages/client/src/scripts/hotkey.ts @@ -53,34 +53,34 @@ const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, c const ignoreElemens = ['input', 'textarea']; -function match(e: KeyboardEvent, patterns: Action['patterns']): boolean { - const key = e.code.toLowerCase(); +function match(ev: KeyboardEvent, patterns: Action['patterns']): boolean { + const key = ev.code.toLowerCase(); return patterns.some(pattern => pattern.which.includes(key) && - pattern.ctrl === e.ctrlKey && - pattern.shift === e.shiftKey && - pattern.alt === e.altKey && - !e.metaKey + pattern.ctrl === ev.ctrlKey && + pattern.shift === ev.shiftKey && + pattern.alt === ev.altKey && + !ev.metaKey ); } export const makeHotkey = (keymap: Keymap) => { const actions = parseKeymap(keymap); - return (e: KeyboardEvent) => { + return (ev: KeyboardEvent) => { if (document.activeElement) { if (ignoreElemens.some(el => document.activeElement!.matches(el))) return; if (document.activeElement.attributes['contenteditable']) return; } for (const action of actions) { - const matched = match(e, action.patterns); + const matched = match(ev, action.patterns); if (matched) { - if (!action.allowRepeat && e.repeat) return; + if (!action.allowRepeat && ev.repeat) return; - e.preventDefault(); - e.stopPropagation(); - action.callback(e); + ev.preventDefault(); + ev.stopPropagation(); + action.callback(ev); break; } } diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts index 6329c0860e..8106687b61 100644 --- a/packages/client/src/scripts/hpml/evaluator.ts +++ b/packages/client/src/scripts/hpml/evaluator.ts @@ -36,7 +36,7 @@ export class Hpml { if (this.opts.enableAiScript) { this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({ storageKey: 'pages:' + this.page.id - }), ...initAiLib(this)}, { + }), ...initAiLib(this) }, { in: (q) => { return new Promise(ok => { os.inputText({ @@ -85,7 +85,7 @@ export class Hpml { public eval() { try { this.vars.value = this.evaluateVars(); - } catch (e) { + } catch (err) { //this.onError(e); } } @@ -103,7 +103,7 @@ export class Hpml { public callAiScript(fn: string) { try { if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []); - } catch (e) {} + } catch (err) {} } @autobind @@ -185,7 +185,7 @@ export class Hpml { if (this.aiscript) { try { return utils.valToJs(this.aiscript.scope.get(expr.value)); - } catch (e) { + } catch (err) { return null; } } else { @@ -194,7 +194,7 @@ export class Hpml { } // Define user function - if (expr.type == 'fn') { + if (expr.type === 'fn') { return { slots: expr.value.slots.map(x => x.name), exec: (slotArg: Record<string, any>) => { diff --git a/packages/client/src/scripts/hpml/lib.ts b/packages/client/src/scripts/hpml/lib.ts index 2a1ac73a40..01a44ffcdf 100644 --- a/packages/client/src/scripts/hpml/lib.ts +++ b/packages/client/src/scripts/hpml/lib.ts @@ -1,9 +1,9 @@ -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; import { Hpml } from './evaluator'; import { values, utils } from '@syuilo/aiscript'; import { Fn, HpmlScope } from '.'; import { Expr } from './expr'; -import * as seedrandom from 'seedrandom'; +import seedrandom from 'seedrandom'; /* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs diff --git a/packages/client/src/scripts/idb-proxy.ts b/packages/client/src/scripts/idb-proxy.ts index 5f76ae30bb..d462a0d7ce 100644 --- a/packages/client/src/scripts/idb-proxy.ts +++ b/packages/client/src/scripts/idb-proxy.ts @@ -13,8 +13,8 @@ let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; if (idbAvailable) { try { await iset('idb-test', 'test'); - } catch (e) { - console.error('idb error', e); + } catch (err) { + console.error('idb error', err); idbAvailable = false; } } diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts index d6dbd5dbd4..7bacfbdf00 100644 --- a/packages/client/src/scripts/initialize-sw.ts +++ b/packages/client/src/scripts/initialize-sw.ts @@ -4,26 +4,26 @@ import { api } from '@/os'; import { lang } from '@/config'; export async function initializeSw() { - if (instance.swPublickey && - ('serviceWorker' in navigator) && - ('PushManager' in window) && - $i && $i.token) { - navigator.serviceWorker.register(`/sw.js`); + if (!('serviceWorker' in navigator)) return; - navigator.serviceWorker.ready.then(registration => { - registration.active?.postMessage({ - msg: 'initialize', - lang, - }); + navigator.serviceWorker.register(`/sw.js`, { scope: '/', type: 'classic' }); + navigator.serviceWorker.ready.then(registration => { + registration.active?.postMessage({ + msg: 'initialize', + lang, + }); + + if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(instance.swPublickey) - }).then(subscription => { + }) + .then(subscription => { function encode(buffer: ArrayBuffer | null) { return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); } - + // Register api('sw/register', { endpoint: subscription.endpoint, @@ -37,15 +37,15 @@ export async function initializeSw() { if (err.name === 'NotAllowedError') { return; } - + // ้ใapplicationServerKey (ใพใใฏ gcm_sender_id)ใฎใตใในใฏใชใใทใงใณใ // ๆขใซๅญๅจใใฆใใใใจใๅๅ ใงใจใฉใผใซใชใฃใๅฏ่ฝๆงใใใใฎใงใ // ใใฎใตใในใฏใชใใทใงใณใ่งฃ้คใใฆใใ const subscription = await registration.pushManager.getSubscription(); if (subscription) subscription.unsubscribe(); }); - }); - } + } + }); } /** diff --git a/packages/client/src/scripts/lookup-user.ts b/packages/client/src/scripts/lookup-user.ts index 8de5c84ce8..2d00e51621 100644 --- a/packages/client/src/scripts/lookup-user.ts +++ b/packages/client/src/scripts/lookup-user.ts @@ -25,12 +25,12 @@ export async function lookupUser() { _notFound = true; } }; - usernamePromise.then(show).catch(e => { - if (e.code === 'NO_SUCH_USER') { + usernamePromise.then(show).catch(err => { + if (err.code === 'NO_SUCH_USER') { notFound(); } }); - idPromise.then(show).catch(e => { + idPromise.then(show).catch(err => { notFound(); }); } diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts new file mode 100644 index 0000000000..08b891ec5b --- /dev/null +++ b/packages/client/src/scripts/navigate.ts @@ -0,0 +1,34 @@ +import { inject } from 'vue'; +import { router } from '@/router'; +import { defaultStore } from '@/store'; + +export type Navigate = (path: string, record?: boolean) => void; + +export class MisskeyNavigator { + public readonly navHook: Navigate | null = null; + public readonly sideViewHook: Navigate | null = null; + + // It should be constructed during vue creating in order for inject function to work + constructor() { + this.navHook = inject<Navigate | null>('navHook', null); + this.sideViewHook = inject<Navigate | null>('sideViewHook', null); + } + + // Use this method instead of router.push() + public push(path: string, record = true) { + if (this.navHook) { + this.navHook(path, record); + } else { + if (defaultStore.state.defaultSideView && this.sideViewHook && path !== '/') { + return this.sideViewHook(path, record); + } + + if (router.currentRoute.value.path === path) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + if (record) router.push(path); + else router.replace(path); + } + } + } +} diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 36e476b6f9..9e657906c2 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -41,9 +41,9 @@ export function physics(container: HTMLElement) { const groundThickness = 1024; const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, { - isStatic: true, - restitution: 0.1, - friction: 2 + isStatic: true, + restitution: 0.1, + friction: 2 }); //const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts); diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts index aeaafa124b..e21a6d2ed3 100644 --- a/packages/client/src/scripts/please-login.ts +++ b/packages/client/src/scripts/please-login.ts @@ -1,14 +1,21 @@ +import { defineAsyncComponent } from 'vue'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -import { alert } from '@/os'; +import { popup } from '@/os'; -export function pleaseLogin() { +export function pleaseLogin(path?: string) { if ($i) return; - alert({ - title: i18n.ts.signinRequired, - text: null - }); + popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), { + autoSet: true, + message: i18n.ts.signinRequired + }, { + cancelled: () => { + if (path) { + window.location.href = path; + } + }, + }, 'closed'); throw new Error('signin required'); } diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index b8286a2a76..580031d0a3 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,9 @@ import * as config from '@/config'; +import { appendQuery } from './url'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; - url += '?zen'; + let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; + url = appendQuery(url, 'zen'); if (w) { const position = w.getBoundingClientRect(); const width = parseInt(getComputedStyle(w, '').width, 10); diff --git a/packages/client/src/scripts/reaction-picker.ts b/packages/client/src/scripts/reaction-picker.ts index 3ac1f63430..b7699cae4a 100644 --- a/packages/client/src/scripts/reaction-picker.ts +++ b/packages/client/src/scripts/reaction-picker.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { defineAsyncComponent, Ref, ref } from 'vue'; import { popup } from '@/os'; class ReactionPicker { @@ -12,7 +12,7 @@ class ReactionPicker { } public async init() { - await popup(import('@/components/emoji-picker-dialog.vue'), { + await popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { src: this.src, asReactionPicker: true, manualShowing: this.manualShowing diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 23df4edf54..461d613b42 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -4,6 +4,7 @@ import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { DriveFile } from 'misskey-js/built/entities'; +import { uploadFile } from '@/scripts/upload'; function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> { return new Promise((res, rej) => { @@ -14,14 +15,14 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv input.type = 'file'; input.multiple = multiple; input.onchange = () => { - const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); + const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); Promise.all(promises).then(driveFiles => { res(multiple ? driveFiles : driveFiles[0]); - }).catch(e => { + }).catch(err => { os.alert({ type: 'error', - text: e + text: err }); }); @@ -53,9 +54,9 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const marker = Math.random().toString(); // TODO: UUIDใจใไฝฟใ const connection = stream.useChannel('main'); - connection.on('urlUploadFinished', data => { - if (data.marker === marker) { - res(multiple ? [data.file] : data.file); + connection.on('urlUploadFinished', urlResponse => { + if (urlResponse.marker === marker) { + res(multiple ? [urlResponse.file] : urlResponse.file); connection.dispose(); } }); diff --git a/packages/client/src/scripts/theme-editor.ts b/packages/client/src/scripts/theme-editor.ts index 3d69d2836a..2c917e280d 100644 --- a/packages/client/src/scripts/theme-editor.ts +++ b/packages/client/src/scripts/theme-editor.ts @@ -1,4 +1,4 @@ -import { v4 as uuid} from 'uuid'; +import { v4 as uuid } from 'uuid'; import { themeProps, Theme } from './theme'; diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 2cb78fae5c..dec9fb355c 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -1,5 +1,6 @@ +import { ref } from 'vue'; import { globalEvents } from '@/events'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; export type Theme = { id: string; @@ -10,30 +11,38 @@ export type Theme = { props: Record<string, string>; }; -export const lightTheme: Theme = require('@/themes/_light.json5'); -export const darkTheme: Theme = require('@/themes/_dark.json5'); +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); -export const builtinThemes = [ - require('@/themes/l-light.json5'), - require('@/themes/l-coffee.json5'), - require('@/themes/l-apricot.json5'), - require('@/themes/l-rainy.json5'), - require('@/themes/l-vivid.json5'), - require('@/themes/l-cherry.json5'), - require('@/themes/l-sushi.json5'), +export const getBuiltinThemes = () => Promise.all( + [ + 'l-light', + 'l-coffee', + 'l-apricot', + 'l-rainy', + 'l-vivid', + 'l-cherry', + 'l-sushi', - require('@/themes/d-dark.json5'), - require('@/themes/d-persimmon.json5'), - require('@/themes/d-astro.json5'), - require('@/themes/d-future.json5'), - require('@/themes/d-botanical.json5'), - require('@/themes/d-cherry.json5'), - require('@/themes/d-ice.json5'), - require('@/themes/d-pumpkin.json5'), - require('@/themes/d-black.json5'), -] as Theme[]; + 'd-dark', + 'd-persimmon', + 'd-astro', + 'd-future', + 'd-botanical', + 'd-cherry', + 'd-ice', + 'd-pumpkin', + 'd-black', + ].map(name => import(`../themes/${name}.json5`).then(({ default: _default }): Theme => _default)) +); + +export const getBuiltinThemesRef = () => { + const builtinThemes = ref<Theme[]>([]); + getBuiltinThemes().then(themes => builtinThemes.value = themes); + return builtinThemes; +}; let timeout = null; diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts new file mode 100644 index 0000000000..2f7b30b58d --- /dev/null +++ b/packages/client/src/scripts/upload.ts @@ -0,0 +1,114 @@ +import { reactive, ref } from 'vue'; +import { defaultStore } from '@/store'; +import { apiUrl } from '@/config'; +import * as Misskey from 'misskey-js'; +import { $i } from '@/account'; +import { readAndCompressImage } from 'browser-image-resizer'; +import { alert } from '@/os'; + +type Uploading = { + id: string; + name: string; + progressMax: number | undefined; + progressValue: number | undefined; + img: string; +}; +export const uploads = ref<Uploading[]>([]); + +const compressTypeMap = { + 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/svg+xml': { quality: 1, mimeType: 'image/png' }, +} as const; + +const mimeTypeMap = { + 'image/webp': 'webp', + 'image/jpeg': 'jpg', + 'image/png': 'png', +} as const; + +export function uploadFile( + file: File, + folder?: any, + name?: string, + keepOriginal: boolean = defaultStore.state.keepOriginalUploading +): Promise<Misskey.entities.DriveFile> { + if (folder && typeof folder === 'object') folder = folder.id; + + return new Promise((resolve, reject) => { + const id = Math.random().toString(); + + const reader = new FileReader(); + reader.onload = async (ev) => { + const ctx = reactive<Uploading>({ + id: id, + name: name || file.name || 'untitled', + progressMax: undefined, + progressValue: undefined, + img: window.URL.createObjectURL(file) + }); + + uploads.value.push(ctx); + + let resizedImage: any; + if (!keepOriginal && file.type in compressTypeMap) { + const imgConfig = compressTypeMap[file.type]; + + const config = { + maxWidth: 2048, + maxHeight: 2048, + debug: true, + ...imgConfig, + }; + + try { + resizedImage = await readAndCompressImage(file, config); + ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].mimeType]}` : ctx.name; + } catch (err) { + console.error('Failed to resize image', err); + } + } + + const formData = new FormData(); + formData.append('i', $i.token); + formData.append('force', 'true'); + formData.append('file', resizedImage || file); + formData.append('name', ctx.name); + if (folder) formData.append('folderId', folder); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', apiUrl + '/drive/files/create', true); + xhr.onload = (ev) => { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { + // TODO: ๆถใใฎใงใฏใชใใฆๅ้ใงใใใใใซใใใ + uploads.value = uploads.value.filter(x => x.id !== id); + + alert({ + type: 'error', + title: 'Failed to upload', + text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}` + }); + + reject(); + return; + } + + const driveFile = JSON.parse(ev.target.response); + + resolve(driveFile); + + uploads.value = uploads.value.filter(x => x.id !== id); + }; + + xhr.upload.onprogress = ev => { + if (ev.lengthComputable) { + ctx.progressMax = ev.total; + ctx.progressValue = ev.loaded; + } + }; + + xhr.send(formData); + }; + reader.readAsArrayBuffer(file); + }); +} diff --git a/packages/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts index c7f2b7c1e7..542b00e0f0 100644 --- a/packages/client/src/scripts/url.ts +++ b/packages/client/src/scripts/url.ts @@ -4,7 +4,7 @@ export function query(obj: {}): string { .reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>); return Object.entries(params) - .map((e) => `${e[0]}=${encodeURIComponent(e[1])}`) + .map((p) => `${p[0]}=${encodeURIComponent(p[1])}`) .join('&'); } diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts index b2a96062c7..f1f976693e 100644 --- a/packages/client/src/scripts/use-note-capture.ts +++ b/packages/client/src/scripts/use-note-capture.ts @@ -11,8 +11,8 @@ export function useNoteCapture(props: { const note = props.note; const connection = $i ? stream : null; - function onStreamNoteUpdated(data): void { - const { type, id, body } = data; + function onStreamNoteUpdated(noteData): void { + const { type, id, body } = noteData; if (id !== note.value.id) return; diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index b9800ec607..deee23951e 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -255,10 +255,13 @@ type Plugin = { /** * ๅธธใซใกใขใชใซใญใผใใใฆใใๅฟ ่ฆใใชใใใใช่จญๅฎๆ ๅ ฑใไฟ็ฎกใใในใใฌใผใธ(้ใชใขใฏใใฃใ) */ +import lightTheme from '@/themes/l-light.json5'; +import darkTheme from '@/themes/d-dark.json5'; + export class ColdDeviceStorage { public static default = { - lightTheme: require('@/themes/l-light.json5') as Theme, - darkTheme: require('@/themes/d-dark.json5') as Theme, + lightTheme, + darkTheme, syncDeviceDarkMode: true, plugins: [] as Plugin[], mediaVolume: 0.5, diff --git a/packages/client/src/sw/compose-notification.ts b/packages/client/src/sw/compose-notification.ts deleted file mode 100644 index e271d30949..0000000000 --- a/packages/client/src/sw/compose-notification.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Notification composer of Service Worker - */ -declare var self: ServiceWorkerGlobalScope; - -import * as misskey from 'misskey-js'; - -function getUserName(user: misskey.entities.User): string { - return user.name || user.username; -} - -export default async function(type, data, i18n): Promise<[string, NotificationOptions] | null | undefined> { - if (!i18n) { - console.log('no i18n'); - return; - } - - switch (type) { - case 'driveFileCreated': // TODO (Server Side) - return [i18n.t('_notification.fileUploaded'), { - body: data.name, - icon: data.url - }]; - case 'notification': - switch (data.type) { - case 'mention': - return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'reply': - return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'renote': - return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'quote': - return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'reaction': - return [`${data.reaction} ${getUserName(data.user)}`, { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'pollVote': - return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'pollEnded': - return [i18n.t('_notification.pollEnded'), { - body: data.note.text, - }]; - - case 'follow': - return [i18n.t('_notification.youWereFollowed'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'receiveFollowRequest': - return [i18n.t('_notification.youReceivedFollowRequest'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'followRequestAccepted': - return [i18n.t('_notification.yourFollowRequestAccepted'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'groupInvited': - return [i18n.t('_notification.youWereInvitedToGroup'), { - body: data.group.name - }]; - - default: - return null; - } - case 'unreadMessagingMessage': - if (data.groupId === null) { - return [i18n.t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.user) }), { - icon: data.user.avatarUrl, - tag: `messaging:user:${data.user.id}` - }]; - } - return [i18n.t('_notification.youGotMessagingMessageFromGroup', { name: data.group.name }), { - icon: data.user.avatarUrl, - tag: `messaging:group:${data.group.id}` - }]; - default: - return null; - } -} diff --git a/packages/client/src/sw/sw.ts b/packages/client/src/sw/sw.ts deleted file mode 100644 index 68c650c771..0000000000 --- a/packages/client/src/sw/sw.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Service Worker - */ -declare var self: ServiceWorkerGlobalScope; - -import { get, set } from 'idb-keyval'; -import composeNotification from '@/sw/compose-notification'; -import { I18n } from '@/scripts/i18n'; - -//#region Variables -const version = _VERSION_; -const cacheName = `mk-cache-${version}`; - -let lang: string; -let i18n: I18n<any>; -let pushesPool: any[] = []; -//#endregion - -//#region Startup -get('lang').then(async prelang => { - if (!prelang) return; - lang = prelang; - return fetchLocale(); -}); -//#endregion - -//#region Lifecycle: Install -self.addEventListener('install', ev => { - self.skipWaiting(); -}); -//#endregion - -//#region Lifecycle: Activate -self.addEventListener('activate', ev => { - ev.waitUntil( - caches.keys() - .then(cacheNames => Promise.all( - cacheNames - .filter((v) => v !== cacheName) - .map(name => caches.delete(name)) - )) - .then(() => self.clients.claim()) - ); -}); -//#endregion - -//#region When: Fetching -self.addEventListener('fetch', ev => { - // Nothing to do -}); -//#endregion - -//#region When: Caught Notification -self.addEventListener('push', ev => { - // ใฏใฉใคใขใณใๅๅพ - ev.waitUntil(self.clients.matchAll({ - includeUncontrolled: true - }).then(async clients => { - // ใฏใฉใคใขใณใใใใฃใใในใใชใผใ ใซๆฅ็ถใใฆใใใจใใใใจใชใฎใง้็ฅใใชใ - if (clients.length != 0) return; - - const { type, body } = ev.data?.json(); - - // localeใ่ชญใฟ่พผใใฆใใใi18nใundefinedใ ใฃใๅ ดๅใฏpushesPoolใซใใใฆใใ - if (!i18n) return pushesPool.push({ type, body }); - - const n = await composeNotification(type, body, i18n); - if (n) return self.registration.showNotification(...n); - })); -}); -//#endregion - -//#region When: Caught a message from the client -self.addEventListener('message', ev => { - switch(ev.data) { - case 'clear': - return; // TODO - default: - break; - } - - if (typeof ev.data === 'object') { - // E.g. '[object Array]' โ 'array' - const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); - - if (otype === 'object') { - if (ev.data.msg === 'initialize') { - lang = ev.data.lang; - set('lang', lang); - fetchLocale(); - } - } - } -}); -//#endregion - -//#region Function: (Re)Load i18n instance -async function fetchLocale() { - //#region localeใใกใคใซใฎ่ชญใฟ่พผใฟ - // Service Workerใฏไฝๅบฆใ่ตทๅใใใฎใใณใซlocaleใ่ชญใฟ่พผใใฎใงใCacheStorageใไฝฟใ - const localeUrl = `/assets/locales/${lang}.${version}.json`; - let localeRes = await caches.match(localeUrl); - - if (!localeRes) { - localeRes = await fetch(localeUrl); - const clone = localeRes?.clone(); - if (!clone?.clone().ok) return; - - caches.open(cacheName).then(cache => cache.put(localeUrl, clone)); - } - - i18n = new I18n(await localeRes.json()); - //#endregion - - //#region i18nใใใกใใจ่ชญใฟ่พผใใ ๅพใซใใใใๅฆ็ - for (const { type, body } of pushesPool) { - const n = await composeNotification(type, body, i18n); - if (n) self.registration.showNotification(...n); - } - pushesPool = []; - //#endregion -} -//#endregion diff --git a/packages/client/src/theme-store.ts b/packages/client/src/theme-store.ts index e7962e7e8e..fdc92ed793 100644 --- a/packages/client/src/theme-store.ts +++ b/packages/client/src/theme-store.ts @@ -14,9 +14,9 @@ export async function fetchThemes(): Promise<void> { try { const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' }); localStorage.setItem(lsCacheKey, JSON.stringify(themes)); - } catch (e) { - if (e.code === 'NO_SUCH_KEY') return; - throw e; + } catch (err) { + if (err.code === 'NO_SUCH_KEY') return; + throw err; } } @@ -28,7 +28,7 @@ export async function addTheme(theme: Theme): Promise<void> { } export async function removeTheme(theme: Theme): Promise<void> { - const themes = getThemes().filter(t => t.id != theme.id); + const themes = getThemes().filter(t => t.id !== theme.id); await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes }); localStorage.setItem(lsCacheKey, JSON.stringify(themes)); } diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue index 05688d7c53..9f7388db53 100644 --- a/packages/client/src/ui/_common_/common.vue +++ b/packages/client/src/ui/_common_/common.vue @@ -17,9 +17,11 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import { popup, popups, uploads, pendingApiRequestsCount } from '@/os'; +import { popup, popups, pendingApiRequestsCount } from '@/os'; +import { uploads } from '@/scripts/upload'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; +import { swInject } from './sw-inject'; import { stream } from '@/stream'; export default defineComponent({ @@ -37,7 +39,7 @@ export default defineComponent({ id: notification.id }); - popup(import('@/components/notification-toast.vue'), { + popup(defineAsyncComponent(() => import('@/components/notification-toast.vue')), { notification }, {}, 'closed'); } @@ -48,6 +50,11 @@ export default defineComponent({ if ($i) { const connection = stream.useChannel('main', null, 'UI'); connection.on('notification', onNotification); + + //#region Listen message from SW + if ('serviceWorker' in navigator) { + swInject(); + } } return { diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue index afcc50725b..41d0837233 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue @@ -33,7 +33,7 @@ </template> <script lang="ts"> -import { computed, defineComponent, ref, toRef, watch } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -61,13 +61,13 @@ export default defineComponent({ otherMenuItemIndicated, post: os.post, search, - openAccountMenu:(ev) => { + openAccountMenu: (ev) => { openAccountMenu({ withExtraOperation: true, }, ev); }, more: () => { - os.popup(import('@/components/launch-pad.vue'), {}, { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {}, { }, 'closed'); }, }; diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index a23b7d4152..d65e776d86 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -33,7 +33,7 @@ </template> <script lang="ts" setup> -import { computed, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, ref, watch } from 'vue'; import * as os from '@/os'; import { menuDef } from '@/menu'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; @@ -69,7 +69,7 @@ function openAccountMenu(ev: MouseEvent) { } function more(ev: MouseEvent) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, }, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/sw-inject.ts b/packages/client/src/ui/_common_/sw-inject.ts new file mode 100644 index 0000000000..371f80ca15 --- /dev/null +++ b/packages/client/src/ui/_common_/sw-inject.ts @@ -0,0 +1,44 @@ +import { inject } from 'vue'; +import { post } from '@/os'; +import { $i, login } from '@/account'; +import { defaultStore } from '@/store'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; +import { router } from '@/router'; + +export function swInject() { + const navHook = inject('navHook', null); + const sideViewHook = inject('sideViewHook', null); + + navigator.serviceWorker.addEventListener('message', ev => { + if (_DEV_) { + console.log('sw msg', ev.data); + } + + if (ev.data.type !== 'order') return; + + if (ev.data.loginId !== $i?.id) { + return getAccountFromId(ev.data.loginId).then(account => { + if (!account) return; + return login(account.token, ev.data.url); + }); + } + + switch (ev.data.order) { + case 'post': + return post(ev.data.options); + case 'push': + if (router.currentRoute.value.path === ev.data.url) { + return window.scroll({ top: 0, behavior: 'smooth' }); + } + if (navHook) { + return navHook(ev.data.url); + } + if (sideViewHook && defaultStore.state.defaultSideView && ev.data.url !== '/') { + return sideViewHook(ev.data.url); + } + return router.push(ev.data.url); + default: + return; + } + }); +} diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue index ab7678a505..f3703d0e8f 100644 --- a/packages/client/src/ui/_common_/upload.vue +++ b/packages/client/src/ui/_common_/upload.vue @@ -20,8 +20,8 @@ <script lang="ts" setup> import { } from 'vue'; import * as os from '@/os'; +import { uploads } from '@/scripts/upload'; -const uploads = os.uploads; const zIndex = os.claimZIndex('high'); </script> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index c7fddbc491..57008aeaed 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -39,7 +39,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -101,7 +101,7 @@ export default defineComponent({ }, more(ev) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, anchor: { x: 'center', y: 'bottom' }, }, { diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index 3364ee39be..6c0ce023e4 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -41,7 +41,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -121,12 +121,12 @@ export default defineComponent({ }, more(ev) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, }, {}, 'closed'); }, - openAccountMenu:(ev) => { + openAccountMenu: (ev) => { openAccountMenu({ withExtraOperation: true, }, ev); diff --git a/packages/client/src/ui/classic.widgets.vue b/packages/client/src/ui/classic.widgets.vue index f42f27e926..6f9d18bde5 100644 --- a/packages/client/src/ui/classic.widgets.vue +++ b/packages/client/src/ui/classic.widgets.vue @@ -44,13 +44,13 @@ export default defineComponent({ }, removeWidget(widget) { - this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); + this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id !== widget.id)); }, updateWidget({ id, data }) { this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { ...w, - data: data + data, } : w)); }, diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 1e0d9a1652..e538a93f06 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -17,7 +17,7 @@ :key="ids[0]" class="column" :column="columns.find(c => c.id === ids[0])" - :is-stacked="false" + :is-stacked="false" :style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" @parent-focus="moveFocus(ids[0], $event)" /> diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue index e0f56c2800..f12f5c6b25 100644 --- a/packages/client/src/ui/deck/antenna-column.vue +++ b/packages/client/src/ui/deck/antenna-column.vue @@ -22,8 +22,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let timeline = $ref<InstanceType<typeof XTimeline>>(); diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue index 485e89a062..2667b6d745 100644 --- a/packages/client/src/ui/deck/column-core.vue +++ b/packages/client/src/ui/deck/column-core.vue @@ -29,7 +29,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); /* diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index 5f8da8cf8f..6db3549fbb 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -61,8 +61,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; - (e: 'change-active-state', v: boolean): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'change-active-state', v: boolean): void; }>(); let body = $ref<HTMLDivElement>(); @@ -94,7 +94,6 @@ onBeforeUnmount(() => { os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd); }); - function onOtherDragStart() { dropready = true; } @@ -193,9 +192,9 @@ function goTop() { }); } -function onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); +function onDragstart(ev) { + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); // ChromeใฎใใฐใงใDragstartใใณใใฉๅ ใงใใใซDOMใๅคๆดใใ(=ใชใขใฏใใฃใใชใใญใใใฃใๅคๆดใใ)ใจDragใ็ตไบใใฆใใพใ // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately @@ -204,35 +203,34 @@ function onDragstart(e) { }, 10); } -function onDragend(e) { +function onDragend(ev) { dragging = false; } -function onDragover(e) { +function onDragover(ev) { // ่ชๅ่ช่บซใใใฉใใฐใใใฆใใๅ ดๅ if (dragging) { // ่ชๅ่ช่บซใซใฏใใญใใใใใชใ - e.dataTransfer.dropEffect = 'none'; - return; + ev.dataTransfer.dropEffect = 'none'; + } else { + const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_; + + ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; + + if (isDeckColumn) draghover = true; } - - const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; - - e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - - if (!dragging && isDeckColumn) draghover = true; } function onDragleave() { draghover = false; } -function onDrop(e) { +function onDrop(ev) { draghover = false; os.deckGlobalEvents.emit('column.dragEnd'); - const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); - if (id != null && id != '') { + const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); + if (id != null && id !== '') { swapColumn(props.column.id, id); } } diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index f7c39ad8fd..c847bf2b43 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -72,8 +72,8 @@ export const loadDeck = async () => { scope: ['client', 'deck', 'profiles'], key: deckStore.state.profile, }); - } catch (e) { - if (e.code === 'NO_SUCH_KEY') { + } catch (err) { + if (err.code === 'NO_SUCH_KEY') { // ๅพๆนไบๆๆงใฎใใ if (deckStore.state.profile === 'default') { saveDeck(); @@ -94,7 +94,7 @@ export const loadDeck = async () => { deckStore.set('layout', [['a'], ['b']]); return; } - throw e; + throw err; } deckStore.set('columns', deck.columns); @@ -114,7 +114,7 @@ export const saveDeck = throttle(1000, () => { }); export function addColumn(column: Column) { - if (column.name == undefined) column.name = null; + if (column.name === undefined) column.name = null; deckStore.push('columns', column); deckStore.push('layout', [column.id]); saveDeck(); @@ -129,10 +129,10 @@ export function removeColumn(id: Column['id']) { } export function swapColumn(a: Column['id'], b: Column['id']) { - const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) != -1); - const aY = deckStore.state.layout[aX].findIndex(id => id == a); - const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) != -1); - const bY = deckStore.state.layout[bX].findIndex(id => id == b); + const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1); + const aY = deckStore.state.layout[aX].findIndex(id => id === a); + const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1); + const bY = deckStore.state.layout[bX].findIndex(id => id === b); const layout = copy(deckStore.state.layout); layout[aX][aY] = b; layout[bX][bY] = a; @@ -259,7 +259,7 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const column = copy(deckStore.state.columns[columnIndex]); if (column == null) return; - column.widgets = column.widgets.filter(w => w.id != widget.id); + column.widgets = column.widgets.filter(w => w.id !== widget.id); columns[columnIndex] = column; deckStore.set('columns', columns); saveDeck(); @@ -276,14 +276,14 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { saveDeck(); } -export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) { +export function updateColumnWidget(id: Column['id'], widgetId: string, WidgetData: any) { const columns = copy(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const column = copy(deckStore.state.columns[columnIndex]); if (column == null) return; column.widgets = column.widgets.map(w => w.id === widgetId ? { ...w, - data: data + data: widgetData, } : w); columns[columnIndex] = column; deckStore.set('columns', columns); diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index ebaba574f4..4837c0ce38 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -18,7 +18,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); const pagination = { diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue index b990516d05..843a3bd1cb 100644 --- a/packages/client/src/ui/deck/list-column.vue +++ b/packages/client/src/ui/deck/list-column.vue @@ -22,8 +22,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let timeline = $ref<InstanceType<typeof XTimeline>>(); diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 57caab44cb..3c97cd4867 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -35,7 +35,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let pageInfo = $ref<Record<string, any> | null>(null); diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index a7a012a7fb..0b6ca3a239 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -18,7 +18,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); const pagination = { diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue index 50ee12a275..6dd040cb8d 100644 --- a/packages/client/src/ui/deck/notifications-column.vue +++ b/packages/client/src/ui/deck/notifications-column.vue @@ -7,7 +7,7 @@ </template> <script lang="ts" setup> -import { } from 'vue'; +import { defineAsyncComponent } from 'vue'; import XColumn from './column.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; @@ -20,11 +20,11 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); function func() { - os.popup(import('@/components/notification-setting-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { includingTypes: props.column.includingTypes, }, { done: async (res) => { diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue index 02b9ef83a1..f3ecda5aa4 100644 --- a/packages/client/src/ui/deck/tl-column.vue +++ b/packages/client/src/ui/deck/tl-column.vue @@ -35,8 +35,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let disabled = $ref(false); diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue index a2edc38357..10c6f5adf6 100644 --- a/packages/client/src/ui/deck/widgets-column.vue +++ b/packages/client/src/ui/deck/widgets-column.vue @@ -3,7 +3,7 @@ <template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template> <div class="wtdtxvec"> - <XWidgets v-if="column.widgets" :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + <XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> </div> </XColumn> </template> @@ -20,7 +20,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let edit = $ref(false); diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue index 2660e80368..7aed083886 100644 --- a/packages/client/src/ui/universal.widgets.vue +++ b/packages/client/src/ui/universal.widgets.vue @@ -3,7 +3,7 @@ <XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> + <button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> @@ -14,7 +14,7 @@ import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; const emit = defineEmits<{ - (e: 'mounted', el: Element): void; + (ev: 'mounted', el: Element): void; }>(); let editMode = $ref(false); @@ -32,13 +32,13 @@ function addWidget(widget) { } function removeWidget(widget) { - defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id)); + defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id !== widget.id)); } function updateWidget({ id, data }) { defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { ...w, - data: data + data, } : w)); } diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue index b833bd65ca..33b95b00db 100644 --- a/packages/client/src/widgets/activity.calendar.vue +++ b/packages/client/src/widgets/activity.calendar.vue @@ -1,13 +1,13 @@ <template> <svg viewBox="0 0 21 7"> - <rect v-for="record in data" class="day" + <rect v-for="record in activity" class="day" width="1" height="1" :x="record.x" :y="record.date.weekday" rx="1" ry="1" fill="transparent"> <title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title> </rect> - <rect v-for="record in data" class="day" + <rect v-for="record in activity" class="day" :width="record.v" :height="record.v" :x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)" rx="1" ry="1" @@ -15,7 +15,7 @@ style="pointer-events: none;"/> <rect class="today" width="1" height="1" - :x="data[0].x" :y="data[0].date.weekday" + :x="activity[0].x" :y="activity[0].date.weekday" rx="1" ry="1" fill="none" stroke-width="0.1" @@ -23,45 +23,41 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +const props = defineProps<{ + activity: any[] +}>(); -export default defineComponent({ - props: ['data'], - created() { - for (const d of this.data) { - d.total = d.notes + d.replies + d.renotes; - } - const peak = Math.max.apply(null, this.data.map(d => d.total)); +for (const d of props.activity) { + d.total = d.notes + d.replies + d.renotes; +} +const peak = Math.max(...props.activity.map(d => d.total)); - const now = new Date(); - const year = now.getFullYear(); - const month = now.getMonth(); - const day = now.getDate(); +const now = new Date(); +const year = now.getFullYear(); +const month = now.getMonth(); +const day = now.getDate(); - let x = 20; - this.data.slice().forEach((d, i) => { - d.x = x; +let x = 20; +props.activity.slice().forEach((d, i) => { + d.x = x; - const date = new Date(year, month, day - i); - d.date = { - year: date.getFullYear(), - month: date.getMonth(), - day: date.getDate(), - weekday: date.getDay() - }; + const date = new Date(year, month, day - i); + d.date = { + year: date.getFullYear(), + month: date.getMonth(), + day: date.getDate(), + weekday: date.getDay() + }; - d.v = peak === 0 ? 0 : d.total / (peak / 2); - if (d.v > 1) d.v = 1; - const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170; - const cs = d.v * 100; - const cl = 15 + ((1 - d.v) * 80); - d.color = `hsl(${ch}, ${cs}%, ${cl}%)`; + d.v = peak === 0 ? 0 : d.total / (peak / 2); + if (d.v > 1) d.v = 1; + const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170; + const cs = d.v * 100; + const cl = 15 + ((1 - d.v) * 80); + d.color = `hsl(${ch}, ${cs}%, ${cl}%)`; - if (d.date.weekday === 0) x--; - }); - } + if (d.date.weekday === 0) x--; }); </script> diff --git a/packages/client/src/widgets/activity.chart.vue b/packages/client/src/widgets/activity.chart.vue index 9702d66663..b7db2af580 100644 --- a/packages/client/src/widgets/activity.chart.vue +++ b/packages/client/src/widgets/activity.chart.vue @@ -24,9 +24,19 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +const props = defineProps<{ + activity: any[] +}>(); + +let viewBoxX: number = $ref(147); +let viewBoxY: number = $ref(60); +let zoom: number = $ref(1); +let pos: number = $ref(0); +let pointsNote: any = $ref(null); +let pointsReply: any = $ref(null); +let pointsRenote: any = $ref(null); +let pointsTotal: any = $ref(null); function dragListen(fn) { window.addEventListener('mousemove', fn); @@ -40,60 +50,35 @@ function dragClear(fn) { window.removeEventListener('mouseup', dragClear); } -export default defineComponent({ - props: ['data'], - data() { - return { - viewBoxX: 147, - viewBoxY: 60, - zoom: 1, - pos: 0, - pointsNote: null, - pointsReply: null, - pointsRenote: null, - pointsTotal: null - }; - }, - created() { - for (const d of this.data) { - d.total = d.notes + d.replies + d.renotes; - } +function onMousedown(ev) { + const clickX = ev.clientX; + const clickY = ev.clientY; + const baseZoom = zoom; + const basePos = pos; - this.render(); - }, - methods: { - render() { - const peak = Math.max.apply(null, this.data.map(d => d.total)); - if (peak != 0) { - const data = this.data.slice().reverse(); - this.pointsNote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); - this.pointsReply = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); - this.pointsRenote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); - this.pointsTotal = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); - } - }, - onMousedown(e) { - const clickX = e.clientX; - const clickY = e.clientY; - const baseZoom = this.zoom; - const basePos = this.pos; + // ๅใใใๆ + dragListen(me => { + let moveLeft = me.clientX - clickX; + let moveTop = me.clientY - clickY; - // ๅใใใๆ - dragListen(me => { - let moveLeft = me.clientX - clickX; - let moveTop = me.clientY - clickY; + zoom = Math.max(1, baseZoom + (-moveTop / 20)); + pos = Math.min(0, basePos + moveLeft); + if (pos < -(((props.activity.length - 1) * zoom) - viewBoxX)) pos = -(((props.activity.length - 1) * zoom) - viewBoxX); - this.zoom = baseZoom + (-moveTop / 20); - this.pos = basePos + moveLeft; - if (this.zoom < 1) this.zoom = 1; - if (this.pos > 0) this.pos = 0; - if (this.pos < -(((this.data.length - 1) * this.zoom) - this.viewBoxX)) this.pos = -(((this.data.length - 1) * this.zoom) - this.viewBoxX); + render(); + }); +} - this.render(); - }); - } +function render() { + const peak = Math.max(...props.activity.map(d => d.total)); + if (peak !== 0) { + const activity = props.activity.slice().reverse(); + pointsNote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.notes / peak)) * viewBoxY}`).join(' '); + pointsReply = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.replies / peak)) * viewBoxY}`).join(' '); + pointsRenote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.renotes / peak)) * viewBoxY}`).join(' '); + pointsTotal = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.total / peak)) * viewBoxY}`).join(' '); } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue index acbbb7a97a..7fb9f5894c 100644 --- a/packages/client/src/widgets/activity.vue +++ b/packages/client/src/widgets/activity.vue @@ -1,13 +1,13 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" class="mkw-activity"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div> <MkLoading v-if="fetching"/> <template v-else> - <XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/> - <XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/> + <XCalendar v-show="widgetProps.view === 0" :activity="[].concat(activity)"/> + <XChart v-show="widgetProps.view === 1" :activity="[].concat(activity)"/> </template> </div> </MkContainer> @@ -47,7 +47,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index 03e394b976..cdd367cc84 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-aichan"> <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> </MkContainer> </template> @@ -24,7 +24,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue index 0a5c0d614d..9fed292a69 100644 --- a/packages/client/src/widgets/aiscript.vue +++ b/packages/client/src/widgets/aiscript.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscript"> <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template> <div class="uylguesu _monospace"> @@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -94,7 +94,7 @@ const run = async () => { let ast; try { ast = parse(widgetProps.script); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: 'Syntax error :(', @@ -103,10 +103,10 @@ const run = async () => { } try { await aiscript.exec(ast); - } catch (e) { + } catch (err) { os.alert({ type: 'error', - text: e, + text: err, }); } }; diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue index a33afd6e7a..ee4e9c6423 100644 --- a/packages/client/src/widgets/button.vue +++ b/packages/client/src/widgets/button.vue @@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -73,7 +73,7 @@ const run = async () => { let ast; try { ast = parse(widgetProps.script); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: 'Syntax error :(', @@ -82,10 +82,10 @@ const run = async () => { } try { await aiscript.exec(ast); - } catch (e) { + } catch (err) { os.alert({ type: 'error', - text: e, + text: err, }); } }; diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index c6a69b3fb8..2a2b035541 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -53,7 +53,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue index 6acb10d74d..fbd2f9e899 100644 --- a/packages/client/src/widgets/clock.vue +++ b/packages/client/src/widgets/clock.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock"> <div class="vubelbmv"> <MkAnalogClock class="clock" :thickness="widgetProps.thickness"/> </div> @@ -39,7 +39,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index 62f052a692..a17ed040c9 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 5f1131dce1..a3862077bb 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable"> +<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" class="mkw-federation"> <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template> <div class="wbrkwalb"> @@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index 4a2a3cf233..8897f240bd 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -73,7 +73,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 450598f65a..8670cb2bac 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-memo"> <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template> <div class="otgbylcu"> @@ -32,7 +32,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue index 8cf29c9271..18c546ee74 100644 --- a/packages/client/src/widgets/notifications.vue +++ b/packages/client/src/widgets/notifications.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" class="mkw-notifications"> <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> <template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template> @@ -15,6 +15,7 @@ import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExp import MkContainer from '@/components/ui/container.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; +import { defineAsyncComponent } from 'vue'; const name = 'notifications'; @@ -40,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -49,7 +50,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, ); const configureNotification = () => { - os.popup(import('@/components/notification-setting-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { includingTypes: widgetProps.includingTypes, }, { done: async (res) => { diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 1746a8314e..eb3184fe9d 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -27,7 +27,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue index 8f948dc643..5d9b9e2984 100644 --- a/packages/client/src/widgets/photos.vue +++ b/packages/client/src/widgets/photos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" class="mkw-photos"> <template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template> <div class=""> @@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue index 51aa8fcf6b..b542913357 100644 --- a/packages/client/src/widgets/post-form.vue +++ b/packages/client/src/widgets/post-form.vue @@ -1,5 +1,5 @@ <template> -<XPostForm class="_panel" :fixed="true" :autofocus="false"/> +<XPostForm class="_panel mkw-postForm" :fixed="true" :autofocus="false"/> </template> <script lang="ts" setup> @@ -19,7 +19,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index 9e2e503602..fc65f11813 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-rss"> <template #header><i class="fas fa-rss-square"></i>RSS</template> <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> @@ -38,7 +38,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue index ad9e6a8b0f..00c3a10c9b 100644 --- a/packages/client/src/widgets/server-metric/cpu-mem.vue +++ b/packages/client/src/widgets/server-metric/cpu-mem.vue @@ -69,79 +69,72 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import { v4 as uuid } from 'uuid'; -export default defineComponent({ - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - viewBoxX: 50, - viewBoxY: 30, - stats: [], - cpuGradientId: uuid(), - cpuMaskId: uuid(), - memGradientId: uuid(), - memMaskId: uuid(), - cpuPolylinePoints: '', - memPolylinePoints: '', - cpuPolygonPoints: '', - memPolygonPoints: '', - cpuHeadX: null, - cpuHeadY: null, - memHeadX: null, - memHeadY: null, - cpuP: '', - memP: '' - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8) - }); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - }, - methods: { - onStats(stats) { - this.stats.push(stats); - if (this.stats.length > 50) this.stats.shift(); +const props = defineProps<{ + connection: any, + meta: any +}>(); - const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu) * this.viewBoxY]); - const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.active / this.meta.mem.total)) * this.viewBoxY]); - this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); - this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); +let viewBoxX: number = $ref(50); +let viewBoxY: number = $ref(30); +let stats: any[] = $ref([]); +const cpuGradientId = uuid(); +const cpuMaskId = uuid(); +const memGradientId = uuid(); +const memMaskId = uuid(); +let cpuPolylinePoints: string = $ref(''); +let memPolylinePoints: string = $ref(''); +let cpuPolygonPoints: string = $ref(''); +let memPolygonPoints: string = $ref(''); +let cpuHeadX: any = $ref(null); +let cpuHeadY: any = $ref(null); +let memHeadX: any = $ref(null); +let memHeadY: any = $ref(null); +let cpuP: string = $ref(''); +let memP: string = $ref(''); - this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - - this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0]; - this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1]; - this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0]; - this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1]; - - this.cpuP = (stats.cpu * 100).toFixed(0); - this.memP = (stats.mem.active / this.meta.mem.total * 100).toFixed(0); - }, - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - } - } +onMounted(() => { + props.connection.on('stats', onStats); + props.connection.on('statsLog', onStatsLog); + props.connection.send('requestLog', { + id: Math.random().toString().substr(2, 8) + }); }); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); + props.connection.off('statsLog', onStatsLog); +}); + +function onStats(connStats) { + stats.push(connStats); + if (stats.length > 50) stats.shift(); + + let cpuPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - s.cpu) * viewBoxY]); + let memPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY]); + cpuPolylinePoints = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + memPolylinePoints = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + + cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`; + memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`; + + cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0]; + cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1]; + memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0]; + memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1]; + + cpuP = (connStats.cpu * 100).toFixed(0); + memP = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0); +} + +function onStatsLog(statsLog) { + for (const revStats of [...statsLog].reverse()) { + onStats(revStats); + } +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue index 4478ee3065..baf802cb8f 100644 --- a/packages/client/src/widgets/server-metric/cpu.vue +++ b/packages/client/src/widgets/server-metric/cpu.vue @@ -9,38 +9,27 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import XPie from './pie.vue'; -export default defineComponent({ - components: { - XPie - }, - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - usage: 0, - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - }, - methods: { - onStats(stats) { - this.usage = stats.cpu; - } - } +const props = defineProps<{ + connection: any, + meta: any +}>(); + +let usage: number = $ref(0); + +function onStats(stats) { + usage = stats.cpu; +} + +onMounted(() => { + props.connection.on('stats', onStats); +}); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); }); </script> diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index 2caa73fa74..9e86b811d1 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -50,7 +50,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -65,7 +65,7 @@ os.api('server-info', {}).then(res => { }); const toggleView = () => { - if (widgetProps.view == 4) { + if (widgetProps.view === 4) { widgetProps.view = 0; } else { widgetProps.view++; diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue index a6ca7b1175..6018eb4265 100644 --- a/packages/client/src/widgets/server-metric/mem.vue +++ b/packages/client/src/widgets/server-metric/mem.vue @@ -10,46 +10,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import XPie from './pie.vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - components: { - XPie - }, - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - usage: 0, - total: 0, - used: 0, - free: 0, - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - }, - methods: { - onStats(stats) { - this.usage = stats.mem.active / this.meta.mem.total; - this.total = this.meta.mem.total; - this.used = stats.mem.active; - this.free = this.meta.mem.total - stats.mem.active; - }, - bytes - } +const props = defineProps<{ + connection: any, + meta: any +}>(); + +let usage: number = $ref(0); +let total: number = $ref(0); +let used: number = $ref(0); +let free: number = $ref(0); + +function onStats(stats) { + usage = stats.mem.active / props.meta.mem.total; + total = props.meta.mem.total; + used = stats.mem.active; + free = total - used; +} + +onMounted(() => { + props.connection.on('stats', onStats); +}); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); }); </script> diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue index 23c148eeb6..b698953f97 100644 --- a/packages/client/src/widgets/server-metric/net.vue +++ b/packages/client/src/widgets/server-metric/net.vue @@ -43,79 +43,71 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - viewBoxX: 50, - viewBoxY: 30, - stats: [], - inPolylinePoints: '', - outPolylinePoints: '', - inPolygonPoints: '', - outPolygonPoints: '', - inHeadX: null, - inHeadY: null, - outHeadX: null, - outHeadY: null, - inRecent: 0, - outRecent: 0 - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8) - }); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - }, - methods: { - onStats(stats) { - this.stats.push(stats); - if (this.stats.length > 50) this.stats.shift(); +const props = defineProps<{ + connection: any, + meta: any +}>(); - const inPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.rx))); - const outPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.tx))); +let viewBoxX: number = $ref(50); +let viewBoxY: number = $ref(30); +let stats: any[] = $ref([]); +let inPolylinePoints: string = $ref(''); +let outPolylinePoints: string = $ref(''); +let inPolygonPoints: string = $ref(''); +let outPolygonPoints: string = $ref(''); +let inHeadX: any = $ref(null); +let inHeadY: any = $ref(null); +let outHeadX: any = $ref(null); +let outHeadY: any = $ref(null); +let inRecent: number = $ref(0); +let outRecent: number = $ref(0); - const inPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * this.viewBoxY]); - const outPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * this.viewBoxY]); - this.inPolylinePoints = inPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); - this.outPolylinePoints = outPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); - - this.inPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.inPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - this.outPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.outPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - - this.inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0]; - this.inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1]; - this.outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0]; - this.outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1]; - - this.inRecent = stats.net.rx; - this.outRecent = stats.net.tx; - }, - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - }, - bytes - } +onMounted(() => { + props.connection.on('stats', onStats); + props.connection.on('statsLog', onStatsLog); + props.connection.send('requestLog', { + id: Math.random().toString().substr(2, 8) + }); }); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); + props.connection.off('statsLog', onStatsLog); +}); + +function onStats(connStats) { + stats.push(connStats); + if (stats.length > 50) stats.shift(); + + const inPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.rx))); + const outPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.tx))); + + let inPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY]); + let outPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY]); + inPolylinePoints = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + outPolylinePoints = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + + inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`; + outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`; + + inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0]; + inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1]; + outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0]; + outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1]; + + inRecent = connStats.net.rx; + outRecent = connStats.net.tx; +} + +function onStatsLog(statsLog) { + for (const revStats of [...statsLog].reverse()) { + onStats(revStats); + } +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index 7b2e539685..fd78edbe40 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -1,5 +1,5 @@ <template> -<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }"> +<div class="kvausudm _panel mkw-slideshow" :style="{ height: widgetProps.height + 'px' }"> <div @click="choose"> <p v-if="widgetProps.folderId == null"> {{ $ts.folder }} @@ -37,7 +37,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -51,7 +51,7 @@ const slideA = ref<HTMLElement>(); const slideB = ref<HTMLElement>(); const change = () => { - if (images.value.length == 0) return; + if (images.value.length === 0) return; const index = Math.floor(Math.random() * images.value.length); const img = `url(${ images.value[index].url })`; diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index 34e3b20e36..3bcad1ae29 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true"> +<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" class="mkw-timeline"> <template #header> <button class="_button" @click="choose"> <i v-if="widgetProps.src === 'home'" class="fas fa-home"></i> @@ -63,7 +63,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -103,19 +103,19 @@ const choose = async (ev) => { os.popupMenu([{ text: i18n.ts._timelines.home, icon: 'fas fa-home', - action: () => { setSrc('home') } + action: () => { setSrc('home'); } }, { text: i18n.ts._timelines.local, icon: 'fas fa-comments', - action: () => { setSrc('local') } + action: () => { setSrc('local'); } }, { text: i18n.ts._timelines.social, icon: 'fas fa-share-alt', - action: () => { setSrc('social') } + action: () => { setSrc('social'); } }, { text: i18n.ts._timelines.global, icon: 'fas fa-globe', - action: () => { setSrc('global') } + action: () => { setSrc('global'); } }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { menuOpened.value = false; }); diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index a34710eae7..9680f1c892 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-trends"> <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template> <div class="wbrkwala"> @@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts index 81239bfb3b..9626d01619 100644 --- a/packages/client/src/widgets/widget.ts +++ b/packages/client/src/widgets/widget.ts @@ -13,7 +13,7 @@ export type WidgetComponentProps<P extends Record<string, unknown>> = { }; export type WidgetComponentEmits<P extends Record<string, unknown>> = { - (e: 'updateProps', props: P); + (ev: 'updateProps', props: P); }; export type WidgetComponentExpose = { @@ -45,7 +45,7 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default: }, { deep: true, immediate: true, }); const save = throttle(3000, () => { - emit('updateProps', widgetProps) + emit('updateProps', widgetProps); }); const configure = async () => { diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index b44cf2f895..f7320a7251 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -18,6 +18,9 @@ "strictNullChecks": true, "experimentalDecorators": true, "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "useDefineForClassFields": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"], @@ -26,14 +29,17 @@ "node_modules/@types", "@types", ], + "types": [ + "vite/client", + ], "lib": [ "esnext", - "dom", - "webworker" + "dom" ] }, "compileOnSave": false, "include": [ + ".eslintrc.js", "./**/*.ts", "./**/*.vue" ] diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts new file mode 100644 index 0000000000..af13e646c6 --- /dev/null +++ b/packages/client/vite.config.ts @@ -0,0 +1,72 @@ +import * as fs from 'fs'; +import pluginVue from '@vitejs/plugin-vue'; +import pluginJson5 from './vite.json5'; +import { defineConfig } from 'vite'; + +import locales from '../../locales'; +import meta from '../../package.json'; + +const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; + +export default defineConfig(({ command, mode }) => { + fs.mkdirSync(__dirname + '/../../built', { recursive: true }); + fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8'); + + return { + base: '/assets/', + + plugins: [ + pluginVue({ + reactivityTransform: true, + }), + pluginJson5(), + ], + + resolve: { + extensions, + alias: { + '@/': __dirname + '/src/', + '/client-assets/': __dirname + '/assets/', + '/static-assets/': __dirname + '/../backend/assets/', + }, + }, + + define: { + _VERSION_: JSON.stringify(meta.version), + _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _ENV_: JSON.stringify(process.env.NODE_ENV), + _DEV_: process.env.NODE_ENV !== 'production', + _PERF_PREFIX_: JSON.stringify('Misskey:'), + _DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'), + _DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'), + _DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'), + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + }, + + build: { + target: [ + 'chrome100', + 'firefox100', + 'safari15', + ], + manifest: 'manifest.json', + rollupOptions: { + input: { + app: './src/init.ts', + }, + output: { + manualChunks: { + vue: ['vue', 'vue-router'], + }, + }, + }, + cssCodeSplit: true, + outDir: __dirname + '/../../built/_client_dist_', + assetsDir: '.', + emptyOutDir: false, + sourcemap: process.env.NODE_ENV !== 'production', + reportCompressedSize: false, + }, + } +}); diff --git a/packages/client/vite.json5.ts b/packages/client/vite.json5.ts new file mode 100644 index 0000000000..693ee7be06 --- /dev/null +++ b/packages/client/vite.json5.ts @@ -0,0 +1,38 @@ +// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json + +import JSON5 from 'json5'; +import { Plugin } from 'rollup'; +import { createFilter, dataToEsm } from '@rollup/pluginutils'; +import { RollupJsonOptions } from '@rollup/plugin-json'; + +export default function json5(options: RollupJsonOptions = {}): Plugin { + const filter = createFilter(options.include, options.exclude); + const indent = 'indent' in options ? options.indent : '\t'; + + return { + name: 'json5', + + // eslint-disable-next-line no-shadow + transform(json, id) { + if (id.slice(-6) !== '.json5' || !filter(id)) return null; + + try { + const parsed = JSON5.parse(json); + return { + code: dataToEsm(parsed, { + preferConst: options.preferConst, + compact: options.compact, + namedExports: options.namedExports, + indent + }), + map: { mappings: '' } + }; + } catch (err) { + const message = 'Could not parse JSON file'; + const position = parseInt(/[\d]/.exec(err.message)[0], 10); + this.warn({ message, id, position }); + return null; + } + } + }; +} diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js deleted file mode 100644 index a50851e17f..0000000000 --- a/packages/client/webpack.config.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * webpack configuration - */ - -const fs = require('fs'); -const webpack = require('webpack'); -const { VueLoaderPlugin } = require('vue-loader'); - -class WebpackOnBuildPlugin { - constructor(callback) { - this.callback = callback; - } - - apply(compiler) { - compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback); - } -} - -const isProduction = process.env.NODE_ENV === 'production'; - -const locales = require('../../locales'); -const meta = require('../../package.json'); - -const postcss = { - loader: 'postcss-loader', - options: { - postcssOptions: { - plugins: [ - require('cssnano')({ - preset: 'default' - }) - ] - } - }, -}; - -module.exports = { - entry: { - app: './src/init.ts', - sw: './src/sw/sw.ts' - }, - module: { - rules: [{ - test: /\.vue$/, - exclude: /node_modules/, - use: [{ - loader: 'vue-loader', - options: { - cssSourceMap: false, - reactivityTransform: true, - compilerOptions: { - preserveWhitespace: false - } - } - }] - }, { - test: /\.scss?$/, - exclude: /node_modules/, - oneOf: [{ - resourceQuery: /module/, - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - modules: true, - esModule: false, // TODO: trueใซใใใจๅฃใใใVue3็งป่กใฎๆใซใฏtrueใซใงใใใใใใใชใ - url: false, - } - }, postcss, { - loader: 'sass-loader', - options: { - implementation: require('sass'), - sassOptions: { - fiber: false - } - } - }] - }, { - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - url: false, - esModule: false, // TODO: trueใซใใใจๅฃใใใVue3็งป่กใฎๆใซใฏtrueใซใงใใใใใใใชใ - } - }, postcss, { - loader: 'sass-loader', - options: { - implementation: require('sass'), - sassOptions: { - fiber: false - } - } - }] - }] - }, { - test: /\.css$/, - oneOf: [{ - resourceQuery: /module/, - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - modules: true, - esModule: false, // TODO: trueใซใใใจๅฃใใใVue3็งป่กใฎๆใซใฏtrueใซใงใใใใใใใชใ - } - }, postcss] - }, { - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - esModule: false, // TODO: trueใซใใใจๅฃใใใVue3็งป่กใฎๆใซใฏtrueใซใงใใใใใใใชใ - } - }, postcss] - }] - }, { - test: /\.svg$/, - use: [ - 'vue-loader', - 'vue-svg-loader', - ], - }, { - test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/, - type: 'asset/resource' - }, { - test: /\.json5$/, - loader: 'json5-loader', - options: { - esModule: false, - }, - type: 'javascript/auto' - }, { - test: /\.ts$/, - exclude: /node_modules/, - use: [{ - loader: 'ts-loader', - options: { - happyPackMode: true, - transpileOnly: true, - configFile: __dirname + '/tsconfig.json', - appendTsSuffixTo: [/\.vue$/] - } - }] - }] - }, - plugins: [ - new webpack.ProgressPlugin({}), - new webpack.DefinePlugin({ - _VERSION_: JSON.stringify(meta.version), - _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), - _ENV_: JSON.stringify(process.env.NODE_ENV), - _DEV_: process.env.NODE_ENV !== 'production', - _PERF_PREFIX_: JSON.stringify('Misskey:'), - _DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'), - _DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'), - _DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'), - __VUE_OPTIONS_API__: true, - __VUE_PROD_DEVTOOLS__: false, - }), - new VueLoaderPlugin(), - new WebpackOnBuildPlugin(() => { - fs.mkdirSync(__dirname + '/../../built', { recursive: true }); - fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8'); - }), - ], - output: { - path: __dirname + '/../../built/_client_dist_', - filename: `[name].${meta.version}.js`, - publicPath: `/assets/`, - pathinfo: false, - }, - resolve: { - extensions: [ - '.js', '.ts', '.json' - ], - alias: { - '@': __dirname + '/src/', - } - }, - resolveLoader: { - modules: ['node_modules'] - }, - experiments: { - topLevelAwait: true - }, - devtool: false, //'source-map', - mode: isProduction ? 'production' : 'development' -}; diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index a4ac6d8712..796c72304a 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -2,27 +2,11 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - "@babel/helper-validator-identifier@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/highlight@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" - integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/parser@^7.16.4": version "7.16.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" @@ -56,6 +40,105 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@cropper/element-canvas@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-canvas/-/element-canvas-2.0.0-beta.tgz#9501e6a2512a78c7503f2974b1fc65f90c7fecca" + integrity sha512-cKbox0AsUx3pMCjT7mQZx3i5FoZTR/Lzz9awuRR8/EciViMN4KkfodGHWSUrIX3zSr0fECsrb2CyNKV8DKZdpQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-crosshair@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-crosshair/-/element-crosshair-2.0.0-beta.tgz#9d6ee1e6ed90196b6d4d2425f84909b83ffc66df" + integrity sha512-V58xxH3+8TrT9PrUzNouRhcyucyX/xBV5hBv03g0zCu09C5p0BZjrhaPo3hkt8oQvnhYT9SbMTe+k5hIoZgkbQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-grid@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-grid/-/element-grid-2.0.0-beta.tgz#af6f3fce213307403ad83d9935839bde39c9beeb" + integrity sha512-F+qVLrjuHjJbaut1Gd6qSruMqYOHudhDB/r0dcLtnRW4b1yPd/QyhM5F0KLtCX7Lh6GUvpz2V9Vb/EYQLZuOkw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-handle@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-handle/-/element-handle-2.0.0-beta.tgz#bd55667e133df402616d44a694110fd0e61eef0b" + integrity sha512-Ty12mLpiUM8XRGQN0lRNB7TKP5SOXbTWaW2Uvli1Tu3Y6iLTtXUvs2VZ/fGR8XvhB7v7Lvo+OPfzuxIRx4gwKg== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-image@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-image/-/element-image-2.0.0-beta.tgz#170dbdfbeef75de2f2c0089d4739ad980d69390a" + integrity sha512-CrHEMBo5svjj72qePBPGV4ut70RTI6n5U2k2YKcZihHSNU2h6SUEx8zkN8lNIgelsv2Bpb/PvSd1eu26BrJbtA== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-selection@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-selection/-/element-selection-2.0.0-beta.tgz#7e1e498773bc26bb09ddaf09b0cafbe5b359ed7b" + integrity sha512-MEK+pn2Bma5cXf1N9mC3fRKNvzi6Aj9V2TdhaCl6KdOn6Bp10a+SR8y555MXd80zzFAU/eR1e7TMTyJiPRJFcw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-shade@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-shade/-/element-shade-2.0.0-beta.tgz#55400aec3e352d959a706bfff1b82afca955d33e" + integrity sha512-vfKTTkRFio/bi0ueIbdyg2ukhS35/ufsgA13dfzOgkyUT/TUsqTLONNJA2fxO0WLKSajTtvrl1ShdrSXE+EKCQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-viewer@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-viewer/-/element-viewer-2.0.0-beta.tgz#9a83b670f5cc667d7fc0071f08a1476817e0ed4e" + integrity sha512-ZsqdOWJ8OIrK1JR00ibmYrvVMYQVFXOudXezYtf8C5lc7DdtN4elmjVOfLQQM2kxG0WvflIVo6oqqyOzFnsAFg== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element/-/element-2.0.0-beta.tgz#7833a92471a16e8860530e10658add42e8781959" + integrity sha512-seS8oDe2+Vpsy+yyqUIHzjIP6WUQRxwhFjLml/s2e+L6jF9o+g0KHzLJkBCV/ASKBnyb00aLjAt0dBXPLW/KgQ== + dependencies: + "@cropper/utils" "^2.0.0-beta" + +"@cropper/elements@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/elements/-/elements-2.0.0-beta.tgz#e73a4edaeff7e41dcca8d096bd1bc2bdc6a376e9" + integrity sha512-Huyptek2Q6141fRiuejhOyec/viX4zmUeMnpi+5h7OBuorTYUowZ823mmfgBZ4bb7+VPdAl79vUECV9EYq/ciw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-crosshair" "^2.0.0-beta" + "@cropper/element-grid" "^2.0.0-beta" + "@cropper/element-handle" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/element-shade" "^2.0.0-beta" + "@cropper/element-viewer" "^2.0.0-beta" + +"@cropper/utils@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/utils/-/utils-2.0.0-beta.tgz#7290b03c8c1dc7a2f33406c8aecc80b339425f0e" + integrity sha512-Bb3hCyHK2w0l0i8OtRw6C9Q5ytUC5qN+l+kx7F3GiAAFZMX7jGyfPB0uLiZ2TwDm5mosnWjyLVXmCGDcTUnYaQ== + "@cypress/request@^2.88.10": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" @@ -88,34 +171,29 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@discordapp/twemoji@13.1.1": - version "13.1.1" - resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" - integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== +"@discordapp/twemoji@14.0.2": + version "14.0.2" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837" + integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA== dependencies: fs-extra "^8.0.1" jsonfile "^5.0.0" - twemoji-parser "13.1.0" + twemoji-parser "14.0.0" universalify "^0.1.2" -"@discoveryjs/json-ext@^0.5.0": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" - integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== - -"@eslint/eslintrc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" - integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" + espree "^9.3.2" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@fortawesome/fontawesome-free@6.1.1": @@ -170,6 +248,29 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@rollup/plugin-alias@3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz#a5d267548fe48441f34be8323fb64d1d4a1b3fdf" + integrity sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw== + dependencies: + slash "^3.0.0" + +"@rollup/plugin-json@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" + integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + +"@rollup/pluginutils@^3.0.8": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@sideway/address@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" @@ -198,16 +299,6 @@ stringz "2.1.0" uuid "7.0.3" -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -218,39 +309,10 @@ resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== -"@types/eslint-scope@^3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" - integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.0.tgz#eb5c5b575237334df24c53195e37b53d66478d7b" - integrity sha512-LpUXkr7fnmPXWGxB0ZuLEzNeTURuHPavkC5zuU4sg62/TgL5ZEjamr5Y8b6AftwHtx2bPJasI+CL0TT2JwQ7aA== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^0.0.46": - version "0.0.46" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" - integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/events@*": version "3.0.0" @@ -309,21 +371,6 @@ resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== -"@types/json-schema@*": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/json-schema@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" - integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== - -"@types/json-schema@^7.0.7": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== - "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -349,10 +396,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mocha@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5" - integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg== +"@types/mocha@9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node@*": version "16.6.2" @@ -371,26 +418,11 @@ dependencies: "@types/node" "*" -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/parse5@6.0.3": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" - integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== - "@types/punycode@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== - "@types/qrcode@1.4.2": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" @@ -418,33 +450,16 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@^1": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4" - integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ== - -"@types/throttle-debounce@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" - integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== +"@types/throttle-debounce@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz#8208087f0af85107bcc681c50fa837fc9505483e" + integrity sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw== "@types/tinycolor2@1.4.3": version "1.4.3" resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== -"@types/uglify-js@*": - version "3.9.0" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.0.tgz#4490a140ca82aa855ad68093829e7fd6ae94ea87" - integrity sha512-3ZcoyPYHVOCcLpnfZwD47KFLr8W/mpUcgjpf1M4Q78TMJIw7KMAHSjiCLJp1z3ZrBR9pTLbe191O0TldFK5zcw== - dependencies: - source-map "^0.6.1" - "@types/undertaker-registry@*": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz#4306d4a03d7acedb974b66530832b90729e1d1da" @@ -479,44 +494,6 @@ "@types/expect" "^1.20.4" "@types/node" "*" -"@types/webpack-sources@*": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141" - integrity sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.6.1" - -"@types/webpack-stream@3.2.12": - version "3.2.12" - resolved "https://registry.yarnpkg.com/@types/webpack-stream/-/webpack-stream-3.2.12.tgz#cf13e64067a662a7acd8cd0524b3f64c86b0ecb6" - integrity sha512-znMUl4kKT0V0SwkUgRgwUNSAO7J5I/jdTCBNy3utkCsgMJ3IHp4FBTDwsQC+tfQ73TWeKIH05QNmbUYmeGThGw== - dependencies: - "@types/node" "*" - "@types/webpack" "^4" - -"@types/webpack@5.28.0": - version "5.28.0" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" - integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== - dependencies: - "@types/node" "*" - tapable "^2.2.0" - webpack "^5" - -"@types/webpack@^4": - version "4.41.27" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.27.tgz#f47da488c8037e7f1b2dbf2714fbbacb61ec0ffc" - integrity sha512-wK/oi5gcHi72VMTbOaQ70VcDxSQ1uX8S2tukBK9ARuGXrYM/+u4ou73roc7trXDNmCxCoerE8zruQqX/wuHszA== - dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" - "@types/websocket@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" @@ -538,454 +515,190 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d" - integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A== +"@typescript-eslint/eslint-plugin@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758" + integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/type-utils" "5.18.0" - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/type-utils" "5.27.1" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" -"@typescript-eslint/type-utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74" - integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA== +"@typescript-eslint/type-utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166" + integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw== dependencies: - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855" - integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA== +"@typescript-eslint/utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f" + integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89" - integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ== +"@vitejs/plugin-vue@2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz#fbf80cc039b82ac21a1acb0f0478de8f61fbf600" + integrity sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw== + +"@vue/compiler-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a" + integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.31" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e" - integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg== +"@vue/compiler-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5" + integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ== dependencies: - "@vue/compiler-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" -"@vue/compiler-sfc@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f" - integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ== +"@vue/compiler-sfc@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4" + integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.31" - "@vue/compiler-dom" "3.2.31" - "@vue/compiler-ssr" "3.2.31" - "@vue/reactivity-transform" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-ssr" "3.2.37" + "@vue/reactivity-transform" "3.2.37" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c" - integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw== +"@vue/compiler-ssr@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff" + integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw== dependencies: - "@vue/compiler-dom" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-dom" "3.2.37" + "@vue/shared" "3.2.37" "@vue/devtools-api@^6.0.0": version "6.0.12" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.12.tgz#7b57cce215ae9f37a86984633b3aa3d595aa5b46" integrity sha512-iO/4FIezHKXhiDBdKySCvJVh8/mZPxHpiQrTy+PXVqJZgpTPTdHy4q8GXulaY+UKEagdkBb0onxNQZ0LNiqVhw== -"@vue/reactivity-transform@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911" - integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA== +"@vue/reactivity-transform@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca" + integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd" - integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw== +"@vue/reactivity@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848" + integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A== dependencies: - "@vue/shared" "3.2.31" + "@vue/shared" "3.2.37" -"@vue/runtime-core@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a" - integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA== +"@vue/runtime-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3" + integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ== dependencies: - "@vue/reactivity" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/reactivity" "3.2.37" + "@vue/shared" "3.2.37" -"@vue/runtime-dom@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff" - integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g== +"@vue/runtime-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd" + integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw== dependencies: - "@vue/runtime-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/runtime-core" "3.2.37" + "@vue/shared" "3.2.37" csstype "^2.6.8" -"@vue/server-renderer@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141" - integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg== +"@vue/server-renderer@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc" + integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA== dependencies: - "@vue/compiler-ssr" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-ssr" "3.2.37" + "@vue/shared" "3.2.37" -"@vue/shared@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e" - integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ== - -"@webassemblyjs/ast@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" - integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" - integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" - integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" - integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" - integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" - integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== - -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== - -"@webassemblyjs/helper-wasm-section@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" - integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - -"@webassemblyjs/ieee754@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" - integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" - integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" - integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== - -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" - integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/helper-wasm-section" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-opt" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - "@webassemblyjs/wast-printer" "1.11.0" - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" - integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" - integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" - integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" - integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356" - integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg== - -"@webpack-cli/info@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea" - integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA== - dependencies: - envinfo "^7.7.3" - -"@webpack-cli/serve@^1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" - integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@vue/shared@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702" + integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw== abort-controller@3.0.0: version "3.0.0" @@ -994,40 +707,20 @@ abort-controller@3.0.0: dependencies: event-target-shim "^5.0.0" -acorn-import-assertions@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" - integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== - -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +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@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4: - version "8.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" - integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== - -acorn@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" - integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== - -acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== - -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== aggregate-error@^3.0.0: version "3.1.0" @@ -1037,12 +730,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== @@ -1069,13 +757,6 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -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.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" @@ -1097,13 +778,6 @@ arch@^2.2.0: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1161,17 +835,10 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async@^2.6.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== asynckit@^0.4.0: version "0.4.0" @@ -1241,16 +908,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big-integer@^1.6.16: - version "1.6.48" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" - integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" @@ -1271,7 +928,7 @@ blurhash@1.1.5: resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.5.tgz#3034104cd5dce5a3e5caa871ae2f0f1f2d0ab566" integrity sha512-a+LO3A2DfxTaTztsmkbLYmUzUeApi0LZuKalwbNmqAHR6HhJGMt1qSV/R3wc+w4DL28holjqO3Bg74aUGavGjg== -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1284,53 +941,47 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +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.1, 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" -broadcast-channel@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198" - integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ== +broadcast-channel@4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.13.0.tgz#21387b2602b9e9ec3b97b03bd8a8d2c198352ff6" + integrity sha512-fcDr8QNJ4SOb6jyjUNZatVNmcHtSWfW4PFcs4xIEFZAtorKCIFoEYtjIjaQ4c0jrbr/Bl8NIwOWiLSyspoAnEQ== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" + microtime "3.0.0" + oblivious-set "1.1.1" p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" +"browser-image-resizer@git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2": + version "2.2.1-misskey.2" + resolved "git+https://github.com/misskey-dev/browser-image-resizer#a58834f5fe2af9f9f31ff115121aef3de6f9d416" + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== - dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" - escalade "^3.1.1" - node-releases "^2.0.1" - picocolors "^1.0.0" - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1374,21 +1025,6 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286: - version "1.0.30001311" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz#682ef3f4e617f1a177ad943de59775ed3032e511" - integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A== - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1402,15 +1038,6 @@ chalk@4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^2.0.0, chalk@^2.4.1: - 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, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1431,20 +1058,20 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada" - integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA== +chart.js@3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" + integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== chartjs-adapter-date-fns@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== -chartjs-plugin-gradient@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.2.2.tgz#e271d8cbaa9cb52581addc99f2facc4adae40e43" - integrity sha512-fb38h1Zl5DDkHvpempZ/rY/lWg9/dgF0I56GI1dbqRj5P/ZoHSX4hx+P+5Az/JMNZ1s1/2zo5TmTTHQo8xJUXQ== +chartjs-plugin-gradient@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.5.0.tgz#907b15102ce164fc32640d43f9c3bad2f5ae3fd5" + integrity sha512-VHys58pMPNYRXngCrN5kvQZb1EiAvl/BhU3G9wNXxf2hETWiPYgN63Ud6RK1hyST+nZdZ61x4us546djZX2rYQ== chartjs-plugin-zoom@1.2.1: version "1.2.1" @@ -1458,7 +1085,7 @@ check-more-types@2.24.0, check-more-types@^2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2: +chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.3: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -1473,13 +1100,6 @@ chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.1.2" -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" - ci-info@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1532,31 +1152,6 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.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" @@ -1564,31 +1159,16 @@ color-convert@^2.0.1: 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 sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - 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== -colord@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e" - integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw== - colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -colorette@^2.0.14: - version "2.0.15" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5" - integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA== - colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -1601,31 +1181,21 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -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@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2" - integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commander@^8.0.0, commander@^8.3.0: +commander@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -1661,16 +1231,13 @@ core-util-is@1.0.2: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== +cropperjs@2.0.0-beta: + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-2.0.0-beta.tgz#bf3f9c19c426657d63c1e6dd55f635546ccec0a5" + integrity sha512-mwupI1Ct84PUynnC9S7KenCtgXiuRYAfLwzxPlJwc392iNX8fZUPP6a8gEpmRQTgvsE9Ubme1tXLM6/HLXksiQ== dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" + "@cropper/elements" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" cross-env@7.0.3: version "7.0.3" @@ -1688,162 +1255,20 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-declaration-sorter@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02" - integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg== - -css-loader@6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" - integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.7" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.5" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-select@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" - integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== - dependencies: - boolbase "^1.0.0" - css-what "^5.0.0" - domhandler "^4.2.0" - domutils "^2.6.0" - nth-check "^2.0.0" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== - dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" - integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== - -css-what@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== - 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== -cssnano-preset-default@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.7.tgz#791e3603fb8f1b46717ac53b47e3c418e950f5f3" - integrity sha512-JiKP38ymZQK+zVKevphPzNSGHSlTI+AOwlasoSRtSVMUU285O7/6uZyd5NbW92ZHp41m0sSHe6JoZosakj63uA== - dependencies: - css-declaration-sorter "^6.2.2" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.0" - postcss-discard-comments "^5.1.1" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.4" - postcss-merge-rules "^5.1.1" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.2" - postcss-minify-selectors "^5.2.0" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.0" - postcss-normalize-repeat-style "^5.1.0" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.0" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.1" - postcss-reduce-initial "^5.1.0" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.7.tgz#99858bef6c76c9240f0cdc9239570bc7db8368be" - integrity sha512-pVsUV6LcTXif7lvKKW9ZrmX+rGRzxkEdJuVJcp5ftUjWITgwam5LMZOgaTvUrWPkcORBey6he7JKb4XAJvrpKg== - dependencies: - cssnano-preset-default "^5.2.7" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== - dependencies: - css-tree "1.0.0-alpha.39" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - csstype@^2.6.8: version "2.6.13" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -cypress@9.5.3: - version "9.5.3" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e" - integrity sha512-ItelIVmqMTnKYbo1JrErhsGgQGjWOxCpHT1TfMvwnIXKXN/OSlPjEK7rbCLYDZhejQL99PmUqul7XORI24Ik0A== +cypress@10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.0.3.tgz#889b4bef863b7d1ef1b608b85b964394ad350c5f" + integrity sha512-8C82XTybsEmJC9POYSNITGUhMLCRwB9LadP0x33H+52QVoBjhsWvIzrI+ybCe0+TyxaF0D5/9IL2kSTgjqCB9A== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -1920,10 +1345,10 @@ debug@4.3.2, debug@^4.3.2: dependencies: ms "2.1.2" -debug@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4.3.4, 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" @@ -1963,7 +1388,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -1975,11 +1400,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - detect-node@2.1.0, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -2021,69 +1441,6 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-serializer@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be" - integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - entities "^2.0.0" - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== - -domhandler@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43" - integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ== - dependencies: - domelementtype "^2.2.0" - -domhandler@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" - integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== - dependencies: - domelementtype "^2.2.0" - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^2.6.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -2097,21 +1454,11 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.4.17: - version "1.4.68" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz#d79447b6bd1bec9183f166bb33d4bef0d5e4e568" - integrity sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA== - 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== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encode-utf8@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" @@ -2124,30 +1471,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.0.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz#d9deae58f9d3773b6a111a5a46831da5be5c9ac0" - integrity sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" - integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^5.9.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" - integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -2155,40 +1478,6 @@ enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== - -envinfo@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" - integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -2215,16 +1504,6 @@ es-abstract@^1.19.0, es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-module-lexer@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.0.tgz#21f4181cc8b7eee06855f1c59e6087c7bc4f77b0" - integrity sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ== - -es-module-lexer@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.0.tgz#fe4c4621977bc668e285c5f1f70ca3b451095fda" - integrity sha512-qU2eN/XHsrl3E4y7mK1wdWnyy5c8gXtCbfP6Xcsemm7fPUR1PIV1JhZfP7ojcN0Fzp69CfrS3u76h2tusvfKiQ== - 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" @@ -2260,6 +1539,132 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +esbuild-android-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64" + integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw== + +esbuild-android-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8" + integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA== + +esbuild-darwin-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46" + integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA== + +esbuild-darwin-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9" + integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ== + +esbuild-freebsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e" + integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig== + +esbuild-freebsd-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6" + integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ== + +esbuild-linux-32@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70" + integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g== + +esbuild-linux-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519" + integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q== + +esbuild-linux-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a" + integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA== + +esbuild-linux-arm@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986" + integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA== + +esbuild-linux-mips64le@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5" + integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ== + +esbuild-linux-ppc64le@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47" + integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q== + +esbuild-linux-riscv64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2" + integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ== + +esbuild-linux-s390x@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0" + integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ== + +esbuild-netbsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95" + integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q== + +esbuild-openbsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd" + integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ== + +esbuild-sunos-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b" + integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA== + +esbuild-windows-32@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1" + integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw== + +esbuild-windows-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107" + integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw== + +esbuild-windows-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54" + integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw== + +esbuild@^0.14.27: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.38.tgz#99526b778cd9f35532955e26e1709a16cca2fb30" + integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA== + optionalDependencies: + esbuild-android-64 "0.14.38" + esbuild-android-arm64 "0.14.38" + esbuild-darwin-64 "0.14.38" + esbuild-darwin-arm64 "0.14.38" + esbuild-freebsd-64 "0.14.38" + esbuild-freebsd-arm64 "0.14.38" + esbuild-linux-32 "0.14.38" + esbuild-linux-64 "0.14.38" + esbuild-linux-arm "0.14.38" + esbuild-linux-arm64 "0.14.38" + esbuild-linux-mips64le "0.14.38" + esbuild-linux-ppc64le "0.14.38" + esbuild-linux-riscv64 "0.14.38" + esbuild-linux-s390x "0.14.38" + esbuild-netbsd-64 "0.14.38" + esbuild-openbsd-64 "0.14.38" + esbuild-sunos-64 "0.14.38" + esbuild-windows-32 "0.14.38" + esbuild-windows-64 "0.14.38" + esbuild-windows-arm64 "0.14.38" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2315,17 +1720,20 @@ eslint-plugin-import@2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-vue@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.6.0.tgz#fbdf0f13f8d208a4cba752bf54042661a1aec5c3" - integrity sha512-abXiF2J18n/7ZPy9foSlJyouKf54IqpKlNvNmzhM93N0zs3QUxZG/oBd3tVPOJTKg7SlhBUtPxugpqzNbgGpQQ== +eslint-plugin-vue@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.1.0.tgz#b528941325e26a24bc5d5c5030c0a8996c36659c" + integrity sha512-EPCeInPicQ/YyfOWJDr1yfEeSNoFCMzUus107lZyYi37xejdOolNzS5MXGXp8+9bkoKZMdv/1AcZzQebME6r+g== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" + nth-check "^2.0.1" + postcss-selector-parser "^6.0.9" semver "^7.3.5" - vue-eslint-parser "^8.0.1" + vue-eslint-parser "^9.0.1" + xml-name-validator "^4.0.0" -eslint-scope@5.1.1, 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== @@ -2333,14 +1741,6 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" - integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" @@ -2361,22 +1761,17 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint-visitor-keys@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" - integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7" - integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ== +eslint@8.17.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21" + integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw== dependencies: - "@eslint/eslintrc" "^1.2.1" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2387,14 +1782,14 @@ eslint@8.13.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.1" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2403,7 +1798,7 @@ eslint@8.13.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -2412,29 +1807,15 @@ eslint@8.13.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" - integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== +espree@^9.3.1, espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.5.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.0.0" - -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== - dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" + acorn "^8.7.1" + acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -2464,6 +1845,11 @@ estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -2502,11 +1888,6 @@ eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -2537,21 +1918,6 @@ execa@5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" - integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -2559,13 +1925,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2621,6 +1980,17 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + 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" @@ -2631,11 +2001,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fastest-levenshtein@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" - integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== - fastq@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" @@ -2678,14 +2043,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-node-modules@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" - integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== - dependencies: - findup-sync "^4.0.0" - merge "^2.1.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -2701,7 +2058,7 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2709,16 +2066,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -2790,6 +2137,11 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2848,7 +2200,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2862,11 +2214,6 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - glob@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -2898,37 +2245,10 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.6.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" - integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== - dependencies: - type-fest "^0.20.2" - -globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -2944,7 +2264,19 @@ globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +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" + +graceful-fs@^4.1.6: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -2954,16 +2286,6 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" @@ -2974,17 +2296,12 @@ has-bigints@^1.0.1: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - 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-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -3008,28 +2325,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash-sum@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" - integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= - -hash-sum@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" - integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== - 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== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -3049,11 +2349,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - idb-keyval@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" @@ -3071,11 +2366,6 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.1.8: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== - ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -3094,14 +2384,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3112,11 +2394,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -3135,11 +2412,6 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - insert-text-at-cursor@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da" @@ -3154,27 +2426,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - -ip-address@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-7.1.0.tgz#4a9c699e75b51cbeb18b38de8ed216efa1a490c5" - integrity sha512-V9pWC/VJf2lsXqP7IWJ+pe3P1/HCYGBMZrrnT62niLGjAfCbeiwXMUxaeHvnVlz19O27pvXP4azs+Pj/A0x+SQ== - dependencies: - jsbn "1.1.0" - sprintf-js "1.1.2" - -ip-cidr@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375" - integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ== - dependencies: - ip-address "^7.1.0" - jsbn "^1.1.0" - ip-regex@^4.0.0, ip-regex@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -3185,11 +2436,6 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -3212,7 +2458,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== @@ -3229,13 +2475,6 @@ is-ci@^3.0.0: dependencies: ci-info "^3.1.1" -is-core-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" - integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== - dependencies: - has "^1.0.3" - is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -3300,13 +2539,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-like@^1.0.3: - version "1.0.8" - resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" - integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== - dependencies: - lodash.isfinite "^3.3.2" - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -3329,19 +2561,12 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-promise@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5: +is-regex@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -3404,44 +2629,16 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.0" -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^27.0.2: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" - integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - joi@^17.4.0: version "17.4.2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7" @@ -3458,11 +2655,6 @@ js-stringify@^1.0.2: resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= -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, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -3470,34 +2662,11 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@1.1.0, jsbn@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA= - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - 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" @@ -3518,16 +2687,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5-loader@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/json5-loader/-/json5-loader-4.0.1.tgz#6d17a1181e8f3c3d9204dca2a4ce4627306c8498" - integrity sha512-c9viNZlZTz0MTIcf/4qvek5Dz1/PU3DNCB4PwUhlEZIV3qb1bSD6vQQymlV17/Wm6ncra1aCvmIPsuRj+KfEEg== - dependencies: - json5 "^2.1.3" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -json5@2.2.1: +json5@2.2.1, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -3539,13 +2699,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -3589,28 +2742,13 @@ jstransformer@1.0.0: is-promise "^2.0.0" promise "^7.0.1" -katex@0.15.3: - version "0.15.3" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.3.tgz#08781a7ed26800b20380d959d1ffcd62bca0ec14" - integrity sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ== +katex@0.15.6: + version "0.15.6" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.6.tgz#c4e2f6ced2ac4de1ef6f737fe7c67d3026baa0e5" + integrity sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA== dependencies: commander "^8.0.0" -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== - -klona@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== - lazy-ass@1.6.0, lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -3624,16 +2762,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lilconfig@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" - integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - listr2@^3.8.3: version "3.11.0" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.0.tgz#9771b02407875aa78e73d6e0ff6541bbec0aaee9" @@ -3647,29 +2775,6 @@ listr2@^3.8.3: through "^2.3.8" wrap-ansi "^7.0.0" -loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== - -loader-utils@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -3692,16 +2797,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.isfinite@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" - integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M= - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -3712,12 +2807,7 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3764,44 +2854,24 @@ matter-js@0.18.0: resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.18.0.tgz#083ced04eb6768f7664dc7ca8948a10e46ad3ed6" integrity sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg== -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +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== -merge@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - -mfm-js@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" - integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== +mfm-js@0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2" + integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ== dependencies: - twemoji-parser "13.1.x" + twemoji-parser "14.0.x" -micromatch@^4.0.0, micromatch@^4.0.2: +micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -3809,17 +2879,28 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== +micromatch@^4.0.4: + 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" + +microtime@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/microtime/-/microtime-3.0.0.tgz#d140914bde88aa89b4f9fd2a18620b435af0f39b" + integrity sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ== + dependencies: + node-addon-api "^1.2.0" + node-gyp-build "^3.8.0" mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -3831,12 +2912,12 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" minimatch@^3.0.4: version "3.0.4" @@ -3866,39 +2947,30 @@ misskey-js@0.0.14: eventemitter3 "^4.0.7" reconnecting-websocket "^4.4.0" -mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mocha@9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" - integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.3" - debug "4.3.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.2.0" - growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "4.2.1" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.1" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -3918,33 +2990,21 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mylas@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800" - integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ== +mylas@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc" + integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= - dependencies: - big-integer "^1.6.16" - -nanoid@3.3.1, nanoid@^3.1.20, nanoid@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" - integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== +nanoid@3.3.3, nanoid@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - nested-property@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-4.0.0.tgz#a67b5a31991e701e03cdbaa6453bc5b1011bb88d" @@ -3960,26 +3020,26 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +node-addon-api@^1.2.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== + +node-gyp-build@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" + integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== + node-gyp-build@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== -node-releases@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== - 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-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -3987,17 +3047,10 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== dependencies: boolbase "^1.0.0" @@ -4016,26 +3069,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, 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.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -4046,24 +3084,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -4073,10 +3093,10 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== +oblivious-set@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.1.1.tgz#d9d38e9491d51f27a5c3ec1681d2ba40aa81e98b" + integrity sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -4135,13 +3155,6 @@ p-limit@^3.0.2: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: - 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@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -4202,26 +3215,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" - integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -4269,10 +3262,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -photoswipe@5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.4.tgz#918fd64c6b41b6a693743e5d70ee1a59747f491d" - integrity sha512-7p+VH7ELUnW9/3rCULCmyXcUCEuZwcsxvxPQYzR4wk3EaXcLCiINMCspc9Qq9AJYNsqYo1qGVL1y1Tch8uKAjw== +photoswipe@5.2.7: + version "5.2.7" + resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.7.tgz#9ff2aaf2a3e03c817ac2835dc6dee0f901e8159d" + integrity sha512-AogMba7W/O5gOtDIZ8cQuou1ltwxlaLNoZY1qi1s+kbYXpZk9D6rXxnNGAfDppl+bfe+sKLW2w2sx+3uQ8oPzg== picocolors@^1.0.0: version "1.0.0" @@ -4284,267 +3277,28 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +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.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== +plimit-lit@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3" + integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw== dependencies: - find-up "^4.0.0" + queue-lit "^1.2.7" pngjs@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== -portscanner@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" - integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== - dependencies: - async "^2.6.0" - is-number-like "^1.0.3" - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" - integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" - integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-loader@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" - integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.5" - -postcss-merge-longhand@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.4.tgz#0f46f8753989a33260efc47de9a0cdc571f2ec5c" - integrity sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.0" - -postcss-merge-rules@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" - integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" - integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== - dependencies: - browserslist "^4.16.6" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" - integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" - integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" - integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" - integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== - dependencies: - browserslist "^4.16.6" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" - integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" - integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" - integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - postcss-selector-parser@^6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" @@ -4553,55 +3307,12 @@ postcss-selector-parser@^6.0.9: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== +postcss@^8.1.10, postcss@^8.4.13: + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -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.12: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== - dependencies: - nanoid "^3.3.1" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.1.10: - version "8.2.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece" - integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.20" - source-map "^0.6.1" - -postcss@^8.4.7: - version "8.4.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032" - integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ== - dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -4615,10 +3326,10 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -prismjs@1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" - integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== +prismjs@1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== private-ip@2.3.3: version "2.3.3" @@ -4775,11 +3486,6 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - qrcode@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" @@ -4800,6 +3506,11 @@ querystring@0.2.1: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== +queue-lit@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763" + integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw== + random-seed@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd" @@ -4826,13 +3537,6 @@ readdirp@~3.3.0: dependencies: picomatch "^2.0.7" -rechoir@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" - integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== - dependencies: - resolve "^1.9.0" - reconnecting-websocket@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" @@ -4870,31 +3574,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.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-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve@^1.15.1, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -4912,14 +3596,6 @@ resolve@^1.22.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.9.0: - version "1.18.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" - integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== - dependencies: - is-core-module "^2.0.0" - path-parse "^1.0.6" - restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -4948,6 +3624,20 @@ rndstr@1.0.0: rangestr "0.0.1" seedrandom "2.4.2" +rollup@2.75.6: + version "2.75.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.6.tgz#ac4dc8600f95942a0180f61c7c9d6200e374b439" + integrity sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA== + optionalDependencies: + fsevents "~2.3.2" + +rollup@^2.59.0: + version "2.70.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d" + integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg== + optionalDependencies: + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -4992,46 +3682,20 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@12.6.0: - version "12.6.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" - integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== - dependencies: - klona "^2.0.4" - neo-async "^2.6.2" - -sass@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.0.tgz#3e407e2ebc53b12f1e35ce45efb226ea6063c7c8" - integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ== +sass@1.52.3: + version "1.52.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.3.tgz#b7cc7ffea2341ccc9a0c4fd372bf1b3f9be1b6cb" + integrity sha512-LNNPJ9lafx+j1ArtA7GyEJm9eawXN8KlA1+5dF6IZyoONg1Tyo/g+muOsENWJH/2Q1FHbbV4UwliU0cXMa/VIA== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sax@^1.2.4, sax@~1.2.4: +sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -schema-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" - integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== - dependencies: - "@types/json-schema" "^7.0.6" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.0.tgz#95986eb604f66daadeed56e379bfe7a7f963cdb9" - integrity sha512-tTEaeYkyIhEZ9uWgAjDerWov3T9MgX8dhhy2r0IGeeX4W8ngtGl1++dUve/RUqzuaASSh7shwCDJjEzthxki8w== - dependencies: - "@types/json-schema" "^7.0.7" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - seedrandom@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.2.tgz#18d78c41287d13aff8eadb29e235938b248aa9ff" @@ -5042,7 +3706,7 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -5056,32 +3720,25 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -serialize-javascript@6.0.0, serialize-javascript@^6.0.0: +semver@^7.3.6, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.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" @@ -5136,39 +3793,16 @@ sortablejs@1.10.2: resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290" integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A== -source-list-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== - -source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.1: 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.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -5181,16 +3815,6 @@ split@0.3: dependencies: through "2" -sprintf-js@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sshpk@^1.14.1: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -5206,11 +3830,6 @@ sshpk@^1.14.1: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - start-server-and-test@1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.14.0.tgz#c57f04f73eac15dd51733b551d775b40837fdde3" @@ -5245,14 +3864,6 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -5261,32 +3872,6 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -5331,34 +3916,14 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -style-loader@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" - integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== - -stylehacks@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" - integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== - dependencies: - browserslist "^4.16.6" - postcss-selector-parser "^6.0.4" - -supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: +supports-color@8.1.1, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -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.0.0, supports-color@^7.1.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== @@ -5370,90 +3935,11 @@ supports-preserve-symlinks-flag@^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== -svgo@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - syuilo-password-strength@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/syuilo-password-strength/-/syuilo-password-strength-0.0.1.tgz#08f71a8f0ecb77db649f3d9a6424510d9d945f52" integrity sha1-CPcajw7Ld9tknz2aZCRRDZ2UX1I= -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" - integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== - -terser-webpack-plugin@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" - integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== - dependencies: - jest-worker "^26.6.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.5.1" - -terser-webpack-plugin@^5.1.3: - version "5.1.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1" - integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA== - dependencies: - jest-worker "^27.0.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.0" - -terser@^5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" - integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - -terser@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784" - integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -5464,15 +3950,15 @@ textarea-caret@3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== -three@0.139.2: - version "0.139.2" - resolved "https://registry.yarnpkg.com/three/-/three-0.139.2.tgz#b110799a15736df673b9293e31653a4ac73648dd" - integrity sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg== +three@0.141.0: + version "0.141.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.141.0.tgz#16677a12b9dd0c3e1568ebad0fd09de15d5a8216" + integrity sha512-JaSDAPWuk4RTzG5BYRQm8YZbERUxTfTDVouWgHMisS2to4E5fotMS9F2zPFNOIJyEFTTQDDKPpsgZVThKU3pXA== -throttle-debounce@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ec763b1c050c3a8f73eddd2e853a720893102a40" - integrity sha512-bO2OiH++k8Z3cTNZccOJRlxY5Sk3Tx3Kz6cQl3VY5pTRcEgqbPxwEKtrC00whFAo2jIBQlaH1ZG5mtfrBef3qw== +throttle-debounce@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" + integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== throttleit@^1.0.0: version "1.0.0" @@ -5521,29 +4007,28 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -ts-loader@9.2.8: - version "9.2.8" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48" - integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw== +tsc-alias@1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa" + integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ== dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.0.0" - micromatch "^4.0.0" - semver "^7.3.4" - -tsc-alias@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.5.0.tgz#bc26f8dccf96e4ea350adc3f64ad3d2325cad967" - integrity sha512-Pb3y7ZjULKFHEV2US5dS58/hV76sE9Sn5iehiPjYqHcm/lx4eCGAJYoSmrVXQMPX+6baTnDFJD0MGOyqn94dIg== - dependencies: - chokidar "^3.5.2" - commander "^8.3.0" - find-node-modules "^2.1.2" + chokidar "^3.5.3" + commander "^9.0.0" globby "^11.0.4" - mylas "^2.1.6" + mylas "^2.1.9" normalize-path "^3.0.0" + plimit-lit "^1.2.6" -tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1: +tsconfig-paths@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== @@ -5582,12 +4067,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twemoji-parser@13.1.0, twemoji-parser@13.1.x: - version "13.1.0" - resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" - integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== - -twemoji-parser@14.0.0: +twemoji-parser@14.0.0, twemoji-parser@14.0.x: version "14.0.0" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== @@ -5626,10 +4106,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== unbox-primitive@^1.0.1: version "1.0.1" @@ -5641,11 +4121,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -5664,11 +4139,6 @@ unload@2.3.1: "@babel/runtime" "^7.6.2" detect-node "2.1.0" -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -5693,16 +4163,6 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - uuid@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" @@ -5737,72 +4197,58 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite@2.9.10: + version "2.9.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.10.tgz#f574d96655622c2e0fbc662edd0ed199c60fe91a" + integrity sha512-TwZRuSMYjpTurLqXspct+HZE7ONiW9d+wSWgvADGxhDPPyoIcNywY+RX4ng+QpK30DCa1l/oZgi2PLZDibhzbQ== + dependencies: + esbuild "^0.14.27" + postcss "^8.4.13" + resolve "^1.22.0" + rollup "^2.59.0" + optionalDependencies: + fsevents "~2.3.2" + void-elements@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= -vue-eslint-parser@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13" - integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A== +vue-eslint-parser@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.2.tgz#d2535516f3f55adb387939427fe741065eb7948a" + integrity sha512-uCPQwTGjOtAYrwnU+76pYxalhjsh7iFBsHwBqDHiOPTxtICDaraO4Szw54WFTNZTAEsgHHzqFOu1mmnBOBRzDA== dependencies: - debug "^4.3.2" - eslint-scope "^6.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" + debug "^4.3.4" + eslint-scope "^7.1.1" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" lodash "^4.17.21" - semver "^7.3.5" - -vue-loader@17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb" - integrity sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg== - dependencies: - chalk "^4.1.0" - hash-sum "^2.0.0" - loader-utils "^2.0.0" + semver "^7.3.6" vue-prism-editor@2.0.0-alpha.2: version "2.0.0-alpha.2" resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69" integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w== -vue-router@4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.14.tgz#ce2028c1c5c33e30c7287950c973f397fce1bd65" - integrity sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw== +vue-router@4.0.16: + version "4.0.16" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.16.tgz#9477beeeef36e80e04d041a1738801a55e6e862e" + integrity sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA== dependencies: "@vue/devtools-api" "^6.0.0" -vue-style-loader@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35" - integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== +vue@3.2.37: + version "3.2.37" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e" + integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ== dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" - -vue-svg-loader@0.17.0-beta.2: - version "0.17.0-beta.2" - resolved "https://registry.yarnpkg.com/vue-svg-loader/-/vue-svg-loader-0.17.0-beta.2.tgz#954b2a08b5488998dd81ec371ab5fb5ea4182ef7" - integrity sha512-iMUGJTKEcuNAG8VXOchjA8443IqEmEi2Aw6EVIHWma2cC4TUQ7Oet5Yry9IFfqXQXXvyzXz5EyttVvfRGTNH4Q== - dependencies: - loader-utils "^2.0.0" - semver "^7.3.2" - svgo "^1.3.2" - -vue@3.2.31: - version "3.2.31" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6" - integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw== - dependencies: - "@vue/compiler-dom" "3.2.31" - "@vue/compiler-sfc" "3.2.31" - "@vue/runtime-dom" "3.2.31" - "@vue/server-renderer" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-sfc" "3.2.37" + "@vue/runtime-dom" "3.2.37" + "@vue/server-renderer" "3.2.37" + "@vue/shared" "3.2.37" vuedraggable@4.0.1: version "4.0.1" @@ -5822,120 +4268,6 @@ wait-on@6.0.0: minimist "^1.2.5" rxjs "^7.1.0" -watchpack@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.0.tgz#b12248f32f0fd4799b7be0802ad1f6573a45955c" - integrity sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -watchpack@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -webpack-cli@4.9.2: - version "4.9.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d" - integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.1.1" - "@webpack-cli/info" "^1.4.1" - "@webpack-cli/serve" "^1.6.1" - colorette "^2.0.14" - commander "^7.0.0" - execa "^5.0.0" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" - webpack-merge "^5.7.3" - -webpack-merge@^5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" - integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" - integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== - dependencies: - source-list-map "^2.0.1" - source-map "^0.6.1" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.72.0: - version "5.72.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28" - integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.2" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" - webpack-sources "^3.2.3" - -webpack@^5: - version "5.33.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.33.2.tgz#c049717c9b038febf5a72fd2f53319ad59a8c1fc" - integrity sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.46" - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/wasm-edit" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - acorn "^8.0.4" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.7.0" - es-module-lexer "^0.4.0" - eslint-scope "^5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.0.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.1" - watchpack "^2.0.0" - webpack-sources "^2.1.1" - websocket@1.0.34: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" @@ -5964,25 +4296,13 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2, which@^2.0.1: +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" -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" @@ -5998,10 +4318,10 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: version "6.2.0" @@ -6026,10 +4346,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== xml-js@^1.6.11: version "1.6.11" @@ -6038,6 +4358,11 @@ xml-js@^1.6.11: dependencies: sax "^1.2.4" +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" @@ -6058,16 +4383,6 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" @@ -6128,8 +4443,3 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" - -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== diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js index 2d3356c3a6..4d9d6f2c85 100644 --- a/packages/shared/.eslintrc.js +++ b/packages/shared/.eslintrc.js @@ -68,5 +68,8 @@ module.exports = { }], 'import/no-unresolved': ['off'], 'import/no-default-export': ['warn'], + 'import/order': ['warn', { + 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + }] }, }; diff --git a/packages/sw/.eslintrc.js b/packages/sw/.eslintrc.js new file mode 100644 index 0000000000..9d56daca83 --- /dev/null +++ b/packages/sw/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + root: true, + env: { + "node": false + }, + parserOptions: { + "parser": "@typescript-eslint/parser", + tsconfigRootDir: __dirname, + //project: ['./tsconfig.json'], + }, + extends: [ + //"../shared/.eslintrc.js", + ], + globals: { + "require": false, + "_DEV_": false, + "_LANGS_": false, + "_VERSION_": false, + "_ENV_": false, + "_PERF_PREFIX_": false, + } +} diff --git a/packages/sw/.npmrc b/packages/sw/.npmrc new file mode 100644 index 0000000000..6b5f38e890 --- /dev/null +++ b/packages/sw/.npmrc @@ -0,0 +1,2 @@ +save-exact = true +package-lock = false diff --git a/packages/sw/.yarnrc b/packages/sw/.yarnrc new file mode 100644 index 0000000000..788570fcd5 --- /dev/null +++ b/packages/sw/.yarnrc @@ -0,0 +1 @@ +network-timeout 600000 diff --git a/packages/sw/build.js b/packages/sw/build.js new file mode 100644 index 0000000000..72d9db9c0f --- /dev/null +++ b/packages/sw/build.js @@ -0,0 +1,37 @@ +const esbuild = require('esbuild'); +const locales = require('../../locales'); +const meta = require('../../package.json'); +const watch = process.argv[2]?.includes('watch'); + +console.log('Starting SW building...'); + +esbuild.build({ + entryPoints: [ `${__dirname}/src/sw.ts` ], + bundle: true, + format: 'esm', + treeShaking: true, + minify: process.env.NODE_ENV === 'production', + absWorkingDir: __dirname, + outbase: `${__dirname}/src`, + outdir: `${__dirname}/../../built/_sw_dist_`, + loader: { + '.ts': 'ts' + }, + tsconfig: `${__dirname}/tsconfig.json`, + define: { + _VERSION_: JSON.stringify(meta.version), + _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _ENV_: JSON.stringify(process.env.NODE_ENV), + _DEV_: process.env.NODE_ENV !== 'production', + _PERF_PREFIX_: JSON.stringify('Misskey:'), + }, + watch: watch ? { + onRebuild(error, result) { + if (error) console.error('SW: watch build failed:', error); + else console.log('SW: watch build succeeded:', result); + }, + } : false, +}).then(result => { + if (watch) console.log('watching...'); + else console.log('done,', JSON.stringify(result)); +}); diff --git a/packages/sw/package.json b/packages/sw/package.json new file mode 100644 index 0000000000..41dfe19b85 --- /dev/null +++ b/packages/sw/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "scripts": { + "watch": "node build.js watch", + "build": "node build.js", + "lint": "eslint --quiet src/**/*.{ts}" + }, + "resolutions": {}, + "dependencies": { + "esbuild": "^0.14.13", + "idb-keyval": "^6.0.3", + "misskey-js": "0.0.14" + }, + "devDependencies": { + "eslint": "^8.2.0" + } +} diff --git a/packages/sw/src/filters/user.ts b/packages/sw/src/filters/user.ts new file mode 100644 index 0000000000..09437eb19a --- /dev/null +++ b/packages/sw/src/filters/user.ts @@ -0,0 +1,14 @@ +import * as misskey from 'misskey-js'; +import * as Acct from 'misskey-js/built/acct'; + +export const acct = (user: misskey.Acct) => { + return Acct.toString(user); +}; + +export const userName = (user: misskey.entities.User) => { + return user.name || user.username; +}; + +export const userPage = (user: misskey.Acct, path?, absolute = false) => { + return `${absolute ? origin : ''}/@${acct(user)}${(path ? `/${path}` : '')}`; +}; diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts new file mode 100644 index 0000000000..6d7ba7d524 --- /dev/null +++ b/packages/sw/src/scripts/create-notification.ts @@ -0,0 +1,237 @@ +/* + * Notification manager for SW + */ +declare var self: ServiceWorkerGlobalScope; + +import { swLang } from '@/scripts/lang'; +import { cli } from '@/scripts/operations'; +import { pushNotificationDataMap } from '@/types'; +import getUserName from '@/scripts/get-user-name'; +import { I18n } from '@/scripts/i18n'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; + +export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { + const n = await composeNotification(data); + + if (n) { + return self.registration.showNotification(...n); + } else { + console.error('Could not compose notification', data); + return createEmptyNotification(); + } +} + +async function composeNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]): Promise<[string, NotificationOptions] | null> { + if (!swLang.i18n) swLang.fetchLocale(); + const i18n = await swLang.i18n as I18n<any>; + const { t } = i18n; + switch (data.type) { + /* + case 'driveFileCreated': // TODO (Server Side) + return [t('_notification.fileUploaded'), { + body: body.name, + icon: body.url, + data + }]; + */ + case 'notification': + switch (data.body.type) { + case 'follow': + // users/showใฎๅๅฎ็พฉใswos.apiใธๅฝใฆใฏใใใฎใๅฐ้ฃใชใฎใงapiFetch.requestใ็ดๆฅไฝฟ็จ + const account = await getAccountFromId(data.userId); + if (!account) return null; + const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token); + return [t('_notification.youWereFollowed'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + actions: userDetail.isFollowing ? [] : [ + { + action: 'follow', + title: t('_notification._actions.followBack') + } + ], + }]; + + case 'mention': + return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + } + ], + }]; + + case 'reply': + return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + } + ], + }]; + + case 'renote': + return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'showUser', + title: getUserName(data.body.user) + } + ], + }]; + + case 'quote': + return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + }, + ...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [ + { + action: 'renote', + title: t('_notification._actions.renote') + } + ] : []) + ], + }]; + + case 'reaction': + return [`${data.body.reaction} ${getUserName(data.body.user)}`, { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'showUser', + title: getUserName(data.body.user) + } + ], + }]; + + case 'pollVote': + return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + }]; + + case 'pollEnded': + return [t('_notification.pollEnded'), { + body: data.body.note.text || '', + data, + }]; + + case 'receiveFollowRequest': + return [t('_notification.youReceivedFollowRequest'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'accept', + title: t('accept') + }, + { + action: 'reject', + title: t('reject') + } + ], + }]; + + case 'followRequestAccepted': + return [t('_notification.yourFollowRequestAccepted'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + }]; + + case 'groupInvited': + return [t('_notification.youWereInvitedToGroup', { userName: getUserName(data.body.user) }), { + body: data.body.invitation.group.name, + data, + actions: [ + { + action: 'accept', + title: t('accept') + }, + { + action: 'reject', + title: t('reject') + } + ], + }]; + + case 'app': + return [data.body.header || data.body.body, { + body: data.body.header && data.body.body, + icon: data.body.icon, + data + }]; + + default: + return null; + } + case 'unreadMessagingMessage': + if (data.body.groupId === null) { + return [t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.body.user) }), { + icon: data.body.user.avatarUrl, + tag: `messaging:user:${data.body.userId}`, + data, + renotify: true, + }]; + } + return [t('_notification.youGotMessagingMessageFromGroup', { name: data.body.group.name }), { + icon: data.body.user.avatarUrl, + tag: `messaging:group:${data.body.groupId}`, + data, + renotify: true, + }]; + default: + return null; + } +} + +export async function createEmptyNotification() { + return new Promise<void>(async res => { + if (!swLang.i18n) swLang.fetchLocale(); + const i18n = await swLang.i18n as I18n<any>; + const { t } = i18n; + + await self.registration.showNotification( + t('_notification.emptyPushNotificationMessage'), + { + silent: true, + tag: 'read_notification', + } + ); + + res(); + + setTimeout(async () => { + for (const n of + [ + ...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })), + ...(await self.registration.getNotifications({ tag: 'read_notification' })) + ] + ) { + n.close(); + } + }, 1000); + }); +} diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts new file mode 100644 index 0000000000..be4cfaeba4 --- /dev/null +++ b/packages/sw/src/scripts/get-account-from-id.ts @@ -0,0 +1,7 @@ +import { get } from 'idb-keyval'; + +export async function getAccountFromId(id: string) { + const accounts = await get('accounts') as { token: string; id: string; }[]; + if (!accounts) console.log('Accounts are not recorded'); + return accounts.find(e => e.id === id); +} diff --git a/packages/sw/src/scripts/get-user-name.ts b/packages/sw/src/scripts/get-user-name.ts new file mode 100644 index 0000000000..d499ea0203 --- /dev/null +++ b/packages/sw/src/scripts/get-user-name.ts @@ -0,0 +1,3 @@ +export default function(user: { name?: string | null, username: string }): string { + return user.name || user.username; +} diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts new file mode 100644 index 0000000000..3fe88e5514 --- /dev/null +++ b/packages/sw/src/scripts/i18n.ts @@ -0,0 +1,29 @@ +export class I18n<T extends Record<string, any>> { + public ts: T; + + constructor(locale: T) { + this.ts = locale; + + //#region BIND + this.t = this.t.bind(this); + //#endregion + } + + // string ใซใใฆใใใฎใฏใใใใๅบๅใใงใฎใในๆๅฎใ่จฑๅฏใใใใ + // ใชใในใใใฎใกใฝใใไฝฟใใใใlocale็ดๆฅๅ็ งใฎๆนใvueใฎใญใฃใใทใฅๅนใใฆใใใฉใผใใณในใ่ฏใใใ + public t(key: string, args?: Record<string, string>): string { + try { + let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; + + if (args) { + for (const [k, v] of Object.entries(args)) { + str = str.replace(`{${k}}`, v); + } + } + return str; + } catch (err) { + console.warn(`missing localization '${key}'`); + return key; + } + } +} diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts new file mode 100644 index 0000000000..2d05404ef9 --- /dev/null +++ b/packages/sw/src/scripts/lang.ts @@ -0,0 +1,47 @@ +/* + * Language manager for SW + */ +declare var self: ServiceWorkerGlobalScope; + +import { get, set } from 'idb-keyval'; +import { I18n } from '@/scripts/i18n'; + +class SwLang { + public cacheName = `mk-cache-${_VERSION_}`; + + public lang: Promise<string> = get('lang').then(async prelang => { + if (!prelang) return 'en-US'; + return prelang; + }); + + public setLang(newLang: string) { + this.lang = Promise.resolve(newLang); + set('lang', newLang); + return this.fetchLocale(); + } + + public i18n: Promise<I18n<any>> | null = null; + + public fetchLocale() { + return this.i18n = this._fetch(); + } + + private async _fetch() { + // Service Workerใฏไฝๅบฆใ่ตทๅใใใฎใใณใซlocaleใ่ชญใฟ่พผใใฎใงใCacheStorageใไฝฟใ + const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`; + let localeRes = await caches.match(localeUrl); + + // _DEV_ใtrueใฎๅ ดๅใฏๅธธใซๆๆฐๅ + if (!localeRes || _DEV_) { + localeRes = await fetch(localeUrl); + const clone = localeRes?.clone(); + if (!clone?.clone().ok) Error('locale fetching error'); + + caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone)); + } + + return new I18n(await localeRes.json()); + } +} + +export const swLang = new SwLang(); diff --git a/packages/sw/src/scripts/login-id.ts b/packages/sw/src/scripts/login-id.ts new file mode 100644 index 0000000000..0f9c6be4a9 --- /dev/null +++ b/packages/sw/src/scripts/login-id.ts @@ -0,0 +1,11 @@ +export function getUrlWithLoginId(url: string, loginId: string) { + const u = new URL(url, origin); + u.searchParams.append('loginId', loginId); + return u.toString(); +} + +export function getUrlWithoutLoginId(url: string) { + const u = new URL(url); + u.searchParams.delete('loginId'); + return u.toString(); +} diff --git a/packages/sw/src/scripts/notification-read.ts b/packages/sw/src/scripts/notification-read.ts new file mode 100644 index 0000000000..8433f902b4 --- /dev/null +++ b/packages/sw/src/scripts/notification-read.ts @@ -0,0 +1,50 @@ +declare var self: ServiceWorkerGlobalScope; + +import { get } from 'idb-keyval'; +import { pushNotificationDataMap } from '@/types'; +import { api } from '@/scripts/operations'; + +type Accounts = { + [x: string]: { + queue: string[], + timeout: number | null + } +}; + +class SwNotificationReadManager { + private accounts: Accounts = {}; + + public async construct() { + const accounts = await get('accounts'); + if (!accounts) Error('Accounts are not recorded'); + + this.accounts = accounts.reduce((acc, e) => { + acc[e.id] = { + queue: [], + timeout: null + }; + return acc; + }, {} as Accounts); + + return this; + } + + // ใใใทใฅ้็ฅใฎๆข่ชญใใตใผใใผใซ้ไฟก + public async read<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { + if (data.type !== 'notification' || !(data.userId in this.accounts)) return; + + const account = this.accounts[data.userId]; + + account.queue.push(data.body.id as string); + + // ๆๅพใฎๅผใณๅบใใใ200msๅพ ใฃใฆใพใจใใฆๅฆ็ใใ + if (account.timeout) clearTimeout(account.timeout); + account.timeout = setTimeout(() => { + account.timeout = null; + + api('notifications/read', data.userId, { notificationIds: account.queue }); + }, 200); + } +} + +export const swNotificationRead = (new SwNotificationReadManager()).construct(); diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts new file mode 100644 index 0000000000..02cf0d96cf --- /dev/null +++ b/packages/sw/src/scripts/operations.ts @@ -0,0 +1,70 @@ +/* + * Operations + * ๅ็จฎๆไฝ + */ +declare var self: ServiceWorkerGlobalScope; + +import * as Misskey from 'misskey-js'; +import { SwMessage, swMessageOrderType } from '@/types'; +import { acct as getAcct } from '@/filters/user'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; +import { getUrlWithLoginId } from '@/scripts/login-id'; + +export const cli = new Misskey.api.APIClient({ origin, fetch: (...args) => fetch(...args) }); + +export async function api<E extends keyof Misskey.Endpoints>(endpoint: E, userId: string, options?: Misskey.Endpoints[E]['req']) { + const account = await getAccountFromId(userId); + if (!account) return; + + return cli.request(endpoint, options, account.token); +} + +// rendered acctใใใฆใผใถใผใ้ใ +export function openUser(acct: string, loginId: string) { + return openClient('push', `/@${acct}`, loginId, { acct }); +} + +// noteIdใใใใผใใ้ใ +export function openNote(noteId: string, loginId: string) { + return openClient('push', `/notes/${noteId}`, loginId, { noteId }); +} + +export async function openChat(body: any, loginId: string) { + if (body.groupId === null) { + return openClient('push', `/my/messaging/${getAcct(body.user)}`, loginId, { body }); + } else { + return openClient('push', `/my/messaging/group/${body.groupId}`, loginId, { body }); + } +} + +// post-formใฎใชใใทใงใณใใๆ็จฟใใฉใผใ ใ้ใ +export async function openPost(options: any, loginId: string) { + // ใฏใจใชใไฝๆใใฆใใ + let url = `/share?`; + if (options.initialText) url += `text=${options.initialText}&`; + if (options.reply) url += `replyId=${options.reply.id}&`; + if (options.renote) url += `renoteId=${options.renote.id}&`; + + return openClient('post', url, loginId, { options }); +} + +export async function openClient(order: swMessageOrderType, url: string, loginId: string, query: any = {}) { + const client = await findClient(); + + if (client) { + client.postMessage({ type: 'order', ...query, order, loginId, url } as SwMessage); + return client; + } + + return self.clients.openWindow(getUrlWithLoginId(url, loginId)); +} + +export async function findClient() { + const clients = await self.clients.matchAll({ + type: 'window' + }); + for (const c of clients) { + if (c.url.indexOf('?zen') < 0) return c; + } + return null; +} diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts new file mode 100644 index 0000000000..0ba6a6e4af --- /dev/null +++ b/packages/sw/src/sw.ts @@ -0,0 +1,200 @@ +declare var self: ServiceWorkerGlobalScope; + +import { createEmptyNotification, createNotification } from '@/scripts/create-notification'; +import { swLang } from '@/scripts/lang'; +import { swNotificationRead } from '@/scripts/notification-read'; +import { pushNotificationDataMap } from '@/types'; +import * as swos from '@/scripts/operations'; +import { acct as getAcct } from '@/filters/user'; + +self.addEventListener('install', ev => { + ev.waitUntil(self.skipWaiting()); +}); + +self.addEventListener('activate', ev => { + ev.waitUntil( + caches.keys() + .then(cacheNames => Promise.all( + cacheNames + .filter((v) => v !== swLang.cacheName) + .map(name => caches.delete(name)) + )) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', ev => { + ev.respondWith( + fetch(ev.request) + .catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 })) + ); +}); + +self.addEventListener('push', ev => { + // ใฏใฉใคใขใณใๅๅพ + ev.waitUntil(self.clients.matchAll({ + includeUncontrolled: true, + type: 'window' + }).then(async <K extends keyof pushNotificationDataMap>(clients: readonly WindowClient[]) => { + const data: pushNotificationDataMap[K] = ev.data?.json(); + + switch (data.type) { + // case 'driveFileCreated': + case 'notification': + case 'unreadMessagingMessage': + // ใฏใฉใคใขใณใใใใฃใใในใใชใผใ ใซๆฅ็ถใใฆใใใจใใใใจใชใฎใง้็ฅใใชใ + if (clients.length != 0) return; + return createNotification(data); + case 'readAllNotifications': + for (const n of await self.registration.getNotifications()) { + if (n?.data?.type === 'notification') n.close(); + } + break; + case 'readAllMessagingMessages': + for (const n of await self.registration.getNotifications()) { + if (n?.data?.type === 'unreadMessagingMessage') n.close(); + } + break; + case 'readNotifications': + for (const n of await self.registration.getNotifications()) { + if (data.body?.notificationIds?.includes(n.data.body.id)) { + n.close(); + } + } + break; + case 'readAllMessagingMessagesOfARoom': + for (const n of await self.registration.getNotifications()) { + if (n.data.type === 'unreadMessagingMessage' + && ('userId' in data.body + ? data.body.userId === n.data.body.userId + : data.body.groupId === n.data.body.groupId) + ) { + n.close(); + } + } + break; + } + + return createEmptyNotification(); + })); +}); + +self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => { + ev.waitUntil((async () => { + if (_DEV_) { + console.log('notificationclick', ev.action, ev.notification.data); + } + + const { action, notification } = ev; + const data: pushNotificationDataMap[K] = notification.data; + const { userId: id } = data; + let client: WindowClient | null = null; + + switch (data.type) { + case 'notification': + switch (action) { + case 'follow': + if ('userId' in data.body) await swos.api('following/create', id, { userId: data.body.userId }); + break; + case 'showUser': + if ('user' in data.body) client = await swos.openUser(getAcct(data.body.user), id); + break; + case 'reply': + if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, id); + break; + case 'renote': + if ('note' in data.body) await swos.api('notes/create', id, { renoteId: data.body.note.id }); + break; + case 'accept': + switch (data.body.type) { + case 'receiveFollowRequest': + await swos.api('following/requests/accept', id, { userId: data.body.userId }); + break; + case 'groupInvited': + await swos.api('users/groups/invitations/accept', id, { invitationId: data.body.invitation.id }); + break; + } + break; + case 'reject': + switch (data.body.type) { + case 'receiveFollowRequest': + await swos.api('following/requests/reject', id, { userId: data.body.userId }); + break; + case 'groupInvited': + await swos.api('users/groups/invitations/reject', id, { invitationId: data.body.invitation.id }); + break; + } + break; + case 'showFollowRequests': + client = await swos.openClient('push', '/my/follow-requests', id); + break; + default: + switch (data.body.type) { + case 'receiveFollowRequest': + client = await swos.openClient('push', '/my/follow-requests', id); + break; + case 'groupInvited': + client = await swos.openClient('push', '/my/groups', id); + break; + case 'reaction': + client = await swos.openNote(data.body.note.id, id); + break; + default: + if ('note' in data.body) { + client = await swos.openNote(data.body.note.id, id); + } else if ('user' in data.body) { + client = await swos.openUser(getAcct(data.body.user), id); + } + break; + } + } + break; + case 'unreadMessagingMessage': + client = await swos.openChat(data.body, id); + break; + } + + if (client) { + client.focus(); + } + if (data.type === 'notification') { + swNotificationRead.then(that => that.read(data)); + } + + notification.close(); + + })()); +}); + +self.addEventListener('notificationclose', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { + const data: pushNotificationDataMap[K] = ev.notification.data; + + if (data.type === 'notification') { + swNotificationRead.then(that => that.read(data)); + } +}); + +self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { + ev.waitUntil((async () => { + switch (ev.data) { + case 'clear': + // Cache Storageๅ จๅ้ค + await caches.keys() + .then(cacheNames => Promise.all( + cacheNames.map(name => caches.delete(name)) + )); + return; // TODO + } + + if (typeof ev.data === 'object') { + // E.g. '[object Array]' โ 'array' + const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); + + if (otype === 'object') { + if (ev.data.msg === 'initialize') { + swLang.setLang(ev.data.lang); + } + } + } + })()); +}); diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts new file mode 100644 index 0000000000..6aa3726eac --- /dev/null +++ b/packages/sw/src/types.ts @@ -0,0 +1,31 @@ +import * as Misskey from 'misskey-js'; + +export type swMessageOrderType = 'post' | 'push'; + +export type SwMessage = { + type: 'order'; + order: swMessageOrderType; + loginId: string; + url: string; + [x: string]: any; +}; + +// Defined also @/services/push-notification.ts#L7-L14 +type pushNotificationDataSourceMap = { + notification: Misskey.entities.Notification; + unreadMessagingMessage: Misskey.entities.MessagingMessage; + readNotifications: { notificationIds: string[] }; + readAllNotifications: undefined; + readAllMessagingMessages: undefined; + readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string }; +}; + +export type pushNotificationData<K extends keyof pushNotificationDataSourceMap> = { + type: K; + body: pushNotificationDataSourceMap[K]; + userId: string; +}; + +export type pushNotificationDataMap = { + [K in keyof pushNotificationDataSourceMap]: pushNotificationData<K>; +}; diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json new file mode 100644 index 0000000000..c3a845f12a --- /dev/null +++ b/packages/sw/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmitOnError": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedParameters": false, + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, + "declaration": false, + "sourceMap": false, + "target": "es2017", + "module": "esnext", + "moduleResolution": "node", + "removeComments": false, + "noLib": false, + "strict": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "isolatedModules": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + }, + "typeRoots": [ + "node_modules/@types", + "@types", + ], + "lib": [ + "esnext", + "webworker" + ] + }, + "compileOnSave": false, + "include": [ + "./**/*.ts" + ] +} diff --git a/packages/sw/yarn.lock b/packages/sw/yarn.lock new file mode 100644 index 0000000000..e6d683bc42 --- /dev/null +++ b/packages/sw/yarn.lock @@ -0,0 +1,710 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.2.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" + integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +ajv@^6.10.0, 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-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" + +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== + +autobind-decorator@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" + integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== + +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== + +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" + +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== + +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" + +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.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== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +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" + +debug@^4.1.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + 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== + +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" + +esbuild-android-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.17.tgz#7216810cb8d5b8cd03ce70bdc241dcdd90c34755" + integrity sha512-y7EJm8ADC9qKbo/dJ2zBXwNdIILJ76tTv7JDGvOkbLT8HJXIsgbpa0NJk7iFhyvP4GpsYvXTbvEQNn0DhyBhLA== + +esbuild-darwin-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.17.tgz#1419e020f41814f8a74ce92b2dcab29a6d47e510" + integrity sha512-V2JAP8yyVbW6qR4SVXsEDqRicYM0x5niUuB05IFiE5itPI45k8j2dA2l+DtirR2SGXr+LEqgX347+2VA6eyTiA== + +esbuild-darwin-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.17.tgz#95acf1022066d48346a63ffc5e4d36a07b83c9b0" + integrity sha512-ENkSKpjF4SImyA2TdHhKiZqtYc1DkMykICe1KSBw0YNF1sentjFI6wu+CRiYMpC7REf/3TQXoems2XPqIqDMlQ== + +esbuild-freebsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.17.tgz#a3455199862110854937b05a0eecbed3e1aeec41" + integrity sha512-2i0nTNJM8ftNTvtR00vdqkru8XpHwAbkR2MBLoK2IDSzjsLStwCj+mxf6v83eVM9Abe3QA8xP+irqOdBlwDQ2g== + +esbuild-freebsd-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.17.tgz#8a70f2a36f5b0da7d2efdd6fd02aa78611007fd0" + integrity sha512-QOmRi1n+uly2G7BbMbHb86YiFA5aM7B2T96A6OF1VG57LNwXwy8LPVM0PVjl7f9cV3pE3fy3VtXPJHJo8XggTA== + +esbuild-linux-32@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.17.tgz#b7123f6e4780687e017454604d909fbe558862e9" + integrity sha512-qG5NDk7FHHUVw01rjHESON0HvigF2X80b645TUlgTKsWRlrbzzHhMCmQguA01O5PiCimKnyoxti8aJIFNHpQnQ== + +esbuild-linux-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.17.tgz#47a6b510c2f7faef595a4d6257a629e65385fdc3" + integrity sha512-De8OcmNvfNyFfQRLWbfuZqau6NpYBJxNTLP7Ls/PqQcw0HAwfaYThutY8ozHpPbKFPa7wgqabXlIC4NVSWT0/A== + +esbuild-linux-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.17.tgz#dfd9022b7215ca660d464fcb20597b88887c7e64" + integrity sha512-WDEOD/YRA4J1lxhETKZff3gRxGYqqZEiVwIOqNfvCh2YcwWU2y6UmNGZsxcuKk18wot4dAXCXQyNZgBkVUTCLw== + +esbuild-linux-arm@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.17.tgz#e6f6bb9fe52def5260d7d49b790fbec0e7c6d9cb" + integrity sha512-ZwsgFUk3gR2pEMJdh5z4Ds18fvGETgElPqmNdx1NtZTCOVlFMAwFB5u/tOR2FrXbMFv+LkGnNxPDh48PYPDz9A== + +esbuild-linux-mips64le@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.17.tgz#bceaad33ff18a822b6da0396c6497a231397b6c3" + integrity sha512-Lf4X9NB7r6imzp/11TaGs4kWL0DUn1JxI9gAAKotnKh6T8Y/0sLvZSvQS8WvSZcr0V8RRCrRZwiQqjOALUU/9g== + +esbuild-linux-ppc64le@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.17.tgz#9562f094d1e5e6c3b61b776b15a9bbd657042654" + integrity sha512-aExhxbrK7/Mh9FArdiC9MbvrQz2bGCDI8cBALKJbmhKg0h7LNt6y1E1S9GGBZ/ZXkHDvV9FFVrXXZKFVU5Qpiw== + +esbuild-linux-s390x@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.17.tgz#2963cfe62c227bbf1da64e36d4ca0b23db8008fe" + integrity sha512-b0T20rNcS7POi5YLw5dFlsiC+riobR5IfppQGn5NWer6QiIkdL1vOx9eC9CUD3z1itpkLboRAZYieZfKfhCA2Q== + +esbuild-netbsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.17.tgz#1d156023f9ae6be79b8627ab0cda2d7feb7f3a48" + integrity sha512-pFgTaAa2JF18nqNfCND9wOu1jbZ/mbDSaMxUp5fTkLlofyHhXeb5aChgXUkeipty2Pgq0OwOnxjHmiAxMI7N4g== + +esbuild-openbsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.17.tgz#3fc44102c9b65375385112f4ce5899ae5e38f349" + integrity sha512-K5+plb6gsAfBcFqB0EG4KvLbgBKslVAfEyJggicwt/QoDwQGJAzao4M6zOA4PG7LlXOwWSqv7VmSFbH+b6DyKw== + +esbuild-sunos-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.17.tgz#5bd24e7a7e863ea89d7e4eafd5364a155c9ea507" + integrity sha512-o1FINkbHRi9JB1YteOSXZdkDOmVUbmnCxRmTLkHvk8pfCFNpv/5/7ktt95teYKbEiJna2dEt3M4ckJ/+UVnW+w== + +esbuild-windows-32@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.17.tgz#8bda31c550fb6b425707114141d2c6ba034dab9b" + integrity sha512-Qutilz0I7OADWBtWrC/FD+2O/TNAkhwbZ+wIns7kF87lxIMtmqpBt3KnMk1e4F47aTrZRr0oH55Zhztd7m2PAA== + +esbuild-windows-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.17.tgz#50b42c06908d3ce9fab8f0f9673199de5d0f9cbc" + integrity sha512-b21/oRV+PHrav0HkRpKjbM2yNRVe34gAfbdMppbZFea416wa8SrjcmVfSd7n4jgqoTQG0xe+MGgOpwXtjiB3DQ== + +esbuild-windows-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.17.tgz#62d3921a810b64a03fcace76dad4db51d2128b45" + integrity sha512-4HN9E1idllewYvptcrrdfTA6DIWgg11kK0Zrv6yjxstJZLJeKxfilGBEaksLGs4Pst2rAYMx3H2vbYq7AWLQNA== + +esbuild@^0.14.13: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.17.tgz#6a634e56447aa0e90b34c42091d472d802d399e5" + integrity sha512-JLgyC6Uv31mv9T9Mm2xF1LntUMCNBSzvg2n32d8cTKZMwFr1wmMFY2FkVum98TSoEsDff0cR+Aj49H2sbBcjKQ== + optionalDependencies: + esbuild-android-arm64 "0.14.17" + esbuild-darwin-64 "0.14.17" + esbuild-darwin-arm64 "0.14.17" + esbuild-freebsd-64 "0.14.17" + esbuild-freebsd-arm64 "0.14.17" + esbuild-linux-32 "0.14.17" + esbuild-linux-64 "0.14.17" + esbuild-linux-arm "0.14.17" + esbuild-linux-arm64 "0.14.17" + esbuild-linux-mips64le "0.14.17" + esbuild-linux-ppc64le "0.14.17" + esbuild-linux-s390x "0.14.17" + esbuild-netbsd-64 "0.14.17" + esbuild-openbsd-64 "0.14.17" + esbuild-sunos-64 "0.14.17" + esbuild-windows-32 "0.14.17" + esbuild-windows-64 "0.14.17" + esbuild-windows-arm64 "0.14.17" + +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-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.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.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@^8.2.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" + integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== + dependencies: + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + 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.1.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + 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@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +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== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +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-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 sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +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" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^6.0.1: + 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@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + +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== + +idb-keyval@^6.0.3: + version "6.1.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" + integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw== + dependencies: + safari-14-idb-fix "^3.0.0" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, 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 sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + 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== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.0, is-glob@^4.0.3: + 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" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +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" + +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 sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +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" + +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== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +misskey-js@0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d" + integrity sha512-bvLx6U3OwQwqHfp/WKwIVwdvNYAAPk0+YblXyxmSG3dwlzCgBRRLcB8o6bNruUDyJgh3t73pLDcOz3myxcUmww== + dependencies: + autobind-decorator "^2.4.0" + eventemitter3 "^4.0.7" + reconnecting-websocket "^4.4.0" + +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== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +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-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 sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +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== + +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== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +reconnecting-websocket@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" + integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +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== + +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" + +safari-14-idb-fix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== + +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== + +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-json-comments@^3.1.0, 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== + +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" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +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== + +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" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +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" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/scripts/build.js b/scripts/build.js index 783af78271..608648b953 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -17,6 +17,14 @@ const execa = require('execa'); stderr: process.stderr, }); + console.log('building packages/sw ...'); + + await execa('npm', ['run', 'build'], { + cwd: __dirname + '/../packages/sw', + stdout: process.stdout, + stderr: process.stderr, + }); + console.log('build finishing ...'); await execa('npm', ['run', 'gulp'], { diff --git a/scripts/clean-all.js b/scripts/clean-all.js index 814ff3f257..456b88032b 100644 --- a/scripts/clean-all.js +++ b/scripts/clean-all.js @@ -7,6 +7,9 @@ const fs = require('fs'); fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true }); })(); diff --git a/scripts/clean.js b/scripts/clean.js index a14f1fb35b..70b9d882b5 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -3,5 +3,6 @@ const fs = require('fs'); (async () => { fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); })(); diff --git a/scripts/dev.js b/scripts/dev.js index b7dd870c41..c5dbb7b35a 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -25,6 +25,12 @@ const execa = require('execa'); stderr: process.stderr, }); + execa('npm', ['run', 'watch'], { + cwd: __dirname + '/../packages/sw', + stdout: process.stdout, + stderr: process.stderr, + }); + const start = async () => { try { await execa('npm', ['run', 'start'], { diff --git a/scripts/install-packages.js b/scripts/install-packages.js index c25063b29a..d1dea3ebe5 100644 --- a/scripts/install-packages.js +++ b/scripts/install-packages.js @@ -3,7 +3,7 @@ const execa = require('execa'); (async () => { console.log('installing dependencies of packages/backend ...'); - await execa('yarn', ['install'], { + await execa('yarn', ['--force', 'install'], { cwd: __dirname + '/../packages/backend', stdout: process.stdout, stderr: process.stderr, @@ -16,4 +16,12 @@ const execa = require('execa'); stdout: process.stdout, stderr: process.stderr, }); + + console.log('installing dependencies of packages/sw ...'); + + await execa('yarn', ['install'], { + cwd: __dirname + '/../packages/sw', + stdout: process.stdout, + stderr: process.stderr, + }); })(); diff --git a/scripts/lint.js b/scripts/lint.js index 11aa4909b1..72a63f4ba3 100644 --- a/scripts/lint.js +++ b/scripts/lint.js @@ -14,4 +14,11 @@ const execa = require('execa'); stdout: process.stdout, stderr: process.stderr, }); + + console.log('linting packages/sw ...'); + await execa('npm', ['run', 'lint'], { + cwd: __dirname + '/../packages/sw', + stdout: process.stdout, + stderr: process.stderr, + }); })(); diff --git a/yarn.lock b/yarn.lock index 2dc0d12ecf..327eac0172 100644 --- a/yarn.lock +++ b/yarn.lock @@ -194,49 +194,49 @@ dependencies: "@types/node" "*" -"@typescript-eslint/parser@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" aggregate-error@^3.0.0: version "3.1.0" @@ -382,7 +382,7 @@ arr-union@^3.1.0: array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== array-initial@^1.0.0: version "1.1.0" @@ -468,9 +468,9 @@ async-settle@^1.0.0: async-done "^1.2.2" async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== asynckit@^0.4.0: version "0.4.0" @@ -1005,12 +1005,12 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-props@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" - integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2" + integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw== dependencies: - each-props "^1.3.0" - is-plain-object "^2.0.1" + each-props "^1.3.2" + is-plain-object "^5.0.0" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1084,10 +1084,10 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -cypress@9.5.3: - version "9.5.3" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e" - integrity sha512-ItelIVmqMTnKYbo1JrErhsGgQGjWOxCpHT1TfMvwnIXKXN/OSlPjEK7rbCLYDZhejQL99PmUqul7XORI24Ik0A== +cypress@10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.0.3.tgz#889b4bef863b7d1ef1b608b85b964394ad350c5f" + integrity sha512-8C82XTybsEmJC9POYSNITGUhMLCRwB9LadP0x33H+52QVoBjhsWvIzrI+ybCe0+TyxaF0D5/9IL2kSTgjqCB9A== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -1173,6 +1173,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +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" + decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1261,7 +1268,7 @@ duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-props@^1.3.0: +each-props@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== @@ -1349,10 +1356,10 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -eslint-visitor-keys@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" - integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== esprima@^2.6.0: version "2.7.3" @@ -1506,10 +1513,10 @@ fancy-log@^1.3.2: parse-node-version "^1.0.0" time-stamp "^1.0.0" -fast-glob@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1826,16 +1833,16 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +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.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" glogg@^1.0.0: @@ -2001,9 +2008,9 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== html-comment-regex@^1.1.0: version "1.1.2" @@ -2034,10 +2041,10 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== indent-string@^4.0.0: version "4.0.0" @@ -2276,6 +2283,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -2622,7 +2634,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +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== @@ -2969,9 +2981,9 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + 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-root-regex@^0.1.0: version "0.1.2" @@ -3658,13 +3670,20 @@ semver-greatest-satisfied-range@^1.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^7.3.2, semver@^7.3.5: +semver@^7.3.2: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4182,10 +4201,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== unc-path-regex@^0.1.2: version "0.1.2"