Merge branch 'develop'

This commit is contained in:
syuilo 2021-09-22 22:53:41 +09:00
commit 338793d891
143 changed files with 3064 additions and 1647 deletions

View file

@ -2,6 +2,7 @@
.github .github
.travis .travis
.vscode .vscode
.config
Dockerfile Dockerfile
build/ build/
built/ built/

32
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Publish Docker image
on:
release:
types: [published]
workflow_dispatch:
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: misskey/misskey
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

4
.gitignore vendored
View file

@ -9,6 +9,10 @@
/node_modules /node_modules
report.*.json report.*.json
# Cypress
cypress/screenshots
cypress/videos
# config # config
/.config/* /.config/*
!/.config/example.yml !/.config/example.yml

View file

@ -7,6 +7,28 @@
--> -->
## 12.91.0 (2021/09/22)
### Improvements
- ActivityPub: リモートユーザーのDeleteアクティビティに対応
- ActivityPub: add resolver check for blocked instance
- ActivityPub: deliverキューのメモリ使用量を削減
- API: 管理者用アカウント削除APIを実装(/admin/accounts/delete)
- リモートユーザーの削除も可能に
- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように
- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように
- リスト、アンテナタイムラインを個別ページとして分割
- UIの改善
- MFMにsparklesエフェクトを追加
- 非ログイン自は更新ダイアログを出さないように
- クライアント起動時、アップデートが利用可能な場合エラー表示およびダイアログ表示しないように
### Bugfixes
- アカウントデータのエクスポート/インポート処理ができない問題を修正
- アンテナの既読が付かない問題を修正
- popupで設定ページを表示すると、アカウントの削除ページにアクセスすることができない問題を修正
- "問題が発生しました"ウィンドウを開くと☓ボタンがなくて閉じれない問題を修正
## 12.90.1 (2021/09/05) ## 12.90.1 (2021/09/05)
### Bugfixes ### Bugfixes

View file

@ -1,42 +1,44 @@
# Contribution guide # Contribution guide
**[✨ English version available](/docs/CONTRIBUTING.en.md)** 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 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.
## Issues ## Issues
Issueを作成する前に、以下をご確認ください: Before creating an issue, please check the following:
- 重複を防ぐため、既に同様の内容のIssueが作成されていないか検索してから新しいIssueを作ってください。 - To avoid duplication, please search for similar issues before creating a new issue.
- Issueを質問に使わないでください。 - Do not use Issues as a question.
- Issueは、要望、提案、問題の報告にのみ使用してください。 - Issues should only be used to feature requests, suggestions, and report problems.
- 質問は、[Misskey Forum](https://forum.misskey.io/)や[Discord](https://discord.gg/Wp8gVStHW3)でお願いします。 - Please ask questions in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3).
## 実装をする前に ## Before implementation
機能追加やバグ修正をしたいときは、まずIssueで設計、方針をレビューしてもらいましょう(無い場合は作ってください)。このステップがないと、せっかく実装してもPRがマージされない可能性が高くなります。 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.
また、実装に取り掛かるときは当該Issueに自分をアサインしてください(自分でできない場合は他メンバーに自分をアサインしてもらうようお願いしてください)。 Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
自分が実装するという意思表示をすることで、作業がバッティングするのを防ぎます。
## PRの作成 ## Well-known branches
PRありがとうございます PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` など
- また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- [`CHANGELOG.md`](/CHANGELOG.md)に変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
- `npm run test`、`npm run lint`でぞれぞれ実施可能です。[詳細](#testing)
- UIに変更がある場合はスクリーンショットを本文内に添付してください。
ご協力ありがとうございます🤗
## ブランチ
- **`master`** branch is tracking the latest release and used for production purposes. - **`master`** branch is tracking the latest release and used for production purposes.
- **`develop`** branch is where we work for the next release. - **`develop`** branch is where we work for the next release.
- PRを作成するときは、基本的にこのブランチに向けてください。 - When you create a PR, basically target it to this branch.
- **`l10n_develop`** branch is reserved for localization management. - **`l10n_develop`** branch is reserved for localization management.
## Creating a PR
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to [`CHANGELOG.md`](/CHANGELOG.md). However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
- You can run it with `npm run test` and `npm run lint`. [See more info](#testing)
- If this PR includes UI changes, please attach a screenshot in the text.
Thanks for your cooperation 🤗
## Localization (l10n) ## Localization (l10n)
Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management. Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
You can improve our translations with your Crowdin account. You can improve our translations with your Crowdin account.

View file

@ -104,6 +104,12 @@ Related projects
- [misskey.js](https://github.com/misskey-dev/misskey.js) - Misskey SDK for JavaScript - [misskey.js](https://github.com/misskey-dev/misskey.js) - Misskey SDK for JavaScript
- [mfm.js](https://github.com/misskey-dev/mfm.js) - MFM parser - [mfm.js](https://github.com/misskey-dev/mfm.js) - MFM parser
Sponsors
----------------------------------------------------------------
<div align="center">
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank" style="display: inline-block;"><img src="https://rss3.io/assets/images/Logo.svg" alt="RSS3" style="display: inline-block; height: 60px;"></a>
</div>
:heart: Backers :heart: Backers
---------------------------------------------------------------- ----------------------------------------------------------------
<!-- PATREON_START --> <!-- PATREON_START -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

View file

@ -1,10 +1,14 @@
describe('Basic', () => { describe('Before setup instance', () => {
before(() => { beforeEach(() => {
cy.request('POST', '/api/reset-db'); cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
}); });
beforeEach(() => { afterEach(() => {
cy.reload(true); // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる
cy.wait(1000);
}); });
it('successfully loads', () => { it('successfully loads', () => {
@ -14,56 +18,172 @@ describe('Basic', () => {
it('setup instance', () => { it('setup instance', () => {
cy.visit('/'); cy.visit('/');
cy.intercept('POST', '/api/admin/accounts/create').as('signup');
cy.get('[data-cy-admin-username] input').type('admin'); cy.get('[data-cy-admin-username] input').type('admin');
cy.get('[data-cy-admin-password] input').type('admin1234'); cy.get('[data-cy-admin-password] input').type('admin1234');
cy.get('[data-cy-admin-ok]').click(); cy.get('[data-cy-admin-ok]').click();
// なぜか動かない
//cy.wait('@signup').should('have.property', 'response.statusCode');
cy.wait('@signup');
});
});
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.request('POST', '/api/admin/accounts/create', {
username: 'admin',
password: 'pass',
}).its('body').as('admin');
cy.get('@admin');
});
afterEach(() => {
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる
cy.wait(1000);
});
it('successfully loads', () => {
cy.visit('/');
}); });
it('signup', () => { it('signup', () => {
cy.visit('/'); cy.visit('/');
cy.intercept('POST', '/api/signup').as('signup');
cy.get('[data-cy-signup]').click(); cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-username] input').type('alice');
cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-password-retype] input').type('alice1234'); cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').click(); cy.get('[data-cy-signup-submit]').click();
cy.wait('@signup');
});
});
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.request('POST', '/api/admin/accounts/create', {
username: 'admin',
password: 'pass',
}).its('body').as('admin');
cy.get('@admin').then(() => {
// ユーザー作成
cy.request('POST', '/api/signup', {
username: 'alice',
password: 'alice1234',
}).its('body').as('alice');
});
cy.get('@alice');
});
afterEach(() => {
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる
cy.wait(1000);
});
it('successfully loads', () => {
cy.visit('/');
}); });
it('signin', () => { it('signin', () => {
cy.visit('/'); cy.visit('/');
cy.intercept('POST', '/api/signin').as('signin');
cy.get('[data-cy-signin]').click(); cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice'); cy.get('[data-cy-signin-username] input').type('alice');
// Enterキーでサインインできるかの確認も兼ねる // Enterキーでサインインできるかの確認も兼ねる
cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
cy.wait('@signin');
});
it('suspend', function() {
cy.request('POST', '/api/admin/suspend-user', {
i: this.admin.token,
userId: this.alice.id,
});
cy.visit('/');
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.contains('アカウントが凍結されています');
});
});
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.request('POST', '/api/admin/accounts/create', {
username: 'admin',
password: 'pass',
}).its('body').as('admin');
cy.get('@admin').then(() => {
// ユーザー作成
cy.request('POST', '/api/signup', {
username: 'alice',
password: 'alice1234',
}).its('body').as('alice');
});
cy.get('@alice').then(() => {
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.get('@signedIn');
});
afterEach(() => {
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる
cy.wait(1000);
});
it('successfully loads', () => {
cy.visit('/');
}); });
it('note', () => { it('note', () => {
cy.visit('/'); cy.visit('/');
//#region TODO: この辺はUI操作ではなくAPI操作でログインする
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice');
// Enterキーでサインインできるかの確認も兼ねる
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
//#endregion
cy.get('[data-cy-open-post-form]').click(); cy.get('[data-cy-open-post-form]').click();
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
cy.get('[data-cy-open-post-form-submit]').click(); cy.get('[data-cy-open-post-form-submit]').click();
// TODO: 投稿した文字列が画面内にあるか(=タイムラインに流れてきたか)のテスト cy.contains('Hello, Misskey!');
}); });
}); });

View file

@ -15,6 +15,7 @@ services:
- external_network - external_network
volumes: volumes:
- ./files:/misskey/files - ./files:/misskey/files
- ./.config:/misskey/.config:ro
redis: redis:
restart: always restart: always

View file

@ -1,66 +0,0 @@
# Contribution guide
:v: Thanks for your contributions :v:
** 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 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.
## Issues
Before creating an issue, please check the following:
- To avoid duplication, please search for similar issues before creating a new issue.
- Do not use Issues as a question.
- Issues should only be used to feature requests, suggestions, and report problems.
- Please ask questions in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3).
## 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.
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
## Well-known branches
- **`master`** branch is tracking the latest release and used for production purposes.
- **`develop`** branch is where we work for the next release.
- When you create a PR, basically target it to this branch.
- **`l10n_develop`** branch is reserved for localization management.
## Creating a PR
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to [`CHANGELOG.md`](/CHANGELOG.md). However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
- You can run it with `npm run test` and `npm run lint`. [See more info](#testing)
- If this PR includes UI changes, please attach a screenshot in the text.
Thanks for your cooperation 🤗
## Localization (l10n)
Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
You can improve our translations with your Crowdin account.
Your changes in Crowdin are automatically submitted as a PR (with the title "New Crowdin translations") to the repository.
The owner [@syuilo](https://github.com/syuilo) merges the PR into the develop branch before the next release.
If your language is not listed in Crowdin, please open an issue.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## Testing
- Test codes are located in [`/test`](/test).
### Run test
```
npm run test
```
#### Run specify test
```
npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT="./test/tsconfig.json" npx mocha test/foo.ts --require ts-node/register
```
### e2e tests
TODO

View file

@ -0,0 +1,28 @@
GitHub Actionsを使用してDocker Hubへpushする方法
================================================================
[/.github/workflows/docker.yml](/.github/workflows/docker.yml) に
GitHub ActionによりDocker Hubへpushするワークフローが記述されています。
オリジナルリポジトリでは、リリースされたタイミングで `latest`, `<リリース名>` それぞれのタグでDocker Hubにpushされます。
※ Docker Hub に`<ブランチ名>`のようなタグがあるかもしれませんが、こちらは自動push対象ではありません。
Fork先でこのワークフローを実行すると失敗します。
以下では、Fork先で自分のDocker Hubリポジトリにpushするようにする方法を記述します。
## 自分のDocker Hubリポジトリにpushするように設定する方法
1. Docker Hubでリポジトリを作成します。
2. ワークフローファイルの [images](https://github.com/misskey-dev/misskey/blob/53f3b779bf16abcda4f6e026c51384f3b8fbcc62/.github/workflows/docker.yml#L20) を作成したリポジトリに置き換えます。
3. GitHubにて [暗号化されたシークレット](https://docs.github.com/ja/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository) を作成します。
作成が必要なのは `DOCKER_USERNAME``DOCKER_PASSWORD` で、それぞれDocker Hubのユーザーとパスワードになります。
## pushする方法
上記設定によりリリース時に自動的にDocker Hubにpushされるようになります。
具体的には、GitHubのリリース機能でリリースしたタイミングで `latest`, `<リリース名>` それぞれのタグでDocker Hubにpushされます。
また、GitHub上から手動でpushすることも出来ます。
それを行うには、Actions => Publish Docker image => Run workflow からbranchを選択してワークフローを実行します。
ただし、この場合作成されるタグは`<ブランチ名>`になります。

View file

@ -529,6 +529,8 @@ removeAllFollowing: "Allen gefolgten Benutzern entfolgen"
removeAllFollowingDescription: "Dies entfolgt allen Benutzerkonten von {host}. Bitte führe dies durch, falls diese Instanz z.B. nicht mehr existiert." removeAllFollowingDescription: "Dies entfolgt allen Benutzerkonten von {host}. Bitte führe dies durch, falls diese Instanz z.B. nicht mehr existiert."
userSuspended: "Dieser Benutzer wurde gesperrt." userSuspended: "Dieser Benutzer wurde gesperrt."
userSilenced: "Dieser Benutzer wurde instanzweit stummgeschaltet." userSilenced: "Dieser Benutzer wurde instanzweit stummgeschaltet."
yourAccountSuspendedTitle: "Dieses Benutzerkonto ist gesperrt"
yourAccountSuspendedDescription: "Dieses Benutzerkonto wurde gesperrt, da es gegen die Nutzungsbedingungen dieses Servers verstoßen hat. Trete mit dem Betreiber in Kontakt, falls du weitere Details erfahren möchtest. Bitte erstelle kein neues Benutzerkonto."
menu: "Menü" menu: "Menü"
divider: "Trenner" divider: "Trenner"
addItem: "Element hinzufügen" addItem: "Element hinzufügen"
@ -748,7 +750,7 @@ switch: "Wechseln"
noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert." noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert."
noBotProtectionWarning: "Bot-Schutz ist nicht konfiguriert." noBotProtectionWarning: "Bot-Schutz ist nicht konfiguriert."
configure: "Konfigurieren" configure: "Konfigurieren"
postToGallery: "Beitrag zu Galerie hinzufügen" postToGallery: "Neuen Galerie-Beitrag erstellen"
gallery: "Galerie" gallery: "Galerie"
recentPosts: "Neue Beiträge" recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge" popularPosts: "Beliebte Beiträge"
@ -874,19 +876,19 @@ _mfm:
flip: "Spiegelung" flip: "Spiegelung"
flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen." flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen."
jelly: "Animation (Dehnen)" jelly: "Animation (Dehnen)"
jellyDescription: "Verleiht eine sich dehnende Animation." jellyDescription: "Verleiht dem Inhalt eine sich dehnende Animation."
tada: "Animation (Tada)" tada: "Animation (Tada)"
tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl" tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl"
jump: "Animation (Sprung)" jump: "Animation (Sprung)"
jumpDescription: "Verleiht eine springende Animation." jumpDescription: "Verleiht dem Inhalt eine springende Animation."
bounce: "Animation (Federn)" bounce: "Animation (Federn)"
bounceDescription: "Verleiht eine federnde Animation." bounceDescription: "Verleiht dem Inhalt eine federnde Animation."
shake: "Animation (Zittern)" shake: "Animation (Zittern)"
shakeDescription: "Verleiht eine zitternde Animation." shakeDescription: "Verleiht dem Inhalt eine zitternde Animation."
twitch: "Animation (Zucken)" twitch: "Animation (Zucken)"
twitchDescription: "Verleiht eine sehr stark zuckende Animation." twitchDescription: "Verleiht dem Inhalt eine sehr stark zuckende Animation."
spin: "Animation (Rotieren)" spin: "Animation (Rotieren)"
spinDescription: "Verleiht eine rotierende Animation." spinDescription: "Verleiht dem Inhalt eine rotierende Animation."
x2: "Groß" x2: "Groß"
x2Description: "Inhalte größer anzeigen." x2Description: "Inhalte größer anzeigen."
x3: "Sehr groß" x3: "Sehr groß"
@ -1125,6 +1127,10 @@ _permissions:
"write:user-groups": "Benutzergruppen bearbeiten oder löschen" "write:user-groups": "Benutzergruppen bearbeiten oder löschen"
"read:channels": "Kanäle lesen" "read:channels": "Kanäle lesen"
"write:channels": "Kanäle bedienen" "write:channels": "Kanäle bedienen"
"read:gallery": "Beiträge deiner Galerie lesen"
"write:gallery": "Deine Galerie bearbeiten"
"read:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge lesen"
"write:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge bearbeiten"
_auth: _auth:
shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?" shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?"
shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?" shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?"

View file

@ -1,7 +1,7 @@
--- ---
_lang_: "English" _lang_: "English"
headlineMisskey: "A network connected by notes" headlineMisskey: "A network connected by notes"
introMisskey: "Welcome! Misskey is an open source, decentralized microblogging service.\nCreate \"notes\" to share what is happening now, or to share it with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀" introMisskey: "Welcome! Misskey is an open source, decentralized microblogging service.\nCreate \"notes\" to share what your thoughts with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀"
monthAndDay: "{month}/{day}" monthAndDay: "{month}/{day}"
search: "Search" search: "Search"
notifications: "Notifications" notifications: "Notifications"
@ -92,7 +92,7 @@ unfollow: "Unfollow"
followRequestPending: "Pending follow request" followRequestPending: "Pending follow request"
enterEmoji: "Enter an emoji" enterEmoji: "Enter an emoji"
renote: "Renote" renote: "Renote"
unrenote: "Take back Renote" unrenote: "Take back renote"
renoted: "Renoted." renoted: "Renoted."
cantRenote: "This post can't be renoted." cantRenote: "This post can't be renoted."
cantReRenote: "A renote can't be renoted." cantReRenote: "A renote can't be renoted."
@ -136,7 +136,7 @@ settingGuide: "Recommended settings"
cacheRemoteFiles: "Cache remote files" cacheRemoteFiles: "Cache remote files"
cacheRemoteFilesDescription: "When this setting is disabled, remote files are loaded directly from the remote instance. Disabling this will decrease storage usage, but increase traffic, as thumbnails will not be generated." cacheRemoteFilesDescription: "When this setting is disabled, remote files are loaded directly from the remote instance. Disabling this will decrease storage usage, but increase traffic, as thumbnails will not be generated."
flagAsBot: "Mark this account as as bot" flagAsBot: "Mark this account as as bot"
flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot." flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot."
flagAsCat: "Mark this account as a cat" flagAsCat: "Mark this account as a cat"
flagAsCatDescription: "Enable this option to mark this account as a cat." flagAsCatDescription: "Enable this option to mark this account as a cat."
autoAcceptFollowed: "Automatically approve follow requests from users you're following" autoAcceptFollowed: "Automatically approve follow requests from users you're following"
@ -199,7 +199,7 @@ done: "Done"
processing: "Processing..." processing: "Processing..."
preview: "Preview" preview: "Preview"
default: "Default" default: "Default"
noCustomEmojis: "There are no emojis" noCustomEmojis: "There are no emoji"
noJobs: "There are no jobs" noJobs: "There are no jobs"
federating: "Federating" federating: "Federating"
blocked: "Blocked" blocked: "Blocked"
@ -213,7 +213,7 @@ instanceFollowers: "Followers of instance"
instanceUsers: "Users of this instance" instanceUsers: "Users of this instance"
changePassword: "Change password" changePassword: "Change password"
security: "Security" security: "Security"
retypedNotMatch: "The inputs does not match." retypedNotMatch: "The inputs do not match."
currentPassword: "Current password" currentPassword: "Current password"
newPassword: "New password" newPassword: "New password"
newPasswordRetype: "Retype new password" newPasswordRetype: "Retype new password"
@ -429,7 +429,7 @@ invitationCode: "Invitation code"
checking: "Checking..." checking: "Checking..."
available: "Available" available: "Available"
unavailable: "Not available" unavailable: "Not available"
usernameInvalidFormat: "You can use upper- and lowercase letters, numbers as well as underscores." usernameInvalidFormat: "You can use upper- and lowercase letters, numbers, and underscores."
tooShort: "Too short" tooShort: "Too short"
tooLong: "Too long" tooLong: "Too long"
weakPassword: "Weak password" weakPassword: "Weak password"
@ -445,7 +445,7 @@ language: "Language"
uiLanguage: "User interface language" uiLanguage: "User interface language"
groupInvited: "You've been invited to a group" groupInvited: "You've been invited to a group"
aboutX: "About {x}" aboutX: "About {x}"
useOsNativeEmojis: "Use OS native Emojis" useOsNativeEmojis: "Use OS native Emoji"
youHaveNoGroups: "You have no groups" youHaveNoGroups: "You have no groups"
joinOrCreateGroup: "Get invited to a group or create your own." joinOrCreateGroup: "Get invited to a group or create your own."
noHistory: "No history available" noHistory: "No history available"
@ -482,7 +482,7 @@ objectStorageBaseUrlDesc: "URL used as reference. Specify the URL of your CDN or
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Please specify the bucket name used at your provider." objectStorageBucketDesc: "Please specify the bucket name used at your provider."
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Files will stored under directories with this prefix." objectStoragePrefixDesc: "Files will be stored under directories with this prefix."
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '<host>' or '<host>:<port>', depending on the service you are using." objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '<host>' or '<host>:<port>', depending on the service you are using."
objectStorageRegion: "Region" objectStorageRegion: "Region"
@ -529,6 +529,8 @@ removeAllFollowing: "Unfollow all followed users"
removeAllFollowingDescription: "Executing this unfollows all accounts from {host}. Please run this if the instance e.g. no longer exists." removeAllFollowingDescription: "Executing this unfollows all accounts from {host}. Please run this if the instance e.g. no longer exists."
userSuspended: "This user has been suspended." userSuspended: "This user has been suspended."
userSilenced: "This user has been silenced." userSilenced: "This user has been silenced."
yourAccountSuspendedTitle: "This account is suspended"
yourAccountSuspendedDescription: "This account has been suspended due to breaking the server's terms of services or similar. Contact the administrator if you would like to know a more detailed reason. Please do not create a new account."
menu: "Menu" menu: "Menu"
divider: "Divider" divider: "Divider"
addItem: "Add Item" addItem: "Add Item"
@ -606,7 +608,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
other: "Other" other: "Other"
regenerateLoginToken: "Regenerate login token" regenerateLoginToken: "Regenerate login token"
regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
setMultipleBySeparatingWithSpace: "You can set multiple by separating them with spaces." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
fileIdOrUrl: "File-ID or URL" fileIdOrUrl: "File-ID or URL"
chatOpenBehavior: "Behavior of the chat window when opened" chatOpenBehavior: "Behavior of the chat window when opened"
behavior: "Behavior" behavior: "Behavior"
@ -649,7 +651,7 @@ pollVotesCount: "Number of sent poll votes"
pollVotedCount: "Number of received poll votes" pollVotedCount: "Number of received poll votes"
yes: "Yes" yes: "Yes"
no: "No" no: "No"
driveFilesCount: "Number of drive files" driveFilesCount: "Number of Drive files"
driveUsage: "Drive space usage" driveUsage: "Drive space usage"
noCrawle: "Reject crawler indexing" noCrawle: "Reject crawler indexing"
noCrawleDescription: "Ask search engines to not index your profile page, notes, Pages, etc." noCrawleDescription: "Ask search engines to not index your profile page, notes, Pages, etc."
@ -657,7 +659,7 @@ lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", yo
alwaysMarkSensitive: "Mark as NSFW by default" alwaysMarkSensitive: "Mark as NSFW by default"
loadRawImages: "Load original images instead of showing thumbnails" loadRawImages: "Load original images instead of showing thumbnails"
disableShowingAnimatedImages: "Don't play animated images" disableShowingAnimatedImages: "Don't play animated images"
verificationEmailSent: "A verification email has been sent. Please access the included link to complete verification." verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
notSet: "Not set" notSet: "Not set"
emailVerified: "Email has been verified" emailVerified: "Email has been verified"
noteFavoritesCount: "Number of favorite notes" noteFavoritesCount: "Number of favorite notes"
@ -688,7 +690,7 @@ sendErrorReportsDescription: "When turned on, detailed error information will be
myTheme: "My theme" myTheme: "My theme"
backgroundColor: "Background color" backgroundColor: "Background color"
accentColor: "Accent color" accentColor: "Accent color"
textColor: "Textfarbe" textColor: "Text color"
saveAs: "Save as..." saveAs: "Save as..."
advanced: "Advanced" advanced: "Advanced"
value: "Value" value: "Value"
@ -724,7 +726,7 @@ fullView: "Full view"
quitFullView: "Exit full view" quitFullView: "Exit full view"
addDescription: "Add description" addDescription: "Add description"
userPagePinTip: "You can display notes here by selecting \"Pin to profile\" from the menu of individual notes." userPagePinTip: "You can display notes here by selecting \"Pin to profile\" from the menu of individual notes."
notSpecifiedMentionWarning: "This note contains mentions of users not included as recipient" notSpecifiedMentionWarning: "This note contains mentions of users not included as recipients"
info: "About" info: "About"
userInfo: "User information" userInfo: "User information"
unknown: "Unknown" unknown: "Unknown"
@ -748,7 +750,7 @@ switch: "Switch"
noMaintainerInformationWarning: "Maintainer information is not configured." noMaintainerInformationWarning: "Maintainer information is not configured."
noBotProtectionWarning: "Bot protection is not configured." noBotProtectionWarning: "Bot protection is not configured."
configure: "Configure" configure: "Configure"
postToGallery: "Post to Gallery" postToGallery: "Create new gallery post"
gallery: "Gallery" gallery: "Gallery"
recentPosts: "Recent posts" recentPosts: "Recent posts"
popularPosts: "Popular posts" popularPosts: "Popular posts"
@ -778,7 +780,7 @@ whatIsNew: "Show changes"
translate: "Translate" translate: "Translate"
translatedFrom: "Translated from {x}" translatedFrom: "Translated from {x}"
accountDeletionInProgress: "Account deletion is currently in progress" accountDeletionInProgress: "Account deletion is currently in progress"
usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames can not be changed later." usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
aiChanMode: "Ai Mode" aiChanMode: "Ai Mode"
keepCw: "Keep Content Warning" keepCw: "Keep Content Warning"
pubSub: "Pub/Sub Accounts" pubSub: "Pub/Sub Accounts"
@ -810,7 +812,7 @@ _gallery:
my: "My Gallery" my: "My Gallery"
liked: "Liked Posts" liked: "Liked Posts"
like: "Like" like: "Like"
unlike: "Undo like" unlike: "Remove like"
_email: _email:
_follow: _follow:
title: "You've got a new follower" title: "You've got a new follower"
@ -850,11 +852,11 @@ _mfm:
url: "URL" url: "URL"
urlDescription: "URLs can be displayed." urlDescription: "URLs can be displayed."
link: "Link" link: "Link"
linkDescription: "Specific parts of text can be displayed as an URL." linkDescription: "Specific parts of text can be displayed as a URL."
bold: "Bold" bold: "Bold"
boldDescription: "Highlights letters by making them thicker." boldDescription: "Highlights letters by making them thicker."
small: "Small" small: "Small"
smallDescription: "Displays contents small and thin." smallDescription: "Displays content small and thin."
center: "Center" center: "Center"
centerDescription: "Displays content centered." centerDescription: "Displays content centered."
inlineCode: "Code (In-line)" inlineCode: "Code (In-line)"
@ -866,7 +868,7 @@ _mfm:
blockMath: "Math (Block)" blockMath: "Math (Block)"
blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block" blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block"
quote: "Quote" quote: "Quote"
quoteDescription: "Displays content as quote." quoteDescription: "Displays content as a quote."
emoji: "Custom Emoji" emoji: "Custom Emoji"
emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed." emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed."
search: "Search" search: "Search"
@ -896,9 +898,11 @@ _mfm:
blur: "Blur" blur: "Blur"
blurDescription: "Content can be blurred via this effect. It will be displayed clearly when hovered over." blurDescription: "Content can be blurred via this effect. It will be displayed clearly when hovered over."
font: "Font" font: "Font"
fontDescription: "Sets the font to display contents in." fontDescription: "Sets the font to display content in."
rainbow: "Rainbow" rainbow: "Rainbow"
rainbowDescription: "Makes the content appear in rainbow colors." rainbowDescription: "Makes the content appear in rainbow colors."
sparkle: "Sparkle"
sparkleDescription: "Gives content a sparkling particle effect."
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Game settings" gameSettings: "Game settings"
@ -958,8 +962,8 @@ _menuDisplay:
_wordMute: _wordMute:
muteWords: "Muted words" muteWords: "Muted words"
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition." muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
muteWordsDescription2: "Surround keywords by slashes to use regular expressions." muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
softDescription: "Hides notes fulfilling the set conditions from the timeline." softDescription: "Hide notes that fulfil the set conditions from the timeline."
hardDescription: "Prevents notes fulfilling the set conditions from being added to the timeline. In addition, these notes will not be added to the timeline even if the conditions are changed." hardDescription: "Prevents notes fulfilling the set conditions from being added to the timeline. In addition, these notes will not be added to the timeline even if the conditions are changed."
soft: "Soft" soft: "Soft"
hard: "Hard" hard: "Hard"
@ -1017,7 +1021,7 @@ _theme:
divider: "Divider" divider: "Divider"
scrollbarHandle: "Scrollbar handle" scrollbarHandle: "Scrollbar handle"
scrollbarHandleHover: "Scrollbar handle (Hover)" scrollbarHandleHover: "Scrollbar handle (Hover)"
dateLabelFg: "Text of date labels" dateLabelFg: "Date label text"
infoBg: "Information background" infoBg: "Information background"
infoFg: "Information text" infoFg: "Information text"
infoWarnBg: "Warning background" infoWarnBg: "Warning background"
@ -1090,19 +1094,19 @@ _tutorial:
_2fa: _2fa:
alreadyRegistered: "You have already registered a 2-factor authentication device." alreadyRegistered: "You have already registered a 2-factor authentication device."
registerDevice: "Register a new device" registerDevice: "Register a new device"
registerKey: "Register a new Security Key" registerKey: "Register a security key"
step1: "First, install an authentication app (such as {a} or {b}) on your device." step1: "First, install an authentication app (such as {a} or {b}) on your device."
step2: "Then, scan the QR code displayed on this screen." step2: "Then, scan the QR code displayed on this screen."
step3: "Enter the token provided by your app to finish setup." step3: "Enter the token provided by your app to finish setup."
step4: "From now, any future login attempts will ask for such a login token." 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 login process." securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account."
_permissions: _permissions:
"read:account": "View your account information" "read:account": "View your account information"
"write:account": "Edit your account information" "write:account": "Edit your account information"
"read:blocks": "View your list of blocked users" "read:blocks": "View your list of blocked users"
"write:blocks": "Edit your list of blocked users" "write:blocks": "Edit your list of blocked users"
"read:drive": "Access your drive files and folders" "read:drive": "Access your Drive files and folders"
"write:drive": "Edit or delete your drive files and folders" "write:drive": "Edit or delete your Drive files and folders"
"read:favorites": "View your list of favorites" "read:favorites": "View your list of favorites"
"write:favorites": "Edit your list of favorites" "write:favorites": "Edit your list of favorites"
"read:following": "View information on who you follow" "read:following": "View information on who you follow"
@ -1125,6 +1129,10 @@ _permissions:
"write:user-groups": "Edit or delete your user groups" "write:user-groups": "Edit or delete your user groups"
"read:channels": "Read your channels" "read:channels": "Read your channels"
"write:channels": "Modify your channels" "write:channels": "Modify your channels"
"read:gallery": "View your gallery"
"write:gallery": "Edit your gallery"
"read:gallery-likes": "View list of liked gallery posts"
"write:gallery-likes": "Edit list of liked gallery posts"
_auth: _auth:
shareAccess: "Would you like to authorize \"{name}\" to access this account?" shareAccess: "Would you like to authorize \"{name}\" to access this account?"
shareAccessAsk: "Are you sure you want to authorize this application to access your account?" shareAccessAsk: "Are you sure you want to authorize this application to access your account?"
@ -1327,23 +1335,23 @@ _rooms:
doll-ai: "Ai doll" doll-ai: "Ai doll"
banknote: "Pile of money" banknote: "Pile of money"
_pages: _pages:
newPage: "Create a page" newPage: "Create a new Page"
editPage: "Edit this page" editPage: "Edit this Page"
readPage: "Source view activated" readPage: "Source view activated"
created: "Page successfully created" created: "Page successfully created"
updated: "Page successfully edited" updated: "Page successfully edited"
deleted: "Page successfully deleted" deleted: "Page successfully deleted"
pageSetting: "Page settings" pageSetting: "Page settings"
nameAlreadyExists: "The specified page URL already exists" nameAlreadyExists: "The specified Page URL already exists"
invalidNameTitle: "The specified page URL is invalid" invalidNameTitle: "The specified Page URL is invalid"
invalidNameText: "Make sure the page title is not empty" invalidNameText: "Make sure the Page title is not empty"
editThisPage: "Edit this page" editThisPage: "Edit this Page"
viewSource: "View source" viewSource: "View source"
viewPage: "View your pages" viewPage: "View your Pages"
like: "Like" like: "Like"
unlike: "Undo like" unlike: "Remove like"
my: "My pages" my: "My Pages"
liked: "Liked pages" liked: "Liked Pages"
featured: "Featured" featured: "Featured"
inspector: "Inspector" inspector: "Inspector"
contents: "Contents" contents: "Contents"
@ -1353,10 +1361,10 @@ _pages:
url: "Page URL" url: "Page URL"
summary: "Page summary" summary: "Page summary"
alignCenter: "Center elements" alignCenter: "Center elements"
hideTitleWhenPinned: "Hide page title when pinned to profile" hideTitleWhenPinned: "Hide Page title when pinned to profile"
font: "Font" font: "Font"
fontSerif: "Serif" fontSerif: "Serif"
fontSansSerif: "Sans serif" fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Set thumbnail" eyeCatchingImageSet: "Set thumbnail"
eyeCatchingImageRemove: "Delete thumbnail" eyeCatchingImageRemove: "Delete thumbnail"
chooseBlock: "Add a block" chooseBlock: "Add a block"
@ -1571,7 +1579,7 @@ _pages:
seedRandomPick: "Randomly choose from list (with seed)" seedRandomPick: "Randomly choose from list (with seed)"
_seedRandomPick: _seedRandomPick:
arg1: "Seed" arg1: "Seed"
arg2: "Liste" arg2: "List"
DRPWPM: "Randomly choose from weighted list (Changes once a day for each user)" DRPWPM: "Randomly choose from weighted list (Changes once a day for each user)"
_DRPWPM: _DRPWPM:
arg1: "Text list" arg1: "Text list"
@ -1651,8 +1659,8 @@ _deck:
columnMargin: "Margin between columns" columnMargin: "Margin between columns"
columnHeaderHeight: "Column header height" columnHeaderHeight: "Column header height"
addColumn: "Add column" addColumn: "Add column"
swapLeft: "Swap to left" swapLeft: "Swap left"
swapRight: "Swap to right" swapRight: "Swap right"
swapUp: "Swap with above" swapUp: "Swap with above"
swapDown: "Swap with below" swapDown: "Swap with below"
stackLeft: "Stack on left column" stackLeft: "Stack on left column"

View file

@ -1,19 +1,19 @@
--- ---
_lang_: "Esperanto" _lang_: "Esperanto"
headlineMisskey: "Reto ligata per notoj" headlineMisskey: "Jen la reto konektita de notoj"
introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza mikrobloga servo.\nKreu \"noto\"n por diskonigu ke nun okazas, aŭ por dissendu pri vi. 📡\nPer la funkcio \"reago\", vi ankaŭ povas rapide esprimi vian senton pri ĉies noto. 👍\nEsploru novan mondon. 🚀" introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza mikrobloga servo.\nKreu \"noto\"n por diskonigu tion kio nun okazas, aŭ por parolu pri vi. 📡\nUzu la funkcion \"reago\" por esprimu rapide vian senton pri ies noto. 👍\nBonvole esploru novan mondon. 🚀"
monthAndDay: "{day}a/{month}" monthAndDay: "{day}a/{month}"
search: "Serĉi" search: "Serĉi"
notifications: "Sciigoj" notifications: "Sciigoj"
username: "Uzantnomo" username: "Uzantnomo"
password: "Pasvorto" password: "Pasvorto"
forgotPassword: "Ĉu vi forgesis pasvorton?" forgotPassword: "Ĉu vi forgesis pasvorton?"
fetchingAsApObject: "Informpetado de Fediverso..." fetchingAsApObject: "Informpetado de kunfederaĵo…"
ok: "Akcepteble" ok: "Akcepteble"
gotIt: "Mi komprenas" gotIt: "Mi komprenas"
cancel: "Nuligi" cancel: "Nuligi"
enterUsername: "Entajpu uzantnomon" enterUsername: "Entajpu uzantnomon"
renotedBy: "Renoto farita de {user}" renotedBy: "Noto plusendita de {user}"
noNotes: "Neniu noto!" noNotes: "Neniu noto!"
noNotifications: "Vi ne havas sciigojn." noNotifications: "Vi ne havas sciigojn."
instance: "Nodo" instance: "Nodo"
@ -23,12 +23,12 @@ otherSettings: "Aliaj agordoj"
openInWindow: "Malfermi en nova fenestro" openInWindow: "Malfermi en nova fenestro"
profile: "Profilo" profile: "Profilo"
timeline: "Templinio" timeline: "Templinio"
noAccountDescription: "Tiu uzanto ne skribis biografieton" noAccountDescription: "Ĉi tiu uzanto ne skribis vivpriskribon."
login: "Ensaluti" login: "Ensaluti"
loggingIn: "Ensalutado..." loggingIn: "Ensalutado"
logout: "Elsaluti" logout: "Elsaluti"
signup: "Krei konton" signup: "Registriĝi"
uploading: "Alŝutado..." uploading: "Alŝutado"
save: "Konservi" save: "Konservi"
users: "Uzantoj" users: "Uzantoj"
addUser: "Aldoni uzanton" addUser: "Aldoni uzanton"
@ -36,6 +36,7 @@ favorite: "Preferi"
favorites: "Preferataĵoj" favorites: "Preferataĵoj"
unfavorite: "Malpreferi" unfavorite: "Malpreferi"
favorited: "Aldonita al preferataĵoj" favorited: "Aldonita al preferataĵoj"
alreadyFavorited: "Ankoraŭ aldonita al via listo de preferaĵoj."
cantFavorite: "Ne aldonita al preferataĵoj" cantFavorite: "Ne aldonita al preferataĵoj"
pin: "Alpingli al la profilo" pin: "Alpingli al la profilo"
unpin: "Depingli" unpin: "Depingli"
@ -43,7 +44,7 @@ copyContent: "Kopii enhavon"
copyLink: "Kopii ligilon" copyLink: "Kopii ligilon"
delete: "Forviŝi" delete: "Forviŝi"
deleteAndEdit: "Forviŝi kaj redakti" deleteAndEdit: "Forviŝi kaj redakti"
deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forigi kaj redakti la noton? Ankaŭ ĉiuj reagoj, renotoj, kaj respondoj al ĝi foriĝos." deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forigi kaj redakti la noton? Kun tio foriĝos reagoj, plusendaĵoj, kaj respondoj ĉiuj de ĝi."
addToList: "Aldoni al listo" addToList: "Aldoni al listo"
sendMessage: "Sendi mesaĝon" sendMessage: "Sendi mesaĝon"
copyUsername: "Kopii uzantnomon" copyUsername: "Kopii uzantnomon"
@ -51,7 +52,7 @@ searchUser: "Serĉi uzanton"
reply: "Respondi" reply: "Respondi"
loadMore: "Vidu pli" loadMore: "Vidu pli"
showMore: "Vidi pli" showMore: "Vidi pli"
youGotNewFollower: "sksekvis vin" youGotNewFollower: "eksekvis vin"
receiveFollowRequest: "Peto de sekvado estas ricevita" receiveFollowRequest: "Peto de sekvado estas ricevita"
followRequestAccepted: "La peto de sekvado akceptita" followRequestAccepted: "La peto de sekvado akceptita"
mention: "Mencioj" mention: "Mencioj"
@ -62,32 +63,33 @@ import: "Importi"
export: "Eksporti" export: "Eksporti"
files: "Dosieroj" files: "Dosieroj"
download: "Elŝuti" download: "Elŝuti"
driveFileDeleteConfirm: "Ĉu vi certas, ke vi volas forviŝi la dosieron \"{name}\"? Ankaŭ notoj kiuj enhavas ĝin forviŝiĝos." driveFileDeleteConfirm: "Ĉu vi certas, ke vi volas forviŝi la dosieron \"{name}\"? Pro tio forviŝiĝos ankaŭ la notoj kiuj enhavas ĝin."
unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}'(o)n?" unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}'(o)n?"
lists: "Listoj" lists: "Listoj"
noLists: "Neniu listo" noLists: "Neniu listo"
note: "Elsendi noto" note: "Notoj"
notes: "Notoj" notes: "Notoj"
following: "Sekvatoj" following: "Sekvatoj"
followers: "Sekvantoj" followers: "Sekvantoj"
followsYou: "Sekvas vin" followsYou: "Sekvas vin"
createList: "Kreii liston" createList: "Krei liston"
manageLists: "Administri liston" manageLists: "Administri liston"
error: "Eraro" error: "Eraro"
somethingHappened: "Problemo okazis." somethingHappened: "Problemo okazis"
retry: "Reprovi" retry: "Provi denove"
enterListName: "Entajpu nomon de la listo" enterListName: "Entajpu nomon de la listo"
privacy: "Privateco" privacy: "Privateco"
defaultNoteVisibility: "Implicitaĵo de videbleco"
follow: "Sekvi" follow: "Sekvi"
followRequest: "Peti de sekvado" followRequest: "Peti de sekvado"
followRequests: "Petoj de sekvado" followRequests: "Petoj de sekvado"
unfollow: "Malsekvi" unfollow: "Ne plu sekvi"
enterEmoji: "Entajpu emoĵion" enterEmoji: "Entajpu emoĵion"
renote: "Fari renoton" renote: "Plusendi la noton"
unrenote: "Malfari renoton" unrenote: "Malfari plusendadon"
renoted: "Renoto fariĝis." renoted: "Sukcese plusendita"
cantRenote: "Tiu noto ne estas resendebla." cantRenote: "Oni ne povas plusendi la noton."
cantReRenote: "Renotoj ne estas renotebla." cantReRenote: "Plusendado ne estas plusendebla."
quote: "Citi" quote: "Citi"
pinnedNote: "Alpinglita noto" pinnedNote: "Alpinglita noto"
pinned: "Alpingli al la profilo" pinned: "Alpingli al la profilo"
@ -96,7 +98,7 @@ clickToShow: "Klaku por malkaŝu"
sensitive: "Enhavo ne estas deca por laborejo (NSFW)" sensitive: "Enhavo ne estas deca por laborejo (NSFW)"
add: "Aldoni" add: "Aldoni"
reaction: "Reagoj" reaction: "Reagoj"
rememberNoteVisibility: "Rememori la videblecon de la noto laste sendita" rememberNoteVisibility: "Rememoru la videblecon de la noto laste sendita"
attachCancel: "Deigi aldonaĵon" attachCancel: "Deigi aldonaĵon"
markAsSensitive: "Troviĝi NSFW" markAsSensitive: "Troviĝi NSFW"
unmarkAsSensitive: "Ne troviĝi NSFW" unmarkAsSensitive: "Ne troviĝi NSFW"
@ -123,11 +125,11 @@ emojiName: "Nomo de emoĵio"
emojiUrl: "URL de la emoĵio" emojiUrl: "URL de la emoĵio"
addEmoji: "Aldoni emoĵion" addEmoji: "Aldoni emoĵion"
settingGuide: "Agordaj rekomendoj" settingGuide: "Agordaj rekomendoj"
cacheRemoteFiles: "Havi staplon de transaj dosieroj" cacheRemoteFiles: "Stapli transajn dosierojn"
flagAsBot: "Agordo por robota uzanto" flagAsBot: "Agordo por robota uzanto"
flagAsCat: "Agordo de katiĝa uzanto" flagAsCat: "Agordo de katiĝa uzanto"
addAccount: "Aldoni konton" addAccount: "Aldoni konton"
showOnRemote: "Vidi sur la fora nodo" showOnRemote: "Vidi ĉe la surloka nodo"
general: "Ĝenerala" general: "Ĝenerala"
wallpaper: "Ekranfonoj" wallpaper: "Ekranfonoj"
setWallpaper: "Apliki ekranfonon" setWallpaper: "Apliki ekranfonon"
@ -135,17 +137,28 @@ removeWallpaper: "Forviŝi ekranfonon. "
searchWith: "Serĉi: {q}" searchWith: "Serĉi: {q}"
youHaveNoLists: "Vi ne havas listojn." youHaveNoLists: "Vi ne havas listojn."
followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?" followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?"
host: "Gastiganto"
selectUser: "Elekti uzanton" selectUser: "Elekti uzanton"
recipient: "Ricevonto"
annotation: "Komentarioj" annotation: "Komentarioj"
federation: "Kunfederaĵo" federation: "Kunfederaĵo"
instances: "Nodo" instances: "Nodoj"
latestRequestSentAt: "Lastatempa sendo"
latestRequestReceivedAt: "Lastatempa ricevo"
latestStatus: "Laŭstato"
perHour: "Po horo" perHour: "Po horo"
perDay: "Po tago" perDay: "Po tago"
blockThisInstance: "Bloki tiun nodon" blockThisInstance: "Bloki la nodon"
operations: "Agoj"
software: "Programaro"
version: "Versio" version: "Versio"
metadata: "Metadatumoj"
withNFiles: "{n} dosiero(j)" withNFiles: "{n} dosiero(j)"
monitor: "Monitoro"
network: "Reto"
disk: "Diskilo" disk: "Diskilo"
instanceInfo: "Informoj pri la nodo" instanceInfo: "Informoj pri la nodo"
statistics: "Statistikoj"
clearCachedFiles: "Malplenigi la staplon" clearCachedFiles: "Malplenigi la staplon"
clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?" clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?"
blockedInstances: "Blokitaj nodoj" blockedInstances: "Blokitaj nodoj"
@ -156,7 +169,9 @@ noUsers: "Sen uzantoj"
editProfile: "Redakti profilon" editProfile: "Redakti profilon"
noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?" noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?"
pinLimitExceeded: "Vi povas alpingli ne pli noton." pinLimitExceeded: "Vi povas alpingli ne pli noton."
processing: "Prilaborado..." done: "Fini"
processing: "Prilaborado…"
preview: "Antaŭmontro"
noCustomEmojis: "Neniu emoĵio" noCustomEmojis: "Neniu emoĵio"
federating: "Nun kunfederanta" federating: "Nun kunfederanta"
blocked: "Blokita" blocked: "Blokita"
@ -165,11 +180,12 @@ all: "Ĉiuj"
subscribing: "Abonata" subscribing: "Abonata"
publishing: "Al kiu dissendas" publishing: "Al kiu dissendas"
notResponding: "Alvokato ne disponeblas" notResponding: "Alvokato ne disponeblas"
instanceFollowing: "Sekvatoj el la nodo" instanceFollowing: "Sekvatoj en la nodo"
instanceFollowers: "Sekvantoj el la nodo" instanceFollowers: "Sekvantoj el la nodo"
instanceUsers: "Uzantoj de ĉi tiu nodo" instanceUsers: "Uzantoj de tiu ĉi nodo"
changePassword: "Ŝanĝi pasvorton" changePassword: "Ŝanĝi pasvorton"
security: "Sekureco" security: "Sekureco"
retypedNotMatch: "Enigitoj ne estas konformaj."
currentPassword: "Aktuala pasvorto" currentPassword: "Aktuala pasvorto"
newPassword: "Nova pasvorto" newPassword: "Nova pasvorto"
newPasswordRetype: "Reentajpu la novan pasvorton" newPasswordRetype: "Reentajpu la novan pasvorton"
@ -191,7 +207,7 @@ upload: "Alŝuti"
fromDrive: "De la disko" fromDrive: "De la disko"
fromUrl: "De URL" fromUrl: "De URL"
uploadFromUrl: "Alŝuti de URL" uploadFromUrl: "Alŝuti de URL"
uploadFromUrlDescription: "URL de la dosiero kiun vi volas alŝuti" uploadFromUrlDescription: "URL de dosiero kiun vi volas alŝuti"
explore: "Esplori" explore: "Esplori"
games: "Miskiaj Ludoj" games: "Miskiaj Ludoj"
messageRead: "Legita" messageRead: "Legita"
@ -200,7 +216,7 @@ nUsersRead: "Legita de {n} homoj"
tos: "Kondiĉoj de uzado" tos: "Kondiĉoj de uzado"
start: "Komenciĝi" start: "Komenciĝi"
home: "Hejma" home: "Hejma"
remoteUserCaution: "Tiu infomoj estas ne tute ekzakta pro distanca uzanto." remoteUserCaution: "Ĉi tiuj infomoj ne estas tute ekzaktaj pro transa uzanto."
activity: "Aktiveco" activity: "Aktiveco"
images: "Bildoj" images: "Bildoj"
birthday: "Naskiĝdato" birthday: "Naskiĝdato"
@ -238,6 +254,7 @@ unwatch: "Malobservi"
accept: "Permesi" accept: "Permesi"
normal: "Normala" normal: "Normala"
instanceName: "Nomo de la nodo" instanceName: "Nomo de la nodo"
instanceDescription: "Mempriskribo de la nodo "
maintainerName: "Nomo de la administranto" maintainerName: "Nomo de la administranto"
maintainerEmail: "Retpoŝto de la administranto" maintainerEmail: "Retpoŝto de la administranto"
tosUrl: "URL de kondiĉoj de uzado" tosUrl: "URL de kondiĉoj de uzado"
@ -253,6 +270,8 @@ disconnectService: "Farkonektiĝi"
enableLocalTimeline: "Ebligi lokan templinion" enableLocalTimeline: "Ebligi lokan templinion"
enableGlobalTimeline: "Ebligi mallokan templinion" enableGlobalTimeline: "Ebligi mallokan templinion"
registration: "Registri" registration: "Registri"
enableRegistration: "Ebligi novan uzanton registriĝon"
invite: "Inviti"
driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto" driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto"
driveCapacityPerRemoteAccount: "Volumo de disko po unu transa uzanto" driveCapacityPerRemoteAccount: "Volumo de disko po unu transa uzanto"
iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)" iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)"
@ -262,6 +281,12 @@ basicInfo: "Baza informo"
pinnedUsers: "Alpinglita uzanto" pinnedUsers: "Alpinglita uzanto"
pinnedPages: "Alpinglitaj paĝoj" pinnedPages: "Alpinglitaj paĝoj"
pinnedNotes: "Alpinglita noto" pinnedNotes: "Alpinglita noto"
hcaptchaSiteKey: "Reteja ŝlosilo"
hcaptchaSecretKey: "Sekreta ŝlosilo"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Ebligi reCAPTCHA'on"
recaptchaSiteKey: "Reteja ŝlosilo"
recaptchaSecretKey: "Sekreta ŝlosilo"
antennas: "Antenoj" antennas: "Antenoj"
manageAntennas: "Administri antenojn" manageAntennas: "Administri antenojn"
name: "Nomo" name: "Nomo"
@ -278,13 +303,15 @@ unsilenceConfirm: "Ĉu vi certas ke vi volas malmutigi la uzanton?"
popularUsers: "Popularaj uzantoj" popularUsers: "Popularaj uzantoj"
recentlyUpdatedUsers: "Uzantoj kiuj lastatempe sendis noton" recentlyUpdatedUsers: "Uzantoj kiuj lastatempe sendis noton"
recentlyRegisteredUsers: "Novaliĝintaj uzantoj" recentlyRegisteredUsers: "Novaliĝintaj uzantoj"
exploreUsersCount: "Tiuj estas {count} uzantoj" recentlyDiscoveredUsers: "Lastatempe trovitaj uzantoj"
exploreUsersCount: "Tio estas {count} uzantoj"
exploreFediverse: "Esplori la Fediverson" exploreFediverse: "Esplori la Fediverson"
popularTags: "Popularaj kradvortoj" popularTags: "Popularaj kradvortoj"
userList: "Listoj" userList: "Listoj"
about: "Informoj" about: "Informoj"
aboutMisskey: "Pri Misskey" aboutMisskey: "Pri Misskey"
administrator: "Administranto" administrator: "Administranto"
twoStepAuthentication: "Dua-faktora aŭtentiko"
moderator: "Kontrolisto" moderator: "Kontrolisto"
nUsersMentioned: "{n} uzanto(j) menciis" nUsersMentioned: "{n} uzanto(j) menciis"
securityKey: "Sekureca ŝlosilo" securityKey: "Sekureca ŝlosilo"
@ -302,6 +329,7 @@ close: "Fermi"
group: "Grupo" group: "Grupo"
groups: "Grupoj" groups: "Grupoj"
createGroup: "Krei grupon" createGroup: "Krei grupon"
invites: "Inviti"
groupName: "Grupa nomo" groupName: "Grupa nomo"
members: "Membroj" members: "Membroj"
messagingWithUser: "Babili private" messagingWithUser: "Babili private"
@ -310,21 +338,31 @@ title: "Titolo"
text: "Teksto" text: "Teksto"
enable: "Ebligi" enable: "Ebligi"
next: "Sekve" next: "Sekve"
retype: "Retajpu"
noteOf: "Noto de {user}" noteOf: "Noto de {user}"
quoteAttached: "Kun citaĵo"
quoteQuestion: "Ĉu vi aldonas citaĵon?"
noMessagesYet: "Ankoraŭ neniu mesaĝo" noMessagesYet: "Ankoraŭ neniu mesaĝo"
newMessageExists: "Vi ricevis novan mesaĝon." newMessageExists: "Vi ricevis novan mesaĝon."
onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo." onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo."
signinRequired: "Bonvolu ensaluti"
invitations: "Inviti"
invitationCode: "Kodo de invito" invitationCode: "Kodo de invito"
unavailable: "Ne disponebla"
passwordMatched: "Konforma"
passwordNotMatched: "Nekonforma"
or: "Aŭ" or: "Aŭ"
language: "Lingvo" language: "Lingvo"
uiLanguage: "Lingvo de la fasado" uiLanguage: "Lingvo de fasado"
aboutX: "Pri {x}" aboutX: "Pri {x}"
useOsNativeEmojis: "Oni uzas la emoĵioj de la denaska sistemo" useOsNativeEmojis: "Oni uzas la emoĵioj de la denaska sistemo"
youHaveNoGroups: "Neniuj grupoj" youHaveNoGroups: "Neniuj grupoj"
doing: "Traktado..."
category: "Kategorio" category: "Kategorio"
tags: "Etikedoj" tags: "Etikedoj"
createAccount: "Krei konton" createAccount: "Krei konton"
existingAccount: "Ekzista konto" existingAccount: "Ekzista konto"
regenerate: "Regeneri"
fontSize: "Tipara grando" fontSize: "Tipara grando"
noFollowRequests: "Vi ne havas peto de sekvado" noFollowRequests: "Vi ne havas peto de sekvado"
openImageInNewTab: "Fermi la bildon en nova tablo" openImageInNewTab: "Fermi la bildon en nova tablo"
@ -332,10 +370,11 @@ dashboard: "Stirpanelo"
local: "Loka" local: "Loka"
remote: "Transa" remote: "Transa"
total: "Entute" total: "Entute"
appearance: "Eksteraĵo"
clientSettings: "Agordoj de kliento" clientSettings: "Agordoj de kliento"
accountSettings: "Agordoj de Konto" accountSettings: "Agordoj de konto"
numberOfDays: "Nombro de tagoj" numberOfDays: "Nombro de tagoj"
hideThisNote: "Kaŝi tiun noton" hideThisNote: "Kaŝi la noton"
objectStorageBaseUrl: "Baza URL" objectStorageBaseUrl: "Baza URL"
objectStorageRegion: "Regiono" objectStorageRegion: "Regiono"
objectStorageUseSSL: "Oni uzas SSL" objectStorageUseSSL: "Oni uzas SSL"
@ -346,11 +385,29 @@ sounds: "Sonoj"
listen: "Aŭdi" listen: "Aŭdi"
none: "Neniu" none: "Neniu"
showInPage: "Vidi en paĝo" showInPage: "Vidi en paĝo"
popout: "Superigi"
volume: "Laŭteco"
masterVolume: "Baza laŭteco"
chooseEmoji: "Elekti emoĵion"
recentUsed: "Lastatempaj uzitaj"
install: "Instali"
uninstall: "Malinstali"
installedApps: "Instalita programo"
nothing: "Neniu"
installedDate: "Dato de instalado"
lastUsedDate: "Lastfoje uzita je"
state: "Stato"
sort: "Ordigado"
output: "Elmeto"
script: "Skripto"
disablePagesScript: "Malebligi AiScripto en la paĝoj"
deleteAllFiles: "Forviŝi ĉiujn dosierojn" deleteAllFiles: "Forviŝi ĉiujn dosierojn"
deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?" deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn dosierojn?"
userSuspended: "Ĉi tiu uzanto estas flostigita." userSuspended: "Ĉi tiu uzanto estas flostigita."
userSilenced: "Ĉi tiu uzanto estas mutigita." userSilenced: "Ĉi tiu uzanto estas mutigita."
menu: "Menuo" menu: "Menuo"
addItem: "Aldoni novaĵon"
rooms: "Ĉambro"
deletedNote: "Forviŝita noto" deletedNote: "Forviŝita noto"
invisibleNote: "Malpublika noto" invisibleNote: "Malpublika noto"
visibility: "Videbleco" visibility: "Videbleco"
@ -358,20 +415,25 @@ poll: "Balotujo"
useCw: "Kaŝi enhavo" useCw: "Kaŝi enhavo"
enablePlayer: "Vidi videon" enablePlayer: "Vidi videon"
disablePlayer: "Fermi videon" disablePlayer: "Fermi videon"
expandTweet: "Disvolvi pepon"
themeEditor: "Redaktilo de koloraroj" themeEditor: "Redaktilo de koloraroj"
description: "Priskribe" description: "Priskribe"
describeFile: "Priskribi la bildon" describeFile: "Priskribi la bildon"
enterFileDescription: "Priskribu"
author: "Aŭtoro" author: "Aŭtoro"
manage: "Administro" manage: "Administro"
plugins: "Kromaĵoj" plugins: "Kromaĵoj"
deck: "Kartaro" deck: "Kartaro"
width: "Larĝeco"
height: "Alteco"
medium: "Meza" medium: "Meza"
small: "Malgranda" small: "Malgranda"
edit: "Redakti" edit: "Redakti"
emailServer: "Retpoŝta servilo" emailServer: "Retpoŝta servilo"
email: "Retpoŝto" email: "Retpoŝto"
emailAddress: "Retpoŝta adreso" emailAddress: "Retpoŝta adreso"
smtpConfig: "Agordoj de la servilo SMTP" smtpConfig: "Agordoj de SMTP servilo"
smtpHost: "Gastiganto"
smtpPort: "Pordo" smtpPort: "Pordo"
smtpUser: "Uzantnomo" smtpUser: "Uzantnomo"
smtpPass: "Pasvorto" smtpPass: "Pasvorto"
@ -380,35 +442,46 @@ userSaysSomething: "{name} parolis ion"
makeActive: "Aktivigi" makeActive: "Aktivigi"
display: "Vidi" display: "Vidi"
copy: "Kopii" copy: "Kopii"
overview: "Resumo"
database: "Datumbazo" database: "Datumbazo"
channel: "Kanalo" channel: "Kanalo"
create: "Krei" create: "Krei"
notificationSetting: "Agordoj de sciigoj" notificationSetting: "Agordoj de sciigoj"
useGlobalSetting: "Oni uzas malloka agordo" useGlobalSetting: "Oni uzas malloka agordo"
fileIdOrUrl: "Dosiera identigilo aŭ URL" fileIdOrUrl: "Dosiera identigilo aŭ URL"
sample: "Ekzemplo"
abuseReports: "Signaloj" abuseReports: "Signaloj"
reportAbuse: "Signalo" reportAbuse: "Signalo"
reportAbuseOf: "Signali kontraŭ {name}'(o)" reportAbuseOf: "Signali kontraŭ {name}'(o)"
send: "Sendi" send: "Sendi"
openInNewTab: "Malfermi en nova langeto" openInNewTab: "Malfermi en nova langeto"
editTheseSettingsMayBreakAccount: "Redakti tiujn agordojn estas eble damaĝi konton." editTheseSettingsMayBreakAccount: "Redakti ĉi tiujn agordojn povas damaĝi vian konton."
instanceTicker: "Informoj pri la nodo kiu dissendas la noton"
random: "Hazarde"
system: "Sistemo"
desktop: "Labortablo"
createNew: "Krei novan"
optional: "Opciaj"
public: "Publika" public: "Publika"
i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}." i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}."
accountInfo: "Kontaj Informoj" accountInfo: "Kontaj Informoj"
notesCount: "Numero de notoj" notesCount: "La nombro de notoj"
repliesCount: "Numero de respondoj senditaj" repliesCount: "La nombro de respondoj senditaj"
renotesCount: "Numero de renotoj kiun vi sendis" renotesCount: "La nombro de notoj kiujn la uzanto plusendis"
repliedCount: "Numero de respondoj ricevitaj" repliedCount: "La nombro de respondoj ricevitaj"
renotedCount: "Numero de renotoj kiun vi ricevis" renotedCount: "La nombro de uzantulaj notoj plusenditaj"
followingCount: "Numero de sekvatoj" followingCount: "La nombro de sekvatoj"
followersCount: "Numero de sekvantoj" followersCount: "La nombro de sekvantoj"
sentReactionsCount: "Numero de sentitaj reagoj" sentReactionsCount: "La nombro de la reagoj senditaj"
receivedReactionsCount: "Numero de ricevitaj reagoj" receivedReactionsCount: "La nombro de la reagoj ricevitaj"
yes: "Jes" yes: "Jes"
no: "Ne" no: "Ne"
driveFilesCount: "Numero de dosieroj sur la disko" driveFilesCount: "La nombro de la dosieroj ĉe la disko"
notSet: "Ne elektita" notSet: "Ne elektita"
noteFavoritesCount: "Numero de la preferataj notoj" emailVerified: "Via retpoŝto estis kontrolita."
noteFavoritesCount: "La nombro de notoj preferataj"
pageLikesCount: "La nombro de paĝoj kiun la uzanto preferas"
pageLikedCount: "La nombro de uzantoj kiuj preferas la paĝon"
contact: "Kontakto" contact: "Kontakto"
makeExplorable: "Videbligi konton sur la paĝo \"Esplori\"" makeExplorable: "Videbligi konton sur la paĝo \"Esplori\""
duplicate: "Duobligi" duplicate: "Duobligi"
@ -435,21 +508,25 @@ newVersionOfClientAvailable: "Nova versio de via kliento estas disponebla."
inUse: "Uzata" inUse: "Uzata"
editCode: "Redakti kodon" editCode: "Redakti kodon"
emailNotification: "Sciigoj per retpoŝto" emailNotification: "Sciigoj per retpoŝto"
publish: "Publikigi"
inChannelSearch: "Serĉi en kanalo" inChannelSearch: "Serĉi en kanalo"
useReactionPickerForContextMenu: "Oni malfermas reago-elektilon per dekstro-kliki" useReactionPickerForContextMenu: "Oni malfermas reago-elektilon per dekstro-kliki"
typingUsers: "{users} estas entajpanta(j)..." typingUsers: "{users} nun entajpas…"
clear: "Vakigi"
goBack: "Reiri antaŭ"
addDescription: "Priskribi" addDescription: "Priskribi"
info: "Informoj" info: "Informoj"
userInfo: "Informoj de uzanto" userInfo: "La informoj de uzanto"
unknown: "Nekonata" unknown: "Nekonata"
online: "Surkonektita" online: "Surkonektita"
offline: "Forkonektita" offline: "Forkonektita"
instanceBlocking: "Blokado de nodoj" instanceBlocking: "Bloki specifajn nodojn"
selectAccount: "Elekti konton" selectAccount: "Elekti konton"
user: "Uzantoj" user: "Uzantoj"
administration: "Administro" administration: "Administro"
accounts: "Kontoj" accounts: "Kontoj"
shareWithNote: "Kundividi en noto"
ads: "Reklamaĵo"
memo: "Memorigilo"
high: "Alta" high: "Alta"
middle: "Meza" middle: "Meza"
low: "Malalta" low: "Malalta"
@ -459,6 +536,7 @@ sent: "Sendi"
received: "Ricevita" received: "Ricevita"
searchResult: "Serĉorezultoj" searchResult: "Serĉorezultoj"
hashtags: "Kradvorto" hashtags: "Kradvorto"
troubleshooting: "Problemsolvi"
learnMore: "Lernu pli" learnMore: "Lernu pli"
translate: "Traduki" translate: "Traduki"
translatedFrom: "Tradukita el {x}" translatedFrom: "Tradukita el {x}"
@ -466,6 +544,8 @@ _docs:
continueReading: "Legi plu" continueReading: "Legi plu"
features: "Funkcioj" features: "Funkcioj"
admin: "Administro" admin: "Administro"
_ad:
back: "Nuligi"
_gallery: _gallery:
liked: "Ŝatitaj notoj" liked: "Ŝatitaj notoj"
like: "Ŝati" like: "Ŝati"
@ -485,7 +565,7 @@ _registry:
_aboutMisskey: _aboutMisskey:
about: "Misskey estas malfermitkoda programo evoluigata de syuilo ekde la 2014." about: "Misskey estas malfermitkoda programo evoluigata de syuilo ekde la 2014."
contributors: "Precipaj kontribuantoj" contributors: "Precipaj kontribuantoj"
allContributors: "Ĉiuj kontribuintoj" allContributors: "Ĉiuj kontribuantoj"
source: "Fontkodo" source: "Fontkodo"
translation: "Traduki Misskey'on" translation: "Traduki Misskey'on"
patrons: "Mecenatoj" patrons: "Mecenatoj"
@ -509,6 +589,7 @@ _mfm:
x2: "Granda" x2: "Granda"
x3: "Grandega" x3: "Grandega"
x4: "Pli grandega" x4: "Pli grandega"
font: "Presliteraro"
_reversi: _reversi:
total: "Entute" total: "Entute"
_instanceTicker: _instanceTicker:
@ -518,26 +599,34 @@ _instanceTicker:
_channel: _channel:
create: "Krei kanalon" create: "Krei kanalon"
edit: "Redakti kanalon" edit: "Redakti kanalon"
setBanner: "Apliki standardan bildon"
removeBanner: "Forviŝi la standardan bildon"
owned: "Posedaĵo" owned: "Posedaĵo"
following: "Sekvante" following: "Sekvante"
usersCount: "{n} partoprenanto(j)" usersCount: "{n} partoprenanto(j)"
_menuDisplay: _menuDisplay:
top: "Supro"
hide: "Kaŝi" hide: "Kaŝi"
_wordMute: _wordMute:
muteWords: "Silentigitaj vortoj" muteWords: "Silentigitaj vortoj"
soft: "En kliento"
hard: "En servilo"
mutedNotes: "Silentigitaj notoj" mutedNotes: "Silentigitaj notoj"
_theme: _theme:
manage: "Administri kolorarojn" manage: "Administri kolorarojn"
code: "Kodo de koloraro" code: "Kodo de koloraro"
description: "Priskribe" description: "Priskribe"
color: "Koloro"
darken: "Malbrileco" darken: "Malbrileco"
lighten: "Brileco" lighten: "Brileco"
keys: keys:
bg: "Fono" bg: "Fono"
navBg: "Fono de flanka stango" navBg: "Fono de flanka stango"
link: "Ligilo"
hashtag: "Kradvorto" hashtag: "Kradvorto"
mention: "Mencioj" mention: "Mencioj"
renote: "Renoto" mentionMe: "Mencio al vi"
renote: "Noto plusendita"
buttonBg: "Fono de butono" buttonBg: "Fono de butono"
driveFolderBg: "Fono de dosierujo de la disko" driveFolderBg: "Fono de dosierujo de la disko"
messageBg: "Fono de retbabilejo" messageBg: "Fono de retbabilejo"
@ -569,29 +658,33 @@ _tutorial:
step1_1: "Bonvenon." step1_1: "Bonvenon."
step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}." step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}."
step7_3: "Do, bonvolu amuziĝi Misskey'on🚀" step7_3: "Do, bonvolu amuziĝi Misskey'on🚀"
_2fa:
registerKey: "Nove registri ŝlosilon"
_permissions: _permissions:
"write:account": "Redakti Informojn de via konto" "read:account": "Legado de la informoj pri via konto"
"write:account": "Redatado de la informoj de via konto"
"read:blocks": "Vidi vian liston de uzantoj blokitaj" "read:blocks": "Vidi vian liston de uzantoj blokitaj"
"write:blocks": "Redakti vian liston de uzantoj blokitaj" "write:blocks": "Redakti vian liston de blokitoj"
"read:drive": "Operacio por legi la informon de dosiero en via disko de Misskey" "read:drive": "Legi vian diskon"
"write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via disko de Misskey" "write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via disko de Misskey"
"read:favorites": "Vidi vian liston de preferataĵoj" "read:favorites": "Vidi vian liston de preferataĵoj"
"write:favorites": "Redakti vian liston de preferataĵoj." "write:favorites": "Redakti vian liston de preferataĵoj."
"read:following": "Vidi tiun kiun vi sekvas" "read:following": "Vidi la infomaciojn pri tio, kion vi sekvas"
"write:following": "Sekvi aŭ malsekvi alian uzanton" "write:following": "Sekvi aŭ malsekvi alian uzanton"
"read:messaging": "Vidi vian retbabiladon" "read:messaging": "Vidi vian retbabiladon"
"read:mutes": "Vidi vian liston de silentigoj" "write:messaging": "Retbabilejo"
"write:mutes": "Redakti vian liston de silentigoj" "read:mutes": "Vidi vian liston de silentigitoj"
"write:mutes": "Redakti vian liston de silentigitoj"
"write:notes": "Krei / Forviŝi noton" "write:notes": "Krei / Forviŝi noton"
"read:notifications": "Vidi sciigojn" "read:notifications": "Vidi sciigojn"
"write:notifications": "Manipulado por viaj sciigoj"
"read:reactions": "Vidi reagojn" "read:reactions": "Vidi reagojn"
"write:reactions": "Redakti viajn reagojn" "write:reactions": "Redakti viajn reagojn"
"read:pages": "Vidi via paĝojn"
"read:page-likes": "Vidi ŝatojn de paĝo" "read:page-likes": "Vidi ŝatojn de paĝo"
"read:channels": "Vidi kanalojn" "read:channels": "Vidi kanalojn"
_antennaSources: _antennaSources:
all: "Ĉiuj notoj" all: "Ĉiuj notoj"
homeTimeline: "Notoj far uzantoj, kiujn vi sekvas" homeTimeline: "Notoj far uzantoj kiujn vi sekvas"
_weekday: _weekday:
sunday: "dimanĉo" sunday: "dimanĉo"
monday: "lundo" monday: "lundo"
@ -622,21 +715,24 @@ _poll:
vote: "Baloti" vote: "Baloti"
closed: "Oni jam balotis ĝin" closed: "Oni jam balotis ĝin"
_visibility: _visibility:
publicDescription: "Via noto aperiĝos sur la templinio Malloka" publicDescription: "Via noto estos videbla de ĉiuj uzantoj"
home: "Hejma" home: "Hejma"
homeDescription: "Elsendi nur sur la templinio Hejmo" homeDescription: "Dissendi nur sur hejma templinio"
followers: "Sekvantoj" followers: "Sekvantoj"
followersDescription: "Nur al sekvantoj al mi" followersDescription: "Nur al sekvantoj al mi"
specified: "Rekta" specified: "Rekta"
specifiedDescription: "Publikigi nur al specifaj uzantoj"
localOnly: "Nur loka" localOnly: "Nur loka"
localOnlyDescription: "Ne montri al transaj uzantoj" localOnlyDescription: "Ne montri al transaj uzantoj"
_postForm: _postForm:
replyPlaceholder: "Respondi al tiu noto..." replyPlaceholder: "Respondi tiun noton…"
quotePlaceholder: "Citado tiun noton..." quotePlaceholder: "Citi tiun noton…"
channelPlaceholder: "Sendi sur la kanalo" channelPlaceholder: "Mencii en kanalo…"
_profile: _profile:
name: "Nomo" name: "Nomo"
username: "Uzantnomo" username: "Uzantnomo"
description: "Pri mi"
metadata: "Kromaj informoj"
metadataEdit: "Redakti kromaj informoj" metadataEdit: "Redakti kromaj informoj"
changeAvatar: "Ŝanĝi profilbildon" changeAvatar: "Ŝanĝi profilbildon"
changeBanner: "Ŝanĝi standardon" changeBanner: "Ŝanĝi standardon"
@ -644,14 +740,14 @@ _exportOrImport:
allNotes: "Ĉiuj notoj" allNotes: "Ĉiuj notoj"
followingList: "Sekvataj uzantoj" followingList: "Sekvataj uzantoj"
muteList: "Silentigoj" muteList: "Silentigoj"
blockingList: "Blokitaj uzantoj" blockingList: "Blokitoj"
userLists: "Listoj" userLists: "Listoj"
_charts: _charts:
federationInstancesTotal: "Tuta numero de nodoj kunfederantaj" federationInstancesTotal: "La totala nombro de nodoj kunfederantaj"
usersTotal: "Tuta numero de uzantoj" usersTotal: "La totala nombro de la uzantoj"
activeUsers: "Numero de aktivaj uzantoj" activeUsers: "La nombro de la uzantoj aktivaj"
notesTotal: "Tuta numero de notoj" notesTotal: "La totala nombro de la notoj"
filesTotal: "Tuta numero de dosieroj" filesTotal: "La totala nombro de la dosieroj"
_timelines: _timelines:
home: "Hejma" home: "Hejma"
local: "Loka" local: "Loka"
@ -661,17 +757,34 @@ _rooms:
translate: "Movi" translate: "Movi"
chooseImage: "Elekti bildon" chooseImage: "Elekti bildon"
_furnitures: _furnitures:
bed: "Lito"
low-table: "Malaltotablo"
desk: "Skribotablo"
chair: "Seĝo"
chair2: "Seĝo 2"
pc: "Komputilo"
eraser: "Skrapileto"
pencil: "Krajono"
pudding: "Flaŭno"
book: "Libro"
book2: "Libro 2"
piano: "Piano"
facial-tissue: "Tualetpaperejo"
server: "Servilo" server: "Servilo"
moon: "La luno" moon: "Luno"
monitor: "Monitoro"
keyboard: "Klavaro"
doll-ai: "Pupa Ai"
_pages: _pages:
newPage: "Krei novan paĝon"
editPage: "Redakti paĝon" editPage: "Redakti paĝon"
deleted: "La paĝo estas forigita." deleted: "Oni forviŝis la paĝon."
editThisPage: "Redakti la paĝon" editThisPage: "Redakti la paĝon"
viewPage: "Vidi via paĝojn" viewPage: "Vidi viajn paĝojn"
my: "Miaj paĝoj" my: "Miaj paĝoj"
featured: "Ravaĵoj" featured: "Ravaĵoj"
contents: "Enhavo" contents: "Enhavo"
content: "Blokado de paĝo" content: "Paĝo en bloko"
url: "URL de paĝo" url: "URL de paĝo"
alignCenter: "Centrigi" alignCenter: "Centrigi"
chooseBlock: "Aldoni blokon" chooseBlock: "Aldoni blokon"
@ -683,8 +796,9 @@ _pages:
button: "Butono" button: "Butono"
_post: _post:
canvasId: "Kanvasa identigilo" canvasId: "Kanvasa identigilo"
textInput: "Enigo el teksto" textInput: "Enmeto el teksto"
textareaInput: "Enigo el teksto en multaj linioj" textareaInput: "Enmeto el teksto en multaj linioj"
numberInput: "Nombra enmeto"
_numberInput: _numberInput:
text: "Titolo" text: "Titolo"
_canvas: _canvas:
@ -733,23 +847,29 @@ _pages:
arg1: "Teksto" arg1: "Teksto"
_splitStrByLine: _splitStrByLine:
arg1: "Teksto" arg1: "Teksto"
_fn:
slots: "Juntoj"
arg1: "Elmeto"
thereIsEmptySlot: "La junto {slot} estas malplena!"
types: types:
string: "Teksto" string: "Teksto"
array: "Listoj" array: "Listoj"
stringArray: "List de teksto" stringArray: "List de teksto"
emptySlot: "Malplena junto"
argVariables: "Eniga junto"
_notification: _notification:
fileUploaded: "La dosiero sukcese alŝutiĝis." fileUploaded: "La dosiero sukcese alŝutiĝis."
youRenoted: "Renoto farita de {name}" youRenoted: "{name} plusendis"
youGotPoll: "{name} balotis" youGotPoll: "{name} balotis"
youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi." youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi."
youGotMessagingMessageFromGroup: "Retbabilan mesaĝon oni sendis al la grupo {name}" youGotMessagingMessageFromGroup: "Retbabilan mesaĝon oni sendis al la grupo {name}"
youWereFollowed: "sksekvis vin" youWereFollowed: "eksekvis vin"
youReceivedFollowRequest: "Vi ricevis peton de sekvado" youReceivedFollowRequest: "Vi ricevis peton de sekvado"
yourFollowRequestAccepted: "Via peto por sekvado estis akceptita." yourFollowRequestAccepted: "Via peto por sekvado estis akceptita."
_types: _types:
follow: "Sekvatoj" follow: "Sekvatoj"
mention: "Mencioj" mention: "Mencioj"
renote: "Fari renoton" renote: "Notoj plusenditaj"
quote: "Citi" quote: "Citi"
reaction: "Reagoj" reaction: "Reagoj"
receiveFollowRequest: "Ricevita peton de sekvado" receiveFollowRequest: "Ricevita peton de sekvado"

View file

@ -529,6 +529,7 @@ removeAllFollowing: "Retenir tous les abonnements"
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si linstance nexiste plus." removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si linstance nexiste plus."
userSuspended: "Cet·te utilisateur·rice a été suspendu·e." userSuspended: "Cet·te utilisateur·rice a été suspendu·e."
userSilenced: "Cette utilisateur·trice a été mis·e en sourdine." userSilenced: "Cette utilisateur·trice a été mis·e en sourdine."
yourAccountSuspendedTitle: "Ce compte est suspendu"
menu: "Menu" menu: "Menu"
divider: "Séparateur" divider: "Séparateur"
addItem: "Ajouter un élément" addItem: "Ajouter un élément"
@ -767,6 +768,7 @@ customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exacteme
global: "Global" global: "Global"
squareAvatars: "Avatars carrés" squareAvatars: "Avatars carrés"
sent: "Envoyer" sent: "Envoyer"
received: "Reçu"
searchResult: "Résultats de la recherche" searchResult: "Résultats de la recherche"
hashtags: "Hashtags" hashtags: "Hashtags"
troubleshooting: "Résolution de problèmes" troubleshooting: "Résolution de problèmes"
@ -778,7 +780,12 @@ translate: "Traduire"
translatedFrom: "Traduit depuis {x}" translatedFrom: "Traduit depuis {x}"
accountDeletionInProgress: "La suppression de votre compte est en cours" accountDeletionInProgress: "La suppression de votre compte est en cours"
usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique. Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice par la suite." usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique. Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice par la suite."
aiChanMode: "Mode Ai"
keepCw: "Garder le CW" keepCw: "Garder le CW"
pubSub: "Comptes Pub/Sub"
lastCommunication: "Dernière communication"
resolved: "Résolu"
unresolved: "En attente"
_accountDelete: _accountDelete:
accountDelete: "Supprimer le compte" accountDelete: "Supprimer le compte"
mayTakeTime: "La suppression de compte nécessitant beaucoup de ressources, l'exécution du processus peut prendre du temps, en fonction de la quantité de contenus que vous avez créés et du nombre de fichiers que vous avez téléversés." mayTakeTime: "La suppression de compte nécessitant beaucoup de ressources, l'exécution du processus peut prendre du temps, en fonction de la quantité de contenus que vous avez créés et du nombre de fichiers que vous avez téléversés."
@ -1119,6 +1126,10 @@ _permissions:
"write:user-groups": "Éditer les groupes des utilisateur·rice·s" "write:user-groups": "Éditer les groupes des utilisateur·rice·s"
"read:channels": "Lire les canaux" "read:channels": "Lire les canaux"
"write:channels": "Gérer les canaux" "write:channels": "Gérer les canaux"
"read:gallery": "Voir la galerie"
"write:gallery": "Éditer la galerie"
"read:gallery-likes": "Voir les mentions « J'aime » dans la galerie"
"write:gallery-likes": "Gérer les mentions « J'aime » dans la galerie"
_auth: _auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?" shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?"
@ -1159,6 +1170,7 @@ _widgets:
jobQueue: "File dattente" jobQueue: "File dattente"
serverMetric: "Statistiques du serveur" serverMetric: "Statistiques du serveur"
aiscript: "Console AiScript" aiscript: "Console AiScript"
aichan: "Ai"
_cw: _cw:
hide: "Masquer" hide: "Masquer"
show: "Afficher plus …" show: "Afficher plus …"

View file

@ -529,6 +529,8 @@ removeAllFollowing: "フォローを全解除"
removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。" removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
userSuspended: "このユーザーは凍結されています。" userSuspended: "このユーザーは凍結されています。"
userSilenced: "このユーザーはサイレンスされています。" userSilenced: "このユーザーはサイレンスされています。"
yourAccountSuspendedTitle: "アカウントが凍結されています"
yourAccountSuspendedDescription: "このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。"
menu: "メニュー" menu: "メニュー"
divider: "分割線" divider: "分割線"
addItem: "項目を追加" addItem: "項目を追加"
@ -910,6 +912,8 @@ _mfm:
fontDescription: "内容のフォントを指定することができます。" fontDescription: "内容のフォントを指定することができます。"
rainbow: "レインボー" rainbow: "レインボー"
rainbowDescription: "内容をレインボーにします。" rainbowDescription: "内容をレインボーにします。"
sparkle: "キラキラ"
sparkleDescription: "キラキラしたパーティクルのエフェクトを追加します。"
_reversi: _reversi:
reversi: "リバーシ" reversi: "リバーシ"
@ -1150,6 +1154,10 @@ _permissions:
"write:user-groups": "ユーザーグループを操作する" "write:user-groups": "ユーザーグループを操作する"
"read:channels": "チャンネルを見る" "read:channels": "チャンネルを見る"
"write:channels": "チャンネルを操作する" "write:channels": "チャンネルを操作する"
"read:gallery": "ギャラリーを見る"
"write:gallery": "ギャラリーを操作する"
"read:gallery-likes": "ギャラリーのいいねを見る"
"write:gallery-likes": "ギャラリーのいいねを操作する"
_auth: _auth:
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"

View file

@ -245,7 +245,7 @@ messageRead: "もう読んだ"
noMoreHistory: "これより過去の履歴はあらへんで" noMoreHistory: "これより過去の履歴はあらへんで"
startMessaging: "チャットやるで" startMessaging: "チャットやるで"
nUsersRead: "{n}人が読んでもうた" nUsersRead: "{n}人が読んでもうた"
agreeTo: "{0}はええで" agreeTo: "{0}に同意したで"
tos: "利用規約" tos: "利用規約"
start: "始める" start: "始める"
home: "ホーム" home: "ホーム"
@ -346,7 +346,7 @@ antennaSource: "受信ソース(このソースは食われへん)"
antennaKeywords: "受信キーワード" antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード" antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や" antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
notifyAntenna: "新しいノートを追加すんで" notifyAntenna: "新しいノートを通知すんで"
withFileAntenna: "なんか添付されたノートだけ" withFileAntenna: "なんか添付されたノートだけ"
enableServiceworker: "ServiceWorkerをつこて" enableServiceworker: "ServiceWorkerをつこて"
antennaUsersDescription: "ユーザー名を改行で区切ったってな" antennaUsersDescription: "ユーザー名を改行で区切ったってな"

View file

@ -529,6 +529,8 @@ removeAllFollowing: "모든 팔로잉 해제"
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요." removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요."
userSuspended: "이 계정은 정지된 상태입니다." userSuspended: "이 계정은 정지된 상태입니다."
userSilenced: "이 계정은 사일런스된 상태입니다." userSilenced: "이 계정은 사일런스된 상태입니다."
yourAccountSuspendedTitle: "계정이 정지되었습니다"
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
menu: "메뉴" menu: "메뉴"
divider: "구분선" divider: "구분선"
addItem: "항목 추가" addItem: "항목 추가"
@ -779,6 +781,12 @@ translate: "번역"
translatedFrom: "{x}에서 번역" translatedFrom: "{x}에서 번역"
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다" accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다." usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
aiChanMode: "아이 모드"
keepCw: "CW 유지하기"
pubSub: "Pub/Sub 계정"
lastCommunication: "마지막 통신"
resolved: "해결됨"
unresolved: "해결되지 않음"
_accountDelete: _accountDelete:
accountDelete: "계정 삭제" accountDelete: "계정 삭제"
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다." mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
@ -1114,11 +1122,15 @@ _permissions:
"read:pages": "페이지를 봅니다" "read:pages": "페이지를 봅니다"
"write:pages": "페이지를 수정합니다" "write:pages": "페이지를 수정합니다"
"read:page-likes": "페이지의 좋아요를 확인합니다" "read:page-likes": "페이지의 좋아요를 확인합니다"
"write:page-likes": "페이지의 좋아요를 추가하거나 삭제합니다" "write:page-likes": "페이지에 좋아요를 추가하거나 취소합니다"
"read:user-groups": "유저 그룹을 조회합니다" "read:user-groups": "유저 그룹을 조회합니다"
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다" "write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
"read:channels": "채널을 보기" "read:channels": "채널을 보기"
"write:channels": "채널을 변경하기" "write:channels": "채널을 추가하거나 삭제합니다"
"read:gallery": "갤러리를 봅니다"
"write:gallery": "갤러리를 추가하거나 삭제합니다"
"read:gallery-likes": "갤러리의 좋아요를 확인합니다"
"write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다"
_auth: _auth:
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?" shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
@ -1159,6 +1171,7 @@ _widgets:
jobQueue: "작업 대기열" jobQueue: "작업 대기열"
serverMetric: "서버 통계" serverMetric: "서버 통계"
aiscript: "AiScript 콘솔" aiscript: "AiScript 콘솔"
aichan: "아이"
_cw: _cw:
hide: "숨기기" hide: "숨기기"
show: "더 보기" show: "더 보기"

View file

@ -529,6 +529,8 @@ removeAllFollowing: "取消所有关注"
removeAllFollowingDescription: "取消{host}的所有关注者。当实例不存在时执行。" removeAllFollowingDescription: "取消{host}的所有关注者。当实例不存在时执行。"
userSuspended: "该用户已被冻结。" userSuspended: "该用户已被冻结。"
userSilenced: "该用户已被禁言。" userSilenced: "该用户已被禁言。"
yourAccountSuspendedTitle: "账户已被冻结"
yourAccountSuspendedDescription: "由于违反了服务器的服务条款或其他原因,该账户已被冻结。 您可以与管理员联系以了解更多信息。 请不要创建一个新的帐户。"
menu: "菜单" menu: "菜单"
divider: "分割线" divider: "分割线"
addItem: "添加项目" addItem: "添加项目"
@ -779,8 +781,10 @@ translate: "翻译"
translatedFrom: "从 {x} 翻译" translatedFrom: "从 {x} 翻译"
accountDeletionInProgress: "正在删除账户" accountDeletionInProgress: "正在删除账户"
usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。" usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。"
aiChanMode: "小蓝模式"
keepCw: "保留CW" keepCw: "保留CW"
pubSub: "Pub/Sub账户" pubSub: "Pub/Sub账户"
lastCommunication: "最近通信"
resolved: "已解决" resolved: "已解决"
unresolved: "未解决" unresolved: "未解决"
_accountDelete: _accountDelete:
@ -1123,6 +1127,10 @@ _permissions:
"write:user-groups": "操作用户组" "write:user-groups": "操作用户组"
"read:channels": "查看频道" "read:channels": "查看频道"
"write:channels": "管理频道" "write:channels": "管理频道"
"read:gallery": "浏览图库"
"write:gallery": "操作图库"
"read:gallery-likes": "读取喜欢的图片"
"write:gallery-likes": "操作喜欢的图片"
_auth: _auth:
shareAccess: "您要授权允许“{name}”访问您的帐户吗?" shareAccess: "您要授权允许“{name}”访问您的帐户吗?"
shareAccessAsk: "您确定要授权此应用程序访问您的帐户吗?" shareAccessAsk: "您确定要授权此应用程序访问您的帐户吗?"
@ -1163,7 +1171,7 @@ _widgets:
jobQueue: "作业队列" jobQueue: "作业队列"
serverMetric: "服务器监控" serverMetric: "服务器监控"
aiscript: "AiScript控制台" aiscript: "AiScript控制台"
aichan: "蓝" aichan: "蓝"
_cw: _cw:
hide: "隐藏" hide: "隐藏"
show: "查看更多" show: "查看更多"

View file

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.90.1", "version": "12.91.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -47,7 +47,7 @@
"@sinonjs/fake-timers": "7.1.2", "@sinonjs/fake-timers": "7.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.3", "@types/bull": "3.15.4",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/dateformat": "3.0.1", "@types/dateformat": "3.0.1",
"@types/escape-regexp": "0.0.0", "@types/escape-regexp": "0.0.0",
@ -55,7 +55,7 @@
"@types/gulp": "4.0.9", "@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@types/is-url": "1.2.30", "@types/is-url": "1.2.30",
"@types/js-yaml": "4.0.2", "@types/js-yaml": "4.0.3",
"@types/jsdom": "16.2.13", "@types/jsdom": "16.2.13",
"@types/jsonld": "1.5.6", "@types/jsonld": "1.5.6",
"@types/katex": "0.11.1", "@types/katex": "0.11.1",
@ -64,16 +64,16 @@
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.1", "@types/koa-logger": "3.1.1",
"@types/koa-mount": "4.0.0", "@types/koa-mount": "4.0.1",
"@types/koa-send": "4.1.3", "@types/koa-send": "4.1.3",
"@types/koa-views": "7.0.0", "@types/koa-views": "7.0.0",
"@types/koa__cors": "3.0.3", "@types/koa__cors": "3.0.3",
"@types/koa__multer": "2.0.3", "@types/koa__multer": "2.0.3",
"@types/koa__router": "8.0.7", "@types/koa__router": "8.0.8",
"@types/markdown-it": "12.0.3", "@types/markdown-it": "12.2.1",
"@types/matter-js": "0.17.5", "@types/matter-js": "0.17.5",
"@types/mocha": "8.2.3", "@types/mocha": "8.2.3",
"@types/node": "16.6.2", "@types/node": "16.9.6",
"@types/node-fetch": "2.5.12", "@types/node-fetch": "2.5.12",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
@ -86,13 +86,13 @@
"@types/qrcode": "1.4.1", "@types/qrcode": "1.4.1",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.2", "@types/ratelimiter": "3.4.2",
"@types/redis": "2.8.31", "@types/redis": "2.8.32",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.2", "@types/rimraf": "3.0.2",
"@types/seedrandom": "2.4.28", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.28.5", "@types/sharp": "0.29.2",
"@types/sinonjs__fake-timers": "6.0.3", "@types/sinonjs__fake-timers": "6.0.4",
"@types/speakeasy": "2.0.6", "@types/speakeasy": "2.0.6",
"@types/throttle-debounce": "2.1.0", "@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
@ -103,19 +103,19 @@
"@types/webpack-stream": "3.2.12", "@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.4", "@types/websocket": "1.0.4",
"@types/ws": "7.4.7", "@types/ws": "7.4.7",
"@typescript-eslint/parser": "4.29.2", "@typescript-eslint/parser": "4.31.2",
"@vue/compiler-sfc": "3.2.4", "@vue/compiler-sfc": "3.2.13",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"apexcharts": "3.27.3", "apexcharts": "3.28.3",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.4", "autosize": "4.0.4",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.966.0", "aws-sdk": "2.992.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.4", "blurhash": "1.1.4",
"broadcast-channel": "4.2.0", "broadcast-channel": "4.2.0",
"bull": "3.28.1", "bull": "3.29.2",
"cacheable-lookup": "6.0.0", "cacheable-lookup": "6.0.1",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "8.0.0", "cbor": "8.0.0",
"chalk": "4.1.2", "chalk": "4.1.2",
@ -126,12 +126,12 @@
"concurrently": "6.2.1", "concurrently": "6.2.1",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "6.2.0", "css-loader": "6.3.0",
"cssnano": "5.0.8", "cssnano": "5.0.8",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-plugin-vue": "7.16.0", "eslint-plugin-vue": "7.18.0",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "16.5.3", "file-type": "16.5.3",
@ -156,7 +156,7 @@
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "5.2.0", "jsonld": "5.2.0",
"jsrsasign": "8.0.20", "jsrsasign": "8.0.20",
"katex": "0.13.13", "katex": "0.13.18",
"koa": "2.13.1", "koa": "2.13.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
@ -182,15 +182,15 @@
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.7.1", "pg": "8.7.1",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.3.6", "postcss": "8.3.7",
"postcss-loader": "6.1.1", "postcss-loader": "6.1.1",
"prismjs": "1.24.1", "prismjs": "1.25.0",
"private-ip": "2.2.1", "private-ip": "2.2.1",
"probe-image-size": "7.2.1", "probe-image-size": "7.2.1",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.3.2", "pureimage": "0.3.5",
"qrcode": "1.4.4", "qrcode": "1.4.4",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
@ -204,53 +204,54 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.38.0", "sass": "1.42.1",
"sass-loader": "12.1.0", "sass-loader": "12.1.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.29.0", "sharp": "0.29.1",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "3.2.1", "style-loader": "3.3.0",
"summaly": "2.4.1", "summaly": "2.4.1",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.8.0", "systeminformation": "5.9.3",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.117.1", "three": "0.117.1",
"throttle-debounce": "3.0.1", "throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "9.2.5", "ts-loader": "9.2.6",
"ts-node": "10.2.1", "ts-node": "10.2.1",
"tsc-alias": "1.3.9", "tsc-alias": "1.3.9",
"tsconfig-paths": "3.10.1", "tsconfig-paths": "3.11.0",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"twemoji-parser": "13.1.0", "twemoji-parser": "13.1.0",
"typeorm": "0.2.37", "typeorm": "0.2.37",
"typescript": "4.3.5", "typescript": "4.4.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
"vue": "3.2.4", "vanilla-tilt": "1.7.2",
"vue-loader": "16.5.0", "vue": "3.2.13",
"vue-loader": "16.7.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.5", "vue-router": "4.0.5",
"vue-style-loader": "4.1.3", "vue-style-loader": "4.1.3",
"vue-svg-loader": "0.17.0-beta.2", "vue-svg-loader": "0.17.0-beta.2",
"vuedraggable": "4.0.1", "vuedraggable": "4.0.1",
"web-push": "3.4.5", "web-push": "3.4.5",
"webpack": "5.51.0", "webpack": "5.53.0",
"webpack-cli": "4.8.0", "webpack-cli": "4.8.0",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "8.2.0", "ws": "8.2.2",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.54", "@redocly/openapi-core": "1.0.0-beta.54",
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.17",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "8.3.0", "cypress": "8.4.1",
"start-server-and-test": "1.13.1" "start-server-and-test": "1.14.0"
} }
} }

View file

@ -3,6 +3,7 @@ import { reactive } from 'vue';
import { apiUrl } from '@client/config'; import { apiUrl } from '@client/config';
import { waiting } from '@client/os'; import { waiting } from '@client/os';
import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; import { unisonReload, reloadChannel } from '@client/scripts/unison-reload';
import { showSuspendedDialog } from './scripts/show-suspended-dialog';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -82,17 +83,20 @@ function fetchAccount(token): Promise<Account> {
i: token i: token
}) })
}) })
.then(res => res.json())
.then(res => { .then(res => {
// When failed to authenticate user if (res.error) {
if (res.status !== 200 && res.status < 500) { if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
return signout(); showSuspendedDialog().then(() => {
signout();
});
} else {
signout();
}
} else {
res.token = token;
done(res);
} }
// Parse response
res.json().then(i => {
i.token = token;
done(i);
});
}) })
.catch(fail); .catch(fail);
}); });

View file

@ -73,6 +73,22 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@keyframes earwiggleleft {
from { transform: rotate(37.6deg) skew(30deg); }
25% { transform: rotate(10deg) skew(30deg); }
50% { transform: rotate(20deg) skew(30deg); }
75% { transform: rotate(0deg) skew(30deg); }
to { transform: rotate(37.6deg) skew(30deg); }
}
@keyframes earwiggleright {
from { transform: rotate(-37.6deg) skew(-30deg); }
30% { transform: rotate(-10deg) skew(-30deg); }
55% { transform: rotate(-20deg) skew(-30deg); }
75% { transform: rotate(0deg) skew(-30deg); }
to { transform: rotate(-37.6deg) skew(-30deg); }
}
.eiwwqkts { .eiwwqkts {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -132,6 +148,16 @@ export default defineComponent({
border-radius: 75% 0 75% 75%; border-radius: 75% 0 75% 75%;
transform: rotate(-37.5deg) skew(-30deg); transform: rotate(-37.5deg) skew(-30deg);
} }
&:hover {
&:before {
animation: earwiggleleft 1s infinite;
}
&:after {
animation: earwiggleright 1s infinite;
}
}
} }
} }
</style> </style>

View file

@ -8,6 +8,7 @@ import { concat } from '@client/../prelude/array';
import MkFormula from '@client/components/formula.vue'; import MkFormula from '@client/components/formula.vue';
import MkCode from '@client/components/code.vue'; import MkCode from '@client/components/code.vue';
import MkGoogle from '@client/components/google.vue'; import MkGoogle from '@client/components/google.vue';
import MkSparkle from '@client/components/sparkle.vue';
import MkA from '@client/components/global/a.vue'; import MkA from '@client/components/global/a.vue';
import { host } from '@client/config'; import { host } from '@client/config';
@ -169,6 +170,19 @@ export default defineComponent({
style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : ''; style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
break; break;
} }
case 'sparkle': {
if (!this.$store.state.animatedMfm) {
return genEl(token.children);
}
let count = token.props.args.count ? parseInt(token.props.args.count) : 10;
if (count > 100) {
count = 100;
}
const speed = token.props.args.speed ? parseFloat(token.props.args.speed) : 1;
return h(MkSparkle, {
count, speed,
}, genEl(token.children));
}
} }
if (style == null) { if (style == null) {
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']); return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);

View file

@ -3,10 +3,10 @@
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id"> <MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id">
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</MkA> </MkA>
<span class="is-bot" v-if="note.user.isBot">bot</span> <div class="is-bot" v-if="note.user.isBot">bot</div>
<span class="username"><MkAcct :user="note.user"/></span> <div class="username"><MkAcct :user="note.user"/></div>
<span class="admin" v-if="note.user.isAdmin"><i class="fas fa-bookmark"></i></span> <div class="admin" v-if="note.user.isAdmin"><i class="fas fa-bookmark"></i></div>
<span class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><i class="far fa-bookmark"></i></span> <div class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><i class="far fa-bookmark"></i></div>
<div class="info"> <div class="info">
<span class="mobile" v-if="note.viaMobile"><i class="fas fa-mobile-alt"></i></span> <span class="mobile" v-if="note.viaMobile"><i class="fas fa-mobile-alt"></i></span>
<MkA class="created-at" :to="notePage(note)"> <MkA class="created-at" :to="notePage(note)">
@ -55,6 +55,7 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
> .name { > .name {
flex-shrink: 1;
display: block; display: block;
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
padding: 0; padding: 0;
@ -81,17 +82,20 @@ export default defineComponent({
> .admin, > .admin,
> .moderator { > .moderator {
flex-shrink: 0;
margin-right: 0.5em; margin-right: 0.5em;
color: var(--badge); color: var(--badge);
} }
> .username { > .username {
flex-shrink: 9999999;
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
> .info { > .info {
flex-shrink: 0;
margin-left: auto; margin-left: auto;
font-size: 0.9em; font-size: 0.9em;

View file

@ -8,7 +8,7 @@
@closed="$emit('closed')" @closed="$emit('closed')"
> >
<template #header> <template #header>
<XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()"/> <XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()" :title-only="true"/>
</template> </template>
<div class="yrolvcoq _flat_"> <div class="yrolvcoq _flat_">
<component :is="component" v-bind="props" :ref="changePage"/> <component :is="component" v-bind="props" :ref="changePage"/>

View file

@ -54,6 +54,7 @@ import { apiUrl, host } from '@client/config';
import { byteify, hexify } from '@client/scripts/2fa'; import { byteify, hexify } from '@client/scripts/2fa';
import * as os from '@client/os'; import * as os from '@client/os';
import { login } from '@client/account'; import { login } from '@client/account';
import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -169,15 +170,7 @@ export default defineComponent({
this.signing = false; this.signing = false;
this.challengeData = res; this.challengeData = res;
return this.queryKey(); return this.queryKey();
}).catch(() => { }).catch(this.loginFailed);
os.dialog({
type: 'error',
text: this.$ts.signinFailed
});
this.challengeData = null;
this.totpLogin = false;
this.signing = false;
});
} else { } else {
this.totpLogin = true; this.totpLogin = true;
this.signing = false; this.signing = false;
@ -190,14 +183,36 @@ export default defineComponent({
}).then(res => { }).then(res => {
this.$emit('login', res); this.$emit('login', res);
this.onLogin(res); this.onLogin(res);
}).catch(() => { }).catch(this.loginFailed);
}
},
loginFailed(err) {
switch (err.id) {
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
os.dialog({ os.dialog({
type: 'error', type: 'error',
text: this.$ts.loginFailed title: this.$ts.loginFailed,
text: this.$ts.noSuchUser
}); });
this.signing = false; break;
}); }
case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
showSuspendedDialog();
break;
}
default: {
os.dialog({
type: 'error',
title: this.$ts.loginFailed,
text: JSON.stringify(err)
});
}
} }
this.challengeData = null;
this.totpLogin = false;
this.signing = false;
}, },
resetPassword() { resetPassword() {

View file

@ -0,0 +1,180 @@
<template>
<span class="mk-sparkle">
<span ref="content">
<slot></slot>
</span>
<canvas ref="canvas"></canvas>
</span>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@client/os';
const sprite = new Image();
sprite.src = "/static-assets/client/sparkle-spritesheet.png";
export default defineComponent({
props: {
count: {
type: Number,
required: true,
},
speed: {
type: Number,
required: true,
},
},
data() {
return {
sprites: [0,6,13,20],
particles: [],
anim: null,
ctx: null,
};
},
methods: {
createSparkles(w, h, count) {
var holder = [];
for (var i = 0; i < count; i++) {
const color = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6);
holder[i] = {
position: {
x: Math.floor(Math.random() * w),
y: Math.floor(Math.random() * h)
},
style: this.sprites[ Math.floor(Math.random() * 4) ],
delta: {
x: Math.floor(Math.random() * 1000) - 500,
y: Math.floor(Math.random() * 1000) - 500
},
color: color,
opacity: Math.random(),
};
}
return holder;
},
draw(time) {
this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
this.ctx.beginPath();
const particleSize = Math.floor(this.fontSize / 2);
this.particles.forEach((particle) => {
var modulus = Math.floor(Math.random()*7);
if (Math.floor(time) % modulus === 0) {
particle.style = this.sprites[ Math.floor(Math.random()*4) ];
}
this.ctx.save();
this.ctx.globalAlpha = particle.opacity;
this.ctx.drawImage(sprite, particle.style, 0, 7, 7, particle.position.x, particle.position.y, particleSize, particleSize);
this.ctx.globalCompositeOperation = "source-atop";
this.ctx.globalAlpha = 0.5;
this.ctx.fillStyle = particle.color;
this.ctx.fillRect(particle.position.x, particle.position.y, particleSize, particleSize);
this.ctx.restore();
});
this.ctx.stroke();
},
tick() {
this.anim = window.requestAnimationFrame((time) => {
if (!this.$refs.canvas) {
return;
}
this.particles.forEach((particle) => {
if (!particle) {
return;
}
var randX = Math.random() > Math.random() * 2;
var randY = Math.random() > Math.random() * 3;
if (randX) {
particle.position.x += (particle.delta.x * this.speed) / 1500;
}
if (!randY) {
particle.position.y -= (particle.delta.y * this.speed) / 800;
}
if( particle.position.x > this.$refs.canvas.width ) {
particle.position.x = -7;
} else if (particle.position.x < -7) {
particle.position.x = this.$refs.canvas.width;
}
if (particle.position.y > this.$refs.canvas.height) {
particle.position.y = -7;
particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
} else if (particle.position.y < -7) {
particle.position.y = this.$refs.canvas.height;
particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
}
particle.opacity -= 0.005;
if (particle.opacity <= 0) {
particle.opacity = 1;
}
});
this.draw(time);
this.tick();
});
},
resize() {
if (this.$refs.content) {
const contentRect = this.$refs.content.getBoundingClientRect();
this.fontSize = parseFloat(getComputedStyle(this.$refs.content).fontSize);
const padding = this.fontSize * 0.2;
this.$refs.canvas.width = parseInt(contentRect.width + padding);
this.$refs.canvas.height = parseInt(contentRect.height + padding);
this.particles = this.createSparkles(this.$refs.canvas.width, this.$refs.canvas.height, this.count);
}
},
},
mounted() {
this.ctx = this.$refs.canvas.getContext('2d');
new ResizeObserver(this.resize).observe(this.$refs.content);
this.resize();
this.tick();
},
updated() {
this.resize();
},
destroyed() {
window.cancelAnimationFrame(this.anim);
},
});
</script>
<style lang="scss" scoped>
.mk-sparkle {
position: relative;
display: inline-block;
> span {
display: inline-block;
}
> canvas {
position: absolute;
top: -0.1em;
left: -0.1em;
pointer-events: none;
}
}
</style>

View file

@ -99,7 +99,8 @@ export default defineComponent({
z-index: 10; z-index: 10;
position: sticky; position: sticky;
top: var(--stickyTop, 0px); top: var(--stickyTop, 0px);
background: var(--panel); padding: var(--x-padding);
background: var(--x-header, var(--panel));
/* TODO panel /* TODO panel
background: var(--X17); background: var(--X17);
-webkit-backdrop-filter: var(--blur, blur(8px)); -webkit-backdrop-filter: var(--blur, blur(8px));

View file

@ -245,7 +245,7 @@ export default defineComponent({
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
background: var(--panel); background: var(--panel);
border: solid 1px var(--inputBorder); border: solid 0.5px var(--inputBorder);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;

View file

@ -41,7 +41,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, ref, unref } from 'vue';
import { focusPrev, focusNext } from '@client/scripts/focus'; import { focusPrev, focusNext } from '@client/scripts/focus';
import contains from '@client/scripts/contains'; import contains from '@client/scripts/contains';
@ -79,21 +79,26 @@ export default defineComponent({
}; };
}, },
}, },
created() { watch: {
const items = ref(this.items.filter(item => item !== undefined)); items: {
handler() {
const items = ref(unref(this.items).filter(item => item !== undefined));
for (let i = 0; i < items.value.length; i++) { for (let i = 0; i < items.value.length; i++) {
const item = items.value[i]; const item = items.value[i];
if (item && item.then) { // if item is Promise if (item && item.then) { // if item is Promise
items.value[i] = { type: 'pending' }; items.value[i] = { type: 'pending' };
item.then(actualItem => { item.then(actualItem => {
items.value[i] = actualItem; items.value[i] = actualItem;
}); });
} }
}
this._items = items;
},
immediate: true
} }
this._items = items;
}, },
mounted() { mounted() {
if (this.viaKeyboard) { if (this.viaKeyboard) {

View file

@ -212,7 +212,7 @@ export default defineComponent({
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
background: var(--panel); background: var(--panel);
border: solid 1px var(--inputBorder); border: solid 0.5px var(--inputBorder);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;

View file

@ -15,7 +15,7 @@ if (localStorage.getItem('accounts') != null) {
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing'; import { Integrations } from '@sentry/tracing';
import { computed, createApp, watch, markRaw } from 'vue'; import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
import compareVersions from 'compare-versions'; import compareVersions from 'compare-versions';
import widgets from '@client/widgets'; import widgets from '@client/widgets';
@ -47,6 +47,8 @@ window.onunhandledrejection = null;
if (_DEV_) { if (_DEV_) {
console.warn('Development mode!!!'); console.warn('Development mode!!!');
console.info(`vue ${vueVersion}`);
(window as any).$i = $i; (window as any).$i = $i;
(window as any).$store = defaultStore; (window as any).$store = defaultStore;
@ -215,7 +217,10 @@ if (lastVersion !== version) {
try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
if (lastVersion != null && compareVersions(version, lastVersion) === 1) { if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
popup(import('@client/components/updated.vue'), {}, {}, 'closed'); // ログインしてる場合だけ
if ($i) {
popup(import('@client/components/updated.vue'), {}, {}, 'closed');
}
} }
} catch (e) { } catch (e) {
} }

View file

@ -1,9 +1,10 @@
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { search } from '@client/scripts/search'; import { search } from '@client/scripts/search';
import * as os from '@client/os'; import * as os from '@client/os';
import { i18n } from '@client/i18n'; import { i18n } from '@client/i18n';
import { $i } from './account'; import { $i } from './account';
import { unisonReload } from '@client/scripts/unison-reload'; import { unisonReload } from '@client/scripts/unison-reload';
import { router } from './router';
export const menuDef = { export const menuDef = {
notifications: { notifications: {
@ -58,7 +59,26 @@ export const menuDef = {
title: 'lists', title: 'lists',
icon: 'fas fa-list-ul', icon: 'fas fa-list-ul',
show: computed(() => $i != null), show: computed(() => $i != null),
to: '/my/lists', active: computed(() => router.currentRoute.value.path.startsWith('/timeline/list/') || router.currentRoute.value.path === '/my/lists' || router.currentRoute.value.path.startsWith('/my/lists/')),
action: (ev) => {
const items = ref([{
type: 'pending'
}]);
os.api('users/lists/list').then(lists => {
const _items = [...lists.map(list => ({
type: 'link',
text: list.name,
to: `/timeline/list/${list.id}`
})), null, {
type: 'link',
to: '/my/lists',
text: i18n.locale.manageLists,
icon: 'fas fa-cog',
}];
items.value = _items;
});
os.popupMenu(items, ev.currentTarget || ev.target);
},
}, },
groups: { groups: {
title: 'groups', title: 'groups',
@ -70,7 +90,26 @@ export const menuDef = {
title: 'antennas', title: 'antennas',
icon: 'fas fa-satellite', icon: 'fas fa-satellite',
show: computed(() => $i != null), show: computed(() => $i != null),
to: '/my/antennas', active: computed(() => router.currentRoute.value.path.startsWith('/timeline/antenna/') || router.currentRoute.value.path === '/my/antennas' || router.currentRoute.value.path.startsWith('/my/antennas/')),
action: (ev) => {
const items = ref([{
type: 'pending'
}]);
os.api('antennas/list').then(antennas => {
const _items = [...antennas.map(antenna => ({
type: 'link',
text: antenna.name,
to: `/timeline/antenna/${antenna.id}`
})), null, {
type: 'link',
to: '/my/antennas',
text: i18n.locale.manageAntennas,
icon: 'fas fa-cog',
}];
items.value = _items;
});
os.popupMenu(items, ev.currentTarget || ev.target);
},
}, },
mentions: { mentions: {
title: 'mentions', title: 'mentions',

View file

@ -372,7 +372,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
}); });
} }
export function popupMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { export function popupMenu(items: any[] | Ref<any[]>, src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let dispose; let dispose;
popup(import('@client/components/ui/popup-menu.vue'), { popup(import('@client/components/ui/popup-menu.vue'), {

View file

@ -0,0 +1,147 @@
<template>
<div class="tqmomfks" v-hotkey.global="keymap" v-size="{ min: [800] }">
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
<div class="tl _block">
<XTimeline ref="tl" class="tl"
:key="antennaId"
src="antenna"
:antenna="antennaId"
:sound="true"
@before="before()"
@after="after()"
@queue="queueUpdated"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, computed } from 'vue';
import Progress from '@client/scripts/loading';
import XTimeline from '@client/components/timeline.vue';
import { scroll } from '@client/scripts/scroll';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
XTimeline,
},
props: {
antennaId: {
type: String,
required: true
}
},
data() {
return {
antenna: null,
queue: 0,
[symbols.PAGE_INFO]: computed(() => this.antenna ? {
title: this.antenna.name,
icon: 'fas fa-satellite',
bg: 'var(--bg)',
actions: [{
icon: 'fas fa-calendar-alt',
text: this.$ts.jumpToSpecifiedDate,
handler: this.timetravel
}, {
icon: 'fas fa-cog',
text: this.$ts.settings,
handler: this.settings
}],
} : null),
};
},
computed: {
keymap(): any {
return {
't': this.focus
};
},
},
watch: {
antennaId: {
async handler() {
this.antenna = await os.api('antennas/show', {
antennaId: this.antennaId
});
},
immediate: true
}
},
methods: {
before() {
Progress.start();
},
after() {
Progress.done();
},
queueUpdated(q) {
this.queue = q;
},
top() {
scroll(this.$el, 0);
},
async timetravel() {
const { canceled, result: date } = await os.dialog({
title: this.$ts.date,
input: {
type: 'date'
}
});
if (canceled) return;
this.$refs.tl.timetravel(new Date(date));
},
settings() {
this.$router.push(`/my/antennas/${this.antennaId}`);
},
focus() {
(this.$refs.tl as any).focus();
}
}
});
</script>
<style lang="scss" scoped>
.tqmomfks {
padding: var(--margin);
> .new {
position: sticky;
top: calc(var(--stickyTop, 0px) + 16px);
z-index: 1000;
width: 100%;
> button {
display: block;
margin: var(--margin) auto 0 auto;
padding: 8px 16px;
border-radius: 32px;
}
}
> .tl {
background: var(--bg);
border-radius: var(--radius);
overflow: clip;
}
&.min-width_800px {
max-width: 800px;
margin: 0 auto;
}
}
</style>

View file

@ -0,0 +1,134 @@
<template>
<div class="driuhtrh">
<div class="query">
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
<template #prefix><i class="fas fa-search"></i></template>
</MkInput>
<div class="tags">
<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
</div>
</div>
<MkFolder class="emojis" v-if="searchEmojis">
<template #header>{{ $ts.searchResult }}</template>
<div class="zuvgdzyt">
<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
</div>
</MkFolder>
<MkFolder class="emojis" v-for="category in customEmojiCategories" :key="category">
<template #header>{{ category || $ts.other }}</template>
<div class="zuvgdzyt">
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
</div>
</MkFolder>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkSelect from '@client/components/ui/select.vue';
import MkFolder from '@client/components/ui/folder.vue';
import MkTab from '@client/components/tab.vue';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
import { emojiCategories, emojiTags } from '@client/instance';
import XEmoji from './emojis.emoji.vue';
export default defineComponent({
components: {
MkButton,
MkInput,
MkSelect,
MkFolder,
MkTab,
XEmoji,
},
data() {
return {
q: '',
customEmojiCategories: emojiCategories,
customEmojis: this.$instance.emojis,
tags: emojiTags,
selectedTags: new Set(),
searchEmojis: null,
}
},
watch: {
q() { this.search(); },
selectedTags: {
handler() {
this.search();
},
deep: true
},
},
methods: {
search() {
if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
this.searchEmojis = null;
return;
}
if (this.selectedTags.size === 0) {
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.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)));
}
},
toggleTag(tag) {
if (this.selectedTags.has(tag)) {
this.selectedTags.delete(tag);
} else {
this.selectedTags.add(tag);
}
}
}
});
</script>
<style lang="scss" scoped>
.driuhtrh {
background: var(--bg);
> .query {
background: var(--bg);
padding: 16px;
> .tags {
> .tag {
display: inline-block;
margin: 8px 8px 0 0;
padding: 4px 8px;
font-size: 0.9em;
background: var(--accentedBg);
border-radius: 5px;
&.active {
background: var(--accent);
color: var(--fgOnAccent);
}
}
}
}
> .emojis {
--x-header: var(--bg);
--x-padding: 0 16px;
.zuvgdzyt {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
grid-gap: 12px;
margin: 0 var(--margin) var(--margin) var(--margin);
}
}
}
</style>

View file

@ -0,0 +1,92 @@
<template>
<button class="zuvgdzyu _button" @click="menu">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.aliases.join(' ') }}</div>
</div>
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@client/os';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
import VanillaTilt from 'vanilla-tilt';
export default defineComponent({
props: {
emoji: {
type: Object,
required: true,
}
},
mounted() {
VanillaTilt.init(this.$el, {
reverse: true,
gyroscope: false,
scale: 1.1,
speed: 500,
});
},
methods: {
menu(ev) {
os.popupMenu([{
type: 'label',
text: ':' + this.emoji.name + ':',
}, {
text: this.$ts.copy,
icon: 'fas fa-copy',
action: () => {
copyToClipboard(`:${this.emoji.name}:`);
os.success();
}
}], ev.currentTarget || ev.target);
}
}
});
</script>
<style lang="scss" scoped>
.zuvgdzyu {
display: flex;
align-items: center;
padding: 12px;
text-align: left;
background: var(--panel);
border-radius: 8px;
transform-style: preserve-3d;
transform: perspective(1000px);
&:hover {
border-color: var(--accent);
}
> .img {
width: 42px;
height: 42px;
transform: translateZ(20px);
}
> .body {
padding: 0 0 0 8px;
white-space: nowrap;
overflow: hidden;
transform: translateZ(10px);
> .name {
text-overflow: ellipsis;
overflow: hidden;
}
> .info {
opacity: 0.5;
font-size: 0.9em;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
</style>

View file

@ -1,151 +1,36 @@
<template> <template>
<div class="driuhtrh"> <div :class="$style.root">
<div class="query"> <XCategory v-if="tab === 'category'"/>
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
<template #prefix><i class="fas fa-search"></i></template>
</MkInput>
</div>
<div class="emojis">
<MkFolder v-if="searchEmojis">
<template #header>{{ $ts.searchResult }}</template>
<div class="zuvgdzyt">
<button v-for="emoji in searchEmojis" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.aliases.join(' ') }}</div>
</div>
</button>
</div>
</MkFolder>
<MkFolder v-for="category in customEmojiCategories" :key="category">
<template #header>{{ category || $ts.other }}</template>
<div class="zuvgdzyt">
<button v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.aliases.join(' ') }}</div>
</div>
</button>
</div>
</MkFolder>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, computed } from 'vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkSelect from '@client/components/ui/select.vue';
import MkFolder from '@client/components/ui/folder.vue';
import * as os from '@client/os'; import * as os from '@client/os';
import * as symbols from '@client/symbols'; import * as symbols from '@client/symbols';
import { emojiCategories } from '@client/instance'; import XCategory from './emojis.category.vue';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
export default defineComponent({ export default defineComponent({
components: { components: {
MkButton, XCategory,
MkInput,
MkSelect,
MkFolder,
}, },
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.customEmojis, title: this.$ts.customEmojis,
icon: 'fas fa-laugh' icon: 'fas fa-laugh',
}, bg: 'var(--bg)',
q: '', })),
customEmojiCategories: emojiCategories, tab: 'category',
customEmojis: this.$instance.emojis,
searchEmojis: null,
} }
}, },
watch: {
q() {
if (this.q === '' || this.q == null) {
this.searchEmojis = null;
return;
}
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
}
},
methods: {
menu(emoji, ev) {
os.popupMenu([{
type: 'label',
text: ':' + emoji.name + ':',
}, {
text: this.$ts.copy,
icon: 'fas fa-copy',
action: () => {
copyToClipboard(`:${emoji.name}:`);
os.success();
}
}], ev.currentTarget || ev.target);
}
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.driuhtrh { .root {
> .query { max-width: 1000px;
background: var(--bg); margin: 0 auto;
padding: 16px;
}
> .emojis {
.zuvgdzyt {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
grid-gap: 12px;
margin: 0 var(--margin) var(--margin) var(--margin);
> .emoji {
display: flex;
align-items: center;
padding: 12px;
text-align: left;
border: solid 1px var(--divider);
border-radius: 8px;
&:hover {
border-color: var(--accent);
}
> .img {
width: 42px;
height: 42px;
}
> .body {
padding: 0 0 0 8px;
white-space: nowrap;
overflow: hidden;
> .name {
text-overflow: ellipsis;
overflow: hidden;
}
> .info {
opacity: 0.5;
font-size: 0.9em;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
}
}
} }
</style> </style>

View file

@ -22,7 +22,8 @@ export default defineComponent({
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.favorites, title: this.$ts.favorites,
icon: 'fas fa-star' icon: 'fas fa-star',
bg: 'var(--bg)',
}, },
pagination: { pagination: {
endpoint: 'i/favorites', endpoint: 'i/favorites',

View file

@ -271,6 +271,16 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section _block">
<div class="title">{{ $ts._mfm.sparkle }}</div>
<div class="content">
<p>{{ $ts._mfm.sparkleDescription }}</p>
<div class="preview">
<Mfm :text="preview_sparkle"/>
<MkTextarea v-model="preview_sparkle"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
</div> </div>
</template> </template>
@ -294,7 +304,7 @@ export default defineComponent({
preview_hashtag: '#test', preview_hashtag: '#test',
preview_url: `https://example.com`, preview_url: `https://example.com`,
preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`, preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`,
preview_emoji: `:${this.$instance.emojis[0].name}:`, preview_emoji: this.$instance.emojis.length ? `:${this.$instance.emojis[0].name}:` : `:emojiname:`,
preview_bold: `**${this.$ts._mfm.dummy}**`, preview_bold: `**${this.$ts._mfm.dummy}**`,
preview_small: `<small>${this.$ts._mfm.dummy}</small>`, preview_small: `<small>${this.$ts._mfm.dummy}</small>`,
preview_center: `<center>${this.$ts._mfm.dummy}</center>`, preview_center: `<center>${this.$ts._mfm.dummy}</center>`,
@ -317,6 +327,7 @@ export default defineComponent({
preview_x4: `$[x4 🍮]`, preview_x4: `$[x4 🍮]`,
preview_blur: `$[blur ${this.$ts._mfm.dummy}]`, preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
preview_rainbow: `$[rainbow 🍮]`, preview_rainbow: `$[rainbow 🍮]`,
preview_sparkle: `$[sparkle 🍮]`,
} }
}, },
}); });

View file

@ -1,37 +1,39 @@
<template> <template>
<div class="fcuexfpr _root"> <div class="fcuexfpr">
<transition name="fade" mode="out-in"> <div class="_root">
<div v-if="note" class="note"> <transition name="fade" mode="out-in">
<div class="_gap" v-if="showNext"> <div v-if="note" class="note">
<XNotes class="_content" :pagination="next" :no-gap="true"/> <div class="_gap" v-if="showNext">
</div> <XNotes class="_content" :pagination="next" :no-gap="true"/>
<div class="main _gap">
<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
<div class="note _gap">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
</div> </div>
<div class="_content clips _gap" v-if="clips && clips.length > 0">
<div class="title">{{ $ts.clip }}</div>
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user">
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
</div>
</MkA>
</div>
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
</div>
<div class="_gap" v-if="showPrev"> <div class="main _gap">
<XNotes class="_content" :pagination="prev" :no-gap="true"/> <MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
<div class="note _gap">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
</div>
<div class="_content clips _gap" v-if="clips && clips.length > 0">
<div class="title">{{ $ts.clip }}</div>
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user">
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
</div>
</MkA>
</div>
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
</div>
<div class="_gap" v-if="showPrev">
<XNotes class="_content" :pagination="prev" :no-gap="true"/>
</div>
</div> </div>
</div> <MkError v-else-if="error" @retry="fetch()"/>
<MkError v-else-if="error" @retry="fetch()"/> <MkLoading v-else/>
<MkLoading v-else/> </transition>
</transition> </div>
</div> </div>
</template> </template>
@ -63,12 +65,14 @@ export default defineComponent({
return { return {
[symbols.PAGE_INFO]: computed(() => this.note ? { [symbols.PAGE_INFO]: computed(() => this.note ? {
title: this.$ts.note, title: this.$ts.note,
subtitle: new Date(this.note.createdAt).toLocaleString(),
avatar: this.note.user, avatar: this.note.user,
path: `/notes/${this.note.id}`, path: `/notes/${this.note.id}`,
share: { share: {
title: this.$t('noteOf', { user: this.note.user.name }), title: this.$t('noteOf', { user: this.note.user.name }),
text: this.note.text, text: this.note.text,
}, },
bg: 'var(--bg)',
} : null), } : null),
note: null, note: null,
clips: null, clips: null,
@ -149,52 +153,54 @@ export default defineComponent({
.fcuexfpr { .fcuexfpr {
background: var(--bg); background: var(--bg);
> .note { > ._root {
> .main { > .note {
> .load { > .main {
min-width: 0; > .load {
margin: 0 auto; min-width: 0;
border-radius: 999px; margin: 0 auto;
border-radius: 999px;
&.next { &.next {
margin-bottom: var(--margin); margin-bottom: var(--margin);
}
&.prev {
margin-top: var(--margin);
}
}
> .note {
> .note {
border-radius: var(--radius);
background: var(--panel);
}
}
> .clips {
> .title {
font-weight: bold;
padding: 12px;
}
> .item {
display: block;
padding: 16px;
> .description {
padding: 8px 0;
} }
> .user { &.prev {
$height: 32px; margin-top: var(--margin);
padding-top: 16px; }
border-top: solid 0.5px var(--divider); }
line-height: $height;
> .avatar { > .note {
width: $height; > .note {
height: $height; border-radius: var(--radius);
background: var(--panel);
}
}
> .clips {
> .title {
font-weight: bold;
padding: 12px;
}
> .item {
display: block;
padding: 16px;
> .description {
padding: 8px 0;
}
> .user {
$height: 32px;
padding-top: 16px;
border-top: solid 0.5px var(--divider);
line-height: $height;
> .avatar {
width: $height;
height: $height;
}
} }
} }
} }

View file

@ -21,6 +21,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.notifications, title: this.$ts.notifications,
icon: 'fas fa-bell', icon: 'fas fa-bell',
bg: 'var(--bg)',
actions: [{ actions: [{
text: this.$ts.markAllAsRead, text: this.$ts.markAllAsRead,
icon: 'fas fa-check', icon: 'fas fa-check',

View file

@ -86,7 +86,8 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const indexInfo = { const indexInfo = {
title: i18n.locale.settings, title: i18n.locale.settings,
icon: 'fas fa-cog' icon: 'fas fa-cog',
bg: 'var(--bg)',
}; };
const INFO = ref(indexInfo); const INFO = ref(indexInfo);
const page = ref(props.initialPage); const page = ref(props.initialPage);

View file

@ -26,7 +26,7 @@
<FormLink to="/bios" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink> <FormLink to="/bios" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink>
<FormLink to="/cli" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink> <FormLink to="/cli" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink>
<FormLink to="./delete-account"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> <FormLink to="/settings/delete-account"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
</FormBase> </FormBase>
</template> </template>

View file

@ -1,31 +1,13 @@
<template> <template>
<div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }"> <div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }">
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/> <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
<div class="tabs">
<div class="left">
<button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><i class="fas fa-home"></i></button>
<button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><i class="fas fa-comments"></i></button>
<button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><i class="fas fa-share-alt"></i></button>
<button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><i class="fas fa-globe"></i></button>
<span class="divider"></span>
<button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><i class="fas fa-at"></i><i v-if="$i.hasUnreadMentions" class="fas fa-circle i"></i></button>
<button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><i class="fas fa-envelope"></i><i v-if="$i.hasUnreadSpecifiedNotes" class="fas fa-circle i"></i></button>
</div>
<div class="right">
<button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><i class="fas fa-satellite-dish"></i><i v-if="$i.hasUnreadChannel" class="fas fa-circle i"></i></button>
<button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><i class="fas fa-satellite"></i><i v-if="$i.hasUnreadAntenna" class="fas fa-circle i"></i></button>
<button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><i class="fas fa-list-ul"></i></button>
</div>
</div>
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
<div class="tl"> <div class="tl _block">
<XTimeline ref="tl" class="tl" <XTimeline ref="tl" class="tl"
:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src" :key="src"
:src="src" :src="src"
:list="list ? list.id : null"
:antenna="antenna ? antenna.id : null"
:channel="channel ? channel.id : null"
:sound="true" :sound="true"
@before="before()" @before="before()"
@after="after()" @after="after()"
@ -56,19 +38,52 @@ export default defineComponent({
data() { data() {
return { return {
src: 'home', src: 'home',
list: null,
antenna: null,
channel: null,
menuOpened: false,
queue: 0, queue: 0,
[symbols.PAGE_INFO]: computed(() => ({ [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.timeline, title: this.$ts.timeline,
subtitle: this.src === 'local' ? this.$ts._timelines.local : this.src === 'social' ? this.$ts._timelines.social : this.src === 'global' ? this.$ts._timelines.global : this.$ts._timelines.home,
icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home',
bg: 'var(--bg)',
actions: [{ actions: [{
icon: 'fas fa-list-ul',
text: this.$ts.lists,
handler: this.chooseList
}, {
icon: 'fas fa-satellite',
text: this.$ts.antennas,
handler: this.chooseAntenna
}, {
icon: 'fas fa-satellite-dish',
text: this.$ts.channel,
handler: this.chooseChannel
}, {
icon: 'fas fa-calendar-alt', icon: 'fas fa-calendar-alt',
text: this.$ts.jumpToSpecifiedDate, text: this.$ts.jumpToSpecifiedDate,
handler: this.timetravel handler: this.timetravel
}],
tabs: [{
active: this.src === 'home',
title: this.$ts._timelines.home,
icon: 'fas fa-home',
iconOnly: true,
onClick: () => { this.src = 'home'; this.saveSrc(); },
}, {
active: this.src === 'local',
title: this.$ts._timelines.local,
icon: 'fas fa-comments',
iconOnly: true,
onClick: () => { this.src = 'local'; this.saveSrc(); },
}, {
active: this.src === 'social',
title: this.$ts._timelines.social,
icon: 'fas fa-share-alt',
iconOnly: true,
onClick: () => { this.src = 'social'; this.saveSrc(); },
}, {
active: this.src === 'global',
title: this.$ts._timelines.global,
icon: 'fas fa-globe',
iconOnly: true,
onClick: () => { this.src = 'global'; this.saveSrc(); },
}] }]
})), })),
}; };
@ -94,32 +109,10 @@ export default defineComponent({
src() { src() {
this.showNav = false; this.showNav = false;
}, },
list(x) {
this.showNav = false;
if (x != null) this.antenna = null;
if (x != null) this.channel = null;
},
antenna(x) {
this.showNav = false;
if (x != null) this.list = null;
if (x != null) this.channel = null;
},
channel(x) {
this.showNav = false;
if (x != null) this.antenna = null;
if (x != null) this.list = null;
},
}, },
created() { created() {
this.src = this.$store.state.tl.src; this.src = this.$store.state.tl.src;
if (this.src === 'list') {
this.list = this.$store.state.tl.arg;
} else if (this.src === 'antenna') {
this.antenna = this.$store.state.tl.arg;
} else if (this.src === 'channel') {
this.channel = this.$store.state.tl.arg;
}
}, },
methods: { methods: {
@ -142,12 +135,9 @@ export default defineComponent({
async chooseList(ev) { async chooseList(ev) {
const lists = await os.api('users/lists/list'); const lists = await os.api('users/lists/list');
const items = lists.map(list => ({ const items = lists.map(list => ({
type: 'link',
text: list.name, text: list.name,
action: () => { to: `/timeline/list/${list.id}`
this.list = list;
this.src = 'list';
this.saveSrc();
}
})); }));
os.popupMenu(items, ev.currentTarget || ev.target); os.popupMenu(items, ev.currentTarget || ev.target);
}, },
@ -155,13 +145,10 @@ export default defineComponent({
async chooseAntenna(ev) { async chooseAntenna(ev) {
const antennas = await os.api('antennas/list'); const antennas = await os.api('antennas/list');
const items = antennas.map(antenna => ({ const items = antennas.map(antenna => ({
type: 'link',
text: antenna.name, text: antenna.name,
indicate: antenna.hasUnreadNote, indicate: antenna.hasUnreadNote,
action: () => { to: `/timeline/antenna/${antenna.id}`
this.antenna = antenna;
this.src = 'antenna';
this.saveSrc();
}
})); }));
os.popupMenu(items, ev.currentTarget || ev.target); os.popupMenu(items, ev.currentTarget || ev.target);
}, },
@ -169,15 +156,10 @@ export default defineComponent({
async chooseChannel(ev) { async chooseChannel(ev) {
const channels = await os.api('channels/followed'); const channels = await os.api('channels/followed');
const items = channels.map(channel => ({ const items = channels.map(channel => ({
type: 'link',
text: channel.name, text: channel.name,
indicate: channel.hasUnreadNote, indicate: channel.hasUnreadNote,
action: () => { to: `/channels/${channel.id}`
// NOTE: 稿
//this.channel = channel;
//this.src = 'channel';
//this.saveSrc();
this.$router.push(`/channels/${channel.id}`);
}
})); }));
os.popupMenu(items, ev.currentTarget || ev.target); os.popupMenu(items, ev.currentTarget || ev.target);
}, },
@ -185,10 +167,6 @@ export default defineComponent({
saveSrc() { saveSrc() {
this.$store.set('tl', { this.$store.set('tl', {
src: this.src, src: this.src,
arg:
this.src === 'list' ? this.list :
this.src === 'antenna' ? this.antenna :
this.channel
}); });
}, },
@ -213,6 +191,8 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.cmuxhskf { .cmuxhskf {
padding: var(--margin);
> .new { > .new {
position: sticky; position: sticky;
top: calc(var(--stickyTop, 0px) + 16px); top: calc(var(--stickyTop, 0px) + 16px);
@ -227,79 +207,15 @@ export default defineComponent({
} }
} }
> .tabs { > .tl {
display: flex; background: var(--bg);
box-sizing: border-box; border-radius: var(--radius);
padding: 0 8px; overflow: clip;
white-space: nowrap;
overflow: auto;
border-bottom: solid 0.5px var(--divider);
//
position: relative;
> .right {
margin-left: auto;
}
> .left, > .right {
> .tab {
position: relative;
height: 50px;
padding: 0 12px;
&:hover {
color: var(--fgHighlighted);
}
&.active {
color: var(--fgHighlighted);
&:after {
content: "";
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
width: 100%;
height: 2px;
background: var(--accent);
}
}
> .i {
position: absolute;
top: 16px;
right: 8px;
color: var(--indicator);
font-size: 8px;
animation: blink 1s infinite;
}
}
> .divider {
display: inline-block;
width: 1px;
height: 28px;
vertical-align: middle;
margin: 0 8px;
background: var(--divider);
}
}
} }
&.min-width_800px { &.min-width_800px {
> .tl { max-width: 800px;
background: var(--bg); margin: 0 auto;
padding: 32px 0;
> .tl {
max-width: 800px;
margin: 0 auto;
}
}
} }
} }
</style> </style>

View file

@ -0,0 +1,147 @@
<template>
<div class="eqqrhokj" v-hotkey.global="keymap" v-size="{ min: [800] }">
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
<div class="tl _block">
<XTimeline ref="tl" class="tl"
:key="listId"
src="list"
:list="listId"
:sound="true"
@before="before()"
@after="after()"
@queue="queueUpdated"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, computed } from 'vue';
import Progress from '@client/scripts/loading';
import XTimeline from '@client/components/timeline.vue';
import { scroll } from '@client/scripts/scroll';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
XTimeline,
},
props: {
listId: {
type: String,
required: true
}
},
data() {
return {
list: null,
queue: 0,
[symbols.PAGE_INFO]: computed(() => this.list ? {
title: this.list.name,
icon: 'fas fa-list-ul',
bg: 'var(--bg)',
actions: [{
icon: 'fas fa-calendar-alt',
text: this.$ts.jumpToSpecifiedDate,
handler: this.timetravel
}, {
icon: 'fas fa-cog',
text: this.$ts.settings,
handler: this.settings
}],
} : null),
};
},
computed: {
keymap(): any {
return {
't': this.focus
};
},
},
watch: {
listId: {
async handler() {
this.list = await os.api('users/lists/show', {
listId: this.listId
});
},
immediate: true
}
},
methods: {
before() {
Progress.start();
},
after() {
Progress.done();
},
queueUpdated(q) {
this.queue = q;
},
top() {
scroll(this.$el, 0);
},
settings() {
this.$router.push(`/my/lists/${this.listId}`);
},
async timetravel() {
const { canceled, result: date } = await os.dialog({
title: this.$ts.date,
input: {
type: 'date'
}
});
if (canceled) return;
this.$refs.tl.timetravel(new Date(date));
},
focus() {
(this.$refs.tl as any).focus();
}
}
});
</script>
<style lang="scss" scoped>
.eqqrhokj {
padding: var(--margin);
> .new {
position: sticky;
top: calc(var(--stickyTop, 0px) + 16px);
z-index: 1000;
width: 100%;
> button {
display: block;
margin: var(--margin) auto 0 auto;
padding: 8px 16px;
border-radius: 32px;
}
}
> .tl {
background: var(--bg);
border-radius: var(--radius);
overflow: clip;
}
&.min-width_800px {
max-width: 800px;
margin: 0 auto;
}
}
</style>

View file

@ -60,23 +60,9 @@
<XPhotos :user="user" :key="user.id" class="_gap"/> <XPhotos :user="user" :key="user.id" class="_gap"/>
</div> </div>
<div class="main"> <div class="main">
<div class="nav _gap"> <div class="actions">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
<i class="fas fa-comment-alt icon"></i> <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
<span>{{ $ts.notes }}</span>
</MkA>
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link">
<i class="fas fa-paperclip icon"></i>
<span>{{ $ts.clips }}</span>
</MkA>
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link">
<i class="fas fa-file-alt icon"></i>
<span>{{ $ts.pages }}</span>
</MkA>
<div class="actions">
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
<MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
</div>
</div> </div>
<template v-if="page === 'index'"> <template v-if="page === 'index'">
<div v-if="user.pinnedNotes.length > 0" class="_gap"> <div v-if="user.pinnedNotes.length > 0" class="_gap">
@ -178,25 +164,6 @@
</div> </div>
<div class="contents"> <div class="contents">
<div class="nav _gap">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link" v-click-anime>
<i class="fas fa-comment-alt icon"></i>
<span>{{ $ts.notes }}</span>
</MkA>
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link" v-click-anime>
<i class="fas fa-paperclip icon"></i>
<span>{{ $ts.clips }}</span>
</MkA>
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link" v-click-anime>
<i class="fas fa-file-alt icon"></i>
<span>{{ $ts.pages }}</span>
</MkA>
<MkA :to="userPage(user, 'gallery')" :class="{ active: page === 'gallery' }" class="link" v-click-anime>
<i class="fas fa-icons icon"></i>
<span>{{ $ts.gallery }}</span>
</MkA>
</div>
<template v-if="page === 'index'"> <template v-if="page === 'index'">
<div> <div>
<div v-if="user.pinnedNotes.length > 0" class="_gap"> <div v-if="user.pinnedNotes.length > 0" class="_gap">
@ -283,6 +250,27 @@ export default defineComponent({
share: { share: {
title: this.user.name, title: this.user.name,
}, },
bg: 'var(--bg)',
tabs: [{
active: this.page === 'index',
title: this.$ts.overview,
icon: 'fas fa-home',
}, {
active: this.page === 'clips',
title: this.$ts.clips,
icon: 'fas fa-paperclip',
onClick: () => { this.page = 'clips'; },
}, {
active: this.page === 'pages',
title: this.$ts.pages,
icon: 'fas fa-file-alt',
onClick: () => { this.page = 'pages'; },
}, {
active: this.page === 'gallery',
title: this.$ts.gallery,
icon: 'fas fa-icons',
onClick: () => { this.page = 'gallery'; },
}]
} : null), } : null),
user: null, user: null,
error: null, error: null,
@ -314,7 +302,7 @@ export default defineComponent({
mounted() { mounted() {
window.requestAnimationFrame(this.parallaxLoop); window.requestAnimationFrame(this.parallaxLoop);
this.narrow = this.$el.clientWidth < 1000; this.narrow = true//this.$el.clientWidth < 1000;
}, },
beforeUnmount() { beforeUnmount() {
@ -772,37 +760,6 @@ export default defineComponent({
} }
> .contents { > .contents {
> .nav {
display: flex;
align-items: center;
font-size: 90%;
> .link {
flex: 1;
display: inline-block;
padding: 16px;
text-align: center;
border-bottom: solid 3px transparent;
&:hover {
text-decoration: none;
}
&.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
&:not(.active):hover {
color: var(--fgHighlighted);
}
> .icon {
margin-right: 6px;
}
}
}
> .content { > .content {
margin-bottom: var(--margin); margin-bottom: var(--margin);
} }

View file

@ -48,6 +48,8 @@ const defaultRoutes = [
{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, { path: '/channels/:channelId/edit', component: page('channel-editor'), props: true },
{ path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) }, { path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) },
{ path: '/clips/:clipId', component: page('clip'), props: route => ({ clipId: route.params.clipId }) }, { path: '/clips/:clipId', component: page('clip'), props: route => ({ clipId: route.params.clipId }) },
{ path: '/timeline/list/:listId', component: page('user-list-timeline'), props: route => ({ listId: route.params.listId }) },
{ path: '/timeline/antenna/:antennaId', component: page('antenna-timeline'), props: route => ({ antennaId: route.params.antennaId }) },
{ path: '/my/notifications', component: page('notifications') }, { path: '/my/notifications', component: page('notifications') },
{ path: '/my/favorites', component: page('favorites') }, { path: '/my/favorites', component: page('favorites') },
{ path: '/my/messages', component: page('messages') }, { path: '/my/messages', component: page('messages') },

View file

@ -0,0 +1,10 @@
import * as os from '@client/os';
import { i18n } from '@client/i18n';
export function showSuspendedDialog() {
return os.dialog({
type: 'error',
title: i18n.locale.yourAccountSuspendedTitle,
text: i18n.locale.yourAccountSuspendedDescription
});
}

View file

@ -245,7 +245,6 @@ hr {
._panel { ._panel {
background: var(--panel); background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);
border: var(--panelBorder);
overflow: clip; overflow: clip;
} }

View file

@ -12,6 +12,7 @@
accent: '#86b300', accent: '#86b300',
accentDarken: ':darken<10<@accent', accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent', accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
focus: ':alpha<0.3<@accent', focus: ':alpha<0.3<@accent',
bg: '#000', bg: '#000',
acrylicBg: ':alpha<0.5<@bg', acrylicBg: ':alpha<0.5<@bg',
@ -36,7 +37,7 @@
navFg: '@fg', navFg: '@fg',
navHoverFg: ':lighten<17<@fg', navHoverFg: ':lighten<17<@fg',
navActive: '@accent', navActive: '@accent',
navIndicator: '@accent', navIndicator: '@indicator',
link: '#44a4c1', link: '#44a4c1',
hashtag: '#ff9156', hashtag: '#ff9156',
mention: '@accent', mention: '@accent',

View file

@ -12,6 +12,7 @@
accent: '#86b300', accent: '#86b300',
accentDarken: ':darken<10<@accent', accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent', accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
focus: ':alpha<0.3<@accent', focus: ':alpha<0.3<@accent',
bg: '#fff', bg: '#fff',
acrylicBg: ':alpha<0.5<@bg', acrylicBg: ':alpha<0.5<@bg',
@ -36,7 +37,7 @@
navFg: '@fg', navFg: '@fg',
navHoverFg: ':darken<17<@fg', navHoverFg: ':darken<17<@fg',
navActive: '@accent', navActive: '@accent',
navIndicator: '@accent', navIndicator: '@indicator',
link: '#44a4c1', link: '#44a4c1',
hashtag: '#ff9156', hashtag: '#ff9156',
mention: '@accent', mention: '@accent',

View file

@ -1,31 +1,41 @@
<template> <template>
<div class="fdidabkb" :class="{ center }" :style="`--height:${height};`" :key="key"> <div class="fdidabkb" :class="{ slim: titleOnly || narrow }" :style="`--height:${height};`" :key="key">
<transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear> <transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear>
<div class="buttons left" v-if="backButton"> <div class="buttons left" v-if="backButton">
<button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button> <button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button>
</div> </div>
</transition> </transition>
<template v-if="info"> <template v-if="info">
<div class="titleContainer"> <div class="titleContainer" @click="showTabsPopup">
<i v-if="info.icon" class="icon" :class="info.icon"></i> <i v-if="info.icon" class="icon" :class="info.icon"></i>
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
<div class="title"> <div class="title">
<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/>
<div v-else-if="info.title" class="title">{{ info.title }}</div> <div v-else-if="info.title" class="title">{{ info.title }}</div>
<div class="subtitle" v-if="info.subtitle"> <div class="subtitle" v-if="!narrow && info.subtitle">
{{ info.subtitle }} {{ info.subtitle }}
</div> </div>
<div class="subtitle activeTab" v-if="narrow && hasTabs">
{{ info.tabs.find(tab => tab.active)?.title }}
<i class="chevron fas fa-chevron-down"></i>
</div>
</div> </div>
</div> </div>
<div class="buttons right"> <div class="tabs" v-if="!narrow">
<template v-if="info.actions && showActions"> <button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title">
<button v-for="action in info.actions" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button> <i v-if="tab.icon" class="icon" :class="tab.icon"></i>
</template> <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span>
<button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button> </button>
<button v-if="closeButton" class="_button button" @click.stop="$emit('close')" @touchstart="preventDrag" v-tooltip="$ts.close"><i class="fas fa-times"></i></button>
</div> </div>
</template> </template>
<div class="buttons right">
<template v-if="info && info.actions && !narrow">
<button v-for="action in info.actions" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button>
</template>
<button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button>
<button v-if="closeButton" class="_button button" @click.stop="$emit('close')" @touchstart="preventDrag" v-tooltip="$ts.close"><i class="fas fa-times"></i></button>
</div>
</div> </div>
</template> </template>
@ -52,24 +62,29 @@ export default defineComponent({
required: false, required: false,
default: false, default: false,
}, },
center: { titleOnly: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: false,
}, },
}, },
data() { data() {
return { return {
showActions: false, narrow: false,
height: 0, height: 0,
key: 0, key: 0,
}; };
}, },
computed: { computed: {
hasTabs(): boolean {
return this.info.tabs && this.info.tabs.length > 0;
},
shouldShowMenu() { shouldShowMenu() {
if (this.info.actions != null && !this.showActions) return true; if (this.info == null) return false;
if (this.info.actions != null && this.narrow) return true;
if (this.info.menu != null) return true; if (this.info.menu != null) return true;
if (this.info.share != null) return true; if (this.info.share != null) return true;
if (this.menu != null) return true; if (this.menu != null) return true;
@ -85,10 +100,10 @@ export default defineComponent({
mounted() { mounted() {
this.height = this.$el.parentElement.offsetHeight + 'px'; this.height = this.$el.parentElement.offsetHeight + 'px';
this.showActions = this.$el.parentElement.offsetWidth >= 500; this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
new ResizeObserver((entries, observer) => { new ResizeObserver((entries, observer) => {
this.height = this.$el.parentElement.offsetHeight + 'px'; this.height = this.$el.parentElement.offsetHeight + 'px';
this.showActions = this.$el.parentElement.offsetWidth >= 500; this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
}).observe(this.$el); }).observe(this.$el);
}, },
@ -102,7 +117,7 @@ export default defineComponent({
showMenu(ev) { showMenu(ev) {
let menu = this.info.menu ? this.info.menu() : []; let menu = this.info.menu ? this.info.menu() : [];
if (!this.showActions && this.info.actions) { if (this.narrow && this.info.actions) {
menu = [...this.info.actions.map(x => ({ menu = [...this.info.actions.map(x => ({
text: x.text, text: x.text,
icon: x.icon, icon: x.icon,
@ -124,6 +139,18 @@ export default defineComponent({
popupMenu(menu, ev.currentTarget || ev.target); popupMenu(menu, ev.currentTarget || ev.target);
}, },
showTabsPopup(ev) {
if (!this.hasTabs) return;
ev.preventDefault();
ev.stopPropagation();
const menu = this.info.tabs.map(tab => ({
text: tab.title,
icon: tab.icon,
action: tab.onClick,
}));
popupMenu(menu, ev.currentTarget || ev.target);
},
preventDrag(ev) { preventDrag(ev) {
ev.stopPropagation(); ev.stopPropagation();
} }
@ -135,7 +162,7 @@ export default defineComponent({
.fdidabkb { .fdidabkb {
display: flex; display: flex;
&.center { &.slim {
text-align: center; text-align: center;
> .titleContainer { > .titleContainer {
@ -190,6 +217,7 @@ export default defineComponent({
overflow: auto; overflow: auto;
white-space: nowrap; white-space: nowrap;
text-align: left; text-align: left;
font-weight: bold;
> .avatar { > .avatar {
$size: 32px; $size: 32px;
@ -219,6 +247,54 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
&.activeTab {
text-align: center;
> .chevron {
display: inline-block;
margin-left: 6px;
}
}
}
}
}
> .tabs {
margin-left: 16px;
font-size: 0.8em;
> .tab {
display: inline-block;
position: relative;
padding: 0 10px;
height: 100%;
font-weight: normal;
opacity: 0.7;
&:hover {
opacity: 1;
}
&.active {
opacity: 1;
&:after {
content: "";
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
width: 100%;
height: 3px;
background: var(--accent);
}
}
> .icon + .title {
margin-left: 8px;
} }
} }
} }

View file

@ -11,28 +11,28 @@
<transition name="nav"> <transition name="nav">
<nav class="nav" :class="{ iconOnly, hidden }" v-show="showing"> <nav class="nav" :class="{ iconOnly, hidden }" v-show="showing">
<div> <div>
<button class="item _button account" @click="openAccountMenu"> <button class="item _button account" @click="openAccountMenu" v-click-anime>
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button> </button>
<MkA class="item index" active-class="active" to="/" exact> <MkA class="item index" active-class="active" to="/" exact v-click-anime>
<i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> <i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</MkA> </MkA>
<template v-for="item in menu"> <template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div> <div v-if="item === '-'" class="divider"></div>
<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to"> <component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to" v-click-anime>
<i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> <i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span>
<span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> <span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span>
</component> </component>
</template> </template>
<div class="divider"></div> <div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" class="item" active-class="active" to="/instance"> <MkA v-if="$i.isAdmin || $i.isModerator" class="item" active-class="active" to="/instance" v-click-anime>
<i class="fas fa-server fa-fw"></i><span class="text">{{ $ts.instance }}</span> <i class="fas fa-server fa-fw"></i><span class="text">{{ $ts.instance }}</span>
</MkA> </MkA>
<button class="item _button" @click="more"> <button class="item _button" @click="more" v-click-anime>
<i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span> <i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherNavItemIndicated" class="indicator"><i class="fas fa-circle"></i></span> <span v-if="otherNavItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
</button> </button>
<MkA class="item" active-class="active" to="/settings"> <MkA class="item" active-class="active" to="/settings" v-click-anime>
<i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span> <i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA> </MkA>
<button class="item _button post" @click="post"> <button class="item _button post" @click="post">
@ -263,24 +263,32 @@ export default defineComponent({
> .item { > .item {
padding-left: 0; padding-left: 0;
padding: 18px 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
font-size: $ui-font-size * 1.1; font-size: $ui-font-size * 1.1;
line-height: 3.7rem; line-height: initial;
> i, > i,
> .avatar { > .avatar {
margin-right: 0; display: block;
margin: 0 auto;
} }
> i { > i {
left: 10px; opacity: 0.7;
} }
> .text { > .text {
display: none; display: none;
} }
&:hover, &.active {
> i, > .text {
opacity: 1;
}
}
&:first-child { &:first-child {
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -314,10 +322,11 @@ export default defineComponent({
height: calc(var(--vh, 1vh) * 100); height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
overflow-x: clip;
background: var(--navBg); background: var(--navBg);
> .divider { > .divider {
margin: 16px 0; margin: 16px 16px;
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
} }
@ -326,7 +335,7 @@ export default defineComponent({
display: block; display: block;
padding-left: 24px; padding-left: 24px;
font-size: $ui-font-size; font-size: $ui-font-size;
line-height: 3rem; line-height: 2.85rem;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
@ -336,6 +345,7 @@ export default defineComponent({
color: var(--navFg); color: var(--navFg);
> i { > i {
position: relative;
width: 32px; width: 32px;
} }
@ -359,6 +369,11 @@ export default defineComponent({
animation: blink 1s infinite; animation: blink 1s infinite;
} }
> .text {
position: relative;
font-size: 0.9em;
}
&:hover { &:hover {
text-decoration: none; text-decoration: none;
color: var(--navHoverFg); color: var(--navHoverFg);
@ -368,6 +383,23 @@ export default defineComponent({
color: var(--navActive); color: var(--navActive);
} }
&:hover, &.active {
&:before {
content: "";
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
background: var(--accentedBg);
}
}
&:first-child, &:last-child { &:first-child, &:last-child {
position: sticky; position: sticky;
z-index: 1; z-index: 1;
@ -380,14 +412,38 @@ export default defineComponent({
&:first-child { &:first-child {
top: 0; top: 0;
margin-bottom: 16px;
border-bottom: solid 0.5px var(--divider); &:hover, &.active {
&:before {
content: none;
}
}
} }
&:last-child { &:last-child {
bottom: 0; bottom: 0;
margin-top: 16px; color: var(--fgOnAccent);
border-top: solid 0.5px var(--divider);
&:before {
content: "";
display: block;
width: calc(100% - 20px);
height: calc(100% - 20px);
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accent);
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
} }
} }
} }

View file

@ -12,7 +12,7 @@
</div> </div>
</template> </template>
<main class="main" @contextmenu.stop="onContextmenu"> <main class="main" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
<header class="header" @click="onHeaderClick"> <header class="header" @click="onHeaderClick">
<XHeader :info="pageInfo" :back-button="true" @back="back()"/> <XHeader :info="pageInfo" :back-button="true" @back="back()"/>
</header> </header>
@ -145,6 +145,15 @@ export default defineComponent({
} }
}, '*'); }, '*');
}, { passive: true }); }, { passive: true });
window.addEventListener('touchmove', ev => {
this.$refs.live2d.contentWindow.postMessage({
type: 'moveCursor',
body: {
x: ev.touches[0].clientX - iframeRect.left,
y: ev.touches[0].clientY - iframeRect.top,
}
}, '*');
}, { passive: true });
} }
}, },

View file

@ -2,8 +2,8 @@
<div class="mk-app" :class="{ wallpaper }"> <div class="mk-app" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/> <XSidebar ref="nav" class="sidebar"/>
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu"> <div class="contents" ref="contents" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
<header class="header" ref="header" @click="onHeaderClick"> <header class="header" ref="header" @click="onHeaderClick" :style="{ background: pageInfo?.bg }">
<XHeader :info="pageInfo" :back-button="true" @back="back()"/> <XHeader :info="pageInfo" :back-button="true" @back="back()"/>
</header> </header>
<main ref="main"> <main ref="main">
@ -258,7 +258,6 @@ export default defineComponent({
} }
> .sidebar { > .sidebar {
border-right: solid 0.5px var(--divider);
} }
> .contents { > .contents {
@ -314,6 +313,7 @@ export default defineComponent({
> .widgets { > .widgets {
padding: 0 var(--margin); padding: 0 var(--margin);
border-left: solid 0.5px var(--divider); border-left: solid 0.5px var(--divider);
background: var(--bg);
@media (max-width: $widgets-hide-threshold) { @media (max-width: $widgets-hide-threshold) {
display: none; display: none;

View file

@ -1,7 +1,7 @@
# AiScript # AiScript
AiScriptは、Misskeyで使用できるスクリプト言語です。 AiScript is a scripting language for Misskey.
<div class="info"> AiScript実装はMisskeyとは別リポジトリで、<a href="https://github.com/syuilo/aiscript" target="_blank">オープンソースで公開されています。</a></div> <div class="info"> AiScript is open source and hosted in a separate repository from Misskey. </a></div>
## 使い方 ## 使い方
AiScriptの構文や組み込み関数などのドキュメントは、[こちら](https://github.com/syuilo/aiscript/tree/master/docs)で公開されています。 AiScript documentation such as syntax and built-in functions can be found [here](https://github.com/syuilo/aiscript/tree/master/docs).

View file

@ -1,7 +1,7 @@
# プラグインの作成 # プラグインの作成
Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。 Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。
## メタデータ ## Metadatumoj
プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。 メタデータは次のプロパティを含むオブジェクトです。 プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。 メタデータは次のプロパティを含むオブジェクトです。
### name ### name
@ -11,7 +11,7 @@ Nomo de kromaĵo
プラグイン作者 プラグイン作者
### version ### version
プラグインバージョン。数値を指定してください。 Versio de kromaĵo.数値を指定してください。
### description ### description
プラグインの説明 プラグインの説明

View file

@ -1,15 +1,15 @@
# キーボードショートカット # Fulmoklavoj
## Malloka ## Malloka
これらのショートカットは基本的にどこでも使えます。 これらのショートカットは基本的にどこでも使えます。
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Fulmoklavoj</th><th>効果</th><th>由来</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr> <tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>Skribi novan noton</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr> <tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr> <tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>Malfermi sekcio de sciigoj</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>Serĉi</td><td><b>S</b>earch</td></tr> <tr><td><kbd class="key">S</kbd></td><td>Serĉi</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr> <tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
</tbody> </tbody>
@ -18,19 +18,19 @@
## 投稿にフォーカスされた状態 ## 投稿にフォーカスされた状態
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Fulmoklavoj</th><th>効果</th><th>Deveno (angla)</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上の投稿にフォーカスを移動</td><td>-</td></tr> <tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上の投稿にフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">J</kbd>, <kbd class="key">Tab</kbd></td><td>下の投稿にフォーカスを移動</td><td>-</td></tr> <tr><td><kbd class="key"></kbd>, <kbd class="key">J</kbd>, <kbd class="key">Tab</kbd></td><td>下の投稿にフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key">R</kbd></td><td>返信フォームを開く</td><td><b>R</b>eply</td></tr> <tr><td><kbd class="key">R</kbd></td><td>返信フォームを開く</td><td><b>R</b>eply</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>Renoteフォームを開く</td><td><b>Q</b>uote</td></tr> <tr><td><kbd class="key">Q</kbd></td><td>Renoteフォームを開く</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr> <tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>Tuj plusendos (sen la fasado)</td><td>-</td></tr>
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr> <tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr> <tr><td><kbd class="key">0</kbd>-<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
<tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>Aldoni vian liston de preferaĵoj</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr> <tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>Aldoni vian liston de preferaĵoj</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr>
<tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>投稿を削除</td><td><b>D</b>elete</tr> <tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>Forviŝi la noton</td><td><b>D</b>elete</tr>
<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr> <tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>Malfelmi poŝtaĵan menuon</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr> <tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr> <tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
</tbody> </tbody>
@ -39,11 +39,11 @@
## Renoteフォーム ## Renoteフォーム
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Fulmoklavoj</th><th>Efektoj</th><th>Deveno (angla)</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key">Enter</kbd></td><td>Fari renoton</td><td>-</td></tr> <tr><td><kbd class="key">Enter</kbd></td><td>Plusendi</td><td>-</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>フォームを展開する</td><td><b>Q</b>uote</td></tr> <tr><td><kbd class="key">Q</kbd></td><td>Malfermi sekcio</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉じる</td><td>-</td></tr> <tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉じる</td><td>-</td></tr>
</tbody> </tbody>
</table> </table>
@ -52,7 +52,7 @@
デフォルトで「👍」にフォーカスが当たっている状態です。 デフォルトで「👍」にフォーカスが当たっている状態です。
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Fulmoklavoj</th><th>効果</th><th>Deveno (angla)</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd></td><td>上のリアクションにフォーカスを移動</td><td>-</td></tr> <tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd></td><td>上のリアクションにフォーカスを移動</td><td>-</td></tr>
@ -60,7 +60,7 @@
<tr><td><kbd class="key"></kbd>, <kbd class="key">H</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>左のリアクションにフォーカスを移動</td><td>-</td></tr> <tr><td><kbd class="key"></kbd>, <kbd class="key">H</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>左のリアクションにフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">L</kbd>, <kbd class="key">Tab</kbd></td><td>右のリアクションにフォーカスを移動</td><td>-</td></tr> <tr><td><kbd class="key"></kbd>, <kbd class="key">L</kbd>, <kbd class="key">Tab</kbd></td><td>右のリアクションにフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key">Enter</kbd>, <kbd class="key">Space</kbd>, <kbd class="key">+</kbd></td><td>リアクション確定</td><td>-</td></tr> <tr><td><kbd class="key">Enter</kbd>, <kbd class="key">Space</kbd>, <kbd class="key">+</kbd></td><td>リアクション確定</td><td>-</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションで確定</td><td>-</td></tr> <tr><td><kbd class="key">0</kbd>-<kbd class="key">9</kbd></td><td>数字に対応したリアクションで確定</td><td>-</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>リアクションするのをやめる</td><td>-</td></tr> <tr><td><kbd class="key">Esc</kbd></td><td>リアクションするのをやめる</td><td>-</td></tr>
</tbody> </tbody>
</table> </table>

View file

@ -5,8 +5,8 @@ MFMは、Misskey Flavored Markdownの略で、Misskeyの様々な場所で使用
- ノート本文 - ノート本文
- CW注釈 - CW注釈
- Nomo de uzanto - Nomo de uzanto
- ユーザーの自己紹介 - Profilo de uzanto
## 開発者向け情報 ## Informoj por programistoj
MFMのパーサー実装はライブラリとして公開されており、簡単にクライアントにMFMを組み込むことが可能です。 MFMのパーサー実装はライブラリとして公開されており、簡単にクライアントにMFMを組み込むことが可能です。
- [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScriptパーサー実装 - [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScriptパーサー実装

View file

@ -12,7 +12,7 @@
<div class="info"> コンピューターのクリップボードに画像データがある状態で、フォーム内のテキストボックスにペーストするとその画像を添付することができます。</div> <div class="info"> コンピューターのクリップボードに画像データがある状態で、フォーム内のテキストボックスにペーストするとその画像を添付することができます。</div>
<div class="info"> テキストボックス内で<kbd class="key">Ctrl + Enter</kbd>を押すことでも投稿できます。</div> <div class="info"> テキストボックス内で<kbd class="key">Ctrl + Enter</kbd>を押すことでも投稿できます。</div>
## Fari renoton ## Plusendi la noton
既にあるートを引用、もしくはそのートを新しいートとして共有する行為、またそれによって作成されたートをRenoteと呼びます。 自分がフォローしているユーザーの、気に入ったノートを自分のフォロワーに共有したい場合や、過去の自分のノートを再度共有したい場合に使います。 同じートに対して無制限にRenoteを行うことができますが、あまり連続して使用すると迷惑になる場合もあるので、注意しましょう。 既にあるートを引用、もしくはそのートを新しいートとして共有する行為、またそれによって作成されたートをRenoteと呼びます。 自分がフォローしているユーザーの、気に入ったノートを自分のフォロワーに共有したい場合や、過去の自分のノートを再度共有したい場合に使います。 同じートに対して無制限にRenoteを行うことができますが、あまり連続して使用すると迷惑になる場合もあるので、注意しましょう。
<div class="warn">⚠️ 公開範囲がフォロワーやダイレクトのートはRenoteできません</div> <div class="warn">⚠️ 公開範囲がフォロワーやダイレクトのートはRenoteできません</div>

View file

@ -14,18 +14,18 @@
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿が流れます。GTLと略されます。 全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿が流れます。GTLと略されます。
## 比較 ## 比較
| ソース | | | Templinio | | | | ソース | | | Templinio | | |
| ------------ | --------- | ----- | --------- | ------- | ------- | | --------------------- | --------- | ----- | --------- | ------- | ------- |
| Uzantoj | Videbleco | Hejma | Loka | Sociala | Malloka | | Uzantoj | Videbleco | Hejma | Loka | Sociala | Malloka |
| ローカル (フォロー) | Publikigi | ✔ | ✔ | ✔ | ✔ | | Lokaj (sekvataj) | Publika | ✔ | ✔ | ✔ | ✔ |
| | Hejma | ✔ | | ✔ | | | | Nur hejma | ✔ | | ✔ | |
| | Sekvantoj | ✔ | ✔ | ✔ | ✔ | | | Sekvantoj | ✔ | ✔ | ✔ | ✔ |
| リモート (フォロー) | Publikigi | ✔ | | ✔ | ✔ | | Transaj (sekvataj) | Publika | ✔ | | ✔ | ✔ |
| | Hejma | ✔ | | ✔ | | | | Nur hejma | ✔ | | ✔ | |
| | Sekvantoj | ✔ | | ✔ | ✔ | | | Sekvantoj | ✔ | | ✔ | ✔ |
| ローカル (未フォロー) | Publikigi | | ✔ | ✔ | ✔ | | Lokaj (ne sekvataj) | Publika | | ✔ | ✔ | ✔ |
| | Hejma | | | | | | | Nur hejma | | | | |
| | Sekvantoj | | | | | | | Sekvantoj | | | | |
| リモート (未フォロー) | Publikigi | | | | ✔ | | Transaj (ne sekvataj) | Publika | | | | ✔ |
| | Hejma | | | | | | | Nur hejma | | | | |
| | Sekvantoj | | | | | | | Sekvantoj | | | | |

View file

@ -10,7 +10,7 @@ Misskeyに関する用語集です。
## API ## API
(読み: えーぴーあい) Misskeyのサーバーが公開している、プログラムからMisskeyを扱うためのインターフェース。詳細は[こちら。](../advanced/api) (読み: えーぴーあい) Misskeyのサーバーが公開している、プログラムからMisskeyを扱うためのインターフェース。詳細は[こちら。](../advanced/api)
## Bot ## Roboto
(読み: ぼっと) プログラムによって動作しているアカウント。 (読み: ぼっと) プログラムによって動作しているアカウント。
## CW ## CW
@ -19,14 +19,14 @@ Misskeyに関する用語集です。
## Fediverso ## Fediverso
(読み: ふぇでぃばーす) Misskeyを含む様々な分散型ソフトウェアのサーバーで構成されたネットワーク。 (読み: ふぇでぃばーす) Misskeyを含む様々な分散型ソフトウェアのサーバーで構成されたネットワーク。
## MTL ## MTL (GTL)
An abbreviation for "Malloka TempLinio".タイムラインの詳細は[こちら。](../features/timeline) Kaplitero de "Malloka TempLinio".タイムラインの詳細は[こちら。](../features/timeline)
## HTL ## HTL
ホームタイムライン(Home TimeLine)の略。タイムラインの詳細は[こちら。](../features/timeline) Kaplitero de "Hejma TempLinio".タイムラインの詳細は[こちら。](../features/timeline)
## LTL ## LTL
An abbreviation for "Loka TempLinio".タイムラインの詳細は[こちら。](../features/timeline) Kaplitero de "Loka TempLinio".タイムラインの詳細は[こちら。](../features/timeline)
## MFM ## MFM
(読み: えむえふえむ) Misskey Flavored Markdownの略で、Misskey上で使用できるマークアップ言語です。詳細は[こちら。](../features/mfm) (読み: えむえふえむ) Misskey Flavored Markdownの略で、Misskey上で使用できるマークアップ言語です。詳細は[こちら。](../features/mfm)
@ -34,25 +34,25 @@ An abbreviation for "Loka TempLinio".タイムラインの詳細は[こちら。
## NSFW ## NSFW
(読み: のっとせーふふぉーわーく) Not Safe For Workの略。画像を「閲覧注意」扱いにし、操作なしには表示しないようにすることができる機能。 (読み: のっとせーふふぉーわーく) Not Safe For Workの略。画像を「閲覧注意」扱いにし、操作なしには表示しないようにすることができる機能。
## Renoto ## Notoj plusenditaj
(読み: りのーと) 既にあるノートを引用、もしくはそのノートを新しいノートとして共有する行為、またそれによって作成されたノート。詳細は[こちら。](../features/note) (読み: りのーと) 既にあるノートを引用、もしくはそのノートを新しいノートとして共有する行為、またそれによって作成されたノート。Rigardu por sciu pli tie[.](../features/note)
## STL ## STL
An abbreviation for "Sociala TempLinio".タイムラインの詳細は[こちら。](../features/timeline) Kaplitero de "Sociala TempLinio".Por sciu pri la templinio, rigardu tie[.](../features/timeline)
## Ai ## Ai
Ai estas oficiala maskoto de Misskey. Ai estas oficiala maskoto de Misskey.
## Aktivaj Uzantoj: ## Aktiva uzanto
インスタンスにアカウントを作っているユーザーのうち、現在も実際にサービスを利用しているユーザーのこと。 インスタンスにアカウントを作っているユーザーのうち、現在も実際にサービスを利用しているユーザーのこと。
## Nodo ## Nodo
todo todo
## Ŝaltpodio ## Personecigitaj emoĵioj
サーバーで用意された絵文字。カスタム絵文字ではない通常の絵文字は「Unicode絵文字」と区別して呼ばれる。 サーバーで用意された絵文字。カスタム絵文字ではない通常の絵文字は「Unicode絵文字」と区別して呼ばれる。
## コントロールパネル ## Ŝaltpodio
インスタンスの設定画面のこと。 インスタンスの設定画面のこと。
## Servilo ## Servilo
@ -61,25 +61,25 @@ todo
## Mutigi ## Mutigi
ノートをパブリックな公開範囲で投稿できなくされている状態。モデレーターの判断でユーザーごとに設定されます。詳細は[こちら。](../features/silence) ノートをパブリックな公開範囲で投稿できなくされている状態。モデレーターの判断でユーザーごとに設定されます。詳細は[こちら。](../features/silence)
## Disko ## ジョブキュー
アクティビティ配送などを順番に行うためのシステム。 アクティビティ配送などを順番に行うためのシステム。
## Flostigi ## Flostigita
アカウントが使用不可に設定されている状態。 アカウントが使用不可に設定されている状態。
## Miskiisto ## Disko
Misskeyにアップロードしたファイルを管理する機能。詳細は[こちら。](../features/drive) Misskeyにアップロードしたファイルを管理する機能。詳細は[こちら。](../features/drive)
## Notoj ## Notoj
Misskeyに投稿される、文章、ファイル、アンケートなどを含めることができるコンテンツ。詳細は[こちら。](../features/note) Misskeyに投稿される、文章、ファイル、アンケートなどを含めることができるコンテンツ。Rigardu por sciu pli tie[.](../features/note)
## Miskiisto ## Miskiisto
Misskeyを使う人のこと。 Uzuloj de Misskey.
## Kontrolisto ## Kontrolisto
スパムの凍結およびサイレンスや不適切な投稿の削除など、コミュニティ運営に関する権限を持つユーザー。 スパムの凍結およびサイレンスや不適切な投稿の削除など、コミュニティ運営に関する権限を持つユーザー。
## Transa/fora ## Transa, Surloka
他サーバーのことを指します。リモートユーザーといったように接頭辞としても使われます。ローカルの逆です。 他サーバーのことを指します。リモートユーザーといったように接頭辞としても使われます。ローカルの逆です。
## Kunfederado ## Kunfederado

View file

@ -1,7 +1,7 @@
# リンク集 # リンク集
## Webサイト ## Webサイト
- [Oficiala Discord](https://discord.gg/Wp8gVStHW3) - Servilo Discord'a oficiala de Misskey - [Oficiala Discord](https://discord.gg/Wp8gVStHW3) - la Servilo Discord'a oficiala de Misskey
- [Misskey Forum](https://forum.misskey.io/) - Misskeyに関する話題を扱うフォーラム - [Misskey Forum](https://forum.misskey.io/) - Misskeyに関する話題を扱うフォーラム
## Kontoj ## Kontoj

View file

@ -1,6 +1,6 @@
# Pri Misskey # Pri Misskey
Misskey estas malfermitkoda distribuita mikroblogo. Ĝia trajtoj estas diversaj funkcioj je disko aŭ reagoj ktp, kaj alte agordebla fasado. Evoluigo ekfaris de syuilo de 2014. Misskey estas malfermitkoda distribuita mikroblogo. Ĝi enhavas diversaj funkcioj ekzemple disko, reagoj, ktp kaj alte agordebla fasado. Evoluigo ekfaris de syuilo de 2014.
## Historio ## Historio
開発当初は掲示板がメインのサービスでしたが、ユーザーが短文を投稿し、それを時系列で流れるタイムライン機能を追加したところ人気が高まり、徐々にそれがメインとして開発が進むようになりました。 当初は分散型ではありませんでしたが、2018年にActivityPubを実装し分散型になったことで、より多くの方に認知され利用されるサービスになり、現在に至ります。 開発当初は掲示板がメインのサービスでしたが、ユーザーが短文を投稿し、それを時系列で流れるタイムライン機能を追加したところ人気が高まり、徐々にそれがメインとして開発が進むようになりました。 当初は分散型ではありませんでしたが、2018年にActivityPubを実装し分散型になったことで、より多くの方に認知され利用されるサービスになり、現在に至ります。

View file

@ -1,4 +1,4 @@
# トラブルシューティング # Problemsolvi
<div class="info"> <a href="./faq">よくある質問</a>も合わせてお役立てください。</div> <div class="info"> <a href="./faq">よくある質問</a>も合わせてお役立てください。</div>
問題が発生したときは、まずこちらをご確認ください。 該当する項目が無い、もしくは手順を試しても効果がない場合は、サーバーの管理者に連絡するか[不具合を報告](./report-issue)してください。 問題が発生したときは、まずこちらをご確認ください。 該当する項目が無い、もしくは手順を試しても効果がない場合は、サーバーの管理者に連絡するか[不具合を報告](./report-issue)してください。

View file

@ -1,8 +1,8 @@
# LTL/STL/GTLの無効化 # 타임라인의 비활성화
Misskeyでは、LTL/STL/GTLをそれぞれ無効化することができます。有効/無効を切り替えるには、インスタンスコントロールパネルで設定します。 Misskey에서는 로컬, 소셜, 글로벌 타임라인을 각각 비활성화할 수 있습니다. 활성화 유무는 인스턴스 설정에서 제어판에서 설정할 수 있습니다.
LTLやSTLは、そのインスタンス全員の投稿が見れるため、新規のユーザーにとってはユーザーを探す必要がなくなり、興味のあるユーザーを見つけやすいという利点があります。 しかし同時に、フォロー機能が活用されなくなったり、不適切な投稿が目につきやすくなったり、チャットのようになることで内輪感が生じて逆に新規ユーザーが参加しにくくなるといったデメリットも持ち合わせています。 サーバーによってメリット/デメリットどちらが優勢かは異なるので、オプションとして無効にできるようになっています。 もしデメリットの方が上回っていると感じたら、それらのタイムラインを無効化することも検討してください。 로컬 타임라인이나 소셜 타임라인에서는 인스턴스에 있는 모든 유저의 게시물을 볼 수 있으므로, 신규 유저가 다른 유저를 찾고, 관심사가 비슷한 유저를 찾기 쉽다는 장점이 있습니다. 그러나, 이런 특징 때문에 팔로우 기능의 활용이 저하되거나, 부적절한 게시물이 노출되기 쉬워지고, 타임라인 대화로 인해 인스턴스가 하나의 친목 커뮤니티화되는 경우 신규 유저의 진입 장벽으로 작용할 수 있는 등의 단점 또한 존재합니다. 각 타임라인의 특성이 인스턴스에 어떠한 영향을 주는 지는 인스턴스의 특성에 따라 다르므로, 설정을 통해 활성화 또는 비활성화할 수 있도록 설계되어 있습니다. 만약 장점에 비해 단점이 더 눈에 띄는 경우, 각각의 타임라인을 비활성화하는 것을 검토해 보시기 바랍니다.
<div class="warn">⚠️ 無効化を行うと、ユーザーが困惑し、短期的に見て利用者が減る可能性があります。そのため、無効化の際は影響を慎重に検討し、事前に説明してフォローを整える期間を一定程度設けることを推奨します。</div> <div class="warn">⚠️ 타임라인을 비활성화할 경우, 유저들의 혼란을 불러 일으키며 단기적으로 이용자가 감소할 수 있습니다. 따라서, 비활성화를 결정하기 이전에 영향에 대해 신중히 검토하고, 사전에 유저에게 공지하고 일정 기간 유예기간을 두어 유저 간 팔로우 관계가 형성되도록 하는 것을 권장합니다.</div>
なお、管理者/モデレーターは、これらのタイムラインの無効化状態は適用されず、引き続き利用することが可能です。 또한, 관리자 및 모더레이터는 타임라인 비활성화 유무에 상관없이 각각의 타임라인을 이용할 수 있습니다.

View file

@ -1,5 +1,5 @@
# よくある質問 # 자주 묻는 질문
ここでは、サーバー管理者向けのよくある質問を掲載しています。 여기에서는 서버 운영에 관련해서 자주 묻는 질문에 대해 다룹니다.
## デフォルトテーマを設定したい ## 기본 테마를 설정하고 싶어요
現在、デフォルトテーマ設定機能は実装されていません。 현재 기본 테마를 설정하는 기능은 구현되어 있지 않습니다.

View file

@ -1,7 +1,7 @@
# AiScript # AiScript
AiScriptは、Misskeyで使用できるスクリプト言語です。 AiScript는, Misskey에서 사용할 수 있는 스크립트 언어입니다.
<div class="info"> AiScript実装はMisskeyとは別リポジトリで、<a href="https://github.com/syuilo/aiscript" target="_blank">オープンソースで公開されています。</a></div> <div class="info"> AiScript 기능은 Misskey와 별도 리포지토리에서 <a href="https://github.com/syuilo/aiscript" target="_blank">오픈소스로 공개하고 있습니다.</a></div>
## 使い方 ## 사용법
AiScriptの構文や組み込み関数などのドキュメントは、[こちら](https://github.com/syuilo/aiscript/tree/master/docs)で公開されています。 AiScript의 구문이나 내장 함수에 대한 문서는 [여기에서](https://github.com/syuilo/aiscript/tree/master/docs) 확인할 수 있습니다.

View file

@ -1,6 +1,6 @@
# サードパーティアプリのリスト # 서드파티 어플리케이션 목록
## クライアント ## 클라이언트
todo todo
## 連携サービス ## 연동 서비스
todo todo

View file

@ -1,5 +1,5 @@
# 更新履歴 # 업데이트 정보
<div class="info"> このサーバーの更新履歴です。Misskeyの最新のリリースについては、<a href="https://github.com/misskey-dev/misskey/blob/master/CHANGELOG.md" target="_blank">GitHub</a>をご確認ください。</div> <div class="info"> 이 서버의 업데이트 정보입니다. Misskey 최신 버전의 업데이트 정보는 <a href="https://github.com/misskey-dev/misskey/blob/master/CHANGELOG.md" target="_blank">Github에서</a> 확인할 수 있습니다.</div>
<!-- For translators: Do not edit these comments. --> <!-- For translators: Do not edit these comments. -->
<!--[CHANGELOG]--> <!--[CHANGELOG]-->

View file

@ -1,28 +1,28 @@
# よくある質問 # 자주 묻는 질문
ここでは利用上のよくある質問について掲載しています。 Misskeyのプロジェクト自体についてのよくある質問は[こちら](./misskey)に掲載されています。 여기에서는 Misskey 이용에 대해서 자주 묻는 질문에 대해 다룹니다. Misskey 프로젝트 자체에 대한 질문은 [여기에서](./misskey) 다루고 있습니다.
## iOS/Androidのアプリはありますか? ## iOS/Android용 어플리케이션이 있나요?
公式にはそういったOSのネイティブアプリを開発していませんが、サードパーティ製のアプリがいくつかあります。 詳しくは[こちら](./apps)をご覧ください。 공식 어플리케이션은 없지만, 비공식 서드파티 어플리케이션을 사용하실 수 있습니다. 자세한 사항은 [여기를](./apps) 참조해 주세요.
ただ、サードパーティ製アプリはどうしても機能への対応が遅れてしまうため、とくに拘りがなければ公式のWebクライアントの利用をおすすめします。 なお、MisskeyのWebクライアントはPWAに対応しているので、ネイティブアプリのように動作させることも可能です。 詳しくは[こちら](todo)をご覧ください。 단, 비공식 어플리케이션의 경우 최신 기능을 사용할 수 있기까지 시간이 걸릴 수 있으므로, 대부분의 경우 공식 Web 클라이언트를 사용하시는 것을 추천드립니다. 또한, Misskey의 Web 클라이언트는 PWA를 지원하여, 브라우저에서 지원하는 경우 네이티브 앱처럼 사용할 수 있습니다. 자세한 사항은 [여기를](todo) 참조해 주세요.
## Mastodonクライアントでログインできないのですが? ## Mastodon 클라이언트로는 로그인할 수 없나요?
MisskeyはMastodonのAPIと互換性がないため、一部を除きMastodonクライアントでMisskeyを利用することはできません。 Misskey는 Mastdon의 API와 호환성이 없기 때문에, 일부를 제외한 Mastodon 클라이언트에서는 Misskey를 이용하실 수 없습니다.
## 他のサーバーのユーザーをフォローするときは? ## 다른 서버의 유저를 팔로우하고 싶어요
メニューから検索を選び、ユーザー名をホスト込みで入力します。例: `@syuilo@misskey.io` 메뉴에서 검색을 선택해서, 유저명과 호스트(도메인)을 입력하세요. 예시: `@syuilo@misskey.io`
## Renoteを削除するには? ## Renote를 취소할래요
Renoteの時刻表示の隣にある「...」を押し、「Renote解除」を選択します。 Renoteについては[こちら](../features/note)をご確認ください。 Renote한 시간 옆에 있는 '...'를 누르고, "Renote 취소"를 선택하세요. Renote에 대해서는 [여기를](../features/note) 참조해 주세요.
## URLのプレビューを表示させたくない ## URL의 미리보기를 숨기고 싶어요
MFMには、そのURLのプレビューを無効にする構文があります。詳細は[MFMチートシート](/mfm-cheat-sheet)をご確認ください。 MFM를 이용하여 URL의 미리보기를 숨길 수 있습니다. 자세한 사항은 [MFM 도움말](/mfm-cheat-sheet)을 확인해 주세요.
## カスタム絵文字を追加したい ## 커스텀 이모지를 추가하고 싶어요
運営者のみがカスタム絵文字を追加、編集、削除できます。それらを希望する場合は運営者に依頼してください。 커스텀 이모지의 추가, 편집 및 삭제는 운영자만 할 수 있습니다. 커스텀 이모지에 대해서는 각 인스턴스의 운영자에게 문의해 주세요.
## Botを開発したい ## Bot을 개발하고 싶어요
Misskey APIを利用してBotの開発が可能です。[こちら](../advanced/develop-bot)をご確認ください。 Misskey API를 이용하여 Bot을 개발할 수 있습니다. 자세한 사항은 [여기를](../advanced/develop-bot) 참조해 주세요.
## ノートの翻訳機能はどのサービスを使用していますか? ## 노트 번역 기능은 어떤 서비스를 사용하나요?
[DeepL](https://www.deepl.com/)を使用しています。 노트 번역에는 [DeepL](https://www.deepl.com/)을 사용하고 있습니다.

View file

@ -1,89 +1,89 @@
# 用語集 # 용어 사전
Misskeyに関する用語集です。 Misskey에서 사용하는 용어 모음입니다.
## ActivityPub ## ActivityPub
(読み: あくてぃびてぃぱぶ) 分散型を実現するために用いられるプロトコル(仕様)。このプロトコルに則ってサーバー同士通信を行うことで、連合が行われ、Fediverseを形成しています。 분산형 서비스를 구현하기 위해 사용되는 프로토콜(통신 규약). 이 프로토콜을 통해 다른 서버와 통신하여 연합을 이루고, 이들이 모여 연합우주(Fediverse)를 이룹니다.
## AiScript ## AiScript
(読み: あいすくりぷと) Misskey上で使用できるプログラミング言語です。詳細は[こちら。](../advanced/aiscript) 아이스크립트. Misskey에서 사용할 수 있는 프로그래밍 언어. 자세한 내용은 [여기로](../advanced/aiscript)
## API ## API
(読み: えーぴーあい) Misskeyのサーバーが公開している、プログラムからMisskeyを扱うためのインターフェース。詳細は[こちら。](../advanced/api) 에이-피-아이. 프로그램이 Misskey와 상호 작용하는 데에 사용하는 인터페이스. 자세한 내용은 [여기로](../advanced/api)
## Bot ## Bot (봇)
(読み: ぼっと) プログラムによって動作しているアカウント。 프로그램 또는 기타 자동화된 수단으로 운영되는 계정.
## CW ## CW
(読み: こんてんつわーにんぐ) Contents Warningの略。ートの内容を、操作なしには表示しないようにできる機能。主に長大な内容を隠すためや、ネタバレ防止などに使われます。 'Contents Warning'의 줄임말. 제목를 설정하여 노트의 내용을 숨기는 기능. 본문이 길 때, 또는 스포일러 등 상대방이 불쾌해할 수 있는 내용을 감추는 데에 주로 사용합니다.
## Fediverse ## 연합우주 (Fediverse)
(読み: ふぇでぃばーす) Misskeyを含む様々な分散型ソフトウェアのサーバーで構成されたネットワーク。 Misskey를 비롯한 분산형 서비스 사이의 통신 및 정보 교환으로 이루어지는 네트워크.
## GTL ## GTL
グローバルタイムライン(Global TimeLine)の略。タイムラインの詳細は[こちら。](../features/timeline) 글로벌 타임라인(Global TimeLine)의 줄임말. 타임라인에 대한 자세한 내용은 [여기로.](../features/timeline)
## HTL ## HTL
ホームタイムライン(Home TimeLine)の略。タイムラインの詳細は[こちら。](../features/timeline) 홈 타임라인(Home TimeLine)의 줄임말. 타임라인에 대한 자세한 내용은 [여기로.](../features/timeline)
## LTL ## LTL
ローカルタイムライン(Local TimeLine)の略。タイムラインの詳細は[こちら。](../features/timeline) 로컬 타임라인(Local TimeLine)의 줄임말. 타임라인에 대한 자세한 내용은 [여기로.](../features/timeline)
## MFM ## MFM
(読み: えむえふえむ) Misskey Flavored Markdownの略で、Misskey上で使用できるマークアップ言語です。詳細は[こちら。](../features/mfm) Misskey Flavored Markdown의 줄임말. Misskey에서 사용할 수 있는 마크업 언어. 자세한 사항은 [여기로.](../features/mfm)
## NSFW ## NSFW
(読み: のっとせーふふぉーわーく) Not Safe For Workの略。画像を「閲覧注意」扱いにし、操作なしには表示しないようにすることができる機能。 Not Safe For Work의 줄임말. 열람 시 주의가 필요한 사진을 감추어, 바로 표시되지 않게 하는 기능.
## Renote ## Renote
(読み: りのーと) 既にあるノートを引用、もしくはそのノートを新しいノートとして共有する行為、またそれによって作成されたノート。詳細は[こちら。](../features/note) 이미 존재하는 노트를 인용, 또는 새로 작성한 노트처럼 타임라인에 공유하는 것. 또는 이를 통해 새로 작성된 노트를 가리키는 말. 자세한 사항은 [여기로.](../features/note)
## STL ## STL
ソーシャルタイムライン(Social TimeLine)の略。タイムラインの詳細は[こちら。](../features/timeline) 소셜 타임라인(Social TimeLine)의 줄임말. 타임라인에 대한 자세한 내용은 [여기로.](../features/timeline)
## ## 아이(Ai)
(読み: あい) Misskeyの看板娘(公式キャラクター)です。 Misskey의 공식 마스코트입니다.
## アクティブユーザー ## 활성 사용자
インスタンスにアカウントを作っているユーザーのうち、現在も実際にサービスを利用しているユーザーのこと。 인스턴스에 계정을 생성한 유저 중에서, 현재 실질적으로 서비스를 이용 중인 사용자를 일컫는 말.
## 인스턴스 ## 인스턴스
todo todo
## 커스텀 이모지 ## 커스텀 이모지
サーバーで用意された絵文字。カスタム絵文字ではない通常の絵文字は「Unicode絵文字」と区別して呼ばれる。 서버 자체적으로 사용할 수 있는 이모지. 커스텀 이모지 이외의 일반적인 이모지는 '유니코드 이모지'로 불린다.
## コントロールパネル ## 제어판
インスタンスの設定画面のこと。 인스턴스 설정 화면을 가리키는 말.
## 서버 ## 서버
todo todo
## 사일런스 ## 사일런스
ノートをパブリックな公開範囲で投稿できなくされている状態。モデレーターの判断でユーザーごとに設定されます。詳細は[こちら。](../features/silence) 노트를 공개 범위로 게시할 수 없게 된 상태. 관리자가 유저별로 설정할 수 있습니다. 자세한 사항은 [여기로.](../features/silence)
## 작업 대기열 ## 작업 대기열
アクティビティ配送などを順番に行うためのシステム。 액티비티 전송 등의 작업을 차례대로 수행하기 위한 시스템.
## 정지 ## 정지
アカウントが使用不可に設定されている状態。 계정을 사용할 수 없게 된 상태.
## 드라이브 ## 드라이브
Misskeyにアップロードしたファイルを管理する機能。詳細は[こちら。](../features/drive) Misskey에 업로드된 파일을 관리하는 기능. 자세한 사항은 [여기로.](../features/drive)
## 노트 ## 노트
Misskeyに投稿される、文章、ファイル、アンケートなどを含めることができるコンテンツ。詳細は[こちら。](../features/note) Misskey에서 글, 파일, 투표 등을 포함하여 작성할 수 있는 콘텐츠. 자세한 사항은 [여기로.](../features/note)
## ミスキスト ## 미스키스트 (Misskist)
Misskeyを使う人のこと。 미스키를 사용하는 사람을 일컫는 말.
## 모더레이터 ## 모더레이터
スパムの凍結およびサイレンスや不適切な投稿の削除など、コミュニティ運営に関する権限を持つユーザー。 스팸 계정의 정지 및 사일런스, 부적절한 게시물을 삭제하는 등 커뮤니티 운영에 대한 권한을 가진 유저.
## 리모트 ## 리모트
他サーバーのことを指します。リモートユーザーといったように接頭辞としても使われます。ローカルの逆です。 자신이 속한 서버와 구별하여, 다른 서버를 가리키는 말. '리모트 유저'와 같이 접두사처럼 사용하기도 합니다. 반대말로는 '로컬'이 있습니다.
## 연합 ## 연합
サーバー上で作成された情報が他のサーバーに伝わること。 서버에서 작성된 정보가 다른 서버로 전달되는 것을 이르는 말.
## 로컬 ## 로컬
自サーバーのことを指します。ローカルユーザー、ローカルタイムラインといったように接頭辞としても使われます。リモートの逆です。 자신이 속한 서버를 가리키는 말. '로컬 유저', '로컬 타임라인'과 같이 접두사처럼 사용하기도 합니다. 반대말로는 '로컬'이 있습니다.

View file

@ -1,12 +1,12 @@
# リンク集 # 참고할 만한 링크
## Webサイト ## 웹 사이트
- [Official Discord](https://discord.gg/Wp8gVStHW3) - Misskey公式Discordサーバー - [Official Discord](https://discord.gg/Wp8gVStHW3) - Misskey 공식 Discord 서버
- [Misskey Forum](https://forum.misskey.io/) - Misskeyに関する話題を扱うフォーラム - [Misskey Forum](https://forum.misskey.io/) - Misskey에 대한 이야기를 나누는 포럼
## 계정 ## 계정
- [@repo@misskey.io](https://misskey.io/@repo) - Misskeyのリポジトリの更新を投稿するbot - [@repo@misskey.io](https://misskey.io/@repo) - Misskey 리포지터리의 변경 사항을 게시하는 Bot
## ライブラリ ## 라이브러리
- [misskey-dev/misskey.js](https://github.com/misskey-dev/misskey.js) - JavaScriptのMisskey SDK - [misskey-dev/misskey.js](https://github.com/misskey-dev/misskey.js) - Javascript용 Misskey SDK
- [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScriptのMFMパーサー実装 - [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - Javascript 기반 MFM 구문 해석기

View file

@ -1,16 +1,16 @@
# Misskey에 대하여 # Misskey에 대하여
Misskeyはオープンソースの分散型マイクロブログプラットフォームプロジェクトです。 開発は日本でsyuiloによって2014年から開始されました。 ドライブ、リアクションなどの豊富な機能や、高いカスタマイズ性を備えたUIを持つことが特徴です。 Misskey는 오픈소스 분산형 마이크로블로깅 플랫폼 프로젝트입니다. 개발은 2014년부터 일본의 syuilo의 주도로 시작되었습니다. 드라이브, 리액션 등의 풍부한 기능과, 다양한 커스터마이징 기능을 가진 UI를 가진 것이 특징입니다.
## 歴史 ## 역사
開発当初は掲示板がメインのサービスでしたが、ユーザーが短文を投稿し、それを時系列で流れるタイムライン機能を追加したところ人気が高まり、徐々にそれがメインとして開発が進むようになりました。 当初は分散型ではありませんでしたが、2018年にActivityPubを実装し分散型になったことで、より多くの方に認知され利用されるサービスになり、現在に至ります。 개발 초기에는 게시판 형태의 서비스였으나, 유저가 게시한 짧은 글이 시간 순서대로 흘러가는 타임라인 기능을 추가함으로 인기를 받게 되어, 이후 이를 중심으로 개발이 진행되었습니다. 초기에는 분산형이 아니었지만, 2018년에 ActivityPub 프로토콜을 지원함으로써 분산형 SNS로 발돋움하여 널리 알려지게 되었습니다.
<div class="info"> Misskeyという名前は、syuiloが当時聴いていたMay'nというアーティストの楽曲、Brain Diverの歌詞に由来します。</div> <div class="info"> Misskey라는 이름은 syuilo가 당시 듣고 있던 May'n의 노래, Brain Diver의 가사에서 유래했습니다.</div>
誰でも開発に参加することができ、現在でも活発に開発が続いています。 누구나 개발에 참가할 수 있으며, 현재도 활발한 개발이 이루어지고 있습니다.
## 分散型とは何か? ## 분산형이 뭐예요?
<b>分散(distributed)型</b>とは、<b>非中央集権(decentralized)</b>とも呼ばれ、コミュニティが多数のサーバーに分散して存在し、それらが相互に<b>通信(連合、federation)</b>することでコンテンツ共有<b>ネットワーク(Fediverse)</b>を形成していることが特徴のサービスです。 単一のサーバーしか存在しない、もしくは複数存在しても互いに独立している場合は中央集権なサービスと言われ、例えばTwitterやFacebookなどほとんどのサービスがそれに該当します。 分散型のメリットは、自分に合った運営者やテーマのサーバーを選択できることです。自分でサーバーを作成することもできます。連合するおかげで、どのサーバーを選んでも、同じコミュニティにアクセスできます。 <b>분산형</b>(distributed), 또는 <b>탈중앙화</b>(decentralized) 서비스의 특징은, 다양한 커뮤니티에 흩어져 있는 유저들이 서로 <b>교류(연합, federation)</b>하여 서로의 정보를 주고 받는 <b>네트워크(연합우주, Fediverse)</b>를 이루는 것입니다. 이에 대비되어 서버가 하나밖에 없거나, 여러 서버가 있더라도 독립되어 있는 경우는 중앙집권 서비스라고 하여, Twitter나 Facebook 등이 이에 속합니다. 분산형 서비스의 장점은 자신에게 맞는 운영자나 테마를 자유롭게 선택할 수 있는 것입니다. 직접 서버를 운영할 수도 있습니다. 다양한 서버가 연합한다는 특성으로, 어떤 서버를 고르더라도 같은 커뮤니티에 접근할 수 있습니다.
## 常にオープンソース ## 常にオープンソース
Misskeyはこれまでもこれからも、オープンソースであり続けます。オープンソースとは、簡単に言うと<b>ソフトウェアのソースコード(プログラム)が公開されている</b>ことです。ソースコードの修正や再配布が可能であることを定義に含めることもあります。 Misskeyのすべてのソースコードは[AGPL](https://github.com/misskey-dev/misskey/blob/develop/LICENSE)というオープンソースライセンスの下に[公開](https://github.com/misskey-dev)されていて、誰でも自由に閲覧、使用、修正、改変、再配布をすることができます。 オープンソースは、自分で好きなように変えたり、有害な処理が含まれていないことを確認することができたり、誰でも開発に参加できるなどの、様々なメリットがあります。 上述の分散型を実現するためにも、オープンソースであるということは必要不可欠な要素です。 再び引き合いに出しますが、TwitterやFacebookなどの利益を得ているほとんどのサービスはオープンソースではありません。 Misskeyはこれまでもこれからも、オープンソースであり続けます。オープンソースとは、簡単に言うと<b>ソフトウェアのソースコード(プログラム)が公開されている</b>ことです。ソースコードの修正や再配布が可能であることを定義に含めることもあります。 Misskeyのすべてのソースコードは[AGPL](https://github.com/misskey-dev/misskey/blob/develop/LICENSE)というオープンソースライセンスの下に[公開](https://github.com/misskey-dev)されていて、誰でも自由に閲覧、使用、修正、改変、再配布をすることができます。 オープンソースは、自分で好きなように変えたり、有害な処理が含まれていないことを確認することができたり、誰でも開発に参加できるなどの、様々なメリットがあります。 上述の分散型を実現するためにも、オープンソースであるということは必要不可欠な要素です。 再び引き合いに出しますが、TwitterやFacebookなどの利益を得ているほとんどのサービスはオープンソースではありません。
@ -43,7 +43,7 @@ Misskeyはビジネスではなく、利用は無料であるため、収益は
## クレジット ## クレジット
Misskeyの開発者や、Misskeyに寄付をしてくださった方の一覧は[こちら](/about-misskey)で見ることができます。 Misskeyの開発者や、Misskeyに寄付をしてくださった方の一覧は[こちら](/about-misskey)で見ることができます。
## よくある質問 ## 자주 묻는 질문
### プロジェクトは何を目指していますか? ### プロジェクトは何を目指していますか?
強いて言うと、漠然的になりますが広く使われる汎用的なプラットフォームになることを目指しています。 Misskeyは他のプロジェクトとは違い、何らかの思想(例えば、反中央集権)やビジョンに基づいて開発が行われているわけではなく、その点ではフラットです。 それが逆に、特定の方向性に縛られないフレキシブルさを生み出すことに繋がっていると感じています。 強いて言うと、漠然的になりますが広く使われる汎用的なプラットフォームになることを目指しています。 Misskeyは他のプロジェクトとは違い、何らかの思想(例えば、反中央集権)やビジョンに基づいて開発が行われているわけではなく、その点ではフラットです。 それが逆に、特定の方向性に縛られないフレキシブルさを生み出すことに繋がっていると感じています。
<!-- TODO: ここにロードマップへのリンク --> <!-- TODO: ここにロードマップへのリンク -->
@ -74,10 +74,10 @@ Misskeyは開発が進むにつれ使用する技術も大きく変わってき
### Mastodonのフォークですか ### Mastodonのフォークですか
いいえ。MisskeyはMastodonやその他のプロジェクトとは全く別のプロジェクトです。 開発に関しても、Misskeyの方が昔から開発されています。ただし、分散型になったのはMastodonの登場より後です。 同じActivityPubという分散のためのプロトコルを実装しているという点以外、両者に特に関りがあるわけでもありません。 いいえ。MisskeyはMastodonやその他のプロジェクトとは全く別のプロジェクトです。 開発に関しても、Misskeyの方が昔から開発されています。ただし、分散型になったのはMastodonの登場より後です。 同じActivityPubという分散のためのプロトコルを実装しているという点以外、両者に特に関りがあるわけでもありません。
### iOS/Androidのアプリはありますか? ### iOS/Android용 어플리케이션이 있나요?
公式にはそういったOSのネイティブアプリを開発していませんが、サードパーティ製のアプリがいくつかあります。 詳しくは[こちら](./apps)をご覧ください。 공식 어플리케이션은 없지만, 비공식 서드파티 어플리케이션을 사용하실 수 있습니다. 자세한 사항은 [여기를](./apps) 참조해 주세요.
ただ、サードパーティ製アプリはどうしても機能への対応が遅れてしまうため、とくに拘りがなければ公式のWebクライアントの利用をおすすめします。 なお、MisskeyのWebクライアントはPWAに対応しているので、ネイティブアプリのように動作させることも可能です。 詳しくは[こちら](todo)をご覧ください。 단, 비공식 어플리케이션의 경우 최신 기능을 사용할 수 있기까지 시간이 걸릴 수 있으므로, 대부분의 경우 공식 Web 클라이언트를 사용하시는 것을 추천드립니다. 또한, Misskey의 Web 클라이언트는 PWA를 지원하여, 브라우저에서 지원하는 경우 네이티브 앱처럼 사용할 수 있습니다. 자세한 사항은 [여기를](todo) 참조해 주세요.
### Misskeyのロゴ、アイコンはどこで入手できますか ### Misskeyのロゴ、アイコンはどこで入手できますか
(準備中) (準備中)

View file

@ -2,7 +2,7 @@
Misskey Web客户端插件功能使您可以扩展客户端并添加各种功能。 我们在这里给出用于创建插件的元数据定义和AiScript API参考。 Misskey Web客户端插件功能使您可以扩展客户端并添加各种功能。 我们在这里给出用于创建插件的元数据定义和AiScript API参考。
## 元数据 ## 元数据
プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。 メタデータは次のプロパティを含むオブジェクトです。 插件必须使用AiScript的元数据嵌入功能将插件的元数据定义为默认值。 元数据是一个包含以下属性的对象:
### name ### name
插件名称 插件名称
@ -62,13 +62,13 @@ AiScript标准内置API将不会公布。
将项目添加到用户菜单。第一个参数是菜单项名字,第二个参数是该菜单项对应的回调函数。 目标用户对象作为第一个参数传给回调函数。 将项目添加到用户菜单。第一个参数是菜单项名字,第二个参数是该菜单项对应的回调函数。 目标用户对象作为第一个参数传给回调函数。
### Plugin:register_note_view_interruptor(fn) ### Plugin:register_note_view_interruptor(fn)
UIに表示されるート情報を書き換えます。 コールバック関数には、第一引数に対象のノートオブジェクトが渡されます。 コールバック関数の返り値でノートが書き換えられます 改写显示在UI上的帖子信息。 目标帖子对象作为第一个参数传给回调函数。 该帖子将会使用回调函数的返回值进行改写
### Plugin:register_note_post_interruptor(fn) ### Plugin:register_note_post_interruptor(fn)
ノート投稿時にノート情報を書き換えます。 コールバック関数には、第一引数に対象のノートオブジェクトが渡されます。 コールバック関数の返り値でノートが書き換えられます 发贴时改写帖子信息。 目标帖子对象作为第一个参数传给回调函数。 该帖子将会使用回调函数的返回值进行改写
### Plugin:open_url(url) ### Plugin:open_url(url)
第一引数に渡されたURLをブラウザの新しいタブで開きます 在浏览器的新标签页中打开第一个参数传入的URL
### Plugin:config ### Plugin:config
プラグインの設定が格納されるオブジェクト。プラグイン定義のconfigで設定したキーで値が入ります 存储插件设置的对象。该值是通过插件定义的配置中设置的键值来传入的

View file

@ -1,6 +1,6 @@
# Botの作成 # 新建 Bot
[Misskey API](./api)を利用してBotの開発が可能です。 また、いくつかのBot実装が公開されているため、ぜひ参考にしてください 您可以使用 [Misskey API](./api)来开发Bot机器人。此外一些机器人已经公开发布您可以把它们作为参考
- [syuilo/ai](https://github.com/syuilo/ai) ... Node.js上で動く、TypeScript製Bot実装 - [syuilo/ai](https://github.com/syuilo/ai) ... 运行在Node.js上的TypeScript实现的Bot
Botを作成したときは、プロフィール設定からBotフラグをオンにしておくことを強くおすすめします 创建机器人时我们强烈建议您在个人资料设置中启用Bot机器人标志

View file

@ -1,33 +1,33 @@
# Misskey黑白棋机器人开发 # Misskey黑白棋机器人开发
Misskeyのリバーシ機能に対応したBotの開発方法をここに記します 下面列出的是为 Misskey 黑白棋功能开发一个Bot机器人的方法
1. `games/reversi`ストリームに以下のパラメータを付けて接続する: 1. 使用以下参数来连接到`games/reversi`流:
* `i`: botアカウントのAPIキー * `i`: bot账号的API Key
2. 対局への招待が来たら、ストリームから`invited`イベントが流れてくる 2. 当出现对局邀请时,流中会触发`invited`事件
* イベントの中身に、`parent`という名前で対局へ誘ってきたユーザーの情報が含まれている * 事件内容中包含邀请您参加游戏的用户信息,用户名字为`parent`。
3. `games/reversi/match`へ、`user_id`として`parent`の`id`が含まれたリクエストを送信する 3. 向`games/reversi/match`发送请求,其中`user_id`包含`parent`的`id`
4. 上手くいくとゲーム情報が返ってくるので、`games/reversi-game`ストリームへ、以下のパラメータを付けて接続する: 4. 请求成功时将返回游戏信息,可以使用以下参数连接到`games/reversi-game`流:
* `i`: botアカウントのAPIキー * `i`: bot账号的API Key
* `game`: `game``id` * `game`: `game``id`
5. この間、相手がゲームの設定を変更するとその都度`update-settings`イベントが流れてくるので、必要であれば何かしらの処理を行う 5. 与此同时,每次对手更改游戏设置时,都会触发`update-settings`事件,如果有必要的话,需要对其进行处理。
6. 設定に満足したら、`{ type: 'accept' }`メッセージをストリームに送信する 6. 满足设定条件时,向流发送`{ type: 'accept' }`消息
7. ゲームが開始すると、`started`イベントが流れてくる 7. 游戏开始时会触发`started`事件
* イベントの中身にはゲーム情報が含まれている * 游戏状态信息会包含在该事件中
8. 石を打つには、ストリームに`{ type: 'set', pos: <位置> }`を送信する(位置の計算方法は後述) 8. 要放置棋子,向流发送`{ type: 'set', pos: <位置> }`(后面会说明位置的计算方法)
9. 相手または自分が石を打つと、ストリームから`set`イベントが流れてくる 9. 当对方或者您放置棋子时,会触发`set`事件
* `color`として石の色が含まれている * `color`中包含该棋子的颜色
* `pos`として位置情報が含まれている * `pos`中包含该棋子的位置
## 位置の計算 ## 位置计算方
8x8のマップを考える場合、各マスの位置(インデックスと呼びます)は次のようになっています: 当棋盘尺寸为8x8时每个方格的位置称为索引如下所示
``` ```
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| | 0| 1| 2| 3| 4| 5| 6| 7|
@ -38,29 +38,29 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
... ...
``` ```
### X,Y座標 から インデックス に変換する ### 从X,Y坐标转换为索引
``` ```
pos = x + (y * mapWidth) pos = x + (y * mapWidth)
``` ```
`mapWidth`は、ゲーム情報の`map`から、次のようにして計算できます: `mapWidth`可以根据游戏信息中的`map`,通过如下方法计算出来:
``` ```
mapWidth = map[0].length mapWidth = map[0].length
``` ```
### インデックス から X,Y座標 に変換する ### 从索引转换为X,Y坐标
``` ```
x = pos % mapWidth x = pos % mapWidth
y = Math.floor(pos / mapWidth) y = Math.floor(pos / mapWidth)
``` ```
## マップ情報 ## 棋盘信息
マップ情報は、ゲーム情報の`map`に入っています。 文字列の配列になっており、ひとつひとつの文字がマス情報を表しています。 それをもとにマップのデザインを知る事が出来ます: 棋盘信息包含在游戏信息的`map`中。 它是一个字符串数组,每个字符代表一块格子的信息。 您可以根据这些来了解地图如何设计:
* `(スペース)` ... マス無し * `(空)` ... 没有格子
* `-` ... マス * `-` ... 格子
* `b` ... 初期配置される黒石 * `b` ... 黑子先下
* `w` ... 初期配置される白石 * `w` ... 白子先下
例えば、4*4の次のような単純なマップがあるとします: 以下面这个4*4的简单棋盘为例
```text ```text
+---+---+---+---+ +---+---+---+---+
| | | | | | | | | |
@ -73,23 +73,23 @@ y = Math.floor(pos / mapWidth)
+---+---+---+---+ +---+---+---+---+
``` ```
この場合、マップデータはこのようになります: 这种情况下,棋盘数据是这样的:
```javascript ```javascript
['----', '-wb-', '-bw-', '----'] ['----', '-wb-', '-bw-', '----']
``` ```
## ユーザーにフォームを提示して対話可能Botを作成する ## 能和用户互动的交互式Bot机器人的创建
ユーザーとのコミュニケーションを行うため、ゲームの設定画面でユーザーにフォームを提示することができます。 例えば、Botの強さをユーザーが設定できるようにする、といったシナリオが考えられます 要和用户交互,您可以在游戏设置屏幕上向用户显示提示窗口。 例如可以让用户选择Bot机器人的难度
フォームを提示するには、`reversi-game`ストリームに次のメッセージを送信します: 要显示窗口,需要向`reversi-game`流发送下列消息:
```javascript ```javascript
{ {
type: 'init-form', type: 'init-form',
body: [フォームコントロールの配列] body: [表单控件数组]
} }
``` ```
フォームコントロールの配列については今から説明します。 フォームコントロールは、次のようなオブジェクトです: 下面说明窗口控件数组的结构。 窗口控件指的是如下面所示的对象:
```javascript ```javascript
{ {
id: 'switch1', id: 'switch1',
@ -98,10 +98,10 @@ y = Math.floor(pos / mapWidth)
value: false value: false
} }
``` ```
`id` ... コントロールのID。 `type` ... コントロールの種類。後述します。 `label` ... コントロールと一緒に表記するテキスト。 `value` ... コントロールのデフォルト値 `id` ... 控件ID。 `type` ... 控件类型。说明详见后文。 `label` ... 控件元素上显示的文字。 `value` ... 控件元素的默认值
### フォームの操作を受け取る ### 控件行为的处理
ユーザーがフォームを操作すると、ストリームから`update-form`イベントが流れてきます。 イベントの中身には、コントロールのIDと、ユーザーが設定した値が含まれています。 例えば、上で示したスイッチをユーザーがオンにしたとすると、次のイベントが流れてきます: 当用户与对话框交互时将会触发流的`update-form`事件。 事件的内容包含控件的ID和用户设置的值。 例如,如果用户将上面显示的开关控件打开,则将触发以下事件:
```javascript ```javascript
{ {
id: 'switch1', id: 'switch1',
@ -109,18 +109,18 @@ y = Math.floor(pos / mapWidth)
} }
``` ```
### フォームコントロールの種類 ### 窗口控件的类型
#### 开关 #### 开关
type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です type: `switch` 显示一个开关。当您想要打开/关闭某些功能时非常有用
##### プロパティ ##### 属性
`label` ... スイッチに表記するテキスト `label` ... 开关上显示的文字
#### ラジオボタン #### 单选按钮
type: `radio` ラジオボタンを表示します。選択肢を提示するのに有用です。例えば、Botの強さを設定させるなどです type: `radio` 显示一个单选按钮。用来表示单项选择。例如可以选择Bot机器人的难度
##### プロパティ ##### 属性
`items` ... ラジオボタンの選択肢。例: `items` ... 单元按钮的选择项。例:
```javascript ```javascript
items: [{ items: [{
label: '弱', label: '弱',
@ -129,32 +129,32 @@ items: [{
label: '中', label: '中',
value: 2 value: 2
}, { }, {
label: '', label: '',
value: 3 value: 3
}] }]
``` ```
#### スライダー #### 滑块
type: `slider` スライダーを表示します type: `slider` 显示一个滑块
##### プロパティ ##### 属性
`min` ... スライダーの下限。 `max` ... スライダーの上限。 `step` ... 入力欄で刻むステップ値 `min` ... 滑块最小值。 `max` ... 滑块最大值。 `step` ... 滑块值的步长
#### テキストボックス #### 文本框
type: `textbox` テキストボックスを表示します。ユーザーになにか入力させる一般的な用途に利用できます type: `textbox` 显示一个文本框。可以在各种需要用户输入的地方使用
## ユーザーにメッセージを表示する ## 向用户显示消息
設定画面でユーザーと対話する、フォーム以外のもうひとつの方法がこれです。ユーザーになにかメッセージを表示することができます。 例えば、ユーザーがBotの対応していないモードやマップを選択したとき、警告を表示するなどです。 メッセージを表示するには、次のメッセージをストリームに送信します: 设置屏幕上与用户交互,是除了对话框外的另一种方法。您可以向用户显示一条消息。 例如当用户选择Bot机器人不支持的模式或棋盘时显示警告。 要显示消息,请将以下消息发送到流:
```javascript ```javascript
{ {
type: 'message', type: 'message',
body: { body: {
text: 'メッセージ内容', text: '消息内容',
type: 'メッセージの種類' type: '消息类型'
} }
} }
``` ```
メッセージの種類: `success`, `info`, `warning`, `error` 消息类型:`success`, `info`, `warning`, `error`
## 投了する ## 认输
投了をするには、<a href="./api/endpoints/games/reversi/games/surrender">このエンドポイント</a>にリクエストします 要认输,请发送请求到<a href="./api/endpoints/games/reversi/games/surrender">这个终端</a>

View file

@ -1,23 +1,23 @@
# 流式API # 流式API
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます 通过流式API您可以实时接收各种信息例如你的时间线中的新帖文收到的消息关注等并进行各种操作
## ストリームに接続する ## 连接到流
ストリーミングAPIを利用するには、まずMisskeyサーバーに**websocket**接続する必要があります 要使用流式API您需要使用**websocket**连接到Misskey服务器
以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例: 请使用参数`i`连接到以下URL并在websocket连接中包含认证信息。例如
``` ```
%WS_URL%/streaming?i=xxxxxxxxxxxxxxx %WS_URL%/streaming?i=xxxxxxxxxxxxxxx
``` ```
認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。 认证信息是您的API密钥从应用程序连接到流时需要引用的用户访问令牌
<div class="info"> 認証情報の取得については、<a href="./api">こちらのドキュメント</a>をご確認ください</div> <div class="info"> 关于如何获取认证信息,请参考<a href="./api">此文档</a></div>
--- ---
認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例: 您可以省略身份验证信息。此时无需登录即可使用,但是可以接收的信息和可以执行的操作将受到限制。例:
``` ```
%WS_URL%/streaming %WS_URL%/streaming
@ -215,7 +215,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `reaction`に、リアクションの種類が設定されます。 * `reaction`に、リアクションの種類が設定されます。
* `userId`に、リアクションを行ったユーザーのIDが設定されます。 * `userId`に、リアクションを行ったユーザーのIDが設定されます。
:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',
@ -235,7 +235,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `deletedAt`に、削除日時が設定されます。 * `deletedAt`に、削除日時が設定されます。
:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',
@ -255,7 +255,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `choice`に、選択肢IDが設定されます。 * `choice`に、選択肢IDが設定されます。
* `userId`に、投票を行ったユーザーのIDが設定されます。 * `userId`に、投票を行ったユーザーのIDが設定されます。
:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',

View file

@ -14,18 +14,18 @@
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿が流れます。GTLと略されます。 全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿が流れます。GTLと略されます。
## 比较 ## 比较
| ソース | | | 时间线 | | | | 来源 | | | 时间线 | | |
| ------------ | --- | -- | --- | -- | -- | | ------- | --- | -- | --- | -- | -- |
| 用户 | 可见性 | 首页 | 本地 | 社交 | 全局 | | 用户 | 可见性 | 首页 | 本地 | 社交 | 全局 |
| ローカル (フォロー) | 发布 | ✔ | ✔ | ✔ | ✔ | | 本地(关注) | 发布 | ✔ | ✔ | ✔ | ✔ |
| | 首页 | ✔ | | ✔ | | | | 首页 | ✔ | | ✔ | |
| | 关注者 | ✔ | ✔ | ✔ | ✔ | | | 关注者 | ✔ | ✔ | ✔ | ✔ |
| リモート (フォロー) | 发布 | ✔ | | ✔ | ✔ | | 跨站(关注) | 发布 | ✔ | | ✔ | ✔ |
| | 首页 | ✔ | | ✔ | | | | 首页 | ✔ | | ✔ | |
| | 关注者 | ✔ | | ✔ | ✔ | | | 关注者 | ✔ | | ✔ | ✔ |
| ローカル (未フォロー) | 发布 | | ✔ | ✔ | ✔ | | 本地(未关注) | 发布 | | ✔ | ✔ | ✔ |
| | 首页 | | | | | | | 首页 | | | | |
| | 关注者 | | | | | | | 关注者 | | | | |
| リモート (未フォロー) | 发布 | | | | ✔ | | 跨站(未关注) | 发布 | | | | ✔ |
| | 首页 | | | | | | | 首页 | | | | |
| | 关注者 | | | | | | | 关注者 | | | | |

View file

@ -1,6 +1,6 @@
# サードパーティアプリのリスト # 第三方应用程序列表
## クライアント ## 客户端
todo todo
## 連携サービス ## 链接服务
todo todo

View file

@ -1,5 +1,5 @@
# 更新履歴 # 更新日志
<div class="info"> このサーバーの更新履歴です。Misskeyの最新のリリースについては、<a href="https://github.com/misskey-dev/misskey/blob/master/CHANGELOG.md" target="_blank">GitHub</a>をご確認ください</div> <div class="info"> 这是该服务器的更新日志。请在 <a href="https://github.com/misskey-dev/misskey/blob/master/CHANGELOG.md" target="_blank">GitHub</a> 上检查 Misskey 的最新发布版本</div>
<!-- For translators: Do not edit these comments. --> <!-- For translators: Do not edit these comments. -->
<!--[CHANGELOG]--> <!--[CHANGELOG]-->

View file

@ -1,12 +1,12 @@
# リンク集 # 相关链接
## Webサイト ## 网站
- [Official Discord](https://discord.gg/Wp8gVStHW3) - Misskey公式Discordサーバー - [Official Discord](https://discord.gg/Wp8gVStHW3) - Misskey官方Discord服务器
- [Misskey Forum](https://forum.misskey.io/) - Misskeyに関する話題を扱うフォーラム - [Misskey Forum](https://forum.misskey.io/) - Misskey相关主题的论坛
## 账户 ## 账户
- [@repo@misskey.io](https://misskey.io/@repo) - 发布Misskey的存储库更新的机器人 - [@repo@misskey.io](https://misskey.io/@repo) - 发布Misskey的存储库更新的机器人
## ライブラリ ##
- [misskey-dev/misskey.js](https://github.com/misskey-dev/misskey.js) - JavaScriptMisskey SDK - [misskey-dev/misskey.js](https://github.com/misskey-dev/misskey.js) - JavaScriptMisskey SDK
- [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScriptのMFMパーサー実装 - [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScript的MFM解析器实现

View file

@ -1,4 +1,4 @@
# 不具合の報 # bug报
不具合と思われる状況に遭遇したときは、まず[トラブルシューティング](./troubleshooting)をご一読ください。 それでも問題が解決しないときは、以下の情報を含めて[フォーラム](https://forum.misskey.io/)に投稿してください。 投稿することで、解決策が見つかったり、不具合と判断されれば開発チームによって修正が行われます。 不具合と思われる状況に遭遇したときは、まず[トラブルシューティング](./troubleshooting)をご一読ください。 それでも問題が解決しないときは、以下の情報を含めて[フォーラム](https://forum.misskey.io/)に投稿してください。 投稿することで、解決策が見つかったり、不具合と判断されれば開発チームによって修正が行われます。
## 含める情報 ## 含める情報

View file

@ -32,3 +32,4 @@ export const kinds = [
'read:gallery-likes', 'read:gallery-likes',
'write:gallery-likes', 'write:gallery-likes',
]; ];
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).

View file

@ -3,13 +3,13 @@ import { Note } from '@/models/entities/note';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { UserListJoinings, UserGroupJoinings } from '@/models/index'; import { UserListJoinings, UserGroupJoinings } from '@/models/index';
import { getFullApAccount } from './convert-host'; import { getFullApAccount } from './convert-host';
import { PackedNote } from '../models/repositories/note';
import { parseAcct } from '@/misc/acct'; import { parseAcct } from '@/misc/acct';
import { Packed } from './schema';
/** /**
* noteUserFollowers / antennaUserFollowing * noteUserFollowers / antennaUserFollowing
*/ */
export async function checkHitAntenna(antenna: Antenna, note: (Note | PackedNote), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> { export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
if (note.visibility === 'specified') return false; if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') { if (note.visibility === 'followers') {

View file

@ -1,15 +1,65 @@
export type Schema = { import { SimpleObj, SimpleSchema } from './simple-schema';
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; import { packedUserSchema } from '@/models/repositories/user';
nullable: boolean; import { packedNoteSchema } from '@/models/repositories/note';
optional: boolean; import { packedUserListSchema } from '@/models/repositories/user-list';
import { packedAppSchema } from '@/models/repositories/app';
import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message';
import { packedNotificationSchema } from '@/models/repositories/notification';
import { packedDriveFileSchema } from '@/models/repositories/drive-file';
import { packedDriveFolderSchema } from '@/models/repositories/drive-folder';
import { packedFollowingSchema } from '@/models/repositories/following';
import { packedMutingSchema } from '@/models/repositories/muting';
import { packedBlockingSchema } from '@/models/repositories/blocking';
import { packedNoteReactionSchema } from '@/models/repositories/note-reaction';
import { packedHashtagSchema } from '@/models/repositories/hashtag';
import { packedPageSchema } from '@/models/repositories/page';
import { packedUserGroupSchema } from '@/models/repositories/user-group';
import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite';
import { packedChannelSchema } from '@/models/repositories/channel';
import { packedAntennaSchema } from '@/models/repositories/antenna';
import { packedClipSchema } from '@/models/repositories/clip';
import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance';
import { packedQueueCountSchema } from '@/models/repositories/queue';
import { packedGalleryPostSchema } from '@/models/repositories/gallery-post';
import { packedEmojiSchema } from '@/models/repositories/emoji';
import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game';
import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching';
export const refs = {
User: packedUserSchema,
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,
Note: packedNoteSchema,
NoteReaction: packedNoteReactionSchema,
NoteFavorite: packedNoteFavoriteSchema,
Notification: packedNotificationSchema,
DriveFile: packedDriveFileSchema,
DriveFolder: packedDriveFolderSchema,
Following: packedFollowingSchema,
Muting: packedMutingSchema,
Blocking: packedBlockingSchema,
Hashtag: packedHashtagSchema,
Page: packedPageSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
ReversiGame: packedReversiGameSchema,
ReversiMatching: packedReversiMatchingSchema,
};
export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>;
export interface Schema extends SimpleSchema {
items?: Schema; items?: Schema;
properties?: Obj; properties?: Obj;
description?: string; ref?: keyof typeof refs;
example?: any; }
format?: string;
ref?: string;
enum?: string[];
};
type NonUndefinedPropertyNames<T extends Obj> = { type NonUndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? never : K [K in keyof T]: T[K]['optional'] extends true ? never : K
@ -22,7 +72,7 @@ type UndefinedPropertyNames<T extends Obj> = {
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>;
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>; type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
export type Obj = { [key: string]: Schema }; export interface Obj extends SimpleObj { [key: string]: Schema; }
export type ObjType<s extends Obj> = export type ObjType<s extends Obj> =
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & { [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } &
@ -48,6 +98,10 @@ export type SchemaType<p extends Schema> =
p['type'] extends 'string' ? NullOrUndefined<p, string> : p['type'] extends 'string' ? NullOrUndefined<p, string> :
p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> :
p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> :
p['type'] extends 'object' ? NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> : p['type'] extends 'object' ? (
p['ref'] extends keyof typeof refs
? NullOrUndefined<p, Packed<p['ref']>>
: NullOrUndefined<p, ObjType<NonNullable<p['properties']>>>
) :
p['type'] extends 'any' ? NullOrUndefined<p, any> : p['type'] extends 'any' ? NullOrUndefined<p, any> :
any; any;

15
src/misc/simple-schema.ts Normal file
View file

@ -0,0 +1,15 @@
export interface SimpleSchema {
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
nullable: boolean;
optional: boolean;
items?: SimpleSchema;
properties?: SimpleObj;
description?: string;
example?: any;
format?: string;
ref?: string;
enum?: string[];
default?: boolean | null;
}
export interface SimpleObj { [key: string]: SimpleSchema; }

View file

@ -1,15 +1,13 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Antenna } from '@/models/entities/antenna'; import { Antenna } from '@/models/entities/antenna';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { AntennaNotes, UserGroupJoinings } from '../index'; import { AntennaNotes, UserGroupJoinings } from '../index';
export type PackedAntenna = SchemaType<typeof packedAntennaSchema>;
@EntityRepository(Antenna) @EntityRepository(Antenna)
export class AntennaRepository extends Repository<Antenna> { export class AntennaRepository extends Repository<Antenna> {
public async pack( public async pack(
src: Antenna['id'] | Antenna, src: Antenna['id'] | Antenna,
): Promise<PackedAntenna> { ): Promise<Packed<'Antenna'>> {
const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src); const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src);
const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null; const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null;

View file

@ -1,9 +1,8 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { App } from '@/models/entities/app'; import { App } from '@/models/entities/app';
import { AccessTokens } from '../index'; import { AccessTokens } from '../index';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { User } from '../entities/user';
export type PackedApp = SchemaType<typeof packedAppSchema>;
@EntityRepository(App) @EntityRepository(App)
export class AppRepository extends Repository<App> { export class AppRepository extends Repository<App> {
@ -15,7 +14,7 @@ export class AppRepository extends Repository<App> {
includeSecret?: boolean, includeSecret?: boolean,
includeProfileImageIds?: boolean includeProfileImageIds?: boolean
} }
): Promise<PackedApp> { ): Promise<Packed<'App'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
includeSecret: false, includeSecret: false,
@ -52,13 +51,9 @@ export const packedAppSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const optional: false as const, nullable: false as const
}, },
createdAt: { callbackUrl: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const optional: false as const, nullable: true as const
},
lastUsedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const
}, },
permission: { permission: {
type: 'array' as const, type: 'array' as const,

View file

@ -2,17 +2,15 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../index'; import { Users } from '../index';
import { Blocking } from '@/models/entities/blocking'; import { Blocking } from '@/models/entities/blocking';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
export type PackedBlocking = SchemaType<typeof packedBlockingSchema>;
@EntityRepository(Blocking) @EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> { export class BlockingRepository extends Repository<Blocking> {
public async pack( public async pack(
src: Blocking['id'] | Blocking, src: Blocking['id'] | Blocking,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
): Promise<PackedBlocking> { ): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src); const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src);
return await awaitAll({ return await awaitAll({
@ -56,7 +54,7 @@ export const packedBlockingSchema = {
blockee: { blockee: {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
ref: 'User', ref: 'User' as const,
}, },
} }
}; };

View file

@ -1,17 +1,15 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Channel } from '@/models/entities/channel'; import { Channel } from '@/models/entities/channel';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index'; import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
export type PackedChannel = SchemaType<typeof packedChannelSchema>;
@EntityRepository(Channel) @EntityRepository(Channel)
export class ChannelRepository extends Repository<Channel> { export class ChannelRepository extends Repository<Channel> {
public async pack( public async pack(
src: Channel['id'] | Channel, src: Channel['id'] | Channel,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
): Promise<PackedChannel> { ): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.findOneOrFail(src); const channel = typeof src === 'object' ? src : await this.findOneOrFail(src);
const meId = me ? me.id : null; const meId = me ? me.id : null;

View file

@ -1,16 +1,14 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Clip } from '@/models/entities/clip'; import { Clip } from '@/models/entities/clip';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { Users } from '../index'; import { Users } from '../index';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
export type PackedClip = SchemaType<typeof packedClipSchema>;
@EntityRepository(Clip) @EntityRepository(Clip)
export class ClipRepository extends Repository<Clip> { export class ClipRepository extends Repository<Clip> {
public async pack( public async pack(
src: Clip['id'] | Clip, src: Clip['id'] | Clip,
): Promise<PackedClip> { ): Promise<Packed<'Clip'>> {
const clip = typeof src === 'object' ? src : await this.findOneOrFail(src); const clip = typeof src === 'object' ? src : await this.findOneOrFail(src);
return await awaitAll({ return await awaitAll({
@ -53,7 +51,7 @@ export const packedClipSchema = {
}, },
user: { user: {
type: 'object' as const, type: 'object' as const,
ref: 'User', ref: 'User' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
}, },
name: { name: {

View file

@ -4,14 +4,12 @@ import { Users, DriveFolders } from '../index';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { toPuny } from '@/misc/convert-host'; import { toPuny } from '@/misc/convert-host';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import config from '@/config/index'; import config from '@/config/index';
import { query, appendQuery } from '@/prelude/url'; import { query, appendQuery } from '@/prelude/url';
import { Meta } from '@/models/entities/meta'; import { Meta } from '@/models/entities/meta';
import { fetchMeta } from '@/misc/fetch-meta'; import { fetchMeta } from '@/misc/fetch-meta';
export type PackedDriveFile = SchemaType<typeof packedDriveFileSchema>;
type PackOptions = { type PackOptions = {
detail?: boolean, detail?: boolean,
self?: boolean, self?: boolean,
@ -99,12 +97,12 @@ export class DriveFileRepository extends Repository<DriveFile> {
return parseInt(sum, 10) || 0; return parseInt(sum, 10) || 0;
} }
public async pack(src: DriveFile['id'], options?: PackOptions): Promise<PackedDriveFile | null>; public async pack(src: DriveFile['id'], options?: PackOptions): Promise<Packed<'DriveFile'> | null>;
public async pack(src: DriveFile, options?: PackOptions): Promise<PackedDriveFile>; public async pack(src: DriveFile, options?: PackOptions): Promise<Packed<'DriveFile'>>;
public async pack( public async pack(
src: DriveFile['id'] | DriveFile, src: DriveFile['id'] | DriveFile,
options?: PackOptions options?: PackOptions
): Promise<PackedDriveFile | null> { ): Promise<Packed<'DriveFile'> | null> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
self: false self: false
@ -234,7 +232,7 @@ export const packedDriveFileSchema = {
folder: { folder: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
ref: 'DriveFolder' ref: 'DriveFolder' as const,
}, },
userId: { userId: {
type: 'string' as const, type: 'string' as const,
@ -245,7 +243,7 @@ export const packedDriveFileSchema = {
user: { user: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
ref: 'User' ref: 'User' as const,
} }
}, },
}; };

View file

@ -2,9 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { DriveFolders, DriveFiles } from '../index'; import { DriveFolders, DriveFiles } from '../index';
import { DriveFolder } from '@/models/entities/drive-folder'; import { DriveFolder } from '@/models/entities/drive-folder';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
export type PackedDriveFolder = SchemaType<typeof packedDriveFolderSchema>;
@EntityRepository(DriveFolder) @EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> { export class DriveFolderRepository extends Repository<DriveFolder> {
@ -20,7 +18,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
options?: { options?: {
detail: boolean detail: boolean
} }
): Promise<PackedDriveFolder> { ): Promise<Packed<'DriveFolder'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false detail: false
}, options); }, options);
@ -87,7 +85,7 @@ export const packedDriveFolderSchema = {
parent: { parent: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
ref: 'DriveFolder' ref: 'DriveFolder' as const,
}, },
}, },
}; };

View file

@ -1,11 +1,12 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Emoji } from '@/models/entities/emoji'; import { Emoji } from '@/models/entities/emoji';
import { Packed } from '@/misc/schema';
@EntityRepository(Emoji) @EntityRepository(Emoji)
export class EmojiRepository extends Repository<Emoji> { export class EmojiRepository extends Repository<Emoji> {
public async pack( public async pack(
src: Emoji['id'] | Emoji, src: Emoji['id'] | Emoji,
) { ): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src); const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src);
return { return {
@ -24,3 +25,41 @@ export class EmojiRepository extends Repository<Emoji> {
return Promise.all(emojis.map(x => this.pack(x))); return Promise.all(emojis.map(x => this.pack(x)));
} }
} }
export const packedEmojiSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
category: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
host: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
url: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
}
};

View file

@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../index'; import { Users } from '../index';
import { Following } from '@/models/entities/following'; import { Following } from '@/models/entities/following';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { SchemaType } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
type LocalFollowerFollowing = Following & { type LocalFollowerFollowing = Following & {
@ -29,8 +29,6 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string; followeeSharedInbox: string;
}; };
export type PackedFollowing = SchemaType<typeof packedFollowingSchema>;
@EntityRepository(Following) @EntityRepository(Following)
export class FollowingRepository extends Repository<Following> { export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing { public isLocalFollower(following: Following): following is LocalFollowerFollowing {
@ -56,7 +54,7 @@ export class FollowingRepository extends Repository<Following> {
populateFollowee?: boolean; populateFollowee?: boolean;
populateFollower?: boolean; populateFollower?: boolean;
} }
): Promise<PackedFollowing> { ): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.findOneOrFail(src); const following = typeof src === 'object' ? src : await this.findOneOrFail(src);
if (opts == null) opts = {}; if (opts == null) opts = {};
@ -110,7 +108,7 @@ export const packedFollowingSchema = {
followee: { followee: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
ref: 'User', ref: 'User' as const,
}, },
followerId: { followerId: {
type: 'string' as const, type: 'string' as const,
@ -120,7 +118,7 @@ export const packedFollowingSchema = {
follower: { follower: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
ref: 'User', ref: 'User' as const,
}, },
} }
}; };

View file

@ -1,18 +1,16 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { GalleryPost } from '@/models/entities/gallery-post'; import { GalleryPost } from '@/models/entities/gallery-post';
import { SchemaType } from '../../misc/schema'; import { Packed } from '@/misc/schema';
import { Users, DriveFiles, GalleryLikes } from '../index'; import { Users, DriveFiles, GalleryLikes } from '../index';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
export type PackedGalleryPost = SchemaType<typeof packedGalleryPostSchema>;
@EntityRepository(GalleryPost) @EntityRepository(GalleryPost)
export class GalleryPostRepository extends Repository<GalleryPost> { export class GalleryPostRepository extends Repository<GalleryPost> {
public async pack( public async pack(
src: GalleryPost['id'] | GalleryPost, src: GalleryPost['id'] | GalleryPost,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
): Promise<PackedGalleryPost> { ): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null; const meId = me ? me.id : null;
const post = typeof src === 'object' ? src : await this.findOneOrFail(src); const post = typeof src === 'object' ? src : await this.findOneOrFail(src);
@ -76,7 +74,7 @@ export const packedGalleryPostSchema = {
}, },
user: { user: {
type: 'object' as const, type: 'object' as const,
ref: 'User', ref: 'User' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
}, },
fileIds: { fileIds: {
@ -94,7 +92,7 @@ export const packedGalleryPostSchema = {
items: { items: {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
ref: 'DriveFile' ref: 'DriveFile' as const,
} }
}, },
tags: { tags: {

View file

@ -2,6 +2,7 @@ import { User } from '@/models/entities/user';
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../../../index'; import { Users } from '../../../index';
import { ReversiGame } from '@/models/entities/games/reversi/game'; import { ReversiGame } from '@/models/entities/games/reversi/game';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiGame) @EntityRepository(ReversiGame)
export class ReversiGameRepository extends Repository<ReversiGame> { export class ReversiGameRepository extends Repository<ReversiGame> {
@ -11,7 +12,7 @@ export class ReversiGameRepository extends Repository<ReversiGame> {
options?: { options?: {
detail?: boolean detail?: boolean
} }
) { ): Promise<Packed<'ReversiGame'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: true detail: true
}, options); }, options);
@ -20,8 +21,8 @@ export class ReversiGameRepository extends Repository<ReversiGame> {
return { return {
id: game.id, id: game.id,
createdAt: game.createdAt, createdAt: game.createdAt.toISOString(),
startedAt: game.startedAt, startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted, isStarted: game.isStarted,
isEnded: game.isEnded, isEnded: game.isEnded,
form1: game.form1, form1: game.form1,
@ -41,9 +42,150 @@ export class ReversiGameRepository extends Repository<ReversiGame> {
canPutEverywhere: game.canPutEverywhere, canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard, loopedBoard: game.loopedBoard,
...(opts.detail ? { ...(opts.detail ? {
logs: game.logs, logs: game.logs.map(log => ({
at: log.at.toISOString(),
color: log.color,
pos: log.pos
})),
map: game.map, map: game.map,
} : {}) } : {})
}; };
} }
} }
export const packedReversiGameSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
startedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
isStarted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
isEnded: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
form1: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
form2: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
user1Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user2Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user1Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user2Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user1: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
user2: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
winnerId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
winner: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
surrendered: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
black: {
type: 'number' as const,
optional: false as const, nullable: true as const,
},
bw: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
isLlotheo: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
canPutEverywhere: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
loopedBoard: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
logs: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: true as const, nullable: false as const,
properties: {
at: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
color: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
pos: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
}
}
},
map: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
}
}
}
};

View file

@ -3,18 +3,19 @@ import { ReversiMatching } from '@/models/entities/games/reversi/matching';
import { Users } from '../../../index'; import { Users } from '../../../index';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll } from '@/prelude/await-all';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiMatching) @EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> { export class ReversiMatchingRepository extends Repository<ReversiMatching> {
public async pack( public async pack(
src: ReversiMatching['id'] | ReversiMatching, src: ReversiMatching['id'] | ReversiMatching,
me: { id: User['id'] } me: { id: User['id'] }
) { ): Promise<Packed<'ReversiMatching'>> {
const matching = typeof src === 'object' ? src : await this.findOneOrFail(src); const matching = typeof src === 'object' ? src : await this.findOneOrFail(src);
return await awaitAll({ return await awaitAll({
id: matching.id, id: matching.id,
createdAt: matching.createdAt, createdAt: matching.createdAt.toISOString(),
parentId: matching.parentId, parentId: matching.parentId,
parent: Users.pack(matching.parentId, me, { parent: Users.pack(matching.parentId, me, {
detail: true detail: true
@ -26,3 +27,43 @@ export class ReversiMatchingRepository extends Repository<ReversiMatching> {
}); });
} }
} }
export const packedReversiMatchingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
parentId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
childId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
child: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
}
};

Some files were not shown because too many files have changed in this diff Show more