Compare commits

..

202 Commits

Author SHA1 Message Date
e765be4205 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-18 02:15:36 +09:00
ce3797c4af 8.46.0 2018-09-18 02:15:19 +09:00
1fde8a8fb0 Merge pull request #2727 from syuilo/greenkeeper/@types/node-10.10.0
Update @types/node to the latest version 🚀
2018-09-18 02:14:32 +09:00
77e53cbf9e Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-18 02:14:16 +09:00
ab83e08bc7 メッセージタイムラインを追加 2018-09-18 02:14:12 +09:00
2fad6e6d5f Refactor 2018-09-18 02:13:42 +09:00
a3604a6c95 Merge pull request #2722 from syuilo/l10n_develop
New Crowdin translations
2018-09-17 23:12:54 +09:00
44f6fe6f1f Refactor: Extract shouldMuteThisNote function 2018-09-17 23:07:15 +09:00
311b4e90ca No lint when test 2018-09-17 22:51:25 +09:00
f5a937c523 Better hashtag parsing 2018-09-17 22:51:10 +09:00
0632a3ed3f fix(package): update @types/node to version 10.10.0 2018-09-17 08:10:08 +00:00
71bada97df 8.45.1 2018-09-17 12:19:54 +09:00
62509edcbe Refactor 2018-09-17 12:18:59 +09:00
f97cdfaa20 Fix #2725 2018-09-17 11:59:24 +09:00
67ec10e86d Add untilId param 2018-09-17 11:43:53 +09:00
481b3f2c58 New translations ja-JP.yml (English) 2018-09-17 09:11:27 +09:00
7d599a68ea pong 2018-09-17 09:07:46 +09:00
7ccff732b8 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-17 09:07:05 +09:00
7587c896d5 8.45.0 2018-09-17 09:06:56 +09:00
91297f1ab3 Merge pull request #2717 from syuilo/l10n_develop
New Crowdin translations
2018-09-17 09:06:25 +09:00
d872a16fe0 🎨 2018-09-17 09:05:51 +09:00
60aa35adf8 New translations ja-JP.yml (Norwegian) 2018-09-17 09:01:59 +09:00
5035b66773 New translations ja-JP.yml (Dutch) 2018-09-17 09:01:57 +09:00
fa9da8ecab New translations ja-JP.yml (Japanese, Kansai) 2018-09-17 09:01:54 +09:00
1f9bca7188 New translations ja-JP.yml (Spanish) 2018-09-17 09:01:52 +09:00
ffa5bdeb50 New translations ja-JP.yml (Russian) 2018-09-17 09:01:49 +09:00
e6bfb7398e New translations ja-JP.yml (Portuguese) 2018-09-17 09:01:47 +09:00
6def0c776f New translations ja-JP.yml (Polish) 2018-09-17 09:01:45 +09:00
24bae9eaed New translations ja-JP.yml (Korean) 2018-09-17 09:01:43 +09:00
fb5175a283 New translations ja-JP.yml (Italian) 2018-09-17 09:01:40 +09:00
6e49437154 New translations ja-JP.yml (German) 2018-09-17 09:01:38 +09:00
2511ed56ac New translations ja-JP.yml (French) 2018-09-17 09:01:35 +09:00
c4bfc99cf5 New translations ja-JP.yml (English) 2018-09-17 09:01:33 +09:00
4efe38440d New translations ja-JP.yml (Chinese Simplified) 2018-09-17 09:01:30 +09:00
4a5f2c3c40 New translations ja-JP.yml (Catalan) 2018-09-17 09:01:27 +09:00
109738ccb9 ハッシュタグタイムラインを実装 2018-09-17 09:00:20 +09:00
433dbe179d 8.44.1 2018-09-17 03:37:22 +09:00
b21b33831a Fix bug 2018-09-17 03:36:58 +09:00
020cc471da 8.44.0 2018-09-17 03:03:58 +09:00
43b47c4494 fontawesomeを5.3.1にアップデート (#2718) 2018-09-17 03:02:58 +09:00
8751d91794 Better stats page 2018-09-17 02:56:57 +09:00
374b276f5c Fix #2720 2018-09-17 02:45:30 +09:00
6138a74231 Fix #2101 2018-09-17 00:20:00 +09:00
25438c4d64 New translations ja-JP.yml (French) 2018-09-17 00:01:10 +09:00
ae6ce19886 8.43.0 2018-09-16 23:19:06 +09:00
e17a9bfd6f Merge pull request #2714 from syuilo/l10n_develop
New Crowdin translations
2018-09-16 23:17:22 +09:00
dc2055f5bc Fix bug 2018-09-16 23:15:15 +09:00
afeb8058b1 Add mentions column (Deck) 2018-09-16 23:15:02 +09:00
9299f99ac3 New translations ja-JP.yml (English) 2018-09-16 23:02:02 +09:00
858fc7ebcc New translations ja-JP.yml (Norwegian) 2018-09-16 22:51:36 +09:00
35089c65d3 New translations ja-JP.yml (Dutch) 2018-09-16 22:51:34 +09:00
643ca42829 New translations ja-JP.yml (Japanese, Kansai) 2018-09-16 22:51:31 +09:00
935dc4fe33 New translations ja-JP.yml (Spanish) 2018-09-16 22:51:29 +09:00
3a9e74feb1 New translations ja-JP.yml (Russian) 2018-09-16 22:51:27 +09:00
92e66fbf0c New translations ja-JP.yml (Portuguese) 2018-09-16 22:51:24 +09:00
a50515f569 New translations ja-JP.yml (Polish) 2018-09-16 22:51:22 +09:00
2f8f47acea New translations ja-JP.yml (Korean) 2018-09-16 22:51:19 +09:00
dcb296db93 New translations ja-JP.yml (Italian) 2018-09-16 22:51:17 +09:00
0bdae9ede7 New translations ja-JP.yml (German) 2018-09-16 22:51:15 +09:00
11290c2a0f New translations ja-JP.yml (French) 2018-09-16 22:51:13 +09:00
428b8f8669 New translations ja-JP.yml (English) 2018-09-16 22:51:10 +09:00
7ced10f84e New translations ja-JP.yml (Chinese Simplified) 2018-09-16 22:51:08 +09:00
8ac54139c9 New translations ja-JP.yml (Catalan) 2018-09-16 22:51:05 +09:00
32afe77a26 自分宛ての投稿をタイムラインで見れるように 2018-09-16 22:48:57 +09:00
6db8e33662 New translations ja-JP.yml (English) 2018-09-16 22:01:26 +09:00
569561f247 New translations ja-JP.yml (Norwegian) 2018-09-16 21:52:05 +09:00
d132d82acf New translations ja-JP.yml (Dutch) 2018-09-16 21:52:02 +09:00
9ba0db9372 New translations ja-JP.yml (Japanese, Kansai) 2018-09-16 21:51:59 +09:00
5d468b542d New translations ja-JP.yml (Spanish) 2018-09-16 21:51:57 +09:00
32273165c7 New translations ja-JP.yml (Russian) 2018-09-16 21:51:55 +09:00
46fdb75bf4 New translations ja-JP.yml (Portuguese) 2018-09-16 21:51:52 +09:00
baf381814b New translations ja-JP.yml (Polish) 2018-09-16 21:51:50 +09:00
e90387c14e New translations ja-JP.yml (Korean) 2018-09-16 21:51:48 +09:00
876790d499 New translations ja-JP.yml (Italian) 2018-09-16 21:51:45 +09:00
8b56edda4b New translations ja-JP.yml (German) 2018-09-16 21:51:43 +09:00
33352256d6 New translations ja-JP.yml (French) 2018-09-16 21:51:41 +09:00
e368ef11fa New translations ja-JP.yml (English) 2018-09-16 21:51:39 +09:00
045f7c3185 New translations ja-JP.yml (Chinese Simplified) 2018-09-16 21:51:36 +09:00
bf40e5a5c5 New translations ja-JP.yml (Catalan) 2018-09-16 21:51:34 +09:00
cda3635d97 enable-animations --> reduce-motion 2018-09-16 21:40:48 +09:00
2eb561f132 New translations ja-JP.yml (Portuguese) 2018-09-16 05:43:02 +09:00
b5f6465d61 New translations ja-JP.yml (Portuguese) 2018-09-16 05:31:03 +09:00
9725076c46 New translations ja-JP.yml (Portuguese) 2018-09-16 05:20:49 +09:00
f7228e79bb 8.42.0 2018-09-16 04:47:16 +09:00
d3e250288a Merge pull request #2713 from syuilo/l10n_develop
New Crowdin translations
2018-09-16 04:46:27 +09:00
38f8043cb2 New translations ja-JP.yml (English) 2018-09-16 04:41:03 +09:00
a61320ca8c Improve usability 2018-09-16 04:34:46 +09:00
4bc9bad34f New translations ja-JP.yml (Norwegian) 2018-09-16 04:33:11 +09:00
4392e64672 New translations ja-JP.yml (Dutch) 2018-09-16 04:33:09 +09:00
74a0d60766 New translations ja-JP.yml (Japanese, Kansai) 2018-09-16 04:33:06 +09:00
012a2b6b00 New translations ja-JP.yml (Spanish) 2018-09-16 04:33:02 +09:00
584bca7658 New translations ja-JP.yml (Russian) 2018-09-16 04:33:00 +09:00
5dcd96d926 New translations ja-JP.yml (Portuguese) 2018-09-16 04:32:58 +09:00
bd2be2815c New translations ja-JP.yml (Polish) 2018-09-16 04:32:55 +09:00
2a5635492a New translations ja-JP.yml (Korean) 2018-09-16 04:32:52 +09:00
eeea7527c1 New translations ja-JP.yml (Italian) 2018-09-16 04:32:49 +09:00
d943a9a2f4 New translations ja-JP.yml (German) 2018-09-16 04:32:47 +09:00
d4335f0e4d New translations ja-JP.yml (French) 2018-09-16 04:32:45 +09:00
054f7cbdaa New translations ja-JP.yml (English) 2018-09-16 04:32:42 +09:00
6ff95dab89 New translations ja-JP.yml (Chinese Simplified) 2018-09-16 04:32:40 +09:00
4461bde5da New translations ja-JP.yml (Catalan) 2018-09-16 04:32:38 +09:00
19152c28cb Fix bug 2018-09-16 04:31:55 +09:00
dda2967e2d Merge pull request #2611 from tamaina/improve-media
メディアリストにバナータイプのメディアビューを追加
2018-09-16 04:28:07 +09:00
a680bcda1f Fix 2018-09-16 04:27:10 +09:00
8d24fcba6a ✌️ 2018-09-16 04:26:01 +09:00
2a96429be8 バグ修正やデザイン調整など 2018-09-16 04:21:48 +09:00
5c6f376f4e New translations ja-JP.yml (Norwegian) 2018-09-16 03:51:51 +09:00
1b24fad95f New translations ja-JP.yml (Dutch) 2018-09-16 03:51:49 +09:00
87743d9ef9 New translations ja-JP.yml (Japanese, Kansai) 2018-09-16 03:51:46 +09:00
8ffd0abb1b New translations ja-JP.yml (Spanish) 2018-09-16 03:51:44 +09:00
2fed09ec18 New translations ja-JP.yml (Russian) 2018-09-16 03:51:42 +09:00
6daabb35de New translations ja-JP.yml (Portuguese) 2018-09-16 03:51:39 +09:00
59e98aa06c New translations ja-JP.yml (Polish) 2018-09-16 03:51:36 +09:00
3601d95733 New translations ja-JP.yml (Korean) 2018-09-16 03:51:34 +09:00
2c57dfd528 New translations ja-JP.yml (Italian) 2018-09-16 03:51:31 +09:00
2348f2586c New translations ja-JP.yml (German) 2018-09-16 03:51:29 +09:00
ed11f954aa New translations ja-JP.yml (French) 2018-09-16 03:51:26 +09:00
5765a8e38e New translations ja-JP.yml (English) 2018-09-16 03:51:24 +09:00
4a925fade1 New translations ja-JP.yml (Chinese Simplified) 2018-09-16 03:51:21 +09:00
fca86f43c4 New translations ja-JP.yml (Catalan) 2018-09-16 03:51:18 +09:00
12005de4c0 Merge pull request #2712 from syuilo/greenkeeper/vue-cropperjs-2.2.2
Update vue-cropperjs to the latest version 🚀
2018-09-16 03:50:03 +09:00
2e3d0d3435 Fix bug 2018-09-16 03:48:16 +09:00
7d76887517 アニメーションを無効にするオプションを実装 2018-09-16 03:46:53 +09:00
bf39ecd1e5 New translations ja-JP.yml (French) 2018-09-16 03:01:14 +09:00
7ebee09f74 fix(package): update vue-cropperjs to version 2.2.2 2018-09-15 13:57:29 +00:00
952a49f749 revert デスクトップの動画埋め込みを廃止 2018-09-15 22:22:41 +09:00
8f8c67ad6d Merge branch 'develop' into improve-media 2018-09-15 22:15:56 +09:00
ce659f9926 8.41.0 2018-09-15 22:03:34 +09:00
c23ec555ff Merge pull request #2709 from syuilo/l10n_develop
New Crowdin translations
2018-09-15 22:02:04 +09:00
25b0a93acd ✌️ 2018-09-15 21:53:04 +09:00
7b2b7d1456 Resolve #2066 2018-09-15 21:48:10 +09:00
37444939ab New translations ja-JP.yml (English) 2018-09-15 05:51:42 +09:00
2fee2e5166 8.40.0 2018-09-15 05:50:08 +09:00
98bd6c3cb8 Merge pull request #2706 from syuilo/l10n_develop
New Crowdin translations
2018-09-15 05:49:13 +09:00
fc31e44fd2 Fix bug 2018-09-15 05:42:14 +09:00
12f89f0e2e New translations ja-JP.yml (Norwegian) 2018-09-15 05:42:13 +09:00
cdf15fc43a New translations ja-JP.yml (Dutch) 2018-09-15 05:42:10 +09:00
2a12af28dd New translations ja-JP.yml (Japanese, Kansai) 2018-09-15 05:42:08 +09:00
f128fceaba New translations ja-JP.yml (Spanish) 2018-09-15 05:42:06 +09:00
26c5d66994 New translations ja-JP.yml (Russian) 2018-09-15 05:42:04 +09:00
bd390d424a New translations ja-JP.yml (Portuguese) 2018-09-15 05:42:01 +09:00
6b85730361 New translations ja-JP.yml (Polish) 2018-09-15 05:41:58 +09:00
d6176d1901 New translations ja-JP.yml (Korean) 2018-09-15 05:41:56 +09:00
43f336bea4 New translations ja-JP.yml (Italian) 2018-09-15 05:41:52 +09:00
d2ed9e965e New translations ja-JP.yml (German) 2018-09-15 05:41:50 +09:00
ea73e9d5de New translations ja-JP.yml (French) 2018-09-15 05:41:48 +09:00
36ef862fc6 New translations ja-JP.yml (English) 2018-09-15 05:41:46 +09:00
999275ca2c New translations ja-JP.yml (Chinese Simplified) 2018-09-15 05:41:44 +09:00
78c36ec260 New translations ja-JP.yml (Catalan) 2018-09-15 05:41:41 +09:00
c985fed3e4 Resolve #2328 2018-09-15 05:40:58 +09:00
f13fe431b8 New translations ja-JP.yml (English) 2018-09-14 21:01:37 +09:00
f1d7cc08bb New translations ja-JP.yml (Norwegian) 2018-09-14 20:41:45 +09:00
e662dfbcfb New translations ja-JP.yml (Dutch) 2018-09-14 20:41:42 +09:00
8aaf667f78 New translations ja-JP.yml (Japanese, Kansai) 2018-09-14 20:41:39 +09:00
fdfea73bdb New translations ja-JP.yml (Spanish) 2018-09-14 20:41:37 +09:00
50161bc84d New translations ja-JP.yml (Russian) 2018-09-14 20:41:34 +09:00
8046a4488d New translations ja-JP.yml (Portuguese) 2018-09-14 20:41:32 +09:00
e818c37a0c New translations ja-JP.yml (Polish) 2018-09-14 20:41:30 +09:00
16ffa0c3c7 New translations ja-JP.yml (Korean) 2018-09-14 20:41:26 +09:00
6a9b839e62 New translations ja-JP.yml (Italian) 2018-09-14 20:41:24 +09:00
b5da01931c New translations ja-JP.yml (German) 2018-09-14 20:41:22 +09:00
99b6896cf4 New translations ja-JP.yml (French) 2018-09-14 20:41:19 +09:00
0ac05df628 New translations ja-JP.yml (English) 2018-09-14 20:41:17 +09:00
83726ddcec New translations ja-JP.yml (Chinese Simplified) 2018-09-14 20:41:15 +09:00
be627d488c New translations ja-JP.yml (Catalan) 2018-09-14 20:41:12 +09:00
6032ec3823 New translations ja-JP.yml (Norwegian) 2018-09-14 20:12:22 +09:00
01ed052ae6 New translations ja-JP.yml (Dutch) 2018-09-14 20:12:19 +09:00
88a9a7c48f New translations ja-JP.yml (Japanese, Kansai) 2018-09-14 20:12:17 +09:00
d74755f0a4 New translations ja-JP.yml (Spanish) 2018-09-14 20:12:15 +09:00
e05871a7f8 New translations ja-JP.yml (Russian) 2018-09-14 20:12:12 +09:00
fed44e2f2b New translations ja-JP.yml (Portuguese) 2018-09-14 20:12:10 +09:00
2189f450df New translations ja-JP.yml (Polish) 2018-09-14 20:12:07 +09:00
288e8f0f75 New translations ja-JP.yml (Korean) 2018-09-14 20:12:05 +09:00
dfe7eaaa04 New translations ja-JP.yml (Italian) 2018-09-14 20:12:02 +09:00
51b166b419 New translations ja-JP.yml (German) 2018-09-14 20:11:59 +09:00
0ac9a85314 New translations ja-JP.yml (French) 2018-09-14 20:11:56 +09:00
d153297294 New translations ja-JP.yml (English) 2018-09-14 20:11:54 +09:00
ec71658087 New translations ja-JP.yml (Chinese Simplified) 2018-09-14 20:11:51 +09:00
3f359e67b3 New translations ja-JP.yml (Catalan) 2018-09-14 20:11:49 +09:00
385116bf30 New translations ja-JP.yml (Norwegian) 2018-09-14 15:22:13 +09:00
f3b476a348 New translations ja-JP.yml (Dutch) 2018-09-14 15:22:11 +09:00
d0dec99222 New translations ja-JP.yml (Japanese, Kansai) 2018-09-14 15:22:09 +09:00
b3fa50d4d1 New translations ja-JP.yml (Spanish) 2018-09-14 15:22:06 +09:00
1dbf245f76 New translations ja-JP.yml (Russian) 2018-09-14 15:22:04 +09:00
7c092bc04c New translations ja-JP.yml (Portuguese) 2018-09-14 15:22:02 +09:00
b8f1a8a243 New translations ja-JP.yml (Polish) 2018-09-14 15:21:59 +09:00
c2b235b4a3 New translations ja-JP.yml (Korean) 2018-09-14 15:21:56 +09:00
19a9d8d254 New translations ja-JP.yml (Italian) 2018-09-14 15:21:53 +09:00
c79d29bd6c New translations ja-JP.yml (German) 2018-09-14 15:21:51 +09:00
14816a1c6d New translations ja-JP.yml (French) 2018-09-14 15:21:48 +09:00
0c86f1c1aa New translations ja-JP.yml (English) 2018-09-14 15:21:46 +09:00
8f440ae633 New translations ja-JP.yml (Chinese Simplified) 2018-09-14 15:21:44 +09:00
863a5ba872 New translations ja-JP.yml (Catalan) 2018-09-14 15:21:41 +09:00
4257fed500 fix mk-media darkmode 2018-09-05 19:46:55 +09:00
299f83684b media-banner darkmode 2018-09-05 18:01:47 +09:00
ebeaef94e2 Improve media list 2018-09-05 01:08:18 +09:00
2399ba05cd wip 2018-09-04 21:07:41 +09:00
8bcfa97349 wip 2018-09-04 21:06:02 +09:00
dd3e3ddcdd wip 2018-09-04 20:21:36 +09:00
2555e23b10 wip 2018-09-04 20:01:15 +09:00
1595683904 merge upstream 2018-09-04 19:24:23 +09:00
d8dcc4da27 Use string interpolation 2018-09-01 23:17:55 +09:00
106 changed files with 2032 additions and 785 deletions

View File

@ -78,7 +78,7 @@ gulp.task('build:copy', ['build:copy:views', 'build:copy:lang'], () =>
]).pipe(gulp.dest('./built/')) ]).pipe(gulp.dest('./built/'))
); );
gulp.task('test', ['lint', 'mocha']); gulp.task('test', ['mocha']);
gulp.task('lint', () => gulp.task('lint', () =>
gulp.src('./src/**/*.ts') gulp.src('./src/**/*.ts')

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "Startseite" home: "Startseite"
local: "Lokal" local: "Lokal"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "Global" global: "Global"
mentions: "あなた宛て"
notifications: "Mitteilungen" notifications: "Mitteilungen"
list: "Listen" list: "Listen"
swap-left: "Nach links" swap-left: "Nach links"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Die Verbindung scheint zu funktionieren. Bitte lade die Seite neu." success-desc: "Die Verbindung scheint zu funktionieren. Bitte lade die Seite neu."
flush: "Cache leeren" flush: "Cache leeren"
set-version: "Version angeben" set-version: "Version angeben"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Datei auswählen" choose-file: "Datei auswählen"
upload: "Dateien von deinem PC hochladen" upload: "Dateien von deinem PC hochladen"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "Lokal" local: "Lokal"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "Global" global: "Global"
mentions: "あなた宛て"
list: "Listen" list: "Listen"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "Make the stone color clear in reversi" use-contrast-reversi-stones: "Make the stone color clear in reversi"
verified-user: "Verified account" verified-user: "Verified account"
disable-animated-mfm: "Disable animated texts in a post" disable-animated-mfm: "Disable animated texts in a post"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "Do not omit the hostname from the username"
reduce-motion: "Reduce motion in UI"
this-setting-is-this-device-only: "Only for this device"
do-not-use-in-production: 'As this is for development, do not use this in production.' do-not-use-in-production: 'As this is for development, do not use this in production.'
reversi: reversi:
drawn: "Draw" drawn: "Draw"
@ -150,7 +155,9 @@ common:
home: "Home" home: "Home"
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
hashtag: "Hashtag"
global: "Global" global: "Global"
mentions: "Mentions"
notifications: "Notifications" notifications: "Notifications"
list: "Lists" list: "Lists"
swap-left: "Move to the left" swap-left: "Move to the left"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Looks like we have a connection. Please reload the page." success-desc: "Looks like we have a connection. Please reload the page."
flush: "Clean cache" flush: "Clean cache"
set-version: "Specify version" set-version: "Specify version"
common/views/components/media-banner.vue:
sensitive: "NSFW"
click-to-show: "Click to show"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "Hide" hide: "Hide"
show: "See more" show: "See more"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "Posts" notes: "Posts"
users: "Users" users: "Users"
drive: "Drive" drive: "Drive"
network: "Network"
charts: charts:
notes: "The number of posts: increase/decrease (Combined)" notes: "The number of posts: increase/decrease (Combined)"
local-notes: "The number of posts: increase/decrease (Local)" local-notes: "The number of posts: increase/decrease (Local)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "Capacity used as the storage: cumulative total" drive-total: "Capacity used as the storage: cumulative total"
drive-files: "The number of files on the storage: increase/decrease" drive-files: "The number of files on the storage: increase/decrease"
drive-files-total: "The number of files on the storage: cumulative total" drive-files-total: "The number of files on the storage: cumulative total"
network-requests: "Requests"
network-time: "Response time"
network-usage: "Traffic"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Choose files" choose-file: "Choose files"
upload: "Upload files from your device" upload: "Upload files from your device"
@ -778,7 +792,7 @@ desktop/views/components/settings.profile.vue:
birthday: "Birthday" birthday: "Birthday"
save: "Update profile" save: "Update profile"
locked-account: "Protect your account" locked-account: "Protect your account"
is-locked: "フォローを承認制にする" is-locked: "Follow request needs approval"
other: "Other" other: "Other"
is-bot: "This account is a Bot" is-bot: "This account is a Bot"
is-cat: "This account is a Cat" is-cat: "This account is a Cat"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
global: "Global" global: "Global"
mentions: "Mentions"
list: "Lists" list: "Lists"
hashtag: "Hashtag"
add-tag-timeline: "Add hashtag tl"
add-list: "Add list"
list-name: "List name"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "Welcome back," welcome-back: "Welcome back,"
adjective: "-san" adjective: "-san"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
global: "Global" global: "Global"
mentions: "Mentions"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "No posts \"{}\" found." no-posts-found: "No posts \"{}\" found."
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:
@ -1160,7 +1180,7 @@ mobile/views/pages/settings/settings.profile.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Banner" banner: "Banner"
is-cat: "This account is a Cat" is-cat: "This account is a Cat"
is-locked: "フォローを承認制にする" is-locked: "Follow request needs approval"
advanced: "Advanced" advanced: "Advanced"
privacy: "Privacy" privacy: "Privacy"
save: "Update profile" save: "Update profile"

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi" use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi"
verified-user: "Cuenta verificada" verified-user: "Cuenta verificada"
disable-animated-mfm: "Desactivar texto animado en una publicación" disable-animated-mfm: "Desactivar texto animado en una publicación"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.' do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.'
reversi: reversi:
drawn: "Empatado" drawn: "Empatado"
@ -150,7 +155,9 @@ common:
home: "Inicio" home: "Inicio"
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
hashtag: "ハッシュタグ"
global: "Global" global: "Global"
mentions: "あなた宛て"
notifications: "Notificaciones" notifications: "Notificaciones"
list: "Listado" list: "Listado"
swap-left: "Desplazar a la izq." swap-left: "Desplazar a la izq."
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Parece que la conexión ha sido posible. Por favor refresca la página." success-desc: "Parece que la conexión ha sido posible. Por favor refresca la página."
flush: "Limpiar la memoria caché" flush: "Limpiar la memoria caché"
set-version: "Escoge la versión" set-version: "Escoge la versión"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "Publicaciones" notes: "Publicaciones"
users: "Usuarios" users: "Usuarios"
drive: "Unidad" drive: "Unidad"
network: "ネットワーク"
charts: charts:
notes: "Número de publicaciones: aumentar/disminuir (Combinado)" notes: "Número de publicaciones: aumentar/disminuir (Combinado)"
local-notes: "Número de publicaciones: aumentar/disminuir (Local)" local-notes: "Número de publicaciones: aumentar/disminuir (Local)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "Capacidad de almacenamiento usada: Acumulativa total" drive-total: "Capacidad de almacenamiento usada: Acumulativa total"
drive-files: "Número de archivos almacenados: aumentar/disminuir" drive-files: "Número de archivos almacenados: aumentar/disminuir"
drive-files-total: "Número de archivos almacenados: Acumulativo total" drive-files-total: "Número de archivos almacenados: Acumulativo total"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Escoger archivos" choose-file: "Escoger archivos"
upload: "Cargar archivos de tu dispositivo" upload: "Cargar archivos de tu dispositivo"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "Bienvenido/a de vuelta," welcome-back: "Bienvenido/a de vuelta,"
adjective: "-san" adjective: "-san"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,7 +109,12 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "Compte vérifié" verified-user: "Compte vérifié"
disable-animated-mfm: "Désactiver les textes animés dans les publications" disable-animated-mfm: "Désactiver les textes animés dans les publications"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "Réduire les animations dans linterface utilisateur"
this-setting-is-this-device-only: "Uniquement sur cet appareil"
do-not-use-in-production: 'Il sagit dune version de développement. Ne pas utiliser dans un environnement de production.'
reversi: reversi:
drawn: "Partie nulle" drawn: "Partie nulle"
my-turn: "Cest votre tour" my-turn: "Cest votre tour"
@ -150,7 +155,9 @@ common:
home: "Accueil" home: "Accueil"
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
hashtag: "ハッシュタグ"
global: "Global" global: "Global"
mentions: "Mentions"
notifications: "Notifications" notifications: "Notifications"
list: "Liste" list: "Liste"
swap-left: "Déplacer à gauche" swap-left: "Déplacer à gauche"
@ -253,9 +260,12 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Succès de la connexion au serveur de Misskey. Veuillez recharger la page." success-desc: "Succès de la connexion au serveur de Misskey. Veuillez recharger la page."
flush: "Vider le cache" flush: "Vider le cache"
set-version: "Choisissez une version" set-version: "Choisissez une version"
common/views/components/media-banner.vue:
sensitive: "Contenu sensible"
click-to-show: "Cliquer pour afficher"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "Masquer"
show: "もっと見る" show: "Voir plus"
common/views/components/messaging.vue: common/views/components/messaging.vue:
search-user: "Trouver un·e utilisateur·trice" search-user: "Trouver un·e utilisateur·trice"
you: "Vous" you: "Vous"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "Publications" notes: "Publications"
users: "Utilisateurs" users: "Utilisateurs"
drive: "Drive" drive: "Drive"
network: "Réseau"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "Requêtes"
network-time: "Temps de réponse"
network-usage: "Traffic"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Sélection de fichiers" choose-file: "Sélection de fichiers"
upload: "Téléverser des fichiers à partir de votre ordinateur" upload: "Téléverser des fichiers à partir de votre ordinateur"
@ -665,7 +679,7 @@ desktop/views/components/settings.vue:
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page." fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
note-visibility: "Visibilité de la publication" note-visibility: "Visibilité de la publication"
default-note-visibility: "Visibilité par défaut" default-note-visibility: "Visibilité par défaut"
remember-note-visibility: "投稿の公開範囲を記憶する" remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
auto-popout: "Fenêtre contextuelle automatique" auto-popout: "Fenêtre contextuelle automatique"
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。" auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
advanced: "Paramètres avancés" advanced: "Paramètres avancés"
@ -778,7 +792,7 @@ desktop/views/components/settings.profile.vue:
birthday: "Date de naissance" birthday: "Date de naissance"
save: "Mettre à jour le profil" save: "Mettre à jour le profil"
locked-account: "Protéger votre compte" locked-account: "Protéger votre compte"
is-locked: "フォローを承認制にする" is-locked: "Demande dabonnement en attente dapprobation"
other: "Autre" other: "Autre"
is-bot: "Ce compte est un Bot" is-bot: "Ce compte est un Bot"
is-cat: "Ce compte est un Chat" is-cat: "Ce compte est un Chat"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
global: "Global" global: "Global"
mentions: "Mentions"
list: "Listes" list: "Listes"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "Content de vous revoir !" welcome-back: "Content de vous revoir !"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
global: "Global" global: "Global"
mentions: "Mentions"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "Pas de message avec un hashtag {} trouvé." no-posts-found: "Pas de message avec un hashtag {} trouvé."
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:
@ -1160,9 +1180,9 @@ mobile/views/pages/settings/settings.profile.vue:
avatar: "Avatar" avatar: "Avatar"
banner: "Bannière" banner: "Bannière"
is-cat: "Ce compte est un Bot" is-cat: "Ce compte est un Bot"
is-locked: "フォローを承認制にする" is-locked: "Demande dabonnement en attente dapprobation"
advanced: "その他" advanced: "Avancé"
privacy: "プライバシー" privacy: "Vie privée"
save: "Mettre à jour le profil" save: "Mettre à jour le profil"
saved: "Profil mis à jour avec succès" saved: "Profil mis à jour avec succès"
uploading: "En cours d'envoi" uploading: "En cours d'envoi"
@ -1184,7 +1204,7 @@ mobile/views/pages/settings.vue:
dark-mode: "Mode nuit" dark-mode: "Mode nuit"
i-am-under-limited-internet: "J'ai un accès Internet limité" i-am-under-limited-internet: "J'ai un accès Internet limité"
circle-icons: "Utiliser des icônes circulaires" circle-icons: "Utiliser des icônes circulaires"
contrasted-acct: "ユーザー名にコントラストを付ける" contrasted-acct: "Nom dutilisateur contrasté"
timeline: "Fil d'actualité" timeline: "Fil d'actualité"
show-reply-target: "Afficher les réponses" show-reply-target: "Afficher les réponses"
show-my-renotes: "Afficher mes republications" show-my-renotes: "Afficher mes republications"

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -119,6 +119,7 @@ common:
always-show-nsfw: "常に閲覧注意のメディアを表示する" always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿" always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない" show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ" this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
@ -165,7 +166,10 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
direct: "ダイレクト投稿"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -277,6 +281,10 @@ common/views/components/connect-failed.troubleshooter.vue:
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -518,6 +526,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -529,6 +538,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
@ -904,7 +916,13 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
@ -1305,6 +1323,8 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!" use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
verified-user: "アメちゃん付きアカウント" verified-user: "アメちゃん付きアカウント"
disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める" disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "おあいこ" drawn: "おあいこ"
@ -150,7 +155,9 @@ common:
home: "うち" home: "うち"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動や!" swap-left: "左に移動や!"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようやわ。ページを再度読み込みしてな。" success-desc: "正常に接続できるようやわ。ページを再度読み込みしてな。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえり、" welcome-back: "おかえり、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへんで。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへんで。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할" disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "무승부" drawn: "무승부"
@ -150,7 +155,9 @@ common:
home: "홈" home: "홈"
local: "로컬" local: "로컬"
hybrid: "소셜" hybrid: "소셜"
hashtag: "ハッシュタグ"
global: "글로벌" global: "글로벌"
mentions: "あなた宛て"
notifications: "통지" notifications: "통지"
list: "목록" list: "목록"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Het verbinden lijkt te lukken. Herlaad de pagina." success-desc: "Het verbinden lijkt te lukken. Herlaad de pagina."
flush: "Cache leegmaken" flush: "Cache leegmaken"
set-version: "Versie opgeven" set-version: "Versie opgeven"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Bestanden kiezen" choose-file: "Bestanden kiezen"
upload: "Bestanden uploaden van je computer" upload: "Bestanden uploaden van je computer"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "Lokaal" local: "Lokaal"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "Algemeen" global: "Algemeen"
mentions: "あなた宛て"
list: "Lijsten" list: "Lijsten"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "Wyłącz animowany tekst we wpisach" disable-animated-mfm: "Wyłącz animowany tekst we wpisach"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "Remis" drawn: "Remis"
@ -150,7 +155,9 @@ common:
home: "Strona główna" home: "Strona główna"
local: "Lokalne" local: "Lokalne"
hybrid: "Społeczność" hybrid: "Społeczność"
hashtag: "ハッシュタグ"
global: "Globalne" global: "Globalne"
mentions: "あなた宛て"
notifications: "Powiadomienia" notifications: "Powiadomienia"
list: "Listy" list: "Listy"
swap-left: "Przesuń w lewo" swap-left: "Przesuń w lewo"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "Wygląda na to, że udało się połączyć. Odśwież stronę." success-desc: "Wygląda na to, że udało się połączyć. Odśwież stronę."
flush: "Wyczyść pamięć podręczną" flush: "Wyczyść pamięć podręczną"
set-version: "Określ wersję" set-version: "Określ wersję"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Wybierz plik" choose-file: "Wybierz plik"
upload: "Wyślij pliki z Twojego komputera" upload: "Wyślij pliki z Twojego komputera"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "Lokalne" local: "Lokalne"
hybrid: "Społeczność" hybrid: "Społeczność"
global: "Globalne" global: "Globalne"
mentions: "あなた宛て"
list: "Listy" list: "Listy"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "Witaj ponownie," welcome-back: "Witaj ponownie,"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "Lokalne" local: "Lokalne"
hybrid: "Społeczność" hybrid: "Społeczność"
global: "Globalne" global: "Globalne"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "Nie znaleziono wpisów zawierających „{}”." no-posts-found: "Nie znaleziono wpisów zawierających „{}”."
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -7,12 +7,12 @@ common:
about-title: "Uma ⭐ do fediverso." about-title: "Uma ⭐ do fediverso."
about: "Obrigado por encontrar Misskey. Uma <b>plataforma descentralizada de microblog</b> nascida na Terra. Já que ela existe no Fediverso (um universo onde várias plataformas de mídia social são organizadas), ela é ligada com outras plataformas.Por que você não tira uma folga do agito e confusão da cidade, e mergulha em uma nova internet?" about: "Obrigado por encontrar Misskey. Uma <b>plataforma descentralizada de microblog</b> nascida na Terra. Já que ela existe no Fediverso (um universo onde várias plataformas de mídia social são organizadas), ela é ligada com outras plataformas.Por que você não tira uma folga do agito e confusão da cidade, e mergulha em uma nova internet?"
intro: intro:
title: "Misskeyって?" title: "O que é Misskey?"
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。" about: "Misskey é um <b>serviço de microblog descentralizado</b>. Personalização sofisticada da interface, variedade de reações a posts, armazenamento de arquivos grátis com gerenciamento integrado e outras funções avançadas estão disponíveis. Um sistema em rede chamado \"Fediverso\" permite que nos comuniquemos com usuários em outras redes sociais. Se você postar algo, por exemplo, seu post não será mandado apenas para o Misskey, mas também para o Mastodon. Apenas imagine que o planeta está enviando ondas de rádio para outros planetas para se comunicar."
features: "特徴" features: "Recursos"
rich-contents: "投稿" rich-contents: "Post"
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。" rich-contents-desc: "Apenas poste suas ideias, temas do momento e qualquer coisa que você queira compartilhar. Você pode querer decorar suas palavras, anexar suas imagens favoritas, enviar arquivos, inclusive vídeos ou criar uma enquete. Essas são as coisas que você pode fazer em Misskey."
reaction: "リアクション" reaction: "Reações"
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。" reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
ui: "インターフェース" ui: "インターフェース"
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。" ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "Conta verificada" verified-user: "Conta verificada"
disable-animated-mfm: "Desativar texto animado nas publicações" disable-animated-mfm: "Desativar texto animado nas publicações"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "Empatado" drawn: "Empatado"
@ -150,7 +155,9 @@ common:
home: "Início" home: "Início"
local: "Local" local: "Local"
hybrid: "Social" hybrid: "Social"
hashtag: "ハッシュタグ"
global: "Global" global: "Global"
mentions: "あなた宛て"
notifications: "Notificações" notifications: "Notificações"
list: "Listas" list: "Listas"
swap-left: "Mover para a esquerda" swap-left: "Mover para a esquerda"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "Limpar o cache" flush: "Limpar o cache"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:
@ -1152,23 +1172,23 @@ mobile/views/pages/games/reversi.vue:
reversi: "リバーシ" reversi: "リバーシ"
mobile/views/pages/settings/settings.profile.vue: mobile/views/pages/settings/settings.profile.vue:
title: "プロフィール" title: "プロフィール"
name: "名前" name: "Nome"
account: "アカウント" account: "Conta"
location: "場所" location: "Lugar"
description: "自己紹介" description: "Biografia"
birthday: "誕生日" birthday: "Data de nascimento"
avatar: "アイコン" avatar: "Avatar"
banner: "バナー" banner: "Capa"
is-cat: "このアカウントはCatです" is-cat: "Esta conta é gato"
is-locked: "フォローを承認制にする" is-locked: "Pedido para seguir precisa ser aprovado"
advanced: "その他" advanced: "Avançado"
privacy: "プライバシー" privacy: "Provacidade"
save: "保存" save: "Atualizar perfil"
saved: "プロフィールを保存しました" saved: "Perfil atualizado"
uploading: "アップロード中" uploading: "Enviando"
upload-failed: "アップロードに失敗しました" upload-failed: "Falha ao enviar"
mobile/views/pages/search.vue: mobile/views/pages/search.vue:
search: "検索" search: "Pesquisar"
empty: "「{}」に関する投稿は見つかりませんでした。" empty: "「{}」に関する投稿は見つかりませんでした。"
not-found: "「{}」に関する投稿は見つかりませんでした。" not-found: "「{}」に関する投稿は見つかりませんでした。"
mobile/views/pages/selectdrive.vue: mobile/views/pages/selectdrive.vue:
@ -1205,47 +1225,47 @@ mobile/views/pages/settings.vue:
load-raw-images: "添付された画像を高画質で表示する" load-raw-images: "添付された画像を高画質で表示する"
load-remote-media: "リモートサーバーのメディアを表示する" load-remote-media: "リモートサーバーのメディアを表示する"
twitter: "Twitter連携" twitter: "Twitter連携"
twitter-connect: "Twitterアカウントに接続する" twitter-connect: "Conectar à sua conta no Twitter"
twitter-reconnect: "再接続する" twitter-reconnect: "Reconectar"
twitter-disconnect: "切断する" twitter-disconnect: "Desconectar"
update: "Misskey Update" update: "Atualizar Misskey"
version: "バージョン:" version: "Versão atual;"
latest-version: "最新のバージョン:" latest-version: "Última versão:"
update-checking: "アップデートを確認中" update-checking: "Verificando atualizações"
check-for-updates: "アップデートを確認" check-for-updates: "Verificar atualizações"
no-updates: "利用可能な更新はありません" no-updates: "Sem atualizações"
no-updates-desc: "お使いのMisskeyは最新です。" no-updates-desc: "Seu Misskey está atualizado"
update-available: "新しいバージョンが利用可能です" update-available: "Uma nova versão está disponível"
update-available-desc: "ページを再度読み込みすると更新が適用されます。" update-available-desc: "Atualizações vão ser aplicadas depois de recarregar a página"
settings: "設定" settings: "Configurações"
signout: "サインアウト" signout: "Sair"
sound: "サウンド" sound: "Sons"
enable-sounds: "サウンドを有効にする" enable-sounds: "Ativar sons"
mobile/views/pages/user.vue: mobile/views/pages/user.vue:
follows-you: "フォローされています" follows-you: "Te segue"
following: "フォロー" following: "Seguindo"
followers: "フォロワー" followers: "Seguidores"
notes: "投稿" notes: "Posts"
overview: "概要" overview: "概要"
timeline: "タイムライン" timeline: "Linha do tempo"
media: "メディア" media: "Mídia"
is-suspended: "このユーザーは凍結されています。" is-suspended: "Esta conta foi suspensa"
is-remote: "Este é uma usuário remoto. O perfil que vê aqui pode não estar completo." is-remote: "Este é uma usuário remoto. O perfil que vê aqui pode não estar completo."
view-remote: "Ver o perfil completo." view-remote: "Ver o perfil completo."
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Notas recentes" recent-notes: "Notas recentes"
images: "Imagens" images: "Imagens"
activity: "Atividade" activity: "Atividade"
keywords: "キーワード" keywords: "Palavras chave"
domains: "頻出ドメイン" domains: "Domínios"
frequently-replied-users: "よく会話するユーザー" frequently-replied-users: "Perguntas frequentes"
followers-you-know: "Seguidores que você conhece" followers-you-know: "Seguidores que você conhece"
last-used-at: "Ativo pela última vez:" last-used-at: "Ativo pela última vez:"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
loading: "Carregando" loading: "Carregando"
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
loading: "読み込み中" loading: "Carregando"
no-users: "よく会話するユーザーはいません" no-users: "よく会話するユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
loading: "Carregando" loading: "Carregando"
@ -1255,14 +1275,14 @@ mobile/views/pages/user/home.photos.vue:
no-photos: "Sem fotos" no-photos: "Sem fotos"
docs: docs:
edit-this-page-on-github: "間違いや改善点を見つけましたか?" edit-this-page-on-github: "間違いや改善点を見つけましたか?"
edit-this-page-on-github-link: "このページをGitHubで編集" edit-this-page-on-github-link: "Edite esta página no GitHub!"
api: api:
entities: entities:
properties: "プロパティ" properties: "Propriedades"
endpoints: endpoints:
params: "パラメータ" params: "Parâmetros"
no-params: "パラメータはありません" no-params: "Sem parâmetros"
res: "レスポンス" res: "Resposta"
require-credential: "このエンドポイントは認証情報が必須です。" require-credential: "このエンドポイントは認証情報が必須です。"
require-permission: "このエンドポイントは{permission}の権限を必要とします。" require-permission: "このエンドポイントは{permission}の権限を必要とします。"
has-limit: "レートリミットがあります。" has-limit: "レートリミットがあります。"

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -109,6 +109,11 @@ common:
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
verified-user: "公式アカウント" verified-user: "公式アカウント"
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
show-full-acct: "ユーザー名のホストを省略しない"
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
reversi: reversi:
drawn: "引き分け" drawn: "引き分け"
@ -150,7 +155,9 @@ common:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
notifications: "通知" notifications: "通知"
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
@ -253,6 +260,9 @@ common/views/components/connect-failed.troubleshooter.vue:
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除" flush: "キャッシュの削除"
set-version: "バージョン指定" set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -461,6 +471,7 @@ desktop/views/components/charts.vue:
notes: "投稿" notes: "投稿"
users: "ユーザー" users: "ユーザー"
drive: "ドライブ" drive: "ドライブ"
network: "ネットワーク"
charts: charts:
notes: "投稿の増減 (統合)" notes: "投稿の増減 (統合)"
local-notes: "投稿の増減 (ローカル)" local-notes: "投稿の増減 (ローカル)"
@ -472,6 +483,9 @@ desktop/views/components/charts.vue:
drive-total: "ドライブ使用量の累計" drive-total: "ドライブ使用量の累計"
drive-files: "ドライブのファイル数の増減" drive-files: "ドライブのファイル数の増減"
drive-files-total: "ドライブのファイル数の累計" drive-files-total: "ドライブのファイル数の累計"
network-requests: "リクエスト"
network-time: "応答時間"
network-usage: "通信量"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "ファイル選択中"
upload: "PCからドライブにファイルをアップロード" upload: "PCからドライブにファイルをアップロード"
@ -795,7 +809,12 @@ desktop/views/components/timeline.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
list: "リスト" list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
desktop/views/components/ui.header.vue: desktop/views/components/ui.header.vue:
welcome-back: "おかえりなさい、" welcome-back: "おかえりなさい、"
adjective: "さん" adjective: "さん"
@ -1120,6 +1139,7 @@ mobile/views/pages/home.vue:
local: "ローカル" local: "ローカル"
hybrid: "ソーシャル" hybrid: "ソーシャル"
global: "グローバル" global: "グローバル"
mentions: "あなた宛て"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue: mobile/views/pages/welcome.vue:

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "8.39.0", "version": "8.46.0",
"clientVersion": "1.0.9650", "clientVersion": "1.0.9851",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -20,10 +20,10 @@
"format": "gulp format" "format": "gulp format"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome": "1.1.8", "@fortawesome/fontawesome-svg-core": "1.2.4",
"@fortawesome/fontawesome-free-brands": "5.0.13", "@fortawesome/free-brands-svg-icons": "5.3.1",
"@fortawesome/fontawesome-free-regular": "5.0.13", "@fortawesome/free-regular-svg-icons": "5.3.1",
"@fortawesome/fontawesome-free-solid": "5.0.13", "@fortawesome/free-solid-svg-icons": "5.3.1",
"@koa/cors": "2.2.2", "@koa/cors": "2.2.2",
"@prezzemolo/rap": "0.1.2", "@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
@ -60,7 +60,7 @@
"@types/mocha": "5.2.3", "@types/mocha": "5.2.3",
"@types/mongodb": "3.1.7", "@types/mongodb": "3.1.7",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "10.9.4", "@types/node": "10.10.0",
"@types/portscanner": "2.1.0", "@types/portscanner": "2.1.0",
"@types/pug": "2.0.4", "@types/pug": "2.0.4",
"@types/qrcode": "1.2.0", "@types/qrcode": "1.2.0",
@ -101,7 +101,6 @@
"dompurify": "1.0.5", "dompurify": "1.0.5",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"elasticsearch": "15.1.1", "elasticsearch": "15.1.1",
"element-ui": "2.4.6",
"emojilib": "2.3.0", "emojilib": "2.3.0",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "5.0.1", "eslint": "5.0.1",
@ -132,7 +131,6 @@
"insert-text-at-cursor": "0.1.1", "insert-text-at-cursor": "0.1.1",
"is-root": "2.0.0", "is-root": "2.0.0",
"is-url": "1.2.4", "is-url": "1.2.4",
"jquery": "3.3.1",
"js-yaml": "3.12.0", "js-yaml": "3.12.0",
"jsdom": "11.12.0", "jsdom": "11.12.0",
"koa": "2.5.1", "koa": "2.5.1",
@ -160,8 +158,6 @@
"ms": "2.1.1", "ms": "2.1.1",
"nan": "2.11.0", "nan": "2.11.0",
"nested-property": "0.0.7", "nested-property": "0.0.7",
"node-sass": "4.9.3",
"node-sass-json-importer": "4.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0", "on-build-webpack": "0.1.0",
@ -179,6 +175,7 @@
"redis": "2.8.0", "redis": "2.8.0",
"request": "2.88.0", "request": "2.88.0",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.5",
"request-stats": "3.0.0",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
@ -209,7 +206,7 @@
"v-animate-css": "0.0.2", "v-animate-css": "0.0.2",
"vue": "2.5.17", "vue": "2.5.17",
"vue-chartjs": "3.4.0", "vue-chartjs": "3.4.0",
"vue-cropperjs": "2.2.1", "vue-cropperjs": "2.2.2",
"vue-js-modal": "1.3.26", "vue-js-modal": "1.3.26",
"vue-json-tree-view": "2.1.4", "vue-json-tree-view": "2.1.4",
"vue-loader": "15.4.2", "vue-loader": "15.4.2",

View File

@ -0,0 +1,13 @@
import Stream from './stream';
import MiOS from '../../../mios';
export class HashtagStream extends Stream {
constructor(os: MiOS, me, q) {
super(os, 'hashtag', me ? {
i: me.token,
q: JSON.stringify(q)
} : {
q: JSON.stringify(q)
});
}
}

View File

@ -50,15 +50,15 @@
</div> </div>
<div class="player" v-if="game.isEnded"> <div class="player" v-if="game.isEnded">
<el-button-group> <div>
<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button> <button @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</button>
<el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</el-button> <button @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</button>
</el-button-group> </div>
<span>{{ logPos }} / {{ logs.length }}</span> <span>{{ logPos }} / {{ logs.length }}</span>
<el-button-group> <div>
<el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</el-button> <button @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</button>
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button> <button @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</button>
</el-button-group> </div>
</div> </div>
<div class="info"> <div class="info">

View File

@ -3,7 +3,6 @@
<h1>%i18n:@title%</h1> <h1>%i18n:@title%</h1>
<p>%i18n:@sub-title%</p> <p>%i18n:@sub-title%</p>
<div class="play"> <div class="play">
<!--<el-button round>フリーマッチ(準備中)</el-button>-->
<form-button primary round @click="match">%i18n:@invite%</form-button> <form-button primary round @click="match">%i18n:@invite%</form-button>
<details> <details>
<summary>%i18n:@rule%</summary> <summary>%i18n:@rule%</summary>

View File

@ -59,11 +59,6 @@
</header> </header>
<div> <div>
<el-alert v-for="message in messages"
:title="message.text"
:type="message.type"
:key="message.id"/>
<template v-for="item in form"> <template v-for="item in form">
<mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch> <mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch>
@ -93,7 +88,7 @@
</header> </header>
<div> <div>
<el-input v-model="item.value" @change="onChangeForm(item)"/> <input v-model="item.value" @change="onChangeForm(item)"/>
</div> </div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,90 @@
<template>
<div class="mk-media-banner">
<div class="sensitive" v-if="media.isSensitive && hide" @click="hide = false">
<span class="icon">%fa:exclamation-triangle%</span>
<b>%i18n:@sensitive%</b>
<span>%i18n:@click-to-show%</span>
</div>
<div class="audio" v-else-if="media.type.startsWith('audio')">
<audio class="audio"
:src="media.url"
:title="media.name"
controls
ref="audio"
preload="metadata" />
</div>
<a class="download" v-else
:href="media.url"
:title="media.name"
:download="media.name"
>
<span class="icon">%fa:download%</span>
<b>{{ media.name }}</b>
</a>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
media: {
type: Object,
required: true
}
},
data() {
return {
hide: true
};
}
})
</script>
<style lang="stylus" scoped>
root(isDark)
width 100%
border-radius 4px
margin-top 4px
overflow hidden
> .download,
> .sensitive
display flex
align-items center
font-size 12px
padding 8px 12px
white-space nowrap
> *
display block
> b
overflow hidden
text-overflow ellipsis
> *:not(:last-child)
margin-right .2em
> .icon
font-size 1.6em
> .download
background isDark ? #21242d : #f7f7f7
> .sensitive
background #111
color #fff
> .audio
.audio
display block
width 100%
.mk-media-banner[data-darkmode]
root(true)
.mk-media-banner:not([data-darkmode])
root(false)
</style>

View File

@ -1,18 +1,27 @@
<template> <template>
<div class="mk-media-list"> <div class="mk-media-list">
<div :data-count="mediaList.length" ref="grid"> <template v-for="media in mediaList.filter(media => !previewable(media))">
<template v-for="media in mediaList"> <x-banner :media="media" :key="media.id"/>
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
<mk-media-image :image="media" :key="media.id" v-else :raw="raw"/>
</template> </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">
<template v-for="media in mediaList">
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
<mk-media-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
</template>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XBanner from './media-banner.vue';
export default Vue.extend({ export default Vue.extend({
components: {
XBanner
},
props: { props: {
mediaList: { mediaList: {
required: true required: true
@ -22,15 +31,25 @@ export default Vue.extend({
} }
}, },
mounted() { mounted() {
// for Safari bug //#region for Safari bug
if (this.$refs.grid) {
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px'; this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
} }
//#endregion
},
methods: {
previewable(file) {
return file.type.startsWith('video') || file.type.startsWith('image');
}
}
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-media-list .mk-media-list
> .gird-container
width 100% width 100%
margin-top 4px
&:before &:before
content '' content ''

View File

@ -108,7 +108,7 @@ export default Vue.extend({
easing: 'easeInBack', easing: 'easeInBack',
complete: () => { complete: () => {
this.$emit('closed'); this.$emit('closed');
this.$destroy(); this.destroyDom();
} }
}); });
} }

View File

@ -64,7 +64,7 @@ export default Vue.extend({
(this as any).api('i/pin', { (this as any).api('i/pin', {
noteId: this.note.id noteId: this.note.id
}).then(() => { }).then(() => {
this.$destroy(); this.destroyDom();
}); });
}, },
@ -73,7 +73,7 @@ export default Vue.extend({
(this as any).api('notes/delete', { (this as any).api('notes/delete', {
noteId: this.note.id noteId: this.note.id
}).then(() => { }).then(() => {
this.$destroy(); this.destroyDom();
}); });
}, },
@ -81,13 +81,13 @@ export default Vue.extend({
(this as any).api('notes/favorites/create', { (this as any).api('notes/favorites/create', {
noteId: this.note.id noteId: this.note.id
}).then(() => { }).then(() => {
this.$destroy(); this.destroyDom();
}); });
}, },
closed() { closed() {
this.$nextTick(() => { this.$nextTick(() => {
this.$destroy(); this.destroyDom();
}); });
} }
} }

View File

@ -95,7 +95,7 @@ export default Vue.extend({
reaction: reaction reaction: reaction
}).then(() => { }).then(() => {
if (this.cb) this.cb(); if (this.cb) this.cb();
this.$destroy(); this.destroyDom();
}); });
}, },
onMouseover(e) { onMouseover(e) {
@ -120,7 +120,7 @@ export default Vue.extend({
scale: 0.5, scale: 0.5,
duration: 200, duration: 200,
easing: 'easeInBack', easing: 'easeInBack',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
} }
} }

View File

@ -3,8 +3,7 @@
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p> <p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<!-- <transition-group v-else tag="div" name="chart"> --> <transition-group v-else tag="div" name="chart">
<div>
<div v-for="stat in stats" :key="stat.tag"> <div v-for="stat in stats" :key="stat.tag">
<div class="tag"> <div class="tag">
<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link> <router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
@ -12,8 +11,7 @@
</div> </div>
<x-chart class="chart" :src="stat.chart"/> <x-chart class="chart" :src="stat.chart"/>
</div> </div>
</div> </transition-group>
<!-- </transition-group> -->
</div> </div>
</template> </template>

View File

@ -101,7 +101,7 @@ export default Vue.extend({
this.$store.commit('device/setVisibility', visibility); this.$store.commit('device/setVisibility', visibility);
} }
this.$emit('chosen', visibility); this.$emit('chosen', visibility);
this.$destroy(); this.destroyDom();
}, },
close() { close() {
(this.$refs.backdrop as any).style.pointerEvents = 'none'; (this.$refs.backdrop as any).style.pointerEvents = 'none';
@ -119,7 +119,7 @@ export default Vue.extend({
scale: 0.5, scale: 0.5,
duration: 200, duration: 200,
easing: 'easeInBack', easing: 'easeInBack',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
} }
} }

View File

@ -167,7 +167,7 @@ class Autocomplete {
private close() { private close() {
if (this.suggestion == null) return; if (this.suggestion == null) return;
this.suggestion.$destroy(); this.suggestion.destroyDom();
this.suggestion = null; this.suggestion = null;
this.textarea.focus(); this.textarea.focus();

View File

@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
// Style // Style
import './style.styl'; import './style.styl';
import '../../element.scss';
import init from '../init'; import init from '../init';
import fuckAdBlock from '../common/scripts/fuck-ad-block'; import fuckAdBlock from '../common/scripts/fuck-ad-block';

View File

@ -19,6 +19,11 @@
<option value="drive">%i18n:@charts.drive%</option> <option value="drive">%i18n:@charts.drive%</option>
<option value="drive-total">%i18n:@charts.drive-total%</option> <option value="drive-total">%i18n:@charts.drive-total%</option>
</optgroup> </optgroup>
<optgroup label="%i18n:@network%">
<option value="network-requests">%i18n:@charts.network-requests%</option>
<option value="network-time">%i18n:@charts.network-time%</option>
<option value="network-usage">%i18n:@charts.network-usage%</option>
</optgroup>
</select> </select>
<div> <div>
<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span> <span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span>
@ -41,7 +46,10 @@ const colors = {
localPlus: 'rgb(52, 178, 118)', localPlus: 'rgb(52, 178, 118)',
remotePlus: 'rgb(158, 255, 209)', remotePlus: 'rgb(158, 255, 209)',
localMinus: 'rgb(255, 97, 74)', localMinus: 'rgb(255, 97, 74)',
remoteMinus: 'rgb(255, 149, 134)' remoteMinus: 'rgb(255, 149, 134)',
incoming: 'rgb(52, 178, 118)',
outgoing: 'rgb(255, 97, 74)',
}; };
const rgba = (color: string): string => { const rgba = (color: string): string => {
@ -75,6 +83,9 @@ export default Vue.extend({
case 'drive-total': return this.driveTotalChart(); case 'drive-total': return this.driveTotalChart();
case 'drive-files': return this.driveFilesChart(); case 'drive-files': return this.driveFilesChart();
case 'drive-files-total': return this.driveFilesTotalChart(); case 'drive-files-total': return this.driveFilesTotalChart();
case 'network-requests': return this.networkRequestsChart();
case 'network-time': return this.networkTimeChart();
case 'network-usage': return this.networkUsageChart();
} }
}, },
@ -89,7 +100,7 @@ export default Vue.extend({
created() { created() {
(this as any).api('chart', { (this as any).api('chart', {
limit: 32 limit: 35
}).then(chart => { }).then(chart => {
this.chart = chart; this.chart = chart;
}); });
@ -544,8 +555,96 @@ export default Vue.extend({
} }
} }
}]; }];
},
networkRequestsChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
requests: x.network.requests
}));
return [{
datasets: [{
label: 'Requests',
fill: true,
backgroundColor: rgba(colors.localPlus),
borderColor: colors.localPlus,
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.requests }))
}]
}];
},
networkTimeChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0,
}));
return [{
datasets: [{
label: 'Avg time (ms)',
fill: true,
backgroundColor: rgba(colors.localPlus),
borderColor: colors.localPlus,
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.time }))
}]
}];
},
networkUsageChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
incoming: x.network.incomingBytes,
outgoing: x.network.outgoingBytes
}));
return [{
datasets: [{
label: 'Incoming',
fill: true,
backgroundColor: rgba(colors.incoming),
borderColor: colors.incoming,
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.incoming }))
}, {
label: 'Outgoing',
fill: true,
backgroundColor: rgba(colors.outgoing),
borderColor: colors.outgoing,
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.outgoing }))
}]
}, {
scales: {
yAxes: [{
ticks: {
callback: value => {
return Vue.filter('bytes')(value, 1);
} }
} }
}]
},
tooltips: {
callbacks: {
label: (tooltipItem, data) => {
const label = data.datasets[tooltipItem.datasetIndex].label || '';
return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`;
}
}
}
}];
},
}
}); });
</script> </script>
@ -582,6 +681,6 @@ export default Vue.extend({
> div > div
> * > *
display block display block
height 320px height 350px
</style> </style>

View File

@ -64,7 +64,7 @@ export default Vue.extend({
}); });
this.$emit('closed'); this.$emit('closed');
this.$destroy(); this.destroyDom();
} }
} }
}); });

View File

@ -78,7 +78,7 @@ export default Vue.extend({
scale: 0.8, scale: 0.8,
duration: 300, duration: 300,
easing: [ 0.5, -0.5, 1, 0.5 ], easing: [ 0.5, -0.5, 1, 0.5 ],
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
}, },
onBgClick() { onBgClick() {

View File

@ -14,7 +14,7 @@
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p> <p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p>
<a class="refresh" @click="refresh">%i18n:@refresh%</a> <a class="refresh" @click="refresh">%i18n:@refresh%</a>
<button class="close" @click="$destroy()" title="%i18n:@close%">%fa:times%</button> <button class="close" @click="destroyDom()" title="%i18n:@close%">%fa:times%</button>
</div> </div>
</template> </template>

View File

@ -26,7 +26,7 @@ export default Vue.extend({
opacity: 0, opacity: 0,
duration: 100, duration: 100,
easing: 'linear', easing: 'linear',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
} }
} }

View File

@ -27,12 +27,13 @@ export default Vue.extend({
}, },
raw: { raw: {
default: false default: false
},
hide: {
type: Boolean,
default: true
} }
}, },
data() {
return {
hide: true
};
},
computed: { computed: {
style(): any { style(): any {
return { return {
@ -89,7 +90,7 @@ export default Vue.extend({
text-align center text-align center
font-size 12px font-size 12px
> b > *
display block display block
</style> </style>

View File

@ -28,7 +28,7 @@ export default Vue.extend({
opacity: 0, opacity: 0,
duration: 100, duration: 100,
easing: 'linear', easing: 'linear',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
} }
} }

View File

@ -36,12 +36,13 @@ export default Vue.extend({
}, },
inlinePlayable: { inlinePlayable: {
default: false default: false
},
hide: {
type: Boolean,
default: true
} }
}, },
data() {
return {
hide: true
};
},
computed: { computed: {
imageStyle(): any { imageStyle(): any {
return { return {
@ -79,7 +80,6 @@ export default Vue.extend({
justify-content center justify-content center
align-items center align-items center
font-size 3.5em font-size 3.5em
cursor zoom-in cursor zoom-in
overflow hidden overflow hidden
background-position center background-position center
@ -101,5 +101,4 @@ export default Vue.extend({
> b > b
display block display block
</style> </style>

View File

@ -10,7 +10,7 @@
</div> </div>
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<transition-group name="mk-notes" class="notes transition" tag="div"> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
<template v-for="(note, i) in _notes"> <template v-for="(note, i) in _notes">
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
@ -18,7 +18,7 @@
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
</transition-group> </component>
<footer v-if="more"> <footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">

View File

@ -2,7 +2,7 @@
<div class="mk-notifications"> <div class="mk-notifications">
<div class="notifications" v-if="notifications.length != 0"> <div class="notifications" v-if="notifications.length != 0">
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<transition-group name="mk-notifications" class="transition" tag="div"> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
<template v-for="(notification, i) in _notifications"> <template v-for="(notification, i) in _notifications">
<div class="notification" :class="notification.type" :key="notification.id"> <div class="notification" :class="notification.type" :key="notification.id">
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
@ -96,7 +96,7 @@
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
</transition-group> </component>
</div> </div>
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }} <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}

View File

@ -49,7 +49,7 @@
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post"> <button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/> {{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
</button> </button>
<input ref="file" type="file" accept="image/*" multiple="multiple" tabindex="-1" @change="onChangeFile"/> <input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
<div class="dropzone" v-if="draghover"></div> <div class="dropzone" v-if="draghover"></div>
</div> </div>
</template> </template>

View File

@ -1,13 +1,19 @@
<template> <template>
<mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy"> <mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy">
<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span> <span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
<mk-settings @done="close"/> <mk-settings :initial-page="initialPage" @done="close"/>
</mk-window> </mk-window>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: {
initialPage: {
type: String,
required: false
}
},
methods: { methods: {
close() { close() {
(this as any).$refs.window.close(); (this as any).$refs.window.close();

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="root"> <div class="root">
<template v-if="!fetching"> <template v-if="!fetching">
<el-progress :text-inside="true" :stroke-width="18" :percentage="Math.floor((usage / capacity) * 100)"/>
<p><b>{{ capacity | bytes }}</b>%i18n:max%<b>{{ usage | bytes }}</b>%i18n:in-use%</p> <p><b>{{ capacity | bytes }}</b>%i18n:max%<b>{{ usage | bytes }}</b>%i18n:in-use%</p>
</template> </template>
</div> </div>

View File

@ -19,7 +19,7 @@
</label> </label>
<label class="ui from group"> <label class="ui from group">
<p>%i18n:@birthday%</p> <p>%i18n:@birthday%</p>
<el-date-picker v-model="birthday" type="date" value-format="yyyy-MM-dd"/> <input type="date" v-model="birthday"/>
</label> </label>
<button class="ui primary" @click="save">%i18n:@save%</button> <button class="ui primary" @click="save">%i18n:@save%</button>
<section> <section>

View File

@ -0,0 +1,65 @@
<template>
<div class="vfcitkilproprqtbnpoertpsziierwzi">
<div v-for="timeline in timelines" class="timeline">
<ui-input v-model="timeline.title" @change="save">
<span>%i18n:@title%</span>
</ui-input>
<ui-textarea :value="timeline.query ? timeline.query.map(tags => tags.join(' ')).join('\n') : ''" @input="onQueryChange(timeline, $event)">
<span>%i18n:@query%</span>
</ui-textarea>
<ui-button class="save" @click="save">%i18n:@save%</ui-button>
</div>
<ui-button class="add" @click="add">%i18n:@add%</ui-button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
export default Vue.extend({
data() {
return {
timelines: this.$store.state.settings.tagTimelines
};
},
methods: {
add() {
this.timelines.push({
id: uuid(),
title: '',
query: ''
});
this.save();
},
save() {
this.$store.dispatch('settings/set', { key: 'tagTimelines', value: this.timelines });
},
onQueryChange(timeline, value) {
timeline.query = value.split('\n').map(tags => tags.split(' '));
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
> .timeline
padding-bottom 16px
border-bottom solid 1px rgba(#000, 0.1)
> .add
margin-top 16px
.vfcitkilproprqtbnpoertpsziierwzi[data-darkmode]
root(true)
.vfcitkilproprqtbnpoertpsziierwzi:not([data-darkmode])
root(false)
</style>

View File

@ -5,6 +5,7 @@
<p :class="{ active: page == 'web' }" @mousedown="page = 'web'">%fa:desktop .fw%Web</p> <p :class="{ active: page == 'web' }" @mousedown="page = 'web'">%fa:desktop .fw%Web</p>
<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p> <p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p>
<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:@drive%</p> <p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:@drive%</p>
<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p>
<p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p> <p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p>
<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p> <p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p>
<p :class="{ active: page == 'twitter' }" @mousedown="page = 'twitter'">%fa:B twitter .fw%Twitter</p> <p :class="{ active: page == 'twitter' }" @mousedown="page = 'twitter'">%fa:B twitter .fw%Twitter</p>
@ -60,6 +61,7 @@
<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button> <button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/> <mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
<mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/> <mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/>
<mk-switch v-model="reduceMotion" text="%i18n:common.reduce-motion%"/>
<mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/> <mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/>
<mk-switch v-model="showFullAcct" text="%i18n:common.show-full-acct%"/> <mk-switch v-model="showFullAcct" text="%i18n:common.show-full-acct%"/>
<mk-switch v-model="gradientWindowHeader" text="%i18n:@gradient-window-header%"/> <mk-switch v-model="gradientWindowHeader" text="%i18n:@gradient-window-header%"/>
@ -87,13 +89,11 @@
<span>%i18n:@enable-sounds-desc%</span> <span>%i18n:@enable-sounds-desc%</span>
</mk-switch> </mk-switch>
<label>%i18n:@volume%</label> <label>%i18n:@volume%</label>
<el-slider <input type="range"
v-model="soundVolume" v-model="soundVolume"
:show-input="true"
:format-tooltip="v => `${v * 100}%`"
:disabled="!enableSounds" :disabled="!enableSounds"
:max="1" max="1"
:step="0.1" step="0.1"
/> />
<button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button> <button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button>
</section> </section>
@ -105,14 +105,15 @@
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>%i18n:@language%</h1> <h1>%i18n:@language%</h1>
<el-select v-model="lang" placeholder="%i18n:@pick-language%"> <select v-model="lang" placeholder="%i18n:@pick-language%">
<el-option-group label="%i18n:@recommended%"> <optgroup label="%i18n:@recommended%">
<el-option label="%i18n:@auto%" :value="null"/> <option value="">%i18n:@auto%</option>
</el-option-group> </optgroup>
<el-option-group label="%i18n:@specify-language%">
<el-option v-for="x in langs" :label="x[1]" :value="x[0]" :key="x[0]"/> <optgroup label="%i18n:@specify-language%">
</el-option-group> <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</el-select> </optgroup>
</select>
<div class="none ui info"> <div class="none ui info">
<p>%fa:info-circle%%i18n:@language-desc%</p> <p>%fa:info-circle%%i18n:@language-desc%</p>
</div> </div>
@ -138,6 +139,11 @@
<x-drive/> <x-drive/>
</section> </section>
<section class="hashtags" v-show="page == 'hashtags'">
<h1>%i18n:@tags%</h1>
<x-tags/>
</section>
<section class="mute" v-show="page == 'mute'"> <section class="mute" v-show="page == 'mute'">
<h1>%i18n:@mute%</h1> <h1>%i18n:@mute%</h1>
<x-mute/> <x-mute/>
@ -207,10 +213,6 @@
<mk-switch v-model="enableExperimentalFeatures" text="%i18n:@experimental%"> <mk-switch v-model="enableExperimentalFeatures" text="%i18n:@experimental%">
<span>%i18n:@experimental-desc%</span> <span>%i18n:@experimental-desc%</span>
</mk-switch> </mk-switch>
<details v-if="debug">
<summary>%i18n:@tools%</summary>
<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
</details>
</section> </section>
</div> </div>
</div> </div>
@ -226,9 +228,9 @@ import XApi from './settings.api.vue';
import XApps from './settings.apps.vue'; import XApps from './settings.apps.vue';
import XSignins from './settings.signins.vue'; import XSignins from './settings.signins.vue';
import XDrive from './settings.drive.vue'; import XDrive from './settings.drive.vue';
import XTags from './settings.tags.vue';
import { url, langs, version } from '../../../config'; import { url, langs, version } from '../../../config';
import checkForUpdate from '../../../common/scripts/check-for-update'; import checkForUpdate from '../../../common/scripts/check-for-update';
import MkTaskManager from './taskmanager.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -239,11 +241,18 @@ export default Vue.extend({
XApi, XApi,
XApps, XApps,
XSignins, XSignins,
XDrive XDrive,
XTags
},
props: {
initialPage: {
type: String,
required: false
}
}, },
data() { data() {
return { return {
page: 'profile', page: this.initialPage || 'profile',
meta: null, meta: null,
version, version,
langs, langs,
@ -252,6 +261,11 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
reduceMotion: {
get() { return this.$store.state.device.reduceMotion; },
set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); }
},
apiViaStream: { apiViaStream: {
get() { return this.$store.state.device.apiViaStream; }, get() { return this.$store.state.device.apiViaStream; },
set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); } set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); }
@ -408,9 +422,6 @@ export default Vue.extend({
}); });
}, },
methods: { methods: {
taskmngr() {
(this as any).os.new(MkTaskManager);
},
customizeHome() { customizeHome() {
this.$router.push('/i/customize-home'); this.$router.push('/i/customize-home');
this.$emit('done'); this.$emit('done');

View File

@ -1,219 +0,0 @@
<template>
<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
<span slot="header" :class="$style.header">%fa:stethoscope%%i18n:@title%</span>
<el-tabs :class="$style.content">
<el-tab-pane label="Requests">
<el-table
:data="os.requests"
style="width: 100%"
:default-sort="{prop: 'date', order: 'descending'}"
>
<el-table-column type="expand">
<template slot-scope="props">
<pre>{{ props.row.data }}</pre>
<pre>{{ props.row.res }}</pre>
</template>
</el-table-column>
<el-table-column
label="Requested at"
prop="date"
sortable
>
<template slot-scope="scope">
<b style="margin-right: 8px">{{ scope.row.date.getTime() }}</b>
<span>(<mk-time :time="scope.row.date"/>)</span>
</template>
</el-table-column>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name }}</b>
</template>
</el-table-column>
<el-table-column
label="Status"
>
<template slot-scope="scope">
<span>{{ scope.row.status || '(pending)' }}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Streams">
<el-table
:data="os.connections"
style="width: 100%"
>
<el-table-column
label="Uptime"
>
<template slot-scope="scope">
<mk-timer v-if="scope.row.connectedAt" :time="scope.row.connectedAt"/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name == '' ? '[Home]' : scope.row.name }}</b>
</template>
</el-table-column>
<el-table-column
label="User"
>
<template slot-scope="scope">
<span>{{ scope.row.user || '(anonymous)' }}</span>
</template>
</el-table-column>
<el-table-column
prop="state"
label="State"
/>
<el-table-column
prop="in"
label="In"
/>
<el-table-column
prop="out"
label="Out"
/>
</el-table>
</el-tab-pane>
<el-tab-pane label="Streams (Inspect)">
<el-tabs type="card" style="height:50%">
<el-tab-pane v-for="c in os.connections" :label="c.name == '' ? '[Home]' : c.name" :key="c.id" :name="c.id" ref="connectionsTab">
<div style="padding: 12px 0 0 12px">
<el-button size="mini" @click="send(c)">Send</el-button>
<el-button size="mini" type="warning" @click="c.isSuspended = true" v-if="!c.isSuspended">Suspend</el-button>
<el-button size="mini" type="success" @click="c.isSuspended = false" v-else>Resume</el-button>
<el-button size="mini" type="danger" @click="c.close">Disconnect</el-button>
</div>
<el-table
:data="c.inout"
style="width: 100%"
:default-sort="{prop: 'at', order: 'descending'}"
>
<el-table-column type="expand">
<template slot-scope="props">
<pre>{{ props.row.data }}</pre>
</template>
</el-table-column>
<el-table-column
label="Date"
prop="at"
sortable
>
<template slot-scope="scope">
<b style="margin-right: 8px">{{ scope.row.at.getTime() }}</b>
<span>(<mk-time :time="scope.row.at"/>)</span>
</template>
</el-table-column>
<el-table-column
label="Type"
>
<template slot-scope="scope">
<span>{{ getMessageType(scope.row.data) }}</span>
</template>
</el-table-column>
<el-table-column
label="Incoming / Outgoing"
prop="type"
/>
</el-table>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Windows">
<el-table
:data="Array.from(os.windows.windows)"
style="width: 100%"
>
<el-table-column
label="Name"
>
<template slot-scope="scope">
<b>{{ scope.row.name || '(unknown)' }}</b>
</template>
</el-table-column>
<el-table-column
label="Operations"
>
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="scope.row.close">Close</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</mk-window>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
mounted() {
(this as any).os.windows.on('added', this.onWindowsChanged);
(this as any).os.windows.on('removed', this.onWindowsChanged);
},
beforeDestroy() {
(this as any).os.windows.off('added', this.onWindowsChanged);
(this as any).os.windows.off('removed', this.onWindowsChanged);
},
methods: {
getMessageType(data): string {
return data.type ? data.type : '-';
},
onWindowsChanged() {
this.$forceUpdate();
},
send(c) {
(this as any).apis.input({
title: 'Send a JSON message',
allowEmpty: false
}).then(json => {
c.send(JSON.parse(json));
});
}
}
});
</script>
<style lang="stylus" module>
.header
> [data-fa]
margin-right 4px
.content
height 100%
overflow auto
</style>
<style>
.el-tabs__header {
margin-bottom: 0 !important;
}
.el-tabs__item {
padding: 0 20px !important;
}
</style>

View File

@ -15,6 +15,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { HashtagStream } from '../../../common/scripts/streaming/hashtag';
const fetchLimit = 10; const fetchLimit = 10;
@ -23,6 +24,9 @@ export default Vue.extend({
src: { src: {
type: String, type: String,
required: true required: true
},
tagTl: {
required: false
} }
}, },
@ -31,9 +35,17 @@ export default Vue.extend({
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
existMore: false, existMore: false,
streamManager: null,
connection: null, connection: null,
connectionId: null, connectionId: null,
date: null date: null,
baseQuery: {
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
},
query: {},
endpoint: null
}; };
}, },
@ -42,37 +54,102 @@ export default Vue.extend({
return this.$store.state.i.followingCount == 0; return this.$store.state.i.followingCount == 0;
}, },
stream(): any {
switch (this.src) {
case 'home': return (this as any).os.stream;
case 'local': return (this as any).os.streams.localTimelineStream;
case 'hybrid': return (this as any).os.streams.hybridTimelineStream;
case 'global': return (this as any).os.streams.globalTimelineStream;
}
},
endpoint(): string {
switch (this.src) {
case 'home': return 'notes/timeline';
case 'local': return 'notes/local-timeline';
case 'hybrid': return 'notes/hybrid-timeline';
case 'global': return 'notes/global-timeline';
}
},
canFetchMore(): boolean { canFetchMore(): boolean {
return !this.moreFetching && !this.fetching && this.existMore; return !this.moreFetching && !this.fetching && this.existMore;
} }
}, },
mounted() { mounted() {
this.connection = this.stream.getConnection(); const prepend = note => {
this.connectionId = this.stream.use(); (this.$refs.timeline as any).prepend(note);
};
this.connection.on('note', this.onNote); if (this.src == 'tag') {
if (this.src == 'home') { this.endpoint = 'notes/search_by_tag';
this.connection.on('follow', this.onChangeFollowing); this.query = {
this.connection.on('unfollow', this.onChangeFollowing); query: this.tagTl.query
};
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.connection.close();
});
} else if (this.src == 'home') {
this.endpoint = 'notes/timeline';
const onChangeFollowing = () => {
this.fetch();
};
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.connection.on('follow', onChangeFollowing);
this.connection.on('unfollow', onChangeFollowing);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.connection.off('follow', onChangeFollowing);
this.connection.off('unfollow', onChangeFollowing);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'local') {
this.endpoint = 'notes/local-timeline';
this.streamManager = (this as any).os.streams.localTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'hybrid') {
this.endpoint = 'notes/hybrid-timeline';
this.streamManager = (this as any).os.streams.hybridTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'global') {
this.endpoint = 'notes/global-timeline';
this.streamManager = (this as any).os.streams.globalTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'mentions') {
this.endpoint = 'notes/mentions';
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('mention', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('mention', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'messages') {
this.endpoint = 'notes/mentions';
this.query = {
visibility: 'specified'
};
const onNote = note => {
if (note.visibility == 'specified') {
prepend(note);
}
};
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('mention', onNote);
this.$once('beforeDestroy', () => {
this.connection.off('mention', onNote);
this.streamManager.dispose(this.connectionId);
});
} }
document.addEventListener('keydown', this.onKeydown); document.addEventListener('keydown', this.onKeydown);
@ -81,13 +158,7 @@ export default Vue.extend({
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('note', this.onNote); this.$emit('beforeDestroy');
if (this.src == 'home') {
this.connection.off('follow', this.onChangeFollowing);
this.connection.off('unfollow', this.onChangeFollowing);
}
this.stream.dispose(this.connectionId);
document.removeEventListener('keydown', this.onKeydown); document.removeEventListener('keydown', this.onKeydown);
}, },
@ -96,13 +167,10 @@ export default Vue.extend({
this.fetching = true; this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => { (this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : undefined
includeMyRenotes: this.$store.state.settings.showMyRenotes, }, this.baseQuery, this.query)).then(notes => {
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
this.existMore = true; this.existMore = true;
@ -119,13 +187,10 @@ export default Vue.extend({
this.moreFetching = true; this.moreFetching = true;
const promise = (this as any).api(this.endpoint, { const promise = (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id
includeMyRenotes: this.$store.state.settings.showMyRenotes, }, this.baseQuery, this.query));
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
});
promise.then(notes => { promise.then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
@ -140,15 +205,6 @@ export default Vue.extend({
return promise; return promise;
}, },
onNote(note) {
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
onChangeFollowing() {
this.fetch();
},
focus() { focus() {
(this.$refs.timeline as any).focus(); (this.$refs.timeline as any).focus();
}, },

View File

@ -5,13 +5,22 @@
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span> <span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span> <span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span> <span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl">%fa:hashtag% {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span> <span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
<button @click="chooseList" title="%i18n:@list%">%fa:list%</button> <div class="buttons">
<button :data-active="src == 'mentions'" @click="src = 'mentions'" title="%i18n:@mentions%">%fa:at%</button>
<button :data-active="src == 'messages'" @click="src = 'messages'" title="%i18n:@messages%">%fa:envelope R%</button>
<button @click="chooseTag" title="%i18n:@hashtag%" ref="tagButton">%fa:hashtag%</button>
<button @click="chooseList" title="%i18n:@list%" ref="listButton">%fa:list%</button>
</div>
</header> </header>
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/> <x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/> <x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/> <x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/> <x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/> <mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div> </div>
</template> </template>
@ -19,7 +28,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XCore from './timeline.core.vue'; import XCore from './timeline.core.vue';
import MkUserListsWindow from './user-lists-window.vue'; import Menu from '../../../common/views/components/menu.vue';
import MkSettingsWindow from './settings-window.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -30,6 +40,7 @@ export default Vue.extend({
return { return {
src: 'home', src: 'home',
list: null, list: null,
tagTl: null,
enableLocalTimeline: false enableLocalTimeline: false
}; };
}, },
@ -39,8 +50,14 @@ export default Vue.extend({
this.saveSrc(); this.saveSrc();
}, },
list() { list(x) {
this.saveSrc(); this.saveSrc();
if (x != null) this.tagTl = null;
},
tagTl(x) {
this.saveSrc();
if (x != null) this.list = null;
} }
}, },
@ -53,6 +70,8 @@ export default Vue.extend({
this.src = this.$store.state.device.tl.src; this.src = this.$store.state.device.tl.src;
if (this.src == 'list') { if (this.src == 'list') {
this.list = this.$store.state.device.tl.arg; this.list = this.$store.state.device.tl.arg;
} else if (this.src == 'tag') {
this.tagTl = this.$store.state.device.tl.arg;
} }
} else if (this.$store.state.i.followingCount == 0) { } else if (this.$store.state.i.followingCount == 0) {
this.src = 'hybrid'; this.src = 'hybrid';
@ -69,7 +88,7 @@ export default Vue.extend({
saveSrc() { saveSrc() {
this.$store.commit('device/setTl', { this.$store.commit('device/setTl', {
src: this.src, src: this.src,
arg: this.list arg: this.src == 'list' ? this.list : this.tagTl
}); });
}, },
@ -77,12 +96,74 @@ export default Vue.extend({
(this.$refs.tl as any).warp(date); (this.$refs.tl as any).warp(date);
}, },
chooseList() { async chooseList() {
const w = (this as any).os.new(MkUserListsWindow); const lists = await (this as any).api('users/lists/list');
w.$once('choosen', list => {
let menu = [{
icon: '%fa:plus%',
text: '%i18n:@add-list%',
action: () => {
(this as any).apis.input({
title: '%i18n:@list-name%',
}).then(async title => {
const list = await (this as any).api('users/lists/create', {
title
});
this.list = list; this.list = list;
this.src = 'list'; this.src = 'list';
w.close(); });
}
}];
if (lists.length > 0) {
menu.push(null);
}
menu = menu.concat(lists.map(list => ({
icon: '%fa:list%',
text: list.title,
action: () => {
this.list = list;
this.src = 'list';
}
})));
this.os.new(Menu, {
source: this.$refs.listButton,
compact: false,
items: menu
});
},
chooseTag() {
let menu = [{
icon: '%fa:plus%',
text: '%i18n:@add-tag-timeline%',
action: () => {
(this as any).os.new(MkSettingsWindow, {
initialPage: 'hashtags'
});
}
}];
if (this.$store.state.settings.tagTimelines.length > 0) {
menu.push(null);
}
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
icon: '%fa:hashtag%',
text: t.title,
action: () => {
this.tagTl = t;
this.src = 'tag';
}
})));
this.os.new(Menu, {
source: this.$refs.tagButton,
compact: false,
items: menu
}); });
} }
} }
@ -104,13 +185,15 @@ root(isDark)
border-radius 6px 6px 0 0 border-radius 6px 6px 0 0
box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08) box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08)
> button > .buttons
position absolute position absolute
z-index 2 z-index 2
top 0 top 0
right 0 right 0
padding 0 padding-right 8px
width 42px
> button
padding 0 8px
font-size 0.9em font-size 0.9em
line-height 42px line-height 42px
color isDark ? #9baec8 : #ccc color isDark ? #9baec8 : #ccc
@ -121,6 +204,20 @@ root(isDark)
&:active &:active
color isDark ? #b2c1d5 : #999 color isDark ? #b2c1d5 : #999
&[data-active]
color $theme-color
cursor default
&:before
content ""
display block
position absolute
bottom 0
left 0
width 100%
height 2px
background $theme-color
> span > span
display inline-block display inline-block
padding 0 10px padding 0 10px

View File

@ -27,7 +27,7 @@ export default Vue.extend({
translateY: -64, translateY: -64,
duration: 500, duration: 500,
easing: 'easeInElastic', easing: 'easeInElastic',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
}, 6000); }, 6000);
}); });

View File

@ -75,7 +75,7 @@ export default Vue.extend({
'margin-top': '-8px', 'margin-top': '-8px',
duration: 200, duration: 200,
easing: 'easeOutQuad', easing: 'easeOutQuad',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
} }
} }

View File

@ -106,7 +106,7 @@ export default Vue.extend({
mounted() { mounted() {
if (this.preventMount) { if (this.preventMount) {
this.$destroy(); this.destroyDom();
return; return;
} }
@ -190,7 +190,7 @@ export default Vue.extend({
}); });
setTimeout(() => { setTimeout(() => {
this.$destroy(); this.destroyDom();
this.$emit('closed'); this.$emit('closed');
}, 300); }, 300);
}, },

View File

@ -6,6 +6,9 @@
<x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked"/> <x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked"/> <x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/> <x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/>
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/>
<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked"/>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -13,12 +16,16 @@ import Vue from 'vue';
import XTlColumn from './deck.tl-column.vue'; import XTlColumn from './deck.tl-column.vue';
import XNotificationsColumn from './deck.notifications-column.vue'; import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue'; import XWidgetsColumn from './deck.widgets-column.vue';
import XMentionsColumn from './deck.mentions-column.vue';
import XDirectColumn from './deck.direct-column.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XTlColumn, XTlColumn,
XNotificationsColumn, XNotificationsColumn,
XWidgetsColumn XWidgetsColumn,
XMentionsColumn,
XDirectColumn
}, },
props: { props: {

View File

@ -0,0 +1,38 @@
<template>
<x-column :name="name" :column="column" :is-stacked="isStacked">
<span slot="header">%fa:envelope R%{{ name }}</span>
<x-direct/>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XDirect from './deck.direct.vue';
export default Vue.extend({
components: {
XColumn,
XDirect
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.direct%';
}
},
});
</script>

View File

@ -0,0 +1,97 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XNotes from './deck.notes.vue';
const fetchLimit = 10;
export default Vue.extend({
components: {
XNotes
},
props: {
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
connection: null,
connectionId: null
};
},
mounted() {
this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use();
this.connection.on('mention', this.onNote);
this.fetch();
},
beforeDestroy() {
this.connection.off('mention', this.onNote);
(this as any).os.stream.dispose(this.connectionId);
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api('notes/mentions', {
limit: fetchLimit + 1,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
visibility: 'specified'
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = (this as any).api('notes/mentions', {
limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
visibility: 'specified'
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
notes.forEach(n => (this.$refs.timeline as any).append(n));
this.moreFetching = false;
});
return promise;
},
onNote(note) {
// Prepend a note
if (note.visibility == 'specified') {
(this.$refs.timeline as any).prepend(note);
}
}
}
});
</script>

View File

@ -0,0 +1,117 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XNotes from './deck.notes.vue';
import { HashtagStream } from '../../../../common/scripts/streaming/hashtag';
const fetchLimit = 10;
export default Vue.extend({
components: {
XNotes
},
props: {
tagTl: {
type: Object,
required: true
},
mediaOnly: {
type: Boolean,
required: false,
default: false
},
mediaView: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
connection: null
};
},
watch: {
mediaOnly() {
this.fetch();
}
},
mounted() {
if (this.connection) this.connection.close();
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
this.connection.on('note', this.onNote);
this.fetch();
},
beforeDestroy() {
this.connection.close();
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api('notes/search_by_tag', {
limit: fetchLimit + 1,
withFiles: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
query: this.tagTl.query
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = (this as any).api('notes/search_by_tag', {
limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id,
withFiles: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
query: this.tagTl.query
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
notes.forEach(n => (this.$refs.timeline as any).append(n));
this.moreFetching = false;
});
return promise;
},
onNote(note) {
if (this.mediaOnly && note.files.length == 0) return;
// Prepend a note
(this.$refs.timeline as any).prepend(note);
}
}
});
</script>

View File

@ -0,0 +1,38 @@
<template>
<x-column :name="name" :column="column" :is-stacked="isStacked">
<span slot="header">%fa:at%{{ name }}</span>
<x-mentions/>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XMentions from './deck.mentions.vue';
export default Vue.extend({
components: {
XColumn,
XMentions
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
}
},
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.mentions%';
}
},
});
</script>

View File

@ -0,0 +1,93 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XNotes from './deck.notes.vue';
const fetchLimit = 10;
export default Vue.extend({
components: {
XNotes
},
props: {
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
connection: null,
connectionId: null
};
},
mounted() {
this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use();
this.connection.on('mention', this.onNote);
this.fetch();
},
beforeDestroy() {
this.connection.off('mention', this.onNote);
(this as any).os.stream.dispose(this.connectionId);
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api('notes/mentions', {
limit: fetchLimit + 1,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = (this as any).api('notes/mentions', {
limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
notes.forEach(n => (this.$refs.timeline as any).append(n));
this.moreFetching = false;
});
return promise;
},
onNote(note) {
// Prepend a note
(this.$refs.timeline as any).prepend(note);
}
}
});
</script>

View File

@ -1,8 +1,7 @@
<template> <template>
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl"> <div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<!--<transition-group name="mk-notifications" class="transition notifications">--> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
<div class="notifications">
<template v-for="(notification, i) in _notifications"> <template v-for="(notification, i) in _notifications">
<x-notification class="notification" :notification="notification" :key="notification.id"/> <x-notification class="notification" :notification="notification" :key="notification.id"/>
<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'"> <p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
@ -10,8 +9,7 @@
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
</div> </component>
<!--</transition-group>-->
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }} <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
</button> </button>

View File

@ -6,6 +6,7 @@
<template v-if="column.type == 'hybrid'">%fa:share-alt%</template> <template v-if="column.type == 'hybrid'">%fa:share-alt%</template>
<template v-if="column.type == 'global'">%fa:globe%</template> <template v-if="column.type == 'global'">%fa:globe%</template>
<template v-if="column.type == 'list'">%fa:list%</template> <template v-if="column.type == 'list'">%fa:list%</template>
<template v-if="column.type == 'hashtag'">%fa:hashtag%</template>
<span>{{ name }}</span> <span>{{ name }}</span>
</span> </span>
@ -14,6 +15,7 @@
<mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/> <mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
</div> </div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/> <x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
<x-hashtag-tl v-if="column.type == 'hashtag'" :tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/> <x-tl v-else :src="column.type" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
</x-column> </x-column>
</template> </template>
@ -23,12 +25,14 @@ import Vue from 'vue';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XTl from './deck.tl.vue'; import XTl from './deck.tl.vue';
import XListTl from './deck.list-tl.vue'; import XListTl from './deck.list-tl.vue';
import XHashtagTl from './deck.hashtag-tl.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XColumn, XColumn,
XTl, XTl,
XListTl XListTl,
XHashtagTl
}, },
props: { props: {
@ -65,6 +69,7 @@ export default Vue.extend({
case 'hybrid': return '%i18n:common.deck.hybrid%'; case 'hybrid': return '%i18n:common.deck.hybrid%';
case 'global': return '%i18n:common.deck.global%'; case 'global': return '%i18n:common.deck.global%';
case 'list': return this.column.list.title; case 'list': return this.column.list.title;
case 'hashtag': return this.$store.state.settings.tagTimelines.find(x => x.id == this.column.tagTlId).title;
} }
} }
}, },

View File

@ -138,6 +138,24 @@ export default Vue.extend({
type: 'global' type: 'global'
}); });
} }
}, {
icon: '%fa:at%',
text: '%i18n:common.deck.mentions%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'mentions'
});
}
}, {
icon: '%fa:envelope R%',
text: '%i18n:common.deck.direct%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'direct'
});
}
}, { }, {
icon: '%fa:list%', icon: '%fa:list%',
text: '%i18n:common.deck.list%', text: '%i18n:common.deck.list%',
@ -152,6 +170,20 @@ export default Vue.extend({
w.close(); w.close();
}); });
} }
}, {
icon: '%fa:hashtag%',
text: '%i18n:common.deck.hashtag%',
action: () => {
(this as any).apis.input({
title: '%i18n:@enter-hashtag-tl-title%'
}).then(title => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'hashtag',
tagTlId: this.$store.state.settings.tagTimelines.find(x => x.title == title).id
});
});
}
}, { }, {
icon: '%fa:bell R%', icon: '%fa:bell R%',
text: '%i18n:common.deck.notifications%', text: '%i18n:common.deck.notifications%',

View File

@ -43,7 +43,7 @@ export default Vue.extend({
> .stats > .stats
display flex display flex
justify-content center justify-content center
margin-bottom 16px margin 0 auto 16px auto
padding 32px padding 32px
background #fff background #fff
box-shadow 0 2px 8px rgba(#000, 0.1) box-shadow 0 2px 8px rgba(#000, 0.1)
@ -60,5 +60,6 @@ export default Vue.extend({
font-size 70% font-size 70%
> div > div
max-width 850px max-width 950px
margin 0 auto
</style> </style>

View File

@ -7,9 +7,6 @@ import Vuex from 'vuex';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import * as TreeView from 'vue-json-tree-view'; import * as TreeView from 'vue-json-tree-view';
import VAnimateCss from 'v-animate-css'; import VAnimateCss from 'v-animate-css';
import Element from 'element-ui';
import ElementLocaleEn from 'element-ui/lib/locale/lang/en';
import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
import VModal from 'vue-js-modal'; import VModal from 'vue-js-modal';
import App from './app.vue'; import App from './app.vue';
@ -17,18 +14,10 @@ import checkForUpdate from './common/scripts/check-for-update';
import MiOS, { API } from './mios'; import MiOS, { API } from './mios';
import { version, codename, lang } from './config'; import { version, codename, lang } from './config';
let elementLocale;
switch (lang) {
case 'ja-JP': elementLocale = ElementLocaleJa; break;
case 'en-US': elementLocale = ElementLocaleEn; break;
default: elementLocale = ElementLocaleEn; break;
}
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(VueRouter); Vue.use(VueRouter);
Vue.use(TreeView); Vue.use(TreeView);
Vue.use(VAnimateCss); Vue.use(VAnimateCss);
Vue.use(Element, { locale: elementLocale });
Vue.use(VModal); Vue.use(VModal);
// Register global directives // Register global directives
@ -42,11 +31,15 @@ require('./common/views/widgets');
require('./common/views/filters'); require('./common/views/filters');
Vue.mixin({ Vue.mixin({
destroyed(this: any) { methods: {
destroyDom() {
this.$destroy();
if (this.$el.parentNode) { if (this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el); this.$el.parentNode.removeChild(this.$el);
} }
} }
}
}); });
/** /**

View File

@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
// Style // Style
import './style.styl'; import './style.styl';
import '../../element.scss';
import init from '../init'; import init from '../init';

View File

@ -78,7 +78,7 @@ export default Vue.extend({
scale: 0.8, scale: 0.8,
duration: 300, duration: 300,
easing: [ 0.5, -0.5, 1, 0.5 ], easing: [ 0.5, -0.5, 1, 0.5 ],
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
}, },
onBgClick() { onBgClick() {

View File

@ -31,15 +31,15 @@ export default Vue.extend({
}, },
onSelected(file) { onSelected(file) {
this.$emit('selected', file); this.$emit('selected', file);
this.$destroy(); this.destroyDom();
}, },
cancel() { cancel() {
this.$emit('canceled'); this.$emit('canceled');
this.$destroy(); this.destroyDom();
}, },
ok() { ok() {
this.$emit('selected', this.files); this.$emit('selected', this.files);
this.$destroy(); this.destroyDom();
} }
} }
}); });

View File

@ -19,11 +19,11 @@ export default Vue.extend({
methods: { methods: {
cancel() { cancel() {
this.$emit('canceled'); this.$emit('canceled');
this.$destroy(); this.destroyDom();
}, },
ok() { ok() {
this.$emit('selected', (this.$refs.browser as any).folder); this.$emit('selected', (this.$refs.browser as any).folder);
this.$destroy(); this.destroyDom();
} }
} }
}); });

View File

@ -47,7 +47,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
close() { close() {
this.$destroy(); this.destroyDom();
} }
} }
}); });

View File

@ -19,12 +19,13 @@ export default Vue.extend({
}, },
raw: { raw: {
default: false default: false
},
hide: {
type: Boolean,
default: true
} }
}, },
data() {
return {
hide: true
};
}
computed: { computed: {
style(): any { style(): any {
let url = `url(${this.image.thumbnailUrl})`; let url = `url(${this.image.thumbnailUrl})`;
@ -65,7 +66,7 @@ export default Vue.extend({
text-align center text-align center
font-size 12px font-size 12px
> b > *
display block display block
</style> </style>

View File

@ -15,25 +15,28 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
props: { props: {
video: { video: {
type: Object, type: Object,
required: true required: true
},
hide: {
type: Boolean,
default: true
} }
}, },
data() {
return {
hide: true
};
},
computed: { computed: {
imageStyle(): any { imageStyle(): any {
return { return {
'background-image': `url(${this.video.url})` 'background-image': `url(${this.video.url})`
}; };
} }
},}) }
});
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>

View File

@ -14,7 +14,7 @@
</div> </div>
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<transition-group name="mk-notes" class="transition" tag="div"> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div">
<template v-for="(note, i) in _notes"> <template v-for="(note, i) in _notes">
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
@ -22,7 +22,7 @@
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
</transition-group> </component>
<footer v-if="more"> <footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">

View File

@ -1,8 +1,7 @@
<template> <template>
<div class="mk-notifications"> <div class="mk-notifications">
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<!-- <transition-group name="mk-notifications" class="transition notifications"> --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
<div class="transition notifications">
<template v-for="(notification, i) in _notifications"> <template v-for="(notification, i) in _notifications">
<mk-notification :notification="notification" :key="notification.id"/> <mk-notification :notification="notification" :key="notification.id"/>
<p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date"> <p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date">
@ -10,8 +9,7 @@
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
</p> </p>
</template> </template>
</div> </component>
<!-- </transition-group> -->
<button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template> <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>

View File

@ -32,7 +32,7 @@ export default Vue.extend({
[this.pos]: `-${this.$el.offsetHeight}px`, [this.pos]: `-${this.$el.offsetHeight}px`,
duration: 500, duration: 500,
easing: 'easeOutQuad', easing: 'easeOutQuad',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
}, 6000); }, 6000);
}); });

View File

@ -79,7 +79,7 @@ export default Vue.extend({
translateY: 16, translateY: 16,
duration: 300, duration: 300,
easing: 'easeOutQuad', easing: 'easeOutQuad',
complete: () => this.$destroy() complete: () => this.destroyDom()
}); });
}, },

View File

@ -42,7 +42,7 @@
<span v-if="visibility === 'private'">%fa:lock%</span> <span v-if="visibility === 'private'">%fa:lock%</span>
</button> </button>
</footer> </footer>
<input ref="file" class="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/> <input ref="file" class="file" type="file" multiple="multiple" @change="onChangeFile"/>
</div> </div>
</div> </div>
<div class="hashtags" v-if="recentHashtags.length > 0 && $store.state.settings.suggestRecentHashtags"> <div class="hashtags" v-if="recentHashtags.length > 0 && $store.state.settings.suggestRecentHashtags">
@ -59,7 +59,7 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho
import getFace from '../../../common/scripts/get-face'; import getFace from '../../../common/scripts/get-face';
import parse from '../../../../../mfm/parse'; import parse from '../../../../../mfm/parse';
import { host } from '../../../config'; import { host } from '../../../config';
import { erase } from '../../../../../prelude/array'; import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz'; import { length } from 'stringz';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';

View File

@ -13,6 +13,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { HashtagStream } from '../../../common/scripts/streaming/hashtag';
const fetchLimit = 10; const fetchLimit = 10;
@ -21,6 +22,9 @@ export default Vue.extend({
src: { src: {
type: String, type: String,
required: true required: true
},
tagTl: {
required: false
} }
}, },
@ -29,10 +33,18 @@ export default Vue.extend({
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
existMore: false, existMore: false,
streamManager: null,
connection: null, connection: null,
connectionId: null, connectionId: null,
unreadCount: 0, unreadCount: 0,
date: null date: null,
baseQuery: {
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
},
query: {},
endpoint: null
}; };
}, },
@ -41,49 +53,109 @@ export default Vue.extend({
return this.$store.state.i.followingCount == 0; return this.$store.state.i.followingCount == 0;
}, },
stream(): any {
switch (this.src) {
case 'home': return (this as any).os.stream;
case 'local': return (this as any).os.streams.localTimelineStream;
case 'hybrid': return (this as any).os.streams.hybridTimelineStream;
case 'global': return (this as any).os.streams.globalTimelineStream;
}
},
endpoint(): string {
switch (this.src) {
case 'home': return 'notes/timeline';
case 'local': return 'notes/local-timeline';
case 'hybrid': return 'notes/hybrid-timeline';
case 'global': return 'notes/global-timeline';
}
},
canFetchMore(): boolean { canFetchMore(): boolean {
return !this.moreFetching && !this.fetching && this.existMore; return !this.moreFetching && !this.fetching && this.existMore;
} }
}, },
mounted() { mounted() {
this.connection = this.stream.getConnection(); const prepend = note => {
this.connectionId = this.stream.use(); (this.$refs.timeline as any).prepend(note);
};
this.connection.on('note', this.onNote); if (this.src == 'tag') {
if (this.src == 'home') { this.endpoint = 'notes/search_by_tag';
this.connection.on('follow', this.onChangeFollowing); this.query = {
this.connection.on('unfollow', this.onChangeFollowing); query: this.tagTl.query
};
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.connection.close();
});
} else if (this.src == 'home') {
this.endpoint = 'notes/timeline';
const onChangeFollowing = () => {
this.fetch();
};
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.connection.on('follow', onChangeFollowing);
this.connection.on('unfollow', onChangeFollowing);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.connection.off('follow', onChangeFollowing);
this.connection.off('unfollow', onChangeFollowing);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'local') {
this.endpoint = 'notes/local-timeline';
this.streamManager = (this as any).os.streams.localTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'hybrid') {
this.endpoint = 'notes/hybrid-timeline';
this.streamManager = (this as any).os.streams.hybridTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'global') {
this.endpoint = 'notes/global-timeline';
this.streamManager = (this as any).os.streams.globalTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('note', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('note', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'mentions') {
this.endpoint = 'notes/mentions';
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('mention', prepend);
this.$once('beforeDestroy', () => {
this.connection.off('mention', prepend);
this.streamManager.dispose(this.connectionId);
});
} else if (this.src == 'messages') {
this.endpoint = 'notes/mentions';
this.query = {
visibility: 'specified'
};
const onNote = note => {
if (note.visibility == 'specified') {
prepend(note);
}
};
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
this.connection.on('mention', onNote);
this.$once('beforeDestroy', () => {
this.connection.off('mention', onNote);
this.streamManager.dispose(this.connectionId);
});
} }
this.fetch(); this.fetch();
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('note', this.onNote); this.$emit('beforeDestroy');
if (this.src == 'home') {
this.connection.off('follow', this.onChangeFollowing);
this.connection.off('unfollow', this.onChangeFollowing);
}
this.stream.dispose(this.connectionId);
}, },
methods: { methods: {
@ -91,13 +163,10 @@ export default Vue.extend({
this.fetching = true; this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => { (this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : undefined
includeMyRenotes: this.$store.state.settings.showMyRenotes, }, this.baseQuery, this.query)).then(notes => {
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
this.existMore = true; this.existMore = true;
@ -114,13 +183,10 @@ export default Vue.extend({
this.moreFetching = true; this.moreFetching = true;
const promise = (this as any).api(this.endpoint, { const promise = (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id
includeMyRenotes: this.$store.state.settings.showMyRenotes, }, this.baseQuery, this.query));
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
});
promise.then(notes => { promise.then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
@ -135,15 +201,6 @@ export default Vue.extend({
return promise; return promise;
}, },
onNote(note) {
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
onChangeFollowing() {
this.fetch();
},
focus() { focus() {
(this.$refs.timeline as any).focus(); (this.$refs.timeline as any).focus();
}, },

View File

@ -6,7 +6,10 @@
<span v-if="src == 'local'">%fa:R comments%%i18n:@local%</span> <span v-if="src == 'local'">%fa:R comments%%i18n:@local%</span>
<span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span> <span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span>
<span v-if="src == 'global'">%fa:globe%%i18n:@global%</span> <span v-if="src == 'global'">%fa:globe%%i18n:@global%</span>
<span v-if="src == 'mentions'">%fa:at%%i18n:@mentions%</span>
<span v-if="src == 'messages'">%fa:envelope R%%i18n:@messages%</span>
<span v-if="src == 'list'">%fa:list%{{ list.title }}</span> <span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
<span v-if="src == 'tag'">%fa:hashtag%{{ tagTl.title }}</span>
</span> </span>
<span style="margin-left:8px"> <span style="margin-left:8px">
<template v-if="!showNav">%fa:angle-down%</template> <template v-if="!showNav">%fa:angle-down%</template>
@ -21,15 +24,22 @@
<main :data-darkmode="$store.state.device.darkmode"> <main :data-darkmode="$store.state.device.darkmode">
<div class="nav" v-if="showNav"> <div class="nav" v-if="showNav">
<div class="bg" @click="showNav = false"></div> <div class="bg" @click="showNav = false"></div>
<div class="pointer"></div>
<div class="body"> <div class="body">
<div> <div>
<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span> <span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span> <span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span> <span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span> <span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
<div class="hr"></div>
<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
<span :data-active="src == 'messages'" @click="src = 'messages'">%fa:envelope R% %i18n:@messages%</span>
<template v-if="lists"> <template v-if="lists">
<div class="hr"></div>
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span> <span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
</template> </template>
<div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id">%fa:hashtag% {{ tl.title }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -39,6 +49,9 @@
<x-tl v-if="src == 'local'" ref="tl" key="local" src="local"/> <x-tl v-if="src == 'local'" ref="tl" key="local" src="local"/>
<x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/> <x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/> <x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
<x-tl v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-tl v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/> <mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div> </div>
</main> </main>
@ -60,6 +73,7 @@ export default Vue.extend({
src: 'home', src: 'home',
list: null, list: null,
lists: null, lists: null,
tagTl: null,
showNav: false, showNav: false,
enableLocalTimeline: false enableLocalTimeline: false
}; };
@ -71,9 +85,16 @@ export default Vue.extend({
this.saveSrc(); this.saveSrc();
}, },
list() { list(x) {
this.showNav = false; this.showNav = false;
this.saveSrc(); this.saveSrc();
if (x != null) this.tagTl = null;
},
tagTl(x) {
this.showNav = false;
this.saveSrc();
if (x != null) this.list = null;
}, },
showNav(v) { showNav(v) {
@ -94,6 +115,8 @@ export default Vue.extend({
this.src = this.$store.state.device.tl.src; this.src = this.$store.state.device.tl.src;
if (this.src == 'list') { if (this.src == 'list') {
this.list = this.$store.state.device.tl.arg; this.list = this.$store.state.device.tl.arg;
} else if (this.src == 'tag') {
this.tagTl = this.$store.state.device.tl.arg;
} }
} else if (this.$store.state.i.followingCount == 0) { } else if (this.$store.state.i.followingCount == 0) {
this.src = 'hybrid'; this.src = 'hybrid';
@ -118,7 +141,7 @@ export default Vue.extend({
saveSrc() { saveSrc() {
this.$store.commit('device/setTl', { this.$store.commit('device/setTl', {
src: this.src, src: this.src,
arg: this.list arg: this.src == 'list' ? this.list : this.tagTl
}); });
}, },
@ -134,6 +157,26 @@ export default Vue.extend({
root(isDark) root(isDark)
> .nav > .nav
> .pointer
position fixed
z-index 10002
top 56px
left 0
right 0
$size = 16px
&:after
content ""
display block
position absolute
top -($size * 2)
left s('calc(50% - %s)', $size)
border-top solid $size transparent
border-left solid $size transparent
border-right solid $size transparent
border-bottom solid $size isDark ? #272f3a : #fff
> .bg > .bg
position fixed position fixed
z-index 10000 z-index 10000
@ -150,28 +193,22 @@ root(isDark)
left 0 left 0
right 0 right 0
width 300px width 300px
max-height calc(100% - 70px)
margin 0 auto margin 0 auto
overflow auto
-webkit-overflow-scrolling touch
background isDark ? #272f3a : #fff background isDark ? #272f3a : #fff
border-radius 8px border-radius 8px
box-shadow 0 0 16px rgba(#000, 0.1) box-shadow 0 0 16px rgba(#000, 0.1)
$balloon-size = 16px
&:after
content ""
display block
position absolute
top -($balloon-size * 2) + 1.5px
left s('calc(50% - %s)', $balloon-size)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
border-bottom solid $balloon-size isDark ? #272f3a : #fff
> div > div
padding 8px 0 padding 8px 0
> * > .hr
margin 8px 0
border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
> *:not(.hr)
display block display block
padding 8px 16px padding 8px 16px
color isDark ? #cdd0d8 : #666 color isDark ? #cdd0d8 : #666

View File

@ -13,6 +13,7 @@
<section> <section>
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch> <ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
<ui-switch v-model="circleIcons">%i18n:@circle-icons%</ui-switch> <ui-switch v-model="circleIcons">%i18n:@circle-icons%</ui-switch>
<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion% (%i18n:common.this-setting-is-this-device-only%)</ui-switch>
<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch> <ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch>
<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch> <ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch>
<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> <ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch>
@ -168,6 +169,11 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); } set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
}, },
reduceMotion: {
get() { return this.$store.state.device.reduceMotion; },
set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); }
},
alwaysShowNsfw: { alwaysShowNsfw: {
get() { return this.$store.state.device.alwaysShowNsfw; }, get() { return this.$store.state.device.alwaysShowNsfw; },
set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); } set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }

View File

@ -10,6 +10,7 @@ const defaultSettings = {
home: null, home: null,
mobileHome: [], mobileHome: [],
deck: null, deck: null,
tagTimelines: [],
fetchOnScroll: true, fetchOnScroll: true,
showMaps: true, showMaps: true,
showPostFormOnTopOfTl: false, showPostFormOnTopOfTl: false,
@ -38,6 +39,7 @@ const defaultSettings = {
}; };
const defaultDeviceSettings = { const defaultDeviceSettings = {
reduceMotion: false,
apiViaStream: true, apiViaStream: true,
autoPopout: false, autoPopout: false,
darkmode: false, darkmode: false,

View File

@ -1,12 +0,0 @@
/* Element variable definitons */
/* SEE: http://element.eleme.io/#/en-US/component/custom-theme */
@import '../const.json';
/* theme color */
$--color-primary: $themeColor;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";

View File

@ -9,7 +9,7 @@ html(lang= lang)
link(rel="stylesheet" href="/docs/assets/style.css") link(rel="stylesheet" href="/docs/assets/style.css")
link(rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css") link(rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css")
script(src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js") script(src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js")
link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous") link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous")
block meta block meta
body body

View File

@ -82,9 +82,13 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
text({ document }, { content }) { text({ document }, { content }) {
const nodes = (content as string).split('\n').map(x => document.createTextNode(x)); const nodes = (content as string).split('\n').map(x => document.createTextNode(x));
for (const x of intersperse(document.createElement('br'), nodes)) { for (const x of intersperse('br', nodes)) {
if (x === 'br') {
document.body.appendChild(document.createElement('br'));
} else {
document.body.appendChild(x); document.body.appendChild(x);
} }
}
}, },
url({ document }, { url }) { url({ document }, { url }) {

View File

@ -9,9 +9,9 @@ export type TextElementHashtag = {
}; };
export default function(text: string, i: number) { export default function(text: string, i: number) {
if (!(/^\s#[^\s]+/.test(text) || (i == 0 && /^#[^\s]+/.test(text)))) return null; if (!(/^\s#[^\s\.,]+/.test(text) || (i == 0 && /^#[^\s\.,]+/.test(text)))) return null;
const isHead = text.startsWith('#'); const isHead = text.startsWith('#');
const hashtag = text.match(/^\s?#[^\s]+/)[0]; const hashtag = text.match(/^\s?#[^\s\.,]+/)[0];
const res: any[] = !isHead ? [{ const res: any[] = !isHead ? [{
type: 'text', type: 'text',
content: text[0] content: text[0]

View File

@ -2,12 +2,12 @@
* Replace fontawesome symbols * Replace fontawesome symbols
*/ */
import * as fontawesome from '@fortawesome/fontawesome'; import * as fontawesome from '@fortawesome/fontawesome-svg-core';
import regular from '@fortawesome/fontawesome-free-regular'; import { far } from '@fortawesome/free-regular-svg-icons';
import solid from '@fortawesome/fontawesome-free-solid'; import { fas } from '@fortawesome/free-solid-svg-icons';
import brands from '@fortawesome/fontawesome-free-brands'; import { fab } from '@fortawesome/free-brands-svg-icons';
fontawesome.library.add(regular, solid, brands); fontawesome.library.add(far, fas, fab);
export const pattern = /%fa:(.+?)%/g; export const pattern = /%fa:(.+?)%/g;

View File

@ -0,0 +1,15 @@
export default function(note: any, mutedUserIds: string[]): boolean {
if (mutedUserIds.indexOf(note.userId) != -1) {
return true;
}
if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) {
return true;
}
if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) {
return true;
}
return false;
}

View File

@ -17,6 +17,8 @@ import Following from './following';
const Note = db.get<INote>('notes'); const Note = db.get<INote>('notes');
Note.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('uri', { sparse: true, unique: true });
Note.createIndex('userId'); Note.createIndex('userId');
Note.createIndex('mentions');
Note.createIndex('visibleUserIds');
Note.createIndex('tagsLower'); Note.createIndex('tagsLower');
Note.createIndex('_files.contentType'); Note.createIndex('_files.contentType');
Note.createIndex({ Note.createIndex({
@ -24,6 +26,21 @@ Note.createIndex({
}); });
export default Note; export default Note;
// 後方互換性のため
Note.findOne({
fileIds: { $exists: true }
}).then(n => {
if (n == null) {
Note.update({}, {
$rename: {
mediaIds: 'fileIds'
}
}, {
multi: true
});
}
});
export function isValidText(text: string): boolean { export function isValidText(text: string): boolean {
return length(text.trim()) <= 1000 && text.trim() != ''; return length(text.trim()) <= 1000 && text.trim() != '';
} }
@ -179,7 +196,7 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
hide = false; hide = false;
} else { } else {
// 指定されているかどうか // 指定されているかどうか
const specified = packedNote.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId)); const specified = packedNote.visibleUserIds.some((id: any) => meId.equals(id));
if (specified) { if (specified) {
hide = false; hide = false;

View File

@ -204,4 +204,30 @@ export interface IStats {
decSize: number; decSize: number;
}; };
}; };
/**
* ネットワークに関する統計
*/
network: {
/**
* サーバーへのリクエスト数
*/
requests: number;
/**
* 応答時間の合計
* TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
*/
totalTime: number;
/**
* 合計受信データ量
*/
incomingBytes: number;
/**
* 合計送信データ量
*/
outgoingBytes: number;
};
} }

View File

@ -1,22 +1,10 @@
import { INote } from '../../../models/note'; import { INote } from '../../../models/note';
import toHtml from '../../../mfm/html'; import toHtml from '../../../mfm/html';
import parse from '../../../mfm/parse'; import parse from '../../../mfm/parse';
import config from '../../../config';
export default function(note: INote) { export default function(note: INote) {
let html = toHtml(parse(note.text), note.mentionedRemoteUsers); let html = toHtml(parse(note.text), note.mentionedRemoteUsers);
if (html == null) html = ''; if (html == null) html = '';
if (note.poll != null) {
const url = `${config.url}/notes/${note._id}`;
// TODO: i18n
html += `<p><a href="${url}">【Misskeyで投票を見る】</a></p>`;
}
if (note.renoteId != null) {
const url = `${config.url}/notes/${note.renoteId}`;
html += `<p>RE: <a href="${url}">${url}</a></p>`;
}
return html; return html;
} }

View File

@ -6,6 +6,7 @@ import DriveFile, { IDriveFile } from '../../../models/drive-file';
import Note, { INote } from '../../../models/note'; import Note, { INote } from '../../../models/note';
import User from '../../../models/user'; import User from '../../../models/user';
import toHtml from '../misc/get-note-html'; import toHtml from '../misc/get-note-html';
import parseMfm from '../../../mfm/parse';
export default async function renderNote(note: INote, dive = true): Promise<any> { export default async function renderNote(note: INote, dive = true): Promise<any> {
const promisedFiles: Promise<IDriveFile[]> = note.fileIds const promisedFiles: Promise<IDriveFile[]> = note.fileIds
@ -81,13 +82,39 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
const files = await promisedFiles; const files = await promisedFiles;
let text = note.text;
if (note.poll != null) {
if (text == null) text = '';
const url = `${config.url}/notes/${note._id}`;
// TODO: i18n
text += `\n\n[投票を見る](${url})`;
}
if (note.renoteId != null) {
if (text == null) text = '';
const url = `${config.url}/notes/${note.renoteId}`;
text += `\n\nRE: ${url}`;
}
// 省略されたメンションのホストを復元する
if (text != null) {
text = parseMfm(text).map(x => {
if (x.type == 'mention' && x.host == null) {
return `${x.content}@${config.host}`;
} else {
return x.content;
}
}).join('');
}
return { return {
id: `${config.url}/notes/${note._id}`, id: `${config.url}/notes/${note._id}`,
type: 'Note', type: 'Note',
attributedTo, attributedTo,
summary: note.cw, summary: note.cw,
content: toHtml(note), content: toHtml(Object.assign({}, note, { text })),
_misskey_content_: note.text, _misskey_content: text,
published: note.createdAt.toISOString(), published: note.createdAt.toISOString(),
to, to,
cc, cc,

View File

@ -3,7 +3,7 @@ import Meta from '../../../../models/meta';
export default () => new Promise(async (res, rej) => { export default () => new Promise(async (res, rej) => {
const meta = await Meta.findOne({}); const meta = await Meta.findOne({});
const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
const span = 1000 * 60 * 60 * 24 * 7; // 1週間 const span = 1000 * 60 * 60 * 24 * 7; // 1週間

View File

@ -6,6 +6,15 @@ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
function migrateStats(stats: IStats[]) { function migrateStats(stats: IStats[]) {
stats.forEach(stat => { stats.forEach(stat => {
if (stat.network == null) {
stat.network = {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
};
}
const isOldData = const isOldData =
stat.users.local.inc == null || stat.users.local.inc == null ||
stat.users.local.dec == null || stat.users.local.dec == null ||
@ -180,6 +189,12 @@ export default (params: any) => new Promise(async (res, rej) => {
decCount: 0, decCount: 0,
decSize: 0 decSize: 0
} }
},
network: {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
} }
}); });
} else { } else {
@ -236,6 +251,12 @@ export default (params: any) => new Promise(async (res, rej) => {
decCount: 0, decCount: 0,
decSize: 0 decSize: 0
} }
},
network: {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
} }
}); });
} }

View File

@ -19,7 +19,7 @@ const max = 5;
*/ */
export default () => new Promise(async (res, rej) => { export default () => new Promise(async (res, rej) => {
const meta = await Meta.findOne({}); const meta = await Meta.findOne({});
const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
const data = await Note.aggregate([{ const data = await Note.aggregate([{

View File

@ -3,6 +3,7 @@ import Note from '../../../../models/note';
import { getFriendIds } from '../../common/get-friends'; import { getFriendIds } from '../../common/get-friends';
import { pack } from '../../../../models/note'; import { pack } from '../../../../models/note';
import { ILocalUser } from '../../../../models/user'; import { ILocalUser } from '../../../../models/user';
import getParams from '../../get-params';
export const meta = { export const meta = {
desc: { desc: {
@ -10,42 +11,55 @@ export const meta = {
'en-US': 'Get mentions of myself.' 'en-US': 'Get mentions of myself.'
}, },
requireCredential: true requireCredential: true,
params: {
following: $.bool.optional.note({
default: false
}),
limit: $.num.optional.range(1, 100).note({
default: 10
}),
sinceId: $.type(ID).optional.note({
}),
untilId: $.type(ID).optional.note({
}),
visibility: $.str.optional.note({
}),
}
}; };
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'following' parameter const [ps, psErr] = getParams(meta, params);
const [following = false, followingError] = if (psErr) throw psErr;
$.bool.optional.get(params.following);
if (followingError) return rej('invalid following param');
// Get 'limit' parameter
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
if (limitErr) return rej('invalid limit param');
// Get 'sinceId' parameter
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
if (sinceIdErr) return rej('invalid sinceId param');
// Get 'untilId' parameter
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
if (untilIdErr) return rej('invalid untilId param');
// Check if both of sinceId and untilId is specified // Check if both of sinceId and untilId is specified
if (sinceId && untilId) { if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId'); return rej('cannot set sinceId and untilId');
} }
// Construct query // Construct query
const query = { const query = {
$or: [{
mentions: user._id mentions: user._id
}, {
visibleUserIds: user._id
}]
} as any; } as any;
const sort = { const sort = {
_id: -1 _id: -1
}; };
if (following) { if (ps.visibility) {
query.visibility = ps.visibility;
}
if (ps.following) {
const followingIds = await getFriendIds(user._id); const followingIds = await getFriendIds(user._id);
query.userId = { query.userId = {
@ -53,26 +67,24 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
}; };
} }
if (sinceId) { if (ps.sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {
$gt: sinceId $gt: ps.sinceId
}; };
} else if (untilId) { } else if (ps.untilId) {
query._id = { query._id = {
$lt: untilId $lt: ps.untilId
}; };
} }
// Issue query // Issue query
const mentions = await Note const mentions = await Note
.find(query, { .find(query, {
limit: limit, limit: ps.limit,
sort: sort sort: sort
}); });
// Serialize // Serialize
res(await Promise.all(mentions.map(async mention => res(await Promise.all(mentions.map(mention => pack(mention, user))));
await pack(mention, user)
)));
}); });

View File

@ -13,12 +13,18 @@ export const meta = {
}, },
params: { params: {
tag: $.str.note({ tag: $.str.optional.note({
desc: { desc: {
'ja-JP': 'タグ' 'ja-JP': 'タグ'
} }
}), }),
query: $.arr($.arr($.str)).optional.note({
desc: {
'ja-JP': 'クエリ'
}
}),
includeUserIds: $.arr($.type(ID)).optional.note({ includeUserIds: $.arr($.type(ID)).optional.note({
default: [] default: []
}), }),
@ -59,11 +65,9 @@ export const meta = {
} }
}), }),
withFiles: $.bool.optional.nullable.note({ withFiles: $.bool.optional.note({
default: null,
desc: { desc: {
'ja-JP': 'ファイルが添付された投稿に限定するか否か' 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
} }
}), }),
@ -83,6 +87,12 @@ export const meta = {
} }
}), }),
untilId: $.type(ID).optional.note({
desc: {
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
}
}),
sinceDate: $.num.optional.note({ sinceDate: $.num.optional.note({
}), }),
@ -126,8 +136,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
} }
const q: any = { const q: any = {
$and: [{ $and: [ps.tag ? {
tagsLower: ps.tag.toLowerCase() tagsLower: ps.tag.toLowerCase()
} : {
$or: ps.query.map(tags => ({
$and: tags.map(t => ({
tagsLower: t.toLowerCase()
}))
}))
}], }],
deletedAt: { $exists: false } deletedAt: { $exists: false }
}; };
@ -281,25 +297,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
if (withFiles != null) {
if (withFiles) { if (withFiles) {
push({ push({
fileIds: { fileIds: { $exists: true, $ne: [] }
$exists: true,
$ne: null
}
}); });
} else {
push({
$or: [{
fileIds: {
$exists: false
}
}, {
fileIds: null
}]
});
}
} }
if (ps.poll != null) { if (ps.poll != null) {
@ -323,6 +324,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
} }
} }
if (ps.untilId) {
push({
_id: {
$lt: ps.untilId
}
});
}
if (ps.sinceDate) { if (ps.sinceDate) {
push({ push({
createdAt: { createdAt: {

View File

@ -3,6 +3,7 @@ import Xev from 'xev';
import { IUser } from '../../../models/user'; import { IUser } from '../../../models/user';
import Mute from '../../../models/mute'; import Mute from '../../../models/mute';
import shouldMuteThisNote from '../../../misc/should-mute-this-note';
export default async function( export default async function(
request: websocket.request, request: websocket.request,
@ -15,17 +16,8 @@ export default async function(
// Subscribe stream // Subscribe stream
subscriber.on('global-timeline', async note => { subscriber.on('global-timeline', async note => {
//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (mutedUserIds.indexOf(note.userId) != -1) { if (shouldMuteThisNote(note, mutedUserIds)) return;
return;
}
if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) {
return;
}
if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) {
return;
}
//#endregion
connection.send(JSON.stringify({ connection.send(JSON.stringify({
type: 'note', type: 'note',

View File

@ -0,0 +1,40 @@
import * as websocket from 'websocket';
import Xev from 'xev';
import { IUser } from '../../../models/user';
import Mute from '../../../models/mute';
import { pack } from '../../../models/note';
import shouldMuteThisNote from '../../../misc/should-mute-this-note';
export default async function(
request: websocket.request,
connection: websocket.connection,
subscriber: Xev,
user?: IUser
) {
const mute = user ? await Mute.find({ muterId: user._id }) : null;
const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : [];
const q: Array<string[]> = JSON.parse((request.resourceURL.query as any).q);
// Subscribe stream
subscriber.on('hashtag', async note => {
const matched = q.some(tags => tags.every(tag => note.tags.map((t: string) => t.toLowerCase()).includes(tag.toLowerCase())));
if (!matched) return;
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await pack(note.renoteId, user, {
detail: true
});
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (shouldMuteThisNote(note, mutedUserIds)) return;
connection.send(JSON.stringify({
type: 'note',
body: note
}));
});
}

View File

@ -8,6 +8,7 @@ import { pack as packNote, pack } from '../../../models/note';
import readNotification from '../common/read-notification'; import readNotification from '../common/read-notification';
import call from '../call'; import call from '../call';
import { IApp } from '../../../models/app'; import { IApp } from '../../../models/app';
import shouldMuteThisNote from '../../../misc/should-mute-this-note';
const log = debug('misskey'); const log = debug('misskey');
@ -45,15 +46,7 @@ export default async function(
//#region 流れてきたメッセージがミュートしているユーザーが関わるものだったら無視する //#region 流れてきたメッセージがミュートしているユーザーが関わるものだったら無視する
if (x.type == 'note') { if (x.type == 'note') {
if (mutedUserIds.includes(x.body.userId)) { if (shouldMuteThisNote(x.body, mutedUserIds)) return;
return;
}
if (x.body.reply != null && mutedUserIds.includes(x.body.reply.userId)) {
return;
}
if (x.body.renote != null && mutedUserIds.includes(x.body.renote.userId)) {
return;
}
} else if (x.type == 'notification') { } else if (x.type == 'notification') {
if (mutedUserIds.includes(x.body.userId)) { if (mutedUserIds.includes(x.body.userId)) {
return; return;

View File

@ -4,6 +4,7 @@ import Xev from 'xev';
import { IUser } from '../../../models/user'; import { IUser } from '../../../models/user';
import Mute from '../../../models/mute'; import Mute from '../../../models/mute';
import { pack } from '../../../models/note'; import { pack } from '../../../models/note';
import shouldMuteThisNote from '../../../misc/should-mute-this-note';
export default async function( export default async function(
request: websocket.request, request: websocket.request,
@ -26,17 +27,8 @@ export default async function(
}); });
} }
//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (mutedUserIds.indexOf(note.userId) != -1) { if (shouldMuteThisNote(note, mutedUserIds)) return;
return;
}
if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) {
return;
}
if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) {
return;
}
//#endregion
connection.send(JSON.stringify({ connection.send(JSON.stringify({
type: 'note', type: 'note',

View File

@ -4,6 +4,7 @@ import Xev from 'xev';
import { IUser } from '../../../models/user'; import { IUser } from '../../../models/user';
import Mute from '../../../models/mute'; import Mute from '../../../models/mute';
import { pack } from '../../../models/note'; import { pack } from '../../../models/note';
import shouldMuteThisNote from '../../../misc/should-mute-this-note';
export default async function( export default async function(
request: websocket.request, request: websocket.request,
@ -23,17 +24,8 @@ export default async function(
}); });
} }
//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (mutedUserIds.indexOf(note.userId) != -1) { if (shouldMuteThisNote(note, mutedUserIds)) return;
return;
}
if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) {
return;
}
if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) {
return;
}
//#endregion
connection.send(JSON.stringify({ connection.send(JSON.stringify({
type: 'note', type: 'note',

View File

@ -14,6 +14,7 @@ import reversiGameStream from './stream/games/reversi-game';
import reversiStream from './stream/games/reversi'; import reversiStream from './stream/games/reversi';
import serverStatsStream from './stream/server-stats'; import serverStatsStream from './stream/server-stats';
import notesStatsStream from './stream/notes-stats'; import notesStatsStream from './stream/notes-stats';
import hashtagStream from './stream/hashtag';
import { ParsedUrlQuery } from 'querystring'; import { ParsedUrlQuery } from 'querystring';
import authenticate from './authenticate'; import authenticate from './authenticate';
@ -44,6 +45,12 @@ module.exports = (server: http.Server) => {
ev.removeAllListeners(); ev.removeAllListeners();
}); });
connection.on('message', async (data) => {
if (data.utf8Data == 'ping') {
connection.send('pong');
}
});
const q = request.resourceURL.query as ParsedUrlQuery; const q = request.resourceURL.query as ParsedUrlQuery;
const [user, app] = await authenticate(q.i as string); const [user, app] = await authenticate(q.i as string);
@ -57,6 +64,11 @@ module.exports = (server: http.Server) => {
return; return;
} }
if (request.resourceURL.pathname === '/hashtag') {
hashtagStream(request, connection, ev, user);
return;
}
if (user == null) { if (user == null) {
connection.send('authentication-failed'); connection.send('authentication-failed');
connection.close(); connection.close();

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