Compare commits

...

166 Commits

Author SHA1 Message Date
6fb7721798 Merge branch 'develop' 2020-03-21 13:36:41 +09:00
0018fd469e 12.22.0 2020-03-21 13:36:21 +09:00
019f7480e8 Fix bug 2020-03-21 13:21:32 +09:00
8e0b088deb 🎨 2020-03-21 13:19:42 +09:00
452005381f Fix bug 2020-03-21 13:10:44 +09:00
9722ed99a3 リモートユーザーでも投稿数とか見れるように 2020-03-21 13:07:15 +09:00
ee6311e83d Resolve #6145 2020-03-21 13:07:02 +09:00
1471e52307 Resolve #6110 2020-03-21 12:48:25 +09:00
f1fc12d9cc wip 2020-03-21 12:32:40 +09:00
ebbc42bebc wip 2020-03-21 00:21:33 +09:00
4785ee8c32 wip 2020-03-20 23:08:45 +09:00
ab40756c1a wip 2020-03-20 22:42:35 +09:00
c88e737a84 wip 2020-03-20 22:37:44 +09:00
def5ea7978 wip 2020-03-20 21:58:04 +09:00
e69ab45044 wip 2020-03-20 19:19:28 +09:00
25d0b4bbf1 wip 2020-03-20 19:06:50 +09:00
f86f5ac6cc wip 2020-03-20 18:58:17 +09:00
07ce365bfd wip 2020-03-20 18:55:15 +09:00
f31c94e2ea wip 2020-03-20 18:11:39 +09:00
933638d035 Fix bug 2020-03-20 18:00:42 +09:00
b0151afa9a Add range component, 音量設定で使用する (#6146)
* add range component, use range component at volume setting

* refactor

* refactor 2

* Update range.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-03-20 14:30:37 +09:00
5bbd4ae703 ElasticSearchで認証ができるように (#6158) 2020-03-20 14:00:34 +09:00
f2f7f532a0 use username if name was empty (#6166) 2020-03-20 13:57:55 +09:00
80eedf7449 連携ログインができないのなどを修正 (#6162)
* 連携ログインができないのを修正

* Cookie名変更, セッションに

* igiはやっぱり非セッションCookieで

* 2回目以降Discordログインできなくなるのを修正
2020-03-20 13:56:22 +09:00
1b48e0d6e0 Revert "Update dependencies (#6167)" (#6168)
This reverts commit 0420c548da.
2020-03-20 02:46:13 +09:00
0420c548da Update dependencies (#6167)
* Update CI (#11)

* Update nodejs.yml

* Fix time

* no docker

* no CI

* build only

* Update dependencies
2020-03-20 02:40:35 +09:00
6e98b75d13 Merge pull request #6165 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-03-19 12:54:00 +09:00
e772cb00d1 Update README.md [AUTOGEN] 2020-03-18 03:57:08 +09:00
b5d5275e9b Auth認証画面から正常にログインできるよう修正 (#6154)
* Fix #6095

Auth認証画面から正常にログインできるよう修正

* Fix #6095
2020-03-14 15:59:02 +09:00
a2d3d22b6e オブジェクトストレージでS3のvirtual-host形式のサポートなど (#6148)
* オブジェクトストレージでS3のvirtual-host形式のサポートなど

* 表記揺れ

* more simply

* S3ならばs3ForcePathStyleしない
2020-03-14 11:33:19 +09:00
1ad8603cc2 fix gif badge (#6153) 2020-03-14 11:31:03 +09:00
aeaf535ea2 Update test 2020-03-07 11:25:39 +09:00
917726fecc wip #6140 2020-03-07 11:23:31 +09:00
49a5b4eb14 Refactor: Better arg name 2020-03-07 09:56:13 +09:00
8946f3ea18 Add test 2020-03-07 01:10:13 +09:00
1947835c51 Resolve #6137 2020-03-07 01:04:36 +09:00
c7c537c8b8 Refactor 2020-03-07 00:35:00 +09:00
4e2f954683 note overflow: hidden (#6138) 2020-03-07 00:31:48 +09:00
abb0184329 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-03-07 00:29:27 +09:00
ee483ecfd3 Refactor 2020-03-07 00:29:09 +09:00
99384b4c22 チャートログの取得範囲の修正 (#5923) 2020-03-07 00:28:21 +09:00
65503bc68d Update commands 2020-03-07 00:12:23 +09:00
ec6aadb5ce Migrate deprecated mocha configuration 2020-03-07 00:00:12 +09:00
5f642886d9 chore: Update commands 2020-03-06 23:58:27 +09:00
4c26e3c54d note_reaction.reaction は 130文字に (#6105) 2020-03-06 23:09:06 +09:00
3ca3712bae ダークテーマ利用時にセレクトが使いにくくなる問題を修正 (#6117) 2020-03-06 22:54:23 +09:00
a471e4b783 MFMをテキストに戻す (#6131)
* Disable Nyaize in quote

* mfmを文字列に戻す、nyaizeにmfmを使用

* Revert "Disable Nyaize in quote"

This reverts commit 1b238905a5535267d32d7e1aec8afd8bb07b0619.

* refactor

* use return type as string
2020-03-06 22:51:50 +09:00
20ac7e62e9 チャートInsert時にロックをかけるように (#6100)
* chart lock

* fix
2020-03-06 22:33:54 +09:00
3e61aa0835 Merge pull request #6130 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-03-04 21:08:05 +09:00
c18f6fde80 lintをGitHub Actions でするように (#6101)
* package.json の lint スクリプトを修正

* lint アクションを追加

* yarn lint --fix

* 手動修正
2020-03-04 11:45:33 +09:00
8b9397a0ce Update README.md [AUTOGEN] 2020-03-04 04:40:12 +09:00
678ff17d0f Merge pull request #6121 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-03-04 04:39:40 +09:00
ea2016c208 Update README.md [AUTOGEN] 2020-03-02 01:58:06 +09:00
b5bdf266d3 Merge pull request #6109 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-03-01 23:54:37 +09:00
2309680c38 Refactor 2020-02-29 16:38:07 +09:00
e99bf569c5 Update README.md [AUTOGEN] 2020-02-29 11:05:09 +09:00
d3fd0f810a ボリュームを0にしてもサウンドが鳴っていることになっていたのを修正 (#6098)
* Update init.ts

* parseFloat
2020-02-27 16:32:10 +09:00
484dc9b08a Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-27 15:13:50 +09:00
3bd827d7da Update google.vue 2020-02-27 15:13:46 +09:00
d425c72134 GitHub Actions のテストで postgres がコケてるのを修正 (#6089)
* github actionsのfail原因調査用

* fix

* fix

* fux

* remove tihs branch from CI target

* ログ表示削除

* fix
2020-02-27 10:08:31 +09:00
969cd16638 Resolve #6091 2020-02-27 07:04:28 +09:00
569be15705 同じホットキーが連続で発動しないように (#6082)
* add cooldown to hotkey

* remove blank

* use repeat flag

* format

* Add Repeatable option to Hotkey

* Boolean型のみに

* console.log消すの忘れてた
2020-02-26 17:22:43 +09:00
03f54c5b02 リアクション絵文字設定をいい感じに (#6074)
* リアクション絵文字設定をいい感じに

* みじかく
2020-02-26 16:48:23 +09:00
06ddc8ec50 Fix: mainStreamのミュート情報が再接続まで反映されない (#6072) 2020-02-26 08:03:23 +09:00
1528935008 GitHub Actionsでテストが動かなくなっているのを修正 (#6088)
* CI test

* Add pg healthcheck

* postgres:10.8

* 試しにhealthcheckなしに

* postgres:10

* Revert "試しにhealthcheckなしに"

This reverts commit 4a7ba19ea9b93d54966f256f8f04090482b9005d.

* は?

* postgres:10.8-alpine

* postgres:10.11-alpine

* テスト用ブランチ指定を削除
2020-02-26 07:57:24 +09:00
7121bdef6b Refactor 2020-02-26 07:56:32 +09:00
f6c376f20d 同じノートを何回リノートしても一回として数えるように (#6086)
* 同じノートを何回リノートしても一回として数えるように

* Update count-same-renotes.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-26 07:54:35 +09:00
241769d6fc Merge pull request #6084 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-02-25 17:47:38 +09:00
7727651871 Update README.md [AUTOGEN] 2020-02-25 17:46:07 +09:00
ce331826ac Merge pull request #6078 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-02-24 09:52:25 +09:00
ae92c52d61 Update README.md [AUTOGEN] 2020-02-24 09:28:06 +09:00
54aef5fe6f Update CHANGELOG.md 2020-02-23 03:11:51 +09:00
d22148b418 12.21.0 2020-02-23 02:53:44 +09:00
5dc75c9cea Fix #6029 2020-02-23 02:34:54 +09:00
200e82decb Fix #6063 2020-02-23 02:28:07 +09:00
50359dbaf4 Resolve #6053 2020-02-22 06:57:54 +09:00
7165f21a62 Fix style 2020-02-22 06:54:35 +09:00
8aab828c65 Better featured injection 2020-02-22 06:49:12 +09:00
c9f8c12f5b 🍕 2020-02-22 06:43:46 +09:00
a347f8fa49 🎨 2020-02-22 06:40:48 +09:00
2d76bdd0f8 Fix bug 2020-02-22 06:36:15 +09:00
c5cdd56edb 🎨 2020-02-22 03:51:31 +09:00
6901ab39ed Merge pull request #6058 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-02-22 03:02:58 +09:00
b851b7f431 12.20.0 2020-02-22 02:38:37 +09:00
ccaa99115c New Crowdin translations (#6047)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Kannada)
2020-02-22 02:38:11 +09:00
813de15e85 🎨 2020-02-22 02:30:41 +09:00
fa33181fa9 Update particle.vue 2020-02-22 02:26:01 +09:00
d4324dc0cb Reaction particle 2020-02-22 01:20:58 +09:00
ccc27bcc14 Fix #6057 (#6061) 2020-02-22 01:03:50 +09:00
014c1673c6 Update README.md [AUTOGEN] 2020-02-21 22:39:06 +09:00
3a3319ff52 Merge pull request #6056 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-02-21 20:11:43 +09:00
5b54ec8fb5 Update README.md [AUTOGEN] 2020-02-21 18:37:07 +09:00
e690556286 patch #6039 (#6052) 2020-02-21 17:16:51 +09:00
660956917f Merge pull request #6034 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-02-21 11:18:52 +09:00
3a5201747b 🎨 2020-02-21 09:17:33 +09:00
b338e8a83f 🎨 2020-02-21 09:11:35 +09:00
5584d56b6a Clean up 2020-02-21 08:36:18 +09:00
c925498120 Improve usability 2020-02-21 07:21:27 +09:00
75615cf503 Update style.scss 2020-02-21 07:11:25 +09:00
39f708b0fc 複数タブで開いてるときに動作がおかしい問題を修正 2020-02-21 03:51:41 +09:00
ac32077221 12.19.0 2020-02-21 00:36:17 +09:00
a5902acacd New Crowdin translations (#6037)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)
2020-02-21 00:36:03 +09:00
c7c08b7511 Resolve #6043 2020-02-21 00:28:45 +09:00
7de915d47b Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-20 23:27:48 +09:00
9107547501 Update CHANGELOG.md 2020-02-20 23:27:32 +09:00
95dc76ca19 Fix comments 2020-02-20 23:26:35 +09:00
49c2a9b372 ボリュームが0のときサウンドを鳴らさないように 2020-02-20 23:17:17 +09:00
b378cabfc7 Fix bug 2020-02-20 23:11:09 +09:00
4263dbef31 Fix #6026 2020-02-20 23:07:20 +09:00
32fc6ae2eb Fix bug 2020-02-20 23:07:03 +09:00
238cb0077f Fix bug 2020-02-20 23:02:55 +09:00
f5a06b6494 Fix #6036 2020-02-20 22:54:26 +09:00
2c01329085 Update README.md [AUTOGEN] 2020-02-20 14:10:06 +09:00
502de89ab1 12.18.1 2020-02-20 13:41:28 +09:00
128de6750c New translations ja-JP.yml (Spanish) (#6027) 2020-02-20 13:41:16 +09:00
e59e2d9f0b Resolve #6028 2020-02-20 13:38:40 +09:00
2504b8391b Better validation 2020-02-20 13:33:41 +09:00
330ea7d210 12.18.0 2020-02-20 07:30:43 +09:00
1edd173a29 Add sounds 2020-02-20 07:29:34 +09:00
98d873a7f9 Update search-by-tag.ts 2020-02-20 07:19:27 +09:00
09175b84df Fix #6016 2020-02-20 07:18:40 +09:00
177e19632a Fix #6016 2020-02-20 07:18:16 +09:00
8e6207f3e9 Remove header transition 2020-02-20 06:42:20 +09:00
ff3a97f6cf Fix #5943 2020-02-20 06:38:19 +09:00
b8e155ab40 🎨 2020-02-20 06:08:54 +09:00
b8e7df198d Improve sound 2020-02-20 06:08:49 +09:00
34311e3181 12.17.0 2020-02-20 03:55:38 +09:00
46115d3f04 New Crowdin translations (#5997)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Kannada)
2020-02-20 03:52:23 +09:00
c1d25d2394 切断時ダイアログのタイミングの変更など (#6014)
* 再接続時インジケーター

* Update ja-JP.yml

* Update stream-indicator.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-20 03:42:35 +09:00
880cea5a56 Better sfx 2020-02-20 03:14:17 +09:00
e7205d9cc2 サウンド設定など 2020-02-20 02:40:53 +09:00
f456feb3ff media-listのgridの高さがsub-note-detailsのdetailsの中だと287pxになってしまっていたのを修正 (#5951)
* fix files grid height

* missing colon

* ✌️

* ✌️

* fix

* remove unused event listener
2020-02-20 01:24:45 +09:00
3f83beedb7 Fix #5943 (#6023) 2020-02-20 00:38:26 +09:00
e6c9b1d9bd LegacyReaction変換にstarを追加 (#6013) 2020-02-19 22:06:54 +09:00
b46114f4fa Update index.home.vue 2020-02-19 17:59:28 +09:00
8d77e2ba22 Fix bug 2020-02-19 17:55:55 +09:00
cb3900921f remove unused event listener 2020-02-19 07:49:53 +09:00
ae2021583d 🎨 2020-02-19 07:00:44 +09:00
36cd88e6b7 12.16.0 2020-02-19 06:42:46 +09:00
517b0908da 🎨 2020-02-19 06:41:30 +09:00
b23b3e4d21 Fix #5984 2020-02-19 06:36:50 +09:00
883fc5dde0 Improve notification 2020-02-19 06:26:29 +09:00
9d044329f6 🎨 2020-02-19 06:17:41 +09:00
d1e9e74cb8 Resolve #5978 2020-02-19 06:16:49 +09:00
98a87ee75f 12.15.0 2020-02-19 04:16:00 +09:00
331491077d New translations ja-JP.yml (French) (#5972) 2020-02-19 04:15:39 +09:00
913c3a6636 Fix page like button 2020-02-19 04:15:14 +09:00
fbaf5fe355 Clean up 2020-02-19 04:12:49 +09:00
804c932f60 Resolve #5995 2020-02-19 04:08:35 +09:00
cef6d1d1b6 モデレーターになってしまっている場合は解除できるように (#5983) 2020-02-19 03:24:37 +09:00
e4e7ab1135 ページ遷移のトランジションをなくした 2020-02-19 03:22:10 +09:00
6ca30df8c4 Some tweaks 2020-02-19 03:16:10 +09:00
a340d4ed8e 固定投稿フォームを実装 (#5994)
* 固定投稿フォームを実装

* fix
2020-02-19 03:11:09 +09:00
ca7cb94358 Fix bug 2020-02-18 23:42:08 +09:00
54779b25f5 Clean up 2020-02-18 23:16:55 +09:00
44d7652171 12.14.0 2020-02-18 21:35:16 +09:00
c9ed15b682 add missing image (#5967)
fix for explore banner
2020-02-18 21:33:51 +09:00
8faad646ae New Crowdin translations (#5971)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Kannada)
2020-02-18 21:33:38 +09:00
1d50bc3382 Fix bug 2020-02-18 21:27:47 +09:00
da4af041af ログビューア実装 2020-02-18 21:27:43 +09:00
e2ff408f2f Implement object storage settings 2020-02-18 21:12:05 +09:00
f799375635 fix 2020-02-15 21:31:56 +09:00
65704bbf01 ✌️ 2020-02-15 21:20:01 +09:00
9cb3882efa ✌️ 2020-02-15 20:18:37 +09:00
a0833ca691 missing colon 2020-02-15 19:34:30 +09:00
a4f197f608 fix files grid height 2020-02-15 19:33:12 +09:00
173 changed files with 2556 additions and 1121 deletions

View File

@ -88,7 +88,9 @@ redis:
#elasticsearch:
# host: localhost
# port: 9200
# pass: null
# ssl: false
# user:
# pass:
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@ -21,6 +21,7 @@ jobs:
- 5432:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:alpine
ports:
@ -40,3 +41,13 @@ jobs:
run: yarn build
- name: Test
run: yarn test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: yarn install
- run: yarn lint

4
.mocharc.json Normal file
View File

@ -0,0 +1,4 @@
{
"timeout": 30000,
"slow": 1000
}

View File

@ -1,6 +1,93 @@
ChangeLog
=========
12.21.0 (2020/02/23)
-------------------
### ✨Improvements
* タイムラインに挿入されるおすすめノートに自分がリアクションしたものは含めないように
* ノートのメニューに詳細ページへのリンクを追加
* UIの調整
### 🐛Fixes
* チャットで自分の送信したURLが視認しにくい問題を修正
* ノートの内のインラインコードが横に突き抜ける問題を修正
* (新しいノートがあります)表示中にタイムラインを切り替えると、表示が消えなくなってしまう問題を修正
* 引用RNフォームを開いた時だけ、textareaにフォーカスが当たっていない問題を修正
12.20.0 (2020/02/22)
-------------------
### ✨Improvements
* UIの調整
### 🐛Fixes
* 複数タブで開いてるときに動作がおかしい問題を修正
* メディア付きートの詳細表示をした後TLに戻るとートがバグる問題を修正
12.19.0 (2020/02/21)
-------------------
### ✨Improvements
* アンテナで除外キーワードを設定できるように
### 🐛Fixes
* ハッシュタグをもっと見るできないのを修正
* 無効になっているタイムラインでも使用できるかのように表示される問題を修正
* バックグラウンドで受信したノートの画像が表示されない問題を修正
* サインインフォームが表示されない場所がある問題を修正
* ボリュームが0のときサウンドを鳴らさないように
12.18.1 (2020/02/20)
-------------------
### 🐛Fixes
* タイムラインのハイライトに自分のノートは含めないように
* ハッシュタグの集計に関する問題を修正
12.18.0 (2020/02/20)
-------------------
### ✨Improvements
* 効果音設定を強化
* UIの調整
### 🐛Fixes
* ユーザープレビューが稀に画面上から消えなくなってしまう問題を修正
* ハッシュタグ検索が遅い問題を修正
12.17.0 (2020/02/20)
-------------------
### ✨Improvements
* 効果音を実装
* 切断時ダイアログを控えめに
### 🐛Fixes
* 新しいノートの通知が崩れる問題を修正
* LegacyReaction変換にstarを追加
* ユーザープレビューが稀に画面上から消えなくなってしまう問題を修正
* media-listのgridの高さがsub-note-detailsのdetailsの中だと287pxになってしまっていたのを修正
12.16.0 (2020/02/19)
-------------------
### ✨Improvements
* 通知一覧をポップアップではなくページで表示できるように
* 返信、引用、メンションの通知を直接ノートとして表示するように
### 🐛Fixes
* v12以前のリアクションが表示されない問題を修正
12.15.0 (2020/02/19)
-------------------
### ✨Improvements
* 固定投稿フォームを実装
* ページ遷移のトランジションを無しに
* スクロールしてるときに新しいノートが来たときにわかるように表示するように
### 🐛Fixes
* ページのいいねボタンを修正
12.14.0 (2020/02/18)
-------------------
### ✨Improvements
* オブジェクトストレージの設定を実装
* サーバーログビューア実装
12.13.0 (2020/02/18)
-------------------
### ✨Improvements

View File

@ -110,7 +110,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=20832595">Roujo</a></td>
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
@ -119,9 +118,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
</tr></table>
<table><tr>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
@ -130,9 +129,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
@ -141,56 +139,64 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=26340354">totokoro</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=5827393">motcha</a></td>
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1</a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
<td><a href="https://www.patreon.com/user?u=28295158">012</a></td>
<td><a href="https://www.patreon.com/nijimiss">nafuchoco</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
<td><a href="https://www.patreon.com/Corset">CG</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
<td><a href="https://www.patreon.com/efertone">Efertone</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Wed, 05 Feb 2020 00:42:12 UTC
**Last updated:** Tue, 17 Mar 2020 18:57:08 UTC
<!-- PATREON_END -->
[backer-url]: #backers

View File

@ -26,14 +26,86 @@ uploading: "Upload läuft"
save: "Speichern"
users: "Benutzer"
addUser: "Benutzer hinzufügen"
favorite: "Favoriten"
favorites: "Favoriten"
unfavorite: "Aus Favoriten entfernen"
pin: "Anheften"
unpin: "Lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
delete: "Löschen"
addToList: "Zur Liste hinzufügen"
sendMessage: "Nachricht senden"
copyUsername: "Benutzernamen kopieren"
reply: "Antworten"
loadMore: "Zeige mehr"
youGotNewFollower: "Sie haben einen neuen Follower"
receiveFollowRequest: "Follow Request erhalten."
followRequestAccepted: "FollowRequestAkzeptiert"
mentions: "Erwähnungen"
directNotes: "Direktnachrichten"
importAndExport: "Importieren und Exportieren"
import: "Importieren"
export: "Exportieren"
files: "Dateien"
download: "Download"
lists: "Listen"
noLists: "Keine Liste!"
note: "Noten"
following: "Folgen"
followers: "Folgende"
manageLists: "Liste verwalten"
error: "Ein Problem ist aufgetreten"
retry: "Wiederholen"
privacy: "Privatsphäre"
defaultNoteVisibility: "Die Standardsichtbarkeit"
follow: "Folgen"
followRequest: "Follower-Anfragen"
followRequests: "Follower-Anfragen"
unfollow: "Nicht mehr folgen"
followRequestPending: "Ausstehend"
clickToShow: "Klicke zum den Inhalt anzusehen"
sensitive: "Dieser Inhalt ist NSFW"
add: "Hinzufügen"
reaction: "Reaktionen"
selectUser: "Benutzer wählen"
instances: "Instanz"
mutedUsers: "Stummgestellte Benutzer"
blockedUsers: "Blockierte Benutzer"
noUsers: "Keine Benutzer"
remove: "Löschen"
nsfw: "Dieser Inhalt ist NSFW"
userList: "Listen"
_sfx:
notification: "Benachrichtigungen"
_widgets:
notifications: "Benachrichtigungen"
timeline: "Zeitleiste"
_cw:
show: "Zeige mehr"
_visibility:
followers: "Folgende"
_profile:
username: "Benutzername"
_exportOrImport:
followingList: "Folgen"
userLists: "Listen"
_pages:
script:
categories:
list: "Listen"
blocks:
_join:
arg1: "Listen"
_randomPick:
arg1: "Listen"
_dailyRandomPick:
arg1: "Listen"
_seedRandomPick:
arg2: "Listen"
_pick:
arg1: "Listen"
_listLen:
arg1: "Listen"
types:
array: "Listen"

View File

@ -239,6 +239,8 @@ avatar: "Avatar"
banner: "Banner"
nsfw: "NSFW"
disconnectedFromServer: "Connection to the server was inturrupted"
reload: "Refresh"
doNothing: "Ignore"
reloadConfirm: "Would you like to retry?"
watch: "Watch"
unwatch: "Undo Watch"
@ -283,7 +285,8 @@ antennas: "Antennas"
manageAntennas: "Manage Antennas"
name: "Name"
antennaSource: "Antenna source"
antennaKeywords: "Antenna keywords"
antennaKeywords: "Keywords to receive"
antennaExcludeKeywords: "Keywords to exclude"
antennaKeywordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
notifyAntenna: "Notify newer notes"
withFileAntenna: "Filter only notes with file attached"
@ -412,6 +415,29 @@ dayOverDayChanges: "Daily"
accessibility: "Accessibility"
clinetSettings: "Client Settings"
accountSettings: "Account Settings"
promotion: "Promoted"
promote: "Promote"
numberOfDays: "Amount of days"
hideThisNote: "Hide this note"
showFeaturedNotesInTimeline: "Show Featured notes in Timeline"
objectStorage: "Object Storage"
useObjectStorage: "Use object storage"
serverLogs: "Server logs"
deleteAll: "Delete all"
showFixedPostForm: "Display the posting form at the top of the timeline"
newNoteRecived: "You've got a new note"
useNotificationsPopup: "Display notification list in popup"
sounds: "Sounds"
listen: "Listen"
none: "None"
volume: "Volume"
_sfx:
note: "New note"
noteMy: "My note"
notification: "Notifications"
chat: "Messaging"
chatBg: "Messaging (Background)"
antenna: "Antenna Reception"
_ago:
unknown: "Unknown"
future: "Future"
@ -513,6 +539,7 @@ _widgets:
clock: "Clock"
rss: "RSS reader"
activity: "Activity"
photos: "Photos"
_cw:
hide: "Hide"
show: "Load more"

View File

@ -239,6 +239,8 @@ avatar: "Avatar"
banner: "Banner"
nsfw: "Marcado como sensible"
disconnectedFromServer: "Desconectado del servidor"
reload: "Recargar"
doNothing: "No hacer nada"
reloadConfirm: "¿Desea recargar?"
watch: "Ver"
unwatch: "Dejar de ver"
@ -283,7 +285,8 @@ antennas: "Antenas"
manageAntennas: "Administrar antenas"
name: "Nombre"
antennaSource: "Origen de la antena"
antennaKeywords: "Palabras clave de la antena"
antennaKeywords: "Palabras clave para recibir"
antennaExcludeKeywords: "Palabras clave para excluir"
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
notifyAntenna: "Notificar nueva nota"
withFileAntenna: "Sólo notas con archivos adjuntados"
@ -412,6 +415,29 @@ dayOverDayChanges: "Dif diaria"
accessibility: "Accesibilidad"
clinetSettings: "Ajustes del cliente"
accountSettings: "Ajustes de cuenta"
promotion: "Promovido"
promote: "Promover"
numberOfDays: "Cantidad de dias"
hideThisNote: "Ocultar esta nota"
showFeaturedNotesInTimeline: "Mostrar notas destacadas en la línea de tiempo"
objectStorage: "Almacenamiento de objetos"
useObjectStorage: "Usar almacenamiento de objetos"
serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
newNoteRecived: "Tienes una nota nuevo"
useNotificationsPopup: "Mostrar lista de notificaciones en ventana emergente"
sounds: "Sonidos"
listen: "Escuchar"
none: "Ninguna"
volume: "Volumen"
_sfx:
note: "Notas"
noteMy: "Nota (a mí mismo)"
notification: "Notificaciones"
chat: "Chat"
chatBg: "Chat (Fondo)"
antenna: "Antena receptora"
_ago:
unknown: "Desconocido"
future: "Futuro"
@ -513,6 +539,7 @@ _widgets:
clock: "Reloj"
rss: "Lector RSS"
activity: "Actividad"
photos: "Fotos"
_cw:
hide: "Ocultar"
show: "Ver más"

View File

@ -239,6 +239,8 @@ avatar: "Avatar"
banner: "Bannière"
nsfw: "Contenu sensible"
disconnectedFromServer: "Déconnecté du serveur"
reload: "Rafraîchir"
doNothing: "Ignorer"
reloadConfirm: "Voulez-vous recharger?"
watch: "Surveiller"
unwatch: "Ne plus surveiller"
@ -283,7 +285,8 @@ antennas: "Antenne"
manageAntennas: "Gestion d'antenne"
name: "Nom"
antennaSource: "Recevoir la source"
antennaKeywords: "Mots clés entrants"
antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Lorsqu'il est séparé par un espace, il devient une spécification ET, et lorsqu'il est séparé par un saut de ligne, il devient une spécification OU."
notifyAntenna: "Notifier les nouvelles notes"
withFileAntenna: "Notes uniquement avec fichiers joints"
@ -412,6 +415,29 @@ dayOverDayChanges: "Diff quotidien"
accessibility: "Accessibilité"
clinetSettings: "Paramètres du client"
accountSettings: "Paramètres du compte"
promotion: "Promu"
promote: "Promouvoir"
numberOfDays: "Nombre de jours"
hideThisNote: "Masquer cette note"
showFeaturedNotesInTimeline: "Afficher les notes en vedette dans Fil d'actualité"
objectStorage: "Stockage d'objets"
useObjectStorage: "Utiliser le stockage d'objets"
serverLogs: "Journaux serveur"
deleteAll: "Supprimer tout"
showFixedPostForm: "Afficher le formulaire en haut du fil d'actualité"
newNoteRecived: "Vous avez un nouveau note"
useNotificationsPopup: "Afficher la liste des notifications dans une fenêtre contextuelle"
sounds: "Sons"
listen: "Écouter"
none: "Rien"
volume: "Volume"
_sfx:
note: "Nouvelle note"
noteMy: "Ma note"
notification: "Notifications"
chat: "Discuter"
chatBg: "Discuter (De fond)"
antenna: "Réception d'antenne"
_ago:
unknown: "Inconnu"
future: "Futur"
@ -431,6 +457,16 @@ _time:
_tutorial:
title: "Comment utiliser Misskey"
step1_1: "Bienvenue,"
step1_2: "Cette page est appelée \"timeline\". Elle montre les \"notes\" des personnes que vous \"suivez\" dans l'ordre chronologique."
step1_3: "Vous n'avez pas encore posté de notes ou ne suivez personne, vous ne devriez donc rien voir dans la chronologie."
step2_1: "Finissons de créer votre profil avant d'écrire une note ou de suivre quelqu'un."
step2_2: "En fournissant quelques informations sur vous, il sera plus facile pour les autres de vous suivre."
step3_1: "Vous avez fini de créer votre profil ?"
step3_2: "Létape suivante consiste à créer une note. Vous pouvez commencer en cliquant sur licône crayon sur lécran."
step3_3: "Remplissez le cadran et cliquez sur le bouton en haut à droite pour envoyer."
step3_4: "Vous n'avez rien à dire ? Essayez de dire \"J'ai commencé à utiliser Misskey\"."
step4_1: "Avez-vous posté votre première notes ?"
step4_2: "Votre première note est maintenant affichée sur votre timeline."
_2fa:
alreadyRegistered: "Cette étape à déjà été complétée"
registerDevice: "Sinscrire l'appareil"
@ -493,6 +529,7 @@ _widgets:
clock: "Horloge"
rss: "Lecteur de flux RSS"
activity: "Activités"
photos: "Photos"
_cw:
hide: "Masquer"
show: "Voir plus"

View File

@ -84,7 +84,7 @@ clickToShow: "クリックして表示"
sensitive: "閲覧注意"
add: "追加"
reaction: "リアクション"
reactionSettingDescription: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
rememberNoteVisibility: "公開範囲を記憶する"
renameFile: "ファイル名を変更"
attachCancel: "添付取り消し"
@ -168,6 +168,7 @@ intro: "Misskeyのインストールが完了しました管理者アカウ
done: "完了"
processing: "処理中"
preview: "プレビュー"
default: "デフォルト"
noCustomEmojis: "絵文字はありません"
customEmojisOfRemote: "リモートの絵文字"
noJobs: "ジョブはありません"
@ -239,6 +240,8 @@ avatar: "アイコン"
banner: "バナー"
nsfw: "閲覧注意"
disconnectedFromServer: "サーバーから切断されました"
reload: "リロード"
doNothing: "なにもしない"
reloadConfirm: "リロードしますか?"
watch: "ウォッチ"
unwatch: "ウォッチ解除"
@ -284,6 +287,7 @@ manageAntennas: "アンテナの管理"
name: "名前"
antennaSource: "受信ソース"
antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ"
@ -417,6 +421,38 @@ promote: "プロモート"
numberOfDays: "日数"
hideThisNote: "このノートを非表示"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
objectStorage: "オブジェクトストレージ"
useObjectStorage: "オブジェクトストレージを使用"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/<bucket>'。"
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "このprefixのディレクトリ下に格納されます。"
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。"
objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。"
objectStorageUseSSL: "SSLを使用する"
objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください"
serverLogs: "サーバーログ"
deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
newNoteRecived: "新しいノートがあります"
sounds: "サウンド"
listen: "聴く"
none: "なし"
volume: "音量"
details: "詳細"
chooseEmoji: "絵文字を選択"
_sfx:
note: "ノート"
noteMy: "ノート(自分)"
notification: "通知"
chat: "チャット"
chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
_ago:
unknown: "謎"

View File

@ -109,6 +109,8 @@ aboutMisskey: "Misskeyってなんや"
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
close: "さいなら"
joinedGroups: "参加しとるグループ"
_sfx:
notification: "通知"
_ago:
unknown: "謎"
future: "未来"

View File

@ -23,9 +23,43 @@ login: "ಪ್ರವೇಶ"
loggingIn: "ಪ್ರವೇಶಿಸುತ್ತಾ..."
logout: "ಆಚೆಗೆ"
signup: "ನೋಂದಣಿ"
uploading: "ಅಪ್‌ಲೋಡಾಗುತ್ತಿದೆ"
save: "ಉಳಿಸಿ"
users: "ಬಳಕೆದಾರ"
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
favorite: "ಮೆಚ್ಚಿನ"
favorites: "ಮೆಚ್ಚಿನವುಗಳು"
unfavorite: "ಮೆಚ್ಚುಗೆ ಅಳಿಸು"
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
copyLink: "ಲಿಂಕನ್ನು ನಕಲಿಸು"
delete: "ಅಳಿಸು"
addToList: "ಪಟ್ಟಿಗೆ ಸೇರಿಸು"
sendMessage: "ಸಂದೇಶ ಕಳುಹಿಸು"
copyUsername: "ಬಳಕೆಹೆಸರು ನಕಲಿಸು"
reply: "ಉತ್ತರಿಸು"
loadMore: "ಇನ್ನಷ್ಟು ನೋಡು"
youGotNewFollower: "ಹಿಂಬಾಲಿಸಿದರು"
receiveFollowRequest: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಬಂದಿದೆ"
followRequestAccepted: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಸ್ವೀಕರಿಸಲಾಯಿತು"
mentions: "ಹೆಸರಿಸಿದ"
directNotes: "ನೇರ ಟಿಪ್ಪಣಿಗಳು"
importAndExport: "ಆಮದು/ರಫ್ತು"
import: "ಆಮದು"
export: "ರಫ್ತು"
files: "ಕಡತಗಳು"
download: "ಜಾಲದಿಂದಿಳಿಸು"
driveFileDeleteConfirm: "\"{name}\" ಕಡತವನ್ನು ಅಳಿಸಲು ನೀವು ಬಯಸುವಿರಾ? ಈ ನೋಡಿರಿ ಲಗತ್ತಿಸಲಾದ ಟಿಪ್ಪಣಿ ಸಹ ಕಣ್ಮರೆಯಾಗುತ್ತದೆ."
unfollowConfirm: "{name}ಅನ್ನು ಹಿಂಬಾಲಿಸದಿರುವುದೇ?"
instances: "ನಿದರ್ಶನ"
remove: "ಅಳಿಸು"
_sfx:
notification: "ಅಧಿಸೂಚನೆಗಳು"
_widgets:
notifications: "ಅಧಿಸೂಚನೆಗಳು"
timeline: "ಸಮಯಸಾಲು"
_cw:
show: "ಇನ್ನಷ್ಟು ನೋಡು"
_profile:
username: "ಬಳಕೆಹೆಸರು"

View File

@ -239,6 +239,8 @@ avatar: "아바타"
banner: "배너"
nsfw: "열람주의"
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
reload: "새로고침"
doNothing: "무시하기"
reloadConfirm: "새로고침 하시겠습니까?"
watch: "지켜보기"
unwatch: "지켜보기 해제"
@ -284,6 +286,7 @@ manageAntennas: "안테나 관리"
name: "이름"
antennaSource: "받을 소스"
antennaKeywords: "받을 키워드"
antennaExcludeKeywords: "제외할 키워드"
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
notifyAntenna: "새로운 노트를 알림"
withFileAntenna: "파일이 첨부된 노트만"
@ -412,6 +415,29 @@ dayOverDayChanges: "어제보다"
accessibility: "접근성"
clinetSettings: "클라이언트 설정"
accountSettings: "계정 설정"
promotion: "프로모션"
promote: "프로모션하기"
numberOfDays: "며칠동안"
hideThisNote: "이 노트를 숨기기"
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
objectStorage: "오브젝트 스토리지"
useObjectStorage: "오브젝트 스토리지를 사용"
serverLogs: "서버 로그"
deleteAll: "모두 삭제"
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
newNoteRecived: "새 노트가 있습니다"
useNotificationsPopup: "알림 목록을 팝업으로 표시"
sounds: "소리"
listen: "듣기"
none: "없음"
volume: "음량"
_sfx:
note: "새 노트"
noteMy: "내 노트"
notification: "알림"
chat: "대화"
chatBg: "대화 (백그라운드)"
antenna: "안테나 수신"
_ago:
unknown: "알 수 없음"
future: "미래"
@ -513,6 +539,7 @@ _widgets:
clock: "시계"
rss: "RSS 리더"
activity: "활동"
photos: "사진"
_cw:
hide: "숨기기"
show: "더 보기"

View File

@ -1,2 +1,36 @@
---
_lang_: "Русский язык"
search: "Поиск"
notifications: "Уведомления"
password: "Пароль"
ok: "Окей"
cancel: "Отмена"
instance: "Экземпляр"
settings: "Настройки"
profile: "Профиль"
timeline: "Лента"
login: "Войти"
logout: "Выйти"
signup: "Регистрация"
save: "Сохранить"
favorite: "Избранное"
favorites: "Избранное"
unfavorite: "Удалить из избранных"
pin: "Закрепить"
unpin: "Открепить"
copyLink: "Скопировать ссылку"
delete: "Удалить"
addToList: "Добавить в список"
reply: "Ответить"
loadMore: "Показать еще"
importAndExport: "Импорт / Экспорт"
files: "Файл"
instances: "Экземпляр"
remove: "Удалить"
_sfx:
notification: "Уведомления"
_widgets:
notifications: "Уведомления"
timeline: "Лента"
_cw:
show: "Показать еще"

View File

@ -315,8 +315,10 @@ moderator: "版主"
nUsersMentioned: "{n} 被提到"
securityKey: "安全密钥"
securityKeyName: "密钥名称"
registerSecurityKey: "注册安全密钥"
lastUsed: "最后使用:"
unregister: "删除账户"
passwordLessLogin: "无密码登录"
resetPassword: "重置密码"
newPasswordIs: "新的密码是「{password}」"
post: "投稿"
@ -327,10 +329,12 @@ autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他
reduceUiAnimation: "减少UI动画"
share: "分享"
notFound: "未找到"
notFoundDescription: "没有与指定URL对应的页面。"
uploadFolder: "默认上传文件夹"
cacheClear: "清空缓存"
markAsReadAllNotifications: "将所有通知标为已读"
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
markAsReadAllTalkMessages: "将所有聊天标记为已读"
help: "帮助"
inputMessageHere: "在此键入信息"
close: "关闭"
@ -343,6 +347,8 @@ invites: "邀请"
groupName: "群组名"
members: "成员"
transfer: "转让"
messagingWithUser: "与用户聊天"
messagingWithGroup: "与群组聊天"
title: "标题"
text: "文本"
enable: "启用"
@ -353,6 +359,7 @@ inviteToGroup: "群组邀请"
maxNoteTextLength: "帖子的字数限制"
quoteAttached: "已引用"
quoteQuestion: "是否将其作为引用附上?"
noMessagesYet: "现在没有新的聊天"
newMessageExists: "新信息"
onlyOneFileCanBeAttached: "只能添加一个附件"
signinRequired: "请先登录"
@ -368,6 +375,8 @@ normalPassword: "密码强度:中等"
strongPassword: "密码强度:强"
passwordMatched: "密码一致"
passwordNotMatched: "密码不一致"
signinWith: "以{x}登录"
tapSecurityKey: "点击安全密钥"
or: "或者"
uiLanguage: "显示语言"
groupInvited: "群组招待"
@ -380,6 +389,7 @@ disableAnimatedMfm: "禁用MFM动画"
doing: "正在进行"
category: "类别"
tags: "标签"
docSource: "文件来源"
createAccount: "注册账户"
existingAcount: "现有的帐户"
regenerate: "重新生成"
@ -395,6 +405,21 @@ dayOverDayChanges: "与前一日相比"
accessibility: "辅助功能"
clinetSettings: "客户端设置"
accountSettings: "账户设置"
numberOfDays: "天数"
hideThisNote: "隐藏这条帖子"
showFeaturedNotesInTimeline: "在时间轴上显示热门推荐"
objectStorage: "对象存储"
useObjectStorage: "使用对象存储"
serverLogs: "服务器日志"
deleteAll: "删除全部"
showFixedPostForm: "在时间线顶部显示帖子表单"
newNoteRecived: "有新的帖子"
useNotificationsPopup: "在弹出窗口中显示通知列表"
none: "空"
_sfx:
note: "帖子"
notification: "通知"
chat: "聊天"
_ago:
unknown: "未知"
future: "未来"
@ -414,6 +439,16 @@ _time:
_tutorial:
title: "Misskey的使用方法"
step1_1: "欢迎!"
step1_2: "这个页面叫做「时间线」,它会按照时间顺序显示所有你「关注」的人所发的「帖子」。"
step1_3: "如果你并没有发布任何帖子,也没有关注其他的人,你的时间线页面应当什么都没有显示。"
step2_1: "在你想发布一些帖子之前,让我们先进行一下个人资料设置。"
step2_2: "如果别人能够更加的了解你,关注你的概率也会得到提升。"
step3_1: "已经设置完个人资料了吗?"
step3_2: "那么接下来,试着写一些什么东西来发布吧。你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
step3_3: "写完内容后,点击窗口右上方的按钮就可以投稿。"
step3_4: "不知道说些什么好吗那就写下「Misskey我来啦」这样的话吧。"
step4_1: "将你的话语发布出去了吗?"
step4_2: "太棒了!现在你可以在你的时间线中看到你刚刚发布的帖子了。"
step7_3: "接下来享受Misskey带来的乐趣吧🚀"
_2fa:
alreadyRegistered: "此设备已被注册"
@ -447,6 +482,7 @@ _auth:
permissionAsk: "这个应用程序需要以下权限"
_antennaSources:
all: "所有帖子"
homeTimeline: "已关注用户的帖子"
_weekday:
sunday: "星期日"
monday: "星期一"
@ -464,6 +500,7 @@ _widgets:
clock: "时钟"
rss: "RSS阅读器"
activity: "活动"
photos: "照片"
_cw:
hide: "隐藏"
show: "查看更多"
@ -534,6 +571,7 @@ _charts:
notesIncDec: "帖子:增加/减少"
notesTotal: "帖子总数"
_instanceCharts:
requests: "请求"
users: "用户数量:增加/减少"
usersTotal: "用户总数"
notes: "帖子:增加/减少"

View File

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class antennaExclude1582210532752 implements MigrationInterface {
name = 'antennaExclude1582210532752'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeKeywords" jsonb NOT NULL DEFAULT '[]'`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeKeywords"`, undefined);
}
}

View File

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class noteReactionLength1582875306439 implements MigrationInterface {
name = 'noteReactionLength1582875306439'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(128)`, undefined);
}
}

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.13.0",
"version": "12.22.0",
"codename": "indigo",
"repository": {
"type": "git",
@ -21,7 +21,7 @@
"gulp": "gulp build",
"clean": "gulp clean",
"cleanall": "gulp cleanall",
"lint": "gulp lint",
"lint": "tslint 'src/**/*.ts'",
"test": "cross-env TS_NODE_FILES=true gulp test",
"format": "gulp format"
},
@ -250,7 +250,6 @@
"vue-meta": "2.3.2",
"vue-prism-component": "1.1.1",
"vue-router": "3.1.5",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.4.5",
"vue-template-compiler": "2.6.11",

View File

@ -171,6 +171,7 @@ declare module 'jsrsasign' {
public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
// tslint:disable-next-line:bool-param-default
public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
public static hextooidstr(hex: ASN1OIDV): OID;
@ -620,9 +621,7 @@ declare module 'jsrsasign' {
public encrypt(text: string): HexString | null;
public encryptOAEP(text: string, hash?: string, hashLen?: number): HexString | null;
public encryptOAEP(text: string, hash?: (s: string) => string, hashLen?: number): HexString | null;
public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null;
//// RSA PRIVATE
@ -638,9 +637,7 @@ declare module 'jsrsasign' {
public decrypt(ctext: HexString): string;
public decryptOAEP(ctext: HexString, hash?: string, hashLen?: number): string | null;
public encryptOAEP(ctext: HexString, hash?: (s: string) => string, hashLen?: number): string | null;
public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null;
//// RSA PEM

View File

@ -50,21 +50,23 @@
<router-link class="item index" active-class="active" to="/" exact v-else>
<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
</router-link>
<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.getters.isSignedIn">
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
</router-link>
<template v-if="$store.getters.isSignedIn">
<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton">
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/messaging">
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/drive">
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
</router-link>
</template>
<div class="divider"></div>
<router-link class="item" active-class="active" to="/featured">
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
@ -87,7 +89,7 @@
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/settings">
<router-link class="item" active-class="active" to="/preferences">
<fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
</router-link>
</div>
@ -143,15 +145,13 @@
<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
</div>
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
<transition name="zoom-in-top">
<x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/>
</transition>
<stream-indicator v-if="$store.getters.isSignedIn"/>
</div>
</template>
@ -164,7 +164,6 @@ import { v4 as uuid } from 'uuid';
import i18n from './i18n';
import { host, instanceName } from './config';
import { search } from './scripts/search';
import contains from './scripts/contains';
import MkToast from './components/toast.vue';
const DESKTOP_THRESHOLD = 1100;
@ -174,7 +173,6 @@ export default Vue.extend({
components: {
XClock: () => import('./components/header-clock.vue').then(m => m.default),
XNotifications: () => import('./components/notifications.vue').then(m => m.default),
MkButton: () => import('./components/ui/button.vue').then(m => m.default),
XDraggable: () => import('vuedraggable'),
},
@ -185,7 +183,6 @@ export default Vue.extend({
pageKey: 0,
showNav: false,
searching: false,
notificationsOpen: false,
accounts: [],
lists: [],
connection: null,
@ -194,7 +191,6 @@ export default Vue.extend({
widgetsEditMode: false,
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
canBack: false,
disconnectedDialog: null as Promise<void> | null,
wallpaper: localStorage.getItem('wallpaper') != null,
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer
};
@ -218,23 +214,10 @@ export default Vue.extend({
watch:{
$route(to, from) {
this.pageKey++;
this.notificationsOpen = false;
this.showNav = false;
this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
},
notificationsOpen(open) {
if (open) {
for (const el of Array.from(document.querySelectorAll('*'))) {
el.addEventListener('mousedown', this.onMousedown);
}
} else {
for (const el of Array.from(document.querySelectorAll('*'))) {
el.removeEventListener('mousedown', this.onMousedown);
}
}
},
isDesktop() {
if (this.isDesktop) this.adjustWidgetsWidth();
}
@ -258,30 +241,6 @@ export default Vue.extend({
}]);
}
}
this.$root.stream.on('_disconnected_', () => {
if (this.disconnectedDialog) return;
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
setTimeout(() => {
if (this.$root.stream.state !== 'reconnecting') return;
this.disconnectedDialog = this.$root.dialog({
type: 'warning',
showCancelButton: true,
title: this.$t('disconnectedFromServer'),
text: this.$t('reloadConfirm'),
}).then(({ canceled }) => {
if (!canceled) {
location.reload();
}
this.disconnectedDialog = null;
});
}, 150)
});
},
mounted() {
@ -571,22 +530,17 @@ export default Vue.extend({
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.$root.stream.send('readNotification', {
id: notification.id
});
if (true) {
this.$root.stream.send('readNotification', {
id: notification.id
});
this.$root.new(MkToast, {
notification
});
},
this.$root.new(MkToast, {
notification
});
}
onMousedown(e) {
e.preventDefault();
if (!contains(this.$refs.notifications.$el, e.target) &&
!contains(this.$refs.notificationButton, e.target) &&
!contains(this.$refs.notificationButton2, e.target)
) this.notificationsOpen = false;
return false;
this.$root.sound('notification');
},
widgetFunc(id) {
@ -638,30 +592,6 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
.header-enter-active, .header-leave-active {
transition: opacity 0.5s, transform 0.5s !important;
}
.header-enter {
opacity: 0;
transform: scale(0.9);
}
.header-leave-to {
opacity: 0;
transform: scale(0.9);
}
.page-enter-active, .page-leave-active {
transition: opacity 0.5s, transform 0.5s !important;
}
.page-enter {
opacity: 0;
transform: translateY(-32px);
}
.page-leave-to {
opacity: 0;
transform: translateY(32px);
}
.nav-enter-active,
.nav-leave-active {
opacity: 1;
@ -688,7 +618,7 @@ export default Vue.extend({
$header-height: 60px;
$nav-width: 250px;
$nav-icon-only-width: 74px;
$main-width: 700px;
$main-width: 650px;
$ui-font-size: 1em;
$nav-icon-only-threshold: 1300px;
$nav-hide-threshold: 700px;
@ -1011,17 +941,21 @@ export default Vue.extend({
> main {
width: $main-width;
min-width: $main-width;
box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider);
@media (max-width: $side-hide-threshold) {
min-width: 0;
}
> .content {
padding: 16px;
box-sizing: border-box;
> * {
&:not(.full) {
padding: var(--margin) 0;
}
@media (max-width: 500px) {
padding: 8px;
&:not(.naked) {
background: var(--pageBg);
}
}
}
@ -1059,6 +993,7 @@ export default Vue.extend({
> .widgets {
box-sizing: border-box;
margin-left: var(--margin);
@media (max-width: $side-hide-threshold) {
display: none;
@ -1211,32 +1146,5 @@ export default Vue.extend({
}
}
}
> .notifications {
position: fixed;
top: 32px;
left: 0;
right: 0;
margin: 0 auto;
z-index: 10001;
width: 350px;
height: 400px;
background: var(--vocsgcxy);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border-radius: 6px;
box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
overflow: hidden;
@media (max-width: 800px) {
width: 320px;
height: 350px;
}
@media (max-width: 500px) {
width: 290px;
height: 310px;
}
}
}
</style>

BIN
src/client/assets/fedi.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
<template>
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed">
<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
<template v-for="(item, i) in items">
<slot :item="item" :i="i"></slot>
<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
@ -9,7 +9,7 @@
</p>
</div>
</template>
</sequential-entrance>
</component>
</template>
<script lang="ts">
@ -27,7 +27,8 @@ export default Vue.extend({
},
direction: {
type: String,
required: false
required: false,
default: 'down'
},
reversed: {
type: Boolean,
@ -63,12 +64,38 @@ export default Vue.extend({
},
focus() {
this.$refs.list.focus();
this.$slots.default[0].elm.focus();
}
}
});
</script>
<style lang="scss">
.sqadhkmv {
> .list-move {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
}
> .list-enter-active {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
}
&[data-direction="up"] {
> .list-enter {
opacity: 0;
transform: translateY(64px);
}
}
&[data-direction="down"] {
> .list-enter {
opacity: 0;
transform: translateY(-64px);
}
}
}
</style>
<style lang="scss" scoped>
.sqadhkmv {
> .separator {
@ -82,8 +109,6 @@ export default Vue.extend({
line-height: 32px;
text-align: center;
font-size: 12px;
border-radius: 64px;
background: var(--dateLabelBg);
color: var(--dateLabelFg);
> span {

View File

@ -55,6 +55,7 @@ import { faTimesCircle, faQuestionCircle } from '@fortawesome/free-regular-svg-i
import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue';
import MkSelect from './ui/select.vue';
import MkSignin from './signin.vue';
import parseAcct from '../../misc/acct/parse';
import i18n from '../i18n';
@ -65,6 +66,7 @@ export default Vue.extend({
MkButton,
MkInput,
MkSelect,
MkSignin,
},
props: {

View File

@ -105,16 +105,6 @@ export default Vue.extend({
text: this.$t('delete'),
icon: faTrashAlt,
action: this.deleteFile
}, null, {
type: 'nest',
text: this.$t('contextmenu.else-files'),
menu: [{
text: this.$t('contextmenu.set-as-avatar'),
action: this.setAsAvatar
}, {
text: this.$t('contextmenu.set-as-banner'),
action: this.setAsBanner
}]
}],
source: ev.currentTarget || ev.target,
});

View File

@ -27,8 +27,6 @@ export default Vue.extend({
<style lang="scss" scoped>
.mjndxjcg {
max-width: 350px;
margin: 0 auto;
padding: 32px;
text-align: center;

View File

@ -1,12 +1,13 @@
<template>
<div class="mk-google">
<input type="search" v-model="query" :placeholder="q">
<button @click="search"><fa icon="search"/> {{ $t('search') }}</button>
<button @click="search"><fa :icon="faSearch"/> {{ $t('search') }}</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
export default Vue.extend({
@ -14,7 +15,8 @@ export default Vue.extend({
props: ['q'],
data() {
return {
query: null
query: null,
faSearch
};
},
mounted() {
@ -42,27 +44,17 @@ export default Vue.extend({
width: 100%;
height: 40px;
font-size: 16px;
color: var(--googleSearchFg);
background: var(--googleSearchBg);
border: solid 1px var(--googleSearchBorder);
border: solid 1px var(--divider);
border-radius: 4px 0 0 4px;
&:hover {
border-color: var(--googleSearchHoverBorder);
}
}
> button {
flex-shrink: 0;
padding: 0 16px;
border: solid 1px var(--googleSearchBorder);
border: solid 1px var(--divider);
border-left: none;
border-radius: 0 4px 4px 0;
&:hover {
background-color: var(--googleSearchHoverButton);
}
&:active {
box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
}

View File

@ -9,8 +9,8 @@ import ellipsis from './ellipsis.vue';
import time from './time.vue';
import url from './url.vue';
import loading from './loading.vue';
import SequentialEntrance from './sequential-entrance.vue';
import error from './error.vue';
import streamIndicator from './stream-indicator.vue';
Vue.component('mfm', mfm);
Vue.component('mk-acct', acct);
@ -22,4 +22,4 @@ Vue.component('mk-time', time);
Vue.component('mk-url', url);
Vue.component('mk-loading', loading);
Vue.component('mk-error', error);
Vue.component('sequential-entrance', SequentialEntrance);
Vue.component('stream-indicator', streamIndicator);

View File

@ -90,7 +90,7 @@ export default Vue.extend({
> div {
background-color: var(--fg);
border-radius: 6px;
color: var(--secondary);
color: var(--accentLighten);
display: inline-block;
font-size: 14px;
font-weight: bold;

View File

@ -3,8 +3,8 @@
<template v-for="media in mediaList.filter(media => !previewable(media))">
<x-banner :media="media" :key="media.id"/>
</template>
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
<div :data-count="mediaList.filter(media => previewable(media)).length" ref="grid">
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter">
<div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle">
<template v-for="media in mediaList">
<x-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
<x-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
@ -32,19 +32,56 @@ export default Vue.extend({
},
raw: {
default: false
},
// specify the parent element
parentElement: {
type: Object
}
},
data() {
return {
gridInnerStyle: {},
sizeWaiting: false
}
},
mounted() {
//#region for Safari bug
if (this.$refs.grid) {
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px`
: '287px';
}
//#endregion
this.size();
window.addEventListener('resize', this.size);
},
beforeDestroy() {
window.removeEventListener('resize', this.size);
},
activated() {
this.size();
},
methods: {
previewable(file) {
return file.type.startsWith('video') || file.type.startsWith('image');
},
size() {
// for Safari bug
if (this.sizeWaiting) return;
this.sizeWaiting = true;
window.requestAnimationFrame(() => {
this.sizeWaiting = false;
if (this.$refs.gridOuter) {
let height = 287;
const parent = this.$props.parentElement || this.$parent.$el;
if (this.$refs.gridOuter.clientHeight) {
height = this.$refs.gridOuter.clientHeight;
} else if (parent) {
height = parent.getBoundingClientRect().width * 9 / 16;
}
this.gridInnerStyle = { height: `${height}px` };
} else {
this.gridInnerStyle = {};
}
});
}
}
});

View File

@ -1,6 +1,6 @@
<template>
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction" ref="items">
<div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items">
<template v-for="(item, i) in items.filter(item => item !== undefined)">
<div v-if="item === null" class="divider" :key="i"></div>
<span v-else-if="item.type === 'label'" class="label item" :key="i">
@ -28,7 +28,7 @@
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
</button>
</template>
</sequential-entrance>
</div>
</x-popup>
</template>
@ -91,7 +91,7 @@ export default Vue.extend({
mounted() {
if (this.viaKeyboard) {
this.$nextTick(() => {
focusNext(this.$refs.items.$slots.default[0].elm, true);
focusNext(this.$refs.items.children[0], true);
});
}
},

View File

@ -37,6 +37,10 @@ export default Vue.extend({
font-size: 0.8em;
}
::v-deep > code {
word-break: break-all;
}
::v-deep .title {
text-align: center;
border-bottom: solid 1px var(--divider);

View File

@ -1,5 +1,5 @@
<template>
<div class="zlrxdaqttccpwhpaagdmkawtzklsccam">
<div class="wrpstxzv" v-size="[{ max: 450 }]">
<mk-avatar class="avatar" :user="note.user"/>
<div class="main">
<x-note-header class="header" :note="note" :mini="true"/>
@ -56,13 +56,12 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
.zlrxdaqttccpwhpaagdmkawtzklsccam {
.wrpstxzv {
display: flex;
padding: 16px 32px;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.03);
@media (max-width: 450px) {
&.max-width_450px {
padding: 14px 16px;
}

View File

@ -79,13 +79,13 @@
<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
</div>
</article>
<x-sub v-for="note in replies" :key="note.id" :note="note"/>
<x-sub v-for="note in replies" :key="note.id" :note="note" class="reply"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight } from '@fortawesome/free-solid-svg-icons';
import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
@ -392,7 +392,7 @@ export default Vue.extend({
}]
source: this.$refs.renoteButton,
viaKeyboard
}).then(this.focus);
});
},
renoteDirectly() {
@ -489,6 +489,11 @@ export default Vue.extend({
noteId: this.appearNote.id
});
menu = [{
type: 'link',
icon: faInfoCircle,
text: this.$t('details'),
to: '/notes/' + this.appearNote.id
}, null, {
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
@ -679,6 +684,7 @@ export default Vue.extend({
.note {
position: relative;
transition: box-shadow 0.1s ease;
overflow: hidden;
&.max-width_500px {
font-size: 0.9em;
@ -744,14 +750,6 @@ export default Vue.extend({
opacity: 1;
}
> *:first-child {
border-radius: var(--radius) var(--radius) 0 0;
}
> *:last-child {
border-radius: 0 0 var(--radius) var(--radius);
}
> .info {
display: flex;
align-items: center;
@ -779,6 +777,11 @@ export default Vue.extend({
padding-top: 8px;
}
> .reply-to {
opacity: 0.7;
padding-bottom: 0;
}
> .renote {
display: flex;
align-items: center;
@ -932,5 +935,9 @@ export default Vue.extend({
}
}
}
> .reply {
border-top: solid 1px var(--divider);
}
}
</style>

View File

@ -7,22 +7,22 @@
<mk-error v-if="error" @retry="init()"/>
<div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<div v-if="more && reversed" style="margin-bottom: var(--margin);">
<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</button>
</div>
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/>
</x-list>
<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<div v-if="more && !reversed" style="margin-top: var(--margin);">
<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</button>
</div>
</div>
</template>
@ -111,16 +111,10 @@ export default Vue.extend({
&.max-width_500px {
> .notes {
> ::v-deep *:not(:last-child) {
margin-bottom: var(--marginHalf);
//margin-bottom: var(--marginHalf);
margin-bottom: 0;
}
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;
height: 48px;
width: 100%;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="mk-notification" :class="notification.type">
<div class="mk-notification" :class="notification.type" v-size="[{ max: 500 }, { max: 600 }]">
<div class="head">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="icon" :class="notification.type">
@ -113,12 +113,17 @@ export default Vue.extend({
.mk-notification {
position: relative;
box-sizing: border-box;
padding: 16px;
padding: 24px 32px;
font-size: 0.9em;
overflow-wrap: break-word;
display: flex;
@media (max-width: 500px) {
&.max-width_600px {
padding: 16px;
font-size: 0.9em;
}
&.max-width_500px {
padding: 12px;
font-size: 0.8em;
}

View File

@ -1,29 +1,28 @@
<template>
<div class="mk-notifications">
<div class="contents">
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
</x-list>
<x-list class="notifications" :items="items" v-slot="{ item: notification }">
<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
<x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
</x-list>
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
</button>
<button class="_panel _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><mk-loading inline/></template>
</button>
<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
<mk-error v-if="error" @retry="init()"/>
</div>
<mk-error v-if="error" @retry="init()"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import paging from '../scripts/paging';
import XNotification from './notification.vue';
import XList from './date-separated-list.vue';
import XNote from './note.vue';
export default Vue.extend({
i18n,
@ -31,6 +30,7 @@ export default Vue.extend({
components: {
XNotification,
XList,
XNote,
},
mixins: [
@ -42,11 +42,6 @@ export default Vue.extend({
type: String,
required: false
},
wide: {
type: Boolean,
required: false,
default: false
}
},
data() {
@ -59,7 +54,6 @@ export default Vue.extend({
includeTypes: this.type ? [this.type] : undefined
})
},
faSpinner
};
},
@ -93,44 +87,23 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-notifications {
> .contents {
overflow: auto;
height: 100%;
padding: 8px 8px 0 8px;
> .notifications {
> ::v-deep * {
margin-bottom: 8px;
}
> .notification {
background: var(--panel);
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
> .notifications {
> ::v-deep * {
//margin-bottom: var(--margin);
margin-bottom: 0;
}
}
> .more {
display: block;
width: 100%;
padding: 16px;
> .empty {
margin: 0;
padding: 16px;
text-align: center;
color: var(--fg);
}
> [data-icon] {
margin-right: 4px;
}
}
> .empty {
margin: 0;
padding: 16px;
text-align: center;
color: var(--fg);
}
> .placeholder {
padding: 32px;
opacity: 0.3;
}
> .placeholder {
padding: 32px;
opacity: 0.3;
}
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<div class="vswabwbm" :style="{ top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }">
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<circle fill="none" cx="64" cy="64">
<animate attributeName="r"
begin="0s" dur="0.5s"
values="4; 32"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="1" />
<animate attributeName="stroke-width"
begin="0s" dur="0.5s"
values="16; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="1" />
</circle>
<g fill="none" fill-rule="evenodd">
<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color">
<animate attributeName="r"
begin="0s" dur="0.8s"
:values="`${particle.size}; 0`"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="1" />
<animate attributeName="cx"
begin="0s" dur="0.8s"
:values="`${particle.xA}; ${particle.xB}`"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="1" />
<animate attributeName="cy"
begin="0s" dur="0.8s"
:values="`${particle.yA}; ${particle.yB}`"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="1" />
</circle>
</g>
</svg>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
x: {
type: Number,
required: true
},
y: {
type: Number,
required: true
}
},
data() {
const particles = [];
const origin = 64;
const colors = ['#FF1493', '#00FFFF', '#FFE202'];
for (let i = 0; i < 12; i++) {
const angle = Math.random() * (Math.PI * 2);
const pos = Math.random() * 16;
const velocity = 16 + (Math.random() * 48);
particles.push({
size: 4 + (Math.random() * 8),
xA: origin + (Math.sin(angle) * pos),
yA: origin + (Math.cos(angle) * pos),
xB: origin + (Math.sin(angle) * (pos + velocity)),
yB: origin + (Math.cos(angle) * (pos + velocity)),
color: colors[Math.floor(Math.random() * colors.length)]
});
}
return {
particles
};
},
mounted() {
setTimeout(() => {
this.destroyDom();
}, 1100);
}
});
</script>
<style lang="scss" scoped>
.vswabwbm {
pointer-events: none;
position: fixed;
z-index: 1000000;
width: 128px;
height: 128px;
> svg {
> circle {
stroke: var(--accent);
}
}
}
</style>

View File

@ -53,7 +53,7 @@ import Vue from 'vue';
import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import { erase } from '../../prelude/array';
import { addTimespan } from '../../prelude/time';
import { addTime } from '../../prelude/time';
import { formatDateTimeString } from '../../misc/format-time-string';
import MkInput from './ui/input.vue';
import MkSelect from './ui/select.vue';
@ -73,7 +73,7 @@ export default Vue.extend({
choices: ['', ''],
multiple: false,
expiration: 'infinite',
atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'),
atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'),
atTime: '00:00',
after: 0,
unit: 'second',

View File

@ -112,8 +112,7 @@ export default Vue.extend({
margin: 4px 0;
padding: 4px 8px;
width: 100%;
color: var(--pollChoiceText);
border: solid 1px var(--pollChoiceBorder);
border: solid 1px var(--divider);
border-radius: 4px;
overflow: hidden;
cursor: pointer;

View File

@ -1,6 +1,6 @@
<template>
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear>
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear @after-leave="$emit('closed');">
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
</transition>
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
@ -17,7 +17,8 @@
:initial-note="initialNote"
:instant="instant"
@posted="onPosted"
@cancel="onCanceled"/>
@cancel="onCanceled"
style="border-radius: var(--radius);"/>
</transition>
</div>
</div>

View File

@ -6,7 +6,7 @@
@drop.stop="onDrop"
>
<header>
<button class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
<button v-if="!fixed" class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
<div>
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
<button class="_button visibility" @click="setVisibility" ref="visibilityButton">
@ -18,7 +18,7 @@
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
</div>
</header>
<div class="form">
<div class="form" :class="{ fixed }">
<x-note-preview class="preview" v-if="reply" :note="reply"/>
<x-note-preview class="preview" v-if="renote" :note="renote"/>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('quoteAttached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
@ -108,6 +108,11 @@ export default Vue.extend({
type: Boolean,
required: false,
default: false
},
fixed: {
type: Boolean,
required: false,
default: false
}
},
@ -581,8 +586,6 @@ export default Vue.extend({
<style lang="scss" scoped>
.gafaadew {
background: var(--panel);
border-radius: var(--radius);
box-shadow: 0 0 2px rgba(#000, 0.1);
> header {
z-index: 1000;
@ -651,6 +654,10 @@ export default Vue.extend({
max-width: 500px;
margin: 0 auto;
&.fixed {
max-width: unset;
}
> .preview {
padding: 16px;
}

View File

@ -1,20 +1,9 @@
<template>
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
<div class="rdfaahpb">
<transition-group
name="reaction-fade"
tag="div"
class="buttons"
ref="buttons"
:class="{ showFocus }"
:css="false"
@before-enter="beforeEnter"
@enter="enter"
mode="out-in"
appear
>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction"><x-reaction-icon :reaction="reaction"/></button>
</transition-group>
<div class="buttons" ref="buttons" :class="{ showFocus }">
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction" v-particle><x-reaction-icon :reaction="reaction"/></button>
</div>
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>
</x-popup>
@ -84,7 +73,7 @@ export default Vue.extend({
watch: {
focus(i) {
this.$refs.buttons.children[i].elm.focus();
this.$refs.buttons.children[i].focus();
}
},
@ -129,21 +118,7 @@ export default Vue.extend({
},
choose() {
this.$refs.buttons.children[this.focus].elm.click();
},
beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'scale(0.7)';
},
enter(el, done) {
el.style.transition = [getComputedStyle(el).transition, 'transform 1s cubic-bezier(0.23, 1, 0.32, 1)', 'opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1)'].filter(x => x != '').join(',');
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'scale(1)';
setTimeout(done, 1000);
}, 0 * el.dataset.index)
this.$refs.buttons.children[this.focus].click();
},
}
});

View File

@ -1,16 +1,17 @@
<template>
<span
class="reaction _button"
<button
class="hkzvhatu _button"
:class="{ reacted: note.myReaction == reaction }"
@click="toggleReaction(reaction)"
v-if="count > 0"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
ref="reaction"
v-particle
>
<x-reaction-icon :reaction="reaction" ref="icon"/>
<span>{{ count }}</span>
</span>
</button>
</template>
<script lang="ts">
@ -136,7 +137,7 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
.reaction {
.hkzvhatu {
display: inline-block;
height: 32px;
margin: 2px;

View File

@ -1,5 +1,5 @@
<template>
<div class="mk-reactions-viewer" :class="{ isMe }">
<div class="tdflqwzn" :class="{ isMe }">
<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>
</div>
</template>
@ -32,7 +32,7 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
.mk-reactions-viewer {
.tdflqwzn {
margin: 4px -2px 0 -2px;
&:empty {

View File

@ -0,0 +1,36 @@
<template>
<div class="jmgmzlwq _panel"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
export default Vue.extend({
i18n,
props: {
href: {
type: String,
required: true
},
},
data() {
return {
faExclamationTriangle
};
}
});
</script>
<style lang="scss" scoped>
.jmgmzlwq {
font-size: 0.8em;
padding: 16px;
> a {
margin-left: 4px;
color: var(--accent);
}
}
</style>

View File

@ -1,80 +0,0 @@
<template>
<transition-group v-if="$store.state.device.animation"
class="uupnnhew"
:data-direction="direction"
:data-reversed="reversed ? 'true' : 'false'"
name="staggered"
tag="div"
appear
>
<slot></slot>
</transition-group>
<div v-else>
<slot></slot>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
delay: {
type: Number,
required: false,
default: 40
},
direction: {
type: String,
required: false,
default: 'down'
},
reversed: {
type: Boolean,
required: false,
default: false
}
},
methods: {
focus() {
this.$slots.default[0].elm.focus();
}
},
});
</script>
<style lang="scss">
.staggered-move {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) !important;
}
.uupnnhew[data-direction="up"] {
.staggered-enter {
opacity: 0;
transform: translateY(64px);
}
}
.uupnnhew[data-direction="down"] {
.staggered-enter {
opacity: 0;
transform: translateY(-64px);
}
}
.uupnnhew[data-reversed="true"] {
@for $i from 1 through 30 {
.staggered-enter-active:nth-last-child(#{$i}) {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
}
}
}
.uupnnhew[data-reversed="false"] {
@for $i from 1 through 30 {
.staggered-enter-active:nth-child(#{$i}) {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
}
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }">
<template #header>{{ $t('login') }}</template>
<x-signin :auto-set="autoSet" @login="onLogin"/>
<mk-signin :auto-set="autoSet" @login="onLogin"/>
</x-window>
</template>
@ -9,13 +9,13 @@
import Vue from 'vue';
import i18n from '../i18n';
import XWindow from './window.vue';
import XSignin from './signin.vue';
import MkSignin from './signin.vue';
export default Vue.extend({
i18n,
components: {
XSignin,
MkSignin,
XWindow,
},

0
src/client/components/signin.vue Normal file → Executable file
View File

View File

@ -0,0 +1,80 @@
<template>
<div class="nsbbhtug" v-if="hasDisconnected" @click="resetDisconnected">
<div>{{ $t('disconnectedFromServer') }}</div>
<div class="command">
<button class="_textButton" @click="reload">{{ $t('reload') }}</button>
<button class="_textButton">{{ $t('doNothing') }}</button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../i18n';
export default Vue.extend({
i18n,
data() {
return {
hasDisconnected: false,
}
},
computed: {
stream() {
return this.$root.stream;
},
},
created() {
this.$root.stream.on('_connected_', this.onConnected);
this.$root.stream.on('_disconnected_', this.onDisconnected);
},
beforeDestroy() {
this.$root.stream.off('_connected_', this.onConnected);
this.$root.stream.off('_disconnected_', this.onDisconnected);
},
methods: {
onConnected() {
if (this.hasDisconnected) {
if (this.$store.state.device.autoReload) {
this.reload();
}
}
},
onDisconnected() {
this.hasDisconnected = true;
},
resetDisconnected() {
this.hasDisconnected = false;
},
reload() {
location.reload();
},
}
});
</script>
<style lang="scss" scoped>
.nsbbhtug {
position: fixed;
z-index: 16385;
bottom: 8px;
right: 8px;
margin: 0;
padding: 6px 12px;
font-size: 0.9em;
color: #fff;
background: #000;
opacity: 0.8;
border-radius: 4px;
max-width: 320px;
> .command {
display: flex;
justify-content: space-around;
> button {
padding: 0.7em;
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/>
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
</template>
<script lang="ts">
@ -21,6 +21,11 @@ export default Vue.extend({
},
antenna: {
required: false
},
sound: {
type: Boolean,
required: false,
default: false,
}
},
@ -46,6 +51,10 @@ export default Vue.extend({
const prepend = note => {
(this.$refs.tl as any).prepend(note);
if (this.sound) {
this.$root.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
}
};
const onUserAdded = () => {

View File

@ -124,7 +124,6 @@ export default Vue.extend({
&.primary {
color: #fff;
background: var(--accent);
box-shadow: 0 6px 16px var(--accentShadow);
&:not(:disabled):hover {
background: var(--jkhztclx);

View File

@ -110,6 +110,7 @@ export default Vue.extend({
> header {
position: relative;
box-shadow: 0 1px 0 0 var(--divider);
z-index: 1;
> .title {
margin: 0;

View File

@ -1,5 +1,5 @@
<template>
<sequential-entrance class="cxiknjgy" :class="{ autoMargin }">
<div class="cxiknjgy" :class="{ autoMargin }">
<slot :items="items"></slot>
<div class="empty" v-if="empty" key="_empty_">
<slot name="empty"></slot>
@ -10,7 +10,7 @@
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</div>
</sequential-entrance>
</div>
</template>
<script lang="ts">

View File

@ -0,0 +1,138 @@
<template>
<div class="timctyfi" :class="{ focused, disabled }">
<div class="icon"><slot name="icon"></slot></div>
<span class="title"><slot name="title"></slot></span>
<input
type="range"
ref="input"
v-model="v"
:disabled="disabled"
:min="min"
:max="max"
:step="step"
:autofocus="autofocus"
@focus="focused = true"
@blur="focused = false"
@input="$emit('input', $event.target.value)"
/>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
props: {
value: {
type: Number,
required: false,
default: 0
},
disabled: {
type: Boolean,
required: false,
default: false
},
min: {
type: Number,
required: false,
default: 0
},
max: {
type: Number,
required: false,
default: 100
},
step: {
type: Number,
required: false,
default: 1
},
autofocus: {
type: Boolean,
required: false
}
},
data() {
return {
v: this.value,
focused: false
};
},
watch: {
value(v) {
this.v = parseFloat(v);
}
},
mounted() {
if (this.autofocus) {
this.$nextTick(() => {
this.$refs.input.focus();
});
}
}
});
</script>
<style lang="scss" scoped>
.timctyfi {
position: relative;
margin: 8px;
> .icon {
display: inline-block;
width: 24px;
text-align: center;
}
> .title {
pointer-events: none;
font-size: 16px;
color: var(--inputLabel);
overflow: hidden;
}
> input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: var(--xxubwiul);
height: 7px;
margin: 0 8px;
outline: 0;
border: 0;
border-radius: 7px;
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
box-sizing: content-box;
}
&::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
}
}
}
</style>

View File

@ -56,7 +56,7 @@ export default Vue.extend({
}
},
filled(): boolean {
return this.v != '' && this.v != null;
return true;
}
},
mounted() {
@ -100,6 +100,7 @@ export default Vue.extend({
> .input {
display: flex;
position: relative;
&:before {
content: '';
@ -151,12 +152,17 @@ export default Vue.extend({
font-weight: normal;
font-size: 16px;
height: 32px;
background: var(--panel);
background: none;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
color: var(--fg);
option,
optgroup {
background: var(--bg);
}
}
> .prefix,

View File

@ -230,8 +230,8 @@ export default Vue.extend({
position: relative;
display: block;
font-size: 14px;
box-shadow: 0 1px 4px var(--tyvedwbe);
border-radius: 4px;
box-shadow: 0 0 0 1px var(--divider);
border-radius: 6px;
overflow: hidden;
&:hover {

View File

@ -51,6 +51,7 @@ export default Vue.extend({
target: self ? null : '_blank',
showTimer: null,
hideTimer: null,
checkTimer: null,
preview: null,
faExternalLinkSquareAlt
};
@ -78,9 +79,14 @@ export default Vue.extend({
}).$mount();
document.body.appendChild(this.preview.$el);
this.checkTimer = setInterval(() => {
if (!document.body.contains(this.$el)) this.closePreview();
}, 1000);
},
closePreview() {
if (this.preview) {
clearInterval(this.checkTimer);
this.preview.destroyDom();
this.preview = null;
}

View File

@ -3,7 +3,7 @@
<template #header><mk-user-name :user="user"/></template>
<div class="vrcsvlkm">
<mk-button @click="resetPassword()" primary>{{ $t('resetPassword') }}</mk-button>
<mk-switch v-if="$store.state.i.isAdmin && !user.isAdmin" @change="toggleModerator()" v-model="moderator">{{ $t('moderator') }}</mk-switch>
<mk-switch v-if="$store.state.i.isAdmin && (this.moderator || !user.isAdmin)" @change="toggleModerator()" v-model="moderator">{{ $t('moderator') }}</mk-switch>
<mk-switch @change="toggleSilence()" v-model="silenced">{{ $t('silence') }}</mk-switch>
<mk-switch @change="toggleSuspend()" v-model="suspended">{{ $t('suspend') }}</mk-switch>
</div>

View File

@ -53,6 +53,7 @@ export default Vue.extend({
return {
u: null,
show: false,
closed: false,
top: 0,
left: 0,
};
@ -68,6 +69,7 @@ export default Vue.extend({
{ userId: this.user };
this.$root.api('users/show', query).then(user => {
if (this.closed) return;
this.u = user;
this.show = true;
});
@ -83,6 +85,7 @@ export default Vue.extend({
methods: {
close() {
this.closed = true;
this.show = false;
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
}

View File

@ -6,15 +6,15 @@
<button class="_button" @click="close()"><fa :icon="faTimes"/></button>
</div>
<sequential-entrance class="users">
<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
<div class="users">
<router-link v-for="item in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
<div class="body">
<mk-user-name :user="extract ? extract(item) : item" class="name"/>
<mk-acct :user="extract ? extract(item) : item" class="acct"/>
</div>
</router-link>
</sequential-entrance>
</div>
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>

View File

@ -1,6 +1,6 @@
<template>
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
<sequential-entrance class="gqyayizv" :delay="30">
<div class="gqyayizv">
<button class="_button" @click="choose('public')" :class="{ active: v == 'public' }" data-index="1" key="public">
<div><fa :icon="faGlobe"/></div>
<div>
@ -29,7 +29,7 @@
<span>{{ $t('_visibility.specifiedDescription') }}</span>
</div>
</button>
</sequential-entrance>
</div>
</x-popup>
</template>

View File

@ -3,8 +3,10 @@ import Vue from 'vue';
import userPreview from './user-preview';
import autocomplete from './autocomplete';
import size from './size';
import particle from './particle';
Vue.directive('autocomplete', autocomplete);
Vue.directive('userPreview', userPreview);
Vue.directive('user-preview', userPreview);
Vue.directive('size', size);
Vue.directive('particle', particle);

View File

@ -0,0 +1,22 @@
import Particle from '../components/particle.vue';
export default {
bind(el, binding, vn) {
el.addEventListener('click', () => {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.clientWidth / 2);
const y = rect.top + (el.clientHeight / 2);
const particle = new Particle({
parent: vn.context,
propsData: {
x,
y
}
}).$mount();
document.body.appendChild(particle.$el);
});
}
};

View File

@ -59,7 +59,7 @@ export default {
const ro = new ResizeObserver((entries, observer) => {
calc();
});
ro.observe(el);
el._ro_ = ro;

View File

@ -8,9 +8,11 @@ export default {
self.tag = null;
self.showTimer = null;
self.hideTimer = null;
self.checkTimer = null;
self.close = () => {
if (self.tag) {
clearInterval(self.checkTimer);
self.tag.close();
self.tag = null;
}
@ -38,6 +40,14 @@ export default {
});
document.body.appendChild(self.tag.$el);
self.checkTimer = setInterval(() => {
if (!document.body.contains(el)) {
clearTimeout(self.showTimer);
clearTimeout(self.hideTimer);
self.close();
}
}, 1000);
};
el.addEventListener('mouseover', () => {
@ -60,8 +70,6 @@ export default {
unbind(el, binding, vn) {
const self = el._userPreviewDirective_;
clearTimeout(self.showTimer);
clearTimeout(self.hideTimer);
self.close();
clearInterval(self.checkTimer);
}
};

View File

@ -81,14 +81,14 @@ if (lang == null) {
// Detect the user agent
const ua = navigator.userAgent.toLowerCase();
let isMobile = /mobile|iphone|ipad|android/.test(ua);
const isMobile = /mobile|iphone|ipad|android/.test(ua);
// Get the <head> element
const head = document.getElementsByTagName('head')[0];
// If mobile, insert the viewport meta tag
if (isMobile || window.innerWidth <= 1024) {
const viewport = document.getElementsByName("viewport").item(0);
const viewport = document.getElementsByName('viewport').item(0);
viewport.setAttribute('content',
`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
head.appendChild(viewport);
@ -136,6 +136,14 @@ document.body.innerHTML = '<div id="app"></div>';
const os = new MiOS();
os.init(async () => {
window.addEventListener('storage', e => {
if (e.key === 'vuex') {
os.store.replaceState(JSON.parse(localStorage['vuex']));
} else if (e.key === 'i') {
location.reload();
}
}, false)
if ('Notification' in window && os.store.getters.isSignedIn) {
// 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') {
@ -189,6 +197,14 @@ os.init(async () => {
if (cb) vm.$once('closed', cb);
(vm as any).focus();
},
sound(type: string) {
if (this.$store.state.device.sfxVolume === 0) return;
const sound = this.$store.state.device['sfx' + type.substr(0, 1).toUpperCase() + type.substr(1)];
if (sound == null) return;
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
}
},
router: router,
render: createEl => createEl(App)
@ -198,4 +214,96 @@ os.init(async () => {
// マウント
app.$mount('#app');
if (app.$store.getters.isSignedIn) {
const main = os.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
app.$store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
app.$store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
app.$store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('unreadMention', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
app.$store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
app.$store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('readAllMessagingMessages', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
app.$store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
app.sound('chatBg');
});
main.on('readAllAntennas', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAntenna: false
});
});
main.on('unreadAntenna', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAntenna: true
});
app.sound('antenna');
});
main.on('readAllAnnouncements', () => {
app.$store.dispatch('mergeMe', {
hasUnreadAnnouncement: false
});
});
main.on('clientSettingUpdated', x => {
app.$store.commit('settings/set', {
key: x.key,
value: x.value
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
os.signout();
});
}
});

View File

@ -3,7 +3,7 @@ import Vue from 'vue';
import { EventEmitter } from 'eventemitter3';
import initStore from './store';
import { apiUrl, version, locale } from './config';
import { apiUrl, version } from './config';
import Progress from './scripts/loading';
import Stream from './scripts/stream';
@ -123,8 +123,13 @@ export default class MiOS extends EventEmitter {
});
} else {
// Get token from localStorage
const i = localStorage.getItem('i');
let i = localStorage.getItem('i');
// 連携ログインの場合用にCookieを参照する
if (i == null || i === 'null') {
i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1];
}
fetchme(i, me => {
if (me) {
this.store.dispatch('login', me);
@ -142,94 +147,6 @@ export default class MiOS extends EventEmitter {
@autobind
private initStream() {
this.stream = new Stream(this);
if (this.store.getters.isSignedIn) {
const main = this.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
this.store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('unreadMention', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('readAllMessagingMessages', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
});
main.on('readAllAntennas', () => {
this.store.dispatch('mergeMe', {
hasUnreadAntenna: false
});
});
main.on('unreadAntenna', () => {
this.store.dispatch('mergeMe', {
hasUnreadAntenna: true
});
});
main.on('readAllAnnouncements', () => {
this.store.dispatch('mergeMe', {
hasUnreadAnnouncement: false
});
});
main.on('clientSettingUpdated', x => {
this.store.commit('settings/set', {
key: x.key,
value: x.value
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
this.signout();
});
}
}
/**

9
src/client/pages/auth.vue Normal file → Executable file
View File

@ -26,7 +26,7 @@
</div>
<div class="signin" v-else>
<h1>{{ $t('sign-in') }}</h1>
<mk-signin/>
<mk-signin @login="onLogin"/>
</div>
</template>
@ -34,11 +34,13 @@
import Vue from 'vue';
import i18n from '../i18n';
import XForm from './auth.form.vue';
import MkSignin from '../components/signin.vue';
export default Vue.extend({
i18n,
components: {
XForm
XForm,
MkSignin,
},
data() {
return {
@ -83,6 +85,9 @@ export default Vue.extend({
if (this.session.app.callbackUrl) {
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
}
}, onLogin(res) {
localStorage.setItem('i', res.i);
location.reload();
}
}
});

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="naked full">
<portal to="header">
<button @click="menu" class="_button _jmoebdiw_">
<fa :icon="faCloud" style="margin-right: 8px;"/>

View File

@ -14,9 +14,12 @@
</button>
</portal>
<div class="new" v-if="queue > 0" :style="{ width: width + 'px' }"><button class="_buttonPrimary" @click="top()">{{ $t('newNoteRecived') }}</button></div>
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
<x-post-form class="post-form _panel" fixed v-if="$store.state.device.showFixedPostForm"/>
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" :sound="true" @before="before()" @after="after()" @queue="queueUpdated"/>
</div>
</template>
@ -27,6 +30,7 @@ import { faComments } from '@fortawesome/free-regular-svg-icons';
import Progress from '../scripts/loading';
import XTimeline from '../components/timeline.vue';
import XTutorial from './index.home.tutorial.vue';
import XPostForm from '../components/post-form.vue';
export default Vue.extend({
metaInfo() {
@ -38,6 +42,7 @@ export default Vue.extend({
components: {
XTimeline,
XTutorial,
XPostForm,
},
props: {
@ -53,6 +58,8 @@ export default Vue.extend({
list: null,
antenna: null,
menuOpened: false,
queue: 0,
width: 0,
faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faComments, faListUl, faSatellite, faCircle
};
},
@ -63,6 +70,10 @@ export default Vue.extend({
't': this.focus
};
},
meta() {
return this.$store.state.instance.meta;
},
},
watch: {
@ -91,6 +102,10 @@ export default Vue.extend({
}
},
mounted() {
this.width = this.$el.offsetWidth;
},
methods: {
before() {
Progress.start();
@ -100,7 +115,17 @@ export default Vue.extend({
Progress.done();
},
queueUpdated(q) {
if (this.$el.offsetWidth !== 0) this.width = this.$el.offsetWidth;
this.queue = q;
},
top() {
window.scroll({ top: 0, behavior: 'instant' });
},
async choose(ev) {
if (this.meta == null) return;
this.menuOpened = true;
const [antennas, lists] = await Promise.all([
this.$root.api('antennas/list'),
@ -128,15 +153,15 @@ export default Vue.extend({
text: this.$t('_timelines.home'),
icon: faHome,
action: () => { this.setSrc('home') }
}, {
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
text: this.$t('_timelines.local'),
icon: faComments,
action: () => { this.setSrc('local') }
}, {
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
text: this.$t('_timelines.social'),
icon: faShareAlt,
action: () => { this.setSrc('social') }
}, {
}, this.meta.disableGlobalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
text: this.$t('_timelines.global'),
icon: faGlobe,
action: () => { this.setSrc('global') }
@ -169,9 +194,26 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-home {
> .new {
position: fixed;
z-index: 1000;
> button {
display: block;
margin: 0 auto;
padding: 8px 16px;
border-radius: 32px;
}
}
> .tutorial {
margin-bottom: var(--margin);
}
> .post-form {
position: relative;
margin-bottom: var(--margin);
}
}
._kjvfvyph_ {

View File

@ -5,6 +5,38 @@
<mk-instance-stats style="margin-bottom: var(--margin);"/>
<section class="_card logs">
<div class="_title"><fa :icon="faStream"/> {{ $t('serverLogs') }}</div>
<div class="_content">
<div class="_inputs">
<mk-input v-model="logDomain" :debounce="true">
<span>{{ $t('domain') }}</span>
</mk-input>
<mk-select v-model="logLevel">
<template #label>{{ $t('level') }}</template>
<option value="all">{{ $t('levels.all') }}</option>
<option value="info">{{ $t('levels.info') }}</option>
<option value="success">{{ $t('levels.success') }}</option>
<option value="warning">{{ $t('levels.warning') }}</option>
<option value="error">{{ $t('levels.error') }}</option>
<option value="debug">{{ $t('levels.debug') }}</option>
</mk-select>
</div>
<div class="logs">
<code v-for="log in logs" :key="log.id" :class="log.level">
<details>
<summary><mk-time :time="log.createdAt"/> [{{ log.domain.join('.') }}] {{ log.message }}</summary>
<vue-json-pretty v-if="log.data" :data="log.data"></vue-json-pretty>
</details>
</code>
</div>
</div>
<div class="_footer">
<mk-button @click="deleteAllLogs()" primary><fa :icon="faTrashAlt"/> {{ $t('deleteAll') }}</mk-button>
</div>
</section>
<section class="_card chart">
<div class="_title"><fa :icon="faMicrochip"/> {{ $t('cpuAndMemory') }}</div>
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
@ -67,9 +99,13 @@
<script lang="ts">
import Vue from 'vue';
import { faServer, faExchangeAlt, faMicrochip, faHdd } from '@fortawesome/free-solid-svg-icons';
import { faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import Chart from 'chart.js';
import VueJsonPretty from 'vue-json-pretty';
import MkInstanceStats from '../../components/instance-stats.vue';
import MkButton from '../../components/ui/button.vue';
import MkSelect from '../../components/ui/select.vue';
import MkInput from '../../components/ui/input.vue';
import { version, url } from '../../config';
import i18n from '../../i18n';
@ -92,6 +128,10 @@ export default Vue.extend({
components: {
MkInstanceStats,
MkButton,
MkSelect,
MkInput,
VueJsonPretty
},
data() {
@ -104,7 +144,10 @@ export default Vue.extend({
memUsage: 0,
chartCpuMem: null,
chartNet: null,
faServer, faExchangeAlt, faMicrochip, faHdd
logs: [],
logLevel: 'all',
logDomain: '',
faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt
}
},
@ -114,7 +157,20 @@ export default Vue.extend({
},
},
watch: {
logLevel() {
this.logs = [];
this.fetchLogs();
},
logDomain() {
this.logs = [];
this.fetchLogs();
}
},
mounted() {
this.fetchLogs();
Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
this.chartCpuMem = new Chart(this.$refs.cpumem, {
@ -330,6 +386,25 @@ export default Vue.extend({
},
methods: {
fetchLogs() {
this.$root.api('admin/logs', {
level: this.logLevel === 'all' ? null : this.logLevel,
domain: this.logDomain === '' ? null : this.logDomain,
limit: 30
}).then(logs => {
this.logs = logs.reverse();
});
},
deleteAllLogs() {
this.$root.api('admin/delete-logs').then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
});
},
onStats(stats) {
const cpu = (stats.cpu * 100).toFixed(0);
const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
@ -389,6 +464,37 @@ export default Vue.extend({
}
}
> .logs {
> ._content {
> .logs {
padding: 8px;
background: #000;
color: #fff;
font-size: 0.9em;
> code {
display: block;
&.error {
color: #f00;
}
&.warning {
color: #ff0;
}
&.success {
color: #0f0;
}
&.debug {
opacity: 0.7;
}
}
}
}
}
> .chart {
> ._content {
> .table {

View File

@ -11,12 +11,12 @@
<canvas ref="chart"></canvas>
</div>
<div class="_content" style="max-height: 180px; overflow: auto;">
<sequential-entrance :delay="15" v-if="jobs.length > 0">
<div v-for="(job, i) in jobs" :key="job[0]">
<div v-if="jobs.length > 0">
<div v-for="job in jobs" :key="job[0]">
<span>{{ job[0] }}</span>
<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
</div>
</sequential-entrance>
</div>
<span v-else style="opacity: 0.5;">{{ $t('noJobs') }}</span>
</div>
</section>

View File

@ -61,10 +61,10 @@
<div class="_content">
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
<template v-if="enableServiceWorker">
<mk-horizon-group inputs class="fit-bottom">
<div class="_inputs">
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
</mk-horizon-group>
</div>
</template>
</div>
<div class="_footer">
@ -97,6 +97,32 @@
</div>
</section>
<section class="_card">
<div class="_title"><fa :icon="faCloud"/> {{ $t('objectStorage') }}</div>
<div class="_content">
<mk-switch v-model="useObjectStorage">{{ $t('useObjectStorage') }}</mk-switch>
<template v-if="useObjectStorage">
<mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">{{ $t('objectStorageBaseUrl') }}<template #desc>{{ $t('objectStorageBaseUrlDesc') }}</template></mk-input>
<div class="_inputs">
<mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('objectStorageBucket') }}<template #desc>{{ $t('objectStorageBucketDesc') }}</template></mk-input>
<mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('objectStoragePrefix') }}<template #desc>{{ $t('objectStoragePrefixDesc') }}</template></mk-input>
</div>
<mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('objectStorageEndpoint') }}<template #desc>{{ $t('objectStorageEndpointDesc') }}</template></mk-input>
<div class="_inputs">
<mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('objectStorageRegion') }}<template #desc>{{ $t('objectStorageRegionDesc') }}</template></mk-input>
</div>
<div class="_inputs">
<mk-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Access key</mk-input>
<mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input>
</div>
<mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></mk-switch>
</template>
</div>
<div class="_footer">
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
</div>
</section>
<section class="_card">
<div class="_title"><fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
<div class="_content">
@ -213,6 +239,16 @@ export default Vue.extend({
enableServiceWorker: false,
swPublicKey: null,
swPrivateKey: null,
useObjectStorage: false,
objectStorageBaseUrl: null,
objectStorageBucket: null,
objectStoragePrefix: null,
objectStorageEndpoint: null,
objectStorageRegion: null,
objectStoragePort: null,
objectStorageAccessKey: null,
objectStorageSecretKey: null,
objectStorageUseSSL: false,
enableTwitterIntegration: false,
twitterConsumerKey: null,
twitterConsumerSecret: null,
@ -257,6 +293,16 @@ export default Vue.extend({
this.enableServiceWorker = this.meta.enableServiceWorker;
this.swPublicKey = this.meta.swPublickey;
this.swPrivateKey = this.meta.swPrivateKey;
this.useObjectStorage = this.meta.useObjectStorage;
this.objectStorageBaseUrl = this.meta.objectStorageBaseUrl;
this.objectStorageBucket = this.meta.objectStorageBucket;
this.objectStoragePrefix = this.meta.objectStoragePrefix;
this.objectStorageEndpoint = this.meta.objectStorageEndpoint;
this.objectStorageRegion = this.meta.objectStorageRegion;
this.objectStoragePort = this.meta.objectStoragePort;
this.objectStorageAccessKey = this.meta.objectStorageAccessKey;
this.objectStorageSecretKey = this.meta.objectStorageSecretKey;
this.objectStorageUseSSL = this.meta.objectStorageUseSSL;
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
this.twitterConsumerKey = this.meta.twitterConsumerKey;
this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
@ -341,6 +387,16 @@ export default Vue.extend({
enableServiceWorker: this.enableServiceWorker,
swPublicKey: this.swPublicKey,
swPrivateKey: this.swPrivateKey,
useObjectStorage: this.useObjectStorage,
objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
objectStorageUseSSL: this.objectStorageUseSSL,
enableTwitterIntegration: this.enableTwitterIntegration,
twitterConsumerKey: this.twitterConsumerKey,
twitterConsumerSecret: this.twitterConsumerSecret,

View File

@ -2,7 +2,7 @@
<div class="thvuemwp" :data-is-me="isMe">
<mk-avatar class="avatar" :user="message.user"/>
<div class="content">
<div class="balloon _panel" :data-no-text="message.text == null">
<div class="balloon" :data-no-text="message.text == null">
<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
<img src="/assets/remove.png" alt="Delete"/>
</button>
@ -243,13 +243,14 @@ export default Vue.extend({
}
&:not([data-is-me]) {
padding-left: var(--margin);
> .content {
padding-left: 16px;
padding-right: 32px;
> .balloon {
$color: var(--panel);
$color: var(--messageBg);
background: $color;
&[data-no-text] {
@ -279,6 +280,7 @@ export default Vue.extend({
&[data-is-me] {
flex-direction: row-reverse;
padding-right: var(--margin);
> .content {
padding-right: 16px;
@ -287,7 +289,6 @@ export default Vue.extend({
> .balloon {
background: $me-balloon-color;
box-shadow: 0 6px 16px var(--accentShadow);
text-align: left;
&[data-no-text] {
@ -310,7 +311,7 @@ export default Vue.extend({
}
> .text {
&, * {
&, ::v-deep * {
color: #fff !important;
}
}

View File

@ -1,5 +1,5 @@
<template>
<div class="mk-messaging-room"
<div class="mk-messaging-room naked"
@dragover.prevent.stop="onDragover"
@drop.prevent.stop="onDrop"
>
@ -184,12 +184,7 @@ export default Vue.extend({
},
onMessage(message) {
// サウンドを再生する
if (this.$store.state.device.enableSounds) {
const sound = new Audio(`${url}/assets/message.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
}
this.$root.sound('chat');
const isBottom = this.isBottom();

View File

@ -5,7 +5,7 @@
<mk-button @click="start" primary class="start"><fa :icon="faPlus"/> {{ $t('startMessaging') }}</mk-button>
<sequential-entrance class="history" v-if="messages.length > 0" :delay="30">
<div class="history" v-if="messages.length > 0">
<router-link v-for="(message, i) in messages"
class="message _panel"
:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
@ -30,7 +30,7 @@
</div>
</div>
</router-link>
</sequential-entrance>
</div>
<div class="no-history" v-if="!fetching && messages.length == 0">
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
<div>{{ $t('noHistory') }}</div>

View File

@ -30,6 +30,10 @@
<span>{{ $t('antennaKeywords') }}</span>
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
</mk-textarea>
<mk-textarea v-model="excludeKeywords">
<span>{{ $t('antennaExcludeKeywords') }}</span>
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
</mk-textarea>
<mk-switch v-model="caseSensitive">{{ $t('caseSensitive') }}</mk-switch>
<mk-switch v-model="withFile">{{ $t('withFileAntenna') }}</mk-switch>
<mk-switch v-model="notify">{{ $t('notifyAntenna') }}</mk-switch>
@ -75,6 +79,7 @@ export default Vue.extend({
userGroupId: null,
users: '',
keywords: '',
excludeKeywords: '',
caseSensitive: false,
withReplies: false,
withFile: false,
@ -107,6 +112,7 @@ export default Vue.extend({
this.userGroupId = this.antenna.userGroupId;
this.users = this.antenna.users.join('\n');
this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n');
this.caseSensitive = this.antenna.caseSensitive;
this.withReplies = this.antenna.withReplies;
this.withFile = this.antenna.withFile;
@ -126,7 +132,8 @@ export default Vue.extend({
notify: this.notify,
caseSensitive: this.caseSensitive,
users: this.users.trim().split('\n').map(x => x.trim()),
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' '))
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
});
this.$emit('created');
} else {
@ -141,7 +148,8 @@ export default Vue.extend({
notify: this.notify,
caseSensitive: this.caseSensitive,
users: this.users.trim().split('\n').map(x => x.trim()),
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' '))
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
});
}

View File

@ -53,6 +53,7 @@ export default Vue.extend({
userGroupId: null,
users: [],
keywords: [],
excludeKeywords: [],
withReplies: false,
caseSensitive: false,
withFile: false,

View File

@ -70,11 +70,10 @@ export default Vue.extend({
},
mounted() {
if (!document.cookie.match(/i=(\w+)/)) {
document.cookie = `i=${this.$store.state.i.token}; path=/;` +
` domain=${document.location.hostname}; max-age=31536000;` +
document.cookie = `igi=${this.$store.state.i.token}; path=/;` +
` max-age=31536000;` +
(document.location.protocol.startsWith('https') ? ' secure' : '');
}
this.$watch('integrations', () => {
if (this.integrations.twitter) {
if (this.twitterForm) this.twitterForm.close();

View File

@ -2,7 +2,10 @@
<section class="_card">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
<div class="_content">
<mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
<mk-input v-model="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif">
{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template>
</mk-input>
<mk-button inline @click="setDefault"><fa :icon="faUndo"/> {{ $t('default') }}</mk-button>
</div>
<div class="_footer">
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
@ -14,24 +17,26 @@
<script lang="ts">
import Vue from 'vue';
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
import MkTextarea from '../../components/ui/textarea.vue';
import { faUndo } from '@fortawesome/free-solid-svg-icons';
import MkInput from '../../components/ui/input.vue';
import MkButton from '../../components/ui/button.vue';
import MkReactionPicker from '../../components/reaction-picker.vue';
import i18n from '../../i18n';
import { emojiRegexWithCustom } from '../../../misc/emoji-regex';
export default Vue.extend({
i18n,
components: {
MkTextarea,
MkInput,
MkButton,
},
data() {
return {
reactions: this.$store.state.settings.reactions.join('\n'),
reactions: this.$store.state.settings.reactions.join(''),
changed: false,
faLaugh, faSave, faEye
faLaugh, faSave, faEye, faUndo
}
},
@ -41,21 +46,40 @@ export default Vue.extend({
}
},
computed: {
splited(): any {
return this.reactions.match(emojiRegexWithCustom);
},
},
methods: {
save() {
this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions.trim().split('\n') });
this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited });
this.changed = false;
},
preview(ev) {
const picker = this.$root.new(MkReactionPicker, {
source: ev.currentTarget || ev.target,
reactions: this.reactions.trim().split('\n'),
reactions: this.splited,
showFocus: false,
});
picker.$once('chosen', reaction => {
picker.close();
});
},
setDefault() {
this.reactions = '👍❤😆🤔😮🎉💢😥😇🍮';
},
async chooseEmoji(ev) {
const vm = this.$root.new(await import('../../components/emoji-picker.vue').then(m => m.default), {
source: ev.currentTarget || ev.target
}).$once('chosen', emoji => {
this.reactions += emoji;
vm.close();
});
}
}
});

View File

@ -1,24 +1,28 @@
<template>
<div class="mk-note-page">
<portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal>
<portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal>
<portal to="title" v-if="note">
<mfm
:text="$t('noteOf', { user: note.user.name || note.user.username })"
:plain="true" :nowrap="true" :custom-emojis="note.user.emojis" :is-note="false"
/>
</portal>
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in">
<div v-if="note">
<mk-button v-if="hasNext && !showNext" @click="showNext = true" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></mk-button>
<x-notes v-if="showNext" ref="next" :pagination="next"/>
<hr v-if="showNext"/>
<div v-if="note">
<button class="_panel _button" v-if="hasNext && !showNext" @click="showNext = true" style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></button>
<x-notes v-if="showNext" ref="next" :pagination="next"/>
<hr v-if="showNext"/>
<x-note :note="note" :key="note.id" :detail="true"/>
<div v-if="error">
<mk-error @retry="fetch()"/>
</div>
<mk-button v-if="hasPrev && !showPrev" @click="showPrev = true" primary style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></mk-button>
<hr v-if="showPrev"/>
<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
<mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/>
<x-note :note="note" :key="note.id" :detail="true"/>
<div v-if="error">
<mk-error @retry="fetch()"/>
</div>
</transition>
<button class="_panel _button" v-if="hasPrev && !showPrev" @click="showPrev = true" style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></button>
<hr v-if="showPrev"/>
<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
</div>
</div>
</template>
@ -29,7 +33,7 @@ import i18n from '../i18n';
import Progress from '../scripts/loading';
import XNote from '../components/note.vue';
import XNotes from '../components/notes.vue';
import MkButton from '../components/ui/button.vue';
import MkRemoteCaution from '../components/remote-caution.vue';
export default Vue.extend({
i18n,
@ -41,7 +45,7 @@ export default Vue.extend({
components: {
XNote,
XNotes,
MkButton,
MkRemoteCaution,
},
data() {
return {

View File

@ -0,0 +1,42 @@
<template>
<div>
<portal to="icon"><fa :icon="faBell"/></portal>
<portal to="title">{{ $t('notifications') }}</portal>
<x-notifications @before="before" @after="after" page/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faBell } from '@fortawesome/free-solid-svg-icons';
import Progress from '../scripts/loading';
import XNotifications from '../components/notifications.vue';
export default Vue.extend({
metaInfo() {
return {
title: this.$t('notifications') as string
};
},
components: {
XNotifications
},
data() {
return {
faBell
};
},
methods: {
before() {
Progress.start();
},
after() {
Progress.done();
}
}
});
</script>

View File

@ -17,8 +17,8 @@
</template>
<router-link :to="`./${page.name}/view-source`">{{ $t('_pages.viewSource') }}</router-link>
<div class="like">
<button @click="unlike()" v-if="page.isLiked" :title="$t('_pages.unlike')"><fa :icon="faHeartS"/></button>
<button @click="like()" v-else :title="$t('_pages.like')"><fa :icon="faHeart"/></button>
<button class="_button" @click="unlike()" v-if="page.isLiked" :title="$t('_pages.unlike')"><fa :icon="faHeartS"/></button>
<button class="_button" @click="like()" v-else :title="$t('_pages.like')"><fa :icon="faHeartR"/></button>
<span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span>
</div>
</div>
@ -28,6 +28,8 @@
<script lang="ts">
import Vue from 'vue';
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
import { faHeart as faHeartR } from '@fortawesome/free-regular-svg-icons';
import XPage from '../components/page/page.vue';
export default Vue.extend({
@ -49,6 +51,7 @@ export default Vue.extend({
data() {
return {
page: null,
faHeartS, faHeartR
};
},
@ -102,7 +105,7 @@ export default Vue.extend({
}).then(() => {
this.$root.dialog({
type: 'success',
splash: true
iconOnly: true, autoClose: true
});
});
}

View File

@ -0,0 +1,260 @@
<template>
<div>
<portal to="icon"><fa :icon="faCog"/></portal>
<portal to="title">{{ $t('clinetSettings') }}</portal>
<x-theme/>
<section class="_card">
<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div>
<div class="_content">
<mk-range v-model="sfxVolume" min="0" max="1" step="0.1">
<fa slot="icon" :icon="volumeIcon"/>
<span slot="title">{{ $t('volume') }}</span>
</mk-range>
</div>
<div class="_content">
<mk-select v-model="sfxNote">
<template #label>{{ $t('_sfx.note') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNote)" v-if="sfxNote"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
<mk-select v-model="sfxNoteMy">
<template #label>{{ $t('_sfx.noteMy') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNoteMy)" v-if="sfxNoteMy"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
<mk-select v-model="sfxNotification">
<template #label>{{ $t('_sfx.notification') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNotification)" v-if="sfxNotification"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
<mk-select v-model="sfxChat">
<template #label>{{ $t('_sfx.chat') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChat)" v-if="sfxChat"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
<mk-select v-model="sfxChatBg">
<template #label>{{ $t('_sfx.chatBg') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
<mk-select v-model="sfxAntenna">
<template #label>{{ $t('_sfx.antenna') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxAntenna)" v-if="sfxAntenna"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</mk-select>
</div>
</section>
<section class="_card">
<div class="_title"><fa :icon="faCog"/> {{ $t('accessibility') }}</div>
<div class="_content">
<mk-switch v-model="autoReload">
{{ $t('autoReloadWhenDisconnected') }}
</mk-switch>
</div>
<div class="_content">
<mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch>
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
<mk-switch v-model="useOsNativeEmojis">
{{ $t('useOsNativeEmojis') }}
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
</mk-switch>
<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>
</div>
<div class="_content">
<mk-select v-model="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</mk-select>
</div>
<div class="_content">
<div>{{ $t('fontSize') }}</div>
<mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio>
</div>
</section>
<mk-button @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</mk-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
import MkInput from '../../components/ui/input.vue';
import MkButton from '../../components/ui/button.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkSelect from '../../components/ui/select.vue';
import MkRadio from '../../components/ui/radio.vue';
import MkRange from '../../components/ui/range.vue';
import XTheme from './theme.vue';
import i18n from '../../i18n';
import { langs } from '../../config';
const sounds = [
null,
'syuilo/up',
'syuilo/down',
'syuilo/pope1',
'syuilo/pope2',
'syuilo/waon',
'syuilo/popo',
'syuilo/triple',
'syuilo/poi1',
'syuilo/poi2',
'aisha/1',
'aisha/2',
'aisha/3',
'noizenecio/kick_gaba',
];
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.$t('settings') as string
};
},
components: {
XTheme,
MkInput,
MkButton,
MkSwitch,
MkSelect,
MkRadio,
MkRange
},
data() {
return {
langs,
lang: localStorage.getItem('lang'),
fontSize: localStorage.getItem('fontSize'),
sounds,
faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute
}
},
computed: {
autoReload: {
get() { return this.$store.state.device.autoReload; },
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
},
reduceAnimation: {
get() { return !this.$store.state.device.animation; },
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
},
disableAnimatedMfm: {
get() { return !this.$store.state.device.animatedMfm; },
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
},
useOsNativeEmojis: {
get() { return this.$store.state.device.useOsNativeEmojis; },
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
},
imageNewTab: {
get() { return this.$store.state.device.imageNewTab; },
set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); }
},
showFixedPostForm: {
get() { return this.$store.state.device.showFixedPostForm; },
set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
},
sfxVolume: {
get() { return this.$store.state.device.sfxVolume; },
set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); }
},
sfxNote: {
get() { return this.$store.state.device.sfxNote; },
set(value) { this.$store.commit('device/set', { key: 'sfxNote', value }); }
},
sfxNoteMy: {
get() { return this.$store.state.device.sfxNoteMy; },
set(value) { this.$store.commit('device/set', { key: 'sfxNoteMy', value }); }
},
sfxNotification: {
get() { return this.$store.state.device.sfxNotification; },
set(value) { this.$store.commit('device/set', { key: 'sfxNotification', value }); }
},
sfxChat: {
get() { return this.$store.state.device.sfxChat; },
set(value) { this.$store.commit('device/set', { key: 'sfxChat', value }); }
},
sfxChatBg: {
get() { return this.$store.state.device.sfxChatBg; },
set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); }
},
sfxAntenna: {
get() { return this.$store.state.device.sfxAntenna; },
set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
},
volumeIcon: {
get() {
return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp;
}
}
},
watch: {
lang() {
localStorage.setItem('lang', this.lang);
localStorage.removeItem('locale');
location.reload();
},
fontSize() {
if (this.fontSize == null) {
localStorage.removeItem('fontSize');
} else {
localStorage.setItem('fontSize', this.fontSize);
}
location.reload();
},
},
methods: {
listen(sound) {
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
},
cacheClear() {
// Clear cache (service worker)
try {
navigator.serviceWorker.controller.postMessage('clear');
navigator.serviceWorker.getRegistrations().then(registrations => {
for (const registration of registrations) registration.unregister();
});
} catch (e) {
console.error(e);
}
// Force reload
location.reload(true);
}
}
});
</script>

View File

@ -73,7 +73,6 @@ export default Vue.extend({
applyTheme(this.themes.find(x => x.id === this.theme));
},
wallpaper() {
if (this.wallpaper == null) {
localStorage.removeItem('wallpaper');

View File

@ -1,145 +0,0 @@
<template>
<div>
<portal to="icon"><fa :icon="faCog"/></portal>
<portal to="title">{{ $t('clinetSettings') }}</portal>
<x-theme/>
<section class="_card">
<div class="_title"><fa :icon="faCog"/> {{ $t('accessibility') }}</div>
<div class="_content">
<mk-switch v-model="autoReload">
{{ $t('autoReloadWhenDisconnected') }}
</mk-switch>
</div>
<div class="_content">
<mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch>
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
<mk-switch v-model="useOsNativeEmojis">
{{ $t('useOsNativeEmojis') }}
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
</mk-switch>
</div>
<div class="_content">
<mk-select v-model="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</mk-select>
</div>
<div class="_content">
<div>{{ $t('fontSize') }}</div>
<mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio>
</div>
</section>
<mk-button @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</mk-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faImage, faCog } from '@fortawesome/free-solid-svg-icons';
import MkInput from '../../components/ui/input.vue';
import MkButton from '../../components/ui/button.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkSelect from '../../components/ui/select.vue';
import MkRadio from '../../components/ui/radio.vue';
import XTheme from './theme.vue';
import i18n from '../../i18n';
import { langs } from '../../config';
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.$t('settings') as string
};
},
components: {
XTheme,
MkInput,
MkButton,
MkSwitch,
MkSelect,
MkRadio,
},
data() {
return {
langs,
lang: localStorage.getItem('lang'),
fontSize: localStorage.getItem('fontSize'),
faImage, faCog
}
},
computed: {
autoReload: {
get() { return this.$store.state.device.autoReload; },
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
},
reduceAnimation: {
get() { return !this.$store.state.device.animation; },
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
},
disableAnimatedMfm: {
get() { return !this.$store.state.device.animatedMfm; },
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
},
useOsNativeEmojis: {
get() { return this.$store.state.device.useOsNativeEmojis; },
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
},
imageNewTab: {
get() { return this.$store.state.device.imageNewTab; },
set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); }
},
},
watch: {
lang() {
localStorage.setItem('lang', this.lang);
localStorage.removeItem('locale');
location.reload();
},
fontSize() {
if (this.fontSize == null) {
localStorage.removeItem('fontSize');
} else {
localStorage.setItem('fontSize', this.fontSize);
}
location.reload();
},
},
methods: {
cacheClear() {
// Clear cache (service worker)
try {
navigator.serviceWorker.controller.postMessage('clear');
navigator.serviceWorker.getRegistrations().then(registrations => {
for (const registration of registrations) registration.unregister();
});
} catch (e) {
console.error(e);
}
// Force reload
location.reload(true);
}
}
});
</script>

View File

@ -3,31 +3,13 @@
<portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal>
<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
<div class="profile _panel" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<mk-user-name class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
</div>
</div>
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
<div class="actions" v-if="$store.getters.isSignedIn">
<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<mk-remote-caution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/>
<div class="profile _panel" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<mk-user-name :user="user" :nowrap="false" class="name"/>
<mk-user-name class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
@ -36,55 +18,71 @@
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
</div>
</div>
<div class="description">
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
</dl>
</div>
<div class="fields" v-if="user.fields.length > 0">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
</dt>
<dd class="value">
<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status" v-if="user.host === null">
<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
<b>{{ user.notesCount | number }}</b>
<span>{{ $t('notes') }}</span>
</router-link>
<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
<b>{{ user.followingCount | number }}</b>
<span>{{ $t('following') }}</span>
</router-link>
<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
<b>{{ user.followersCount | number }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
<div class="actions" v-if="$store.getters.isSignedIn">
<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
</transition>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<div class="title">
<mk-user-name :user="user" :nowrap="false" class="name"/>
<div class="bottom">
<span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
</div>
</div>
<div class="description">
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
</dl>
</div>
<div class="fields" v-if="user.fields.length > 0">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
</dt>
<dd class="value">
<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status">
<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
<b>{{ user.notesCount | number }}</b>
<span>{{ $t('notes') }}</span>
</router-link>
<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
<b>{{ user.followingCount | number }}</b>
<span>{{ $t('following') }}</span>
</router-link>
<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
<b>{{ user.followersCount | number }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
</div>
</div>
<router-view :user="user"></router-view>
<template v-if="$route.name == 'user'">
<sequential-entrance class="pins">
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
</sequential-entrance>
<div class="pins">
<x-note v-for="note in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
</div>
<mk-container :body-togglable="true" class="content">
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
<div>
@ -107,7 +105,7 @@
<script lang="ts">
import Vue from 'vue';
import { faEllipsisH, faRobot, faLock, faBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
import { faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
import * as age from 's-age';
import XUserTimeline from './index.timeline.vue';
@ -115,6 +113,7 @@ import XUserMenu from '../../components/user-menu.vue';
import XNote from '../../components/note.vue';
import MkFollowButton from '../../components/follow-button.vue';
import MkContainer from '../../components/ui/container.vue';
import MkRemoteCaution from '../../components/remote-caution.vue';
import Progress from '../../scripts/loading';
import parseAcct from '../../../misc/acct/parse';
@ -124,6 +123,7 @@ export default Vue.extend({
XNote,
MkFollowButton,
MkContainer,
MkRemoteCaution,
XPhotos: () => import('./index.photos.vue').then(m => m.default),
XActivity: () => import('./index.activity.vue').then(m => m.default),
},
@ -139,7 +139,7 @@ export default Vue.extend({
user: null,
error: null,
parallaxAnimationId: null,
faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
};
},
@ -217,17 +217,6 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-user-page {
> .remote-caution {
font-size: 0.8em;
padding: 16px;
margin-bottom: var(--margin);
> a {
margin-left: 4px;
color: var(--accent);
}
}
> .profile {
position: relative;
margin-bottom: var(--margin);

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