Compare commits

...

145 Commits

Author SHA1 Message Date
syuilo
0b7be70935 10.98.2 2019-03-25 23:36:30 +09:00
MeiMei
818b71abd6 fix Content-Disposition (#4573) 2019-03-25 03:12:08 +09:00
syuilo
25575e8510 10.98.1 2019-03-24 13:03:22 +09:00
Acid Chicken (硫酸鶏)
6c85adcf23 Fix typo 2019-03-23 13:50:10 +09:00
syuilo
5dc92d7a40 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-22 19:07:29 +09:00
syuilo
4e2b966b80 Fix #4559
Close #4564
2019-03-22 19:07:13 +09:00
Acid Chicken (硫酸鶏)
d34f8c3cb9 Update README.md [AUTOGEN] (#4566) 2019-03-22 19:01:29 +09:00
Zero King
9049ecb1cf Add name of series in instance count chart (#4565) 2019-03-22 14:57:09 +09:00
MeiMei
7bebea087c Fix #4546 (#4548)
* Refactor download

* emoji type
2019-03-21 04:50:44 +09:00
MeiMei
1c79e30436 Fix NoteReaction (#4547) 2019-03-20 21:39:02 +09:00
tamaina
1d7933349b Fix #4538 (#4539)
* fix #4538

* clean up

* fix

* fix
2019-03-19 23:35:22 +09:00
syuilo
d002f67140 10.98.0 2019-03-19 18:50:09 +09:00
syuilo
da3447765b Revert "Remove deepcopy dependency"
This reverts commit cbf5663179.
2019-03-19 18:47:14 +09:00
syuilo
cbf5663179 Remove deepcopy dependency 2019-03-19 17:59:44 +09:00
syuilo
b217fba235 Fix #4531 2019-03-19 17:47:40 +09:00
tamaina
7f7e6d5aba 「メッセージ」「トーク」「ダイレクト投稿」( Fix #2748 ) (#4515)
* 「メッセージ」「トーク」「ダイレクト投稿」
トーク: 一対一のチャット機能・画面
メッセージ: トークでやり取りする発言
ダイレクト投稿: 自分向けのダイレクト投稿が一覧されるTL

* Fix display of messaging

* ✌️

* Update ja-JP.yml
2019-03-19 17:45:46 +09:00
tamaina
87c5a9d9a6 Fix #97, Fix #2943 (#4533) 2019-03-19 17:35:50 +09:00
tamaina
8ca1fe3f0a Resolve #1465 (#4532)
* [client] show drive file icon

* 🎨

* exchange icon

* fix

* fuck crlf

* 背景差し戻し

* fix selected color

* 🎨

* fix

* pointer-events none

* fix bug

* ✌️

* Clean up

* ✌️

* Clean up

* Fix
2019-03-19 17:26:07 +09:00
MeiMei
763ae8f1a6 Change Twemoji CDN (#4527) 2019-03-18 22:02:45 +09:00
MeiMei
c65256d02b Fix custom emoji validation (#4528) 2019-03-18 20:02:25 +09:00
syuilo
bd2ac515d1 🎨 2019-03-18 17:37:29 +09:00
MeiMei
681f372889 proxy cropper url (#4525) 2019-03-18 16:41:29 +09:00
MeiMei
c2eec272e6 Content-Disposition in ObjectStrage (#4524)
* Content-Disposition in ObjectStrage

* encode filename
2019-03-18 15:23:45 +09:00
syuilo
bd720491a9 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-18 13:30:13 +09:00
syuilo
a408226509 Refactor 2019-03-18 13:29:58 +09:00
syuilo
c015e99e6e Update black.json5 2019-03-18 13:25:56 +09:00
tamaina
de47a17be7 Improve drive downloading (#4523)
* Improve drive file downloading

* fix name

* wtf crlf

* semicolon
2019-03-18 13:20:49 +09:00
syuilo
d38fc490ad 10.97.2 2019-03-18 02:22:28 +09:00
syuilo
662167e792 Remove unused import 2019-03-18 02:21:29 +09:00
syuilo
36c91f03d9 10.97.1 2019-03-18 01:59:59 +09:00
syuilo
33ccee26b5 Update black.json5 2019-03-18 01:29:58 +09:00
syuilo
ed5cb991e3 10.97.0 2019-03-18 01:11:18 +09:00
syuilo
bea84ec2bf New Crowdin translations (#4509)
* New translations ja-JP.yml (Polish)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)
2019-03-18 01:10:03 +09:00
syuilo
08c176e549 不明なリアクションのフォールバックに star を使うようにするオプション 2019-03-18 01:03:35 +09:00
syuilo
810ed50976 Update black.json5 2019-03-18 00:41:30 +09:00
MeiMei
2684541693 Custom reaction (#4517)
* Custom reaction

* increase limit of reactions/delete

* リアクションの場合は OS標準の絵文字を使用 を迂回する

* カスタムリアクションを無効にする設定

* fix

* disableCustomReaction --> enableEmojiReaction

* Avoid MFM rendering

* 🎨

* 🎨

* Auto accept

* custom emoji reaction

* Improve usability

* Extract emojiRegex

* Fix

* Clean up

* 🎨

* 🎨

* toDbReaction で reaction は必須に

あとフォールバックは like に

* Clean up

* Make required

* 3eb08748fe (r266241728)

* Refactor

* Allow null
2019-03-18 00:03:57 +09:00
syuilo
a5b12bac54 Change default dark theme 2019-03-17 20:48:55 +09:00
syuilo
fea1b06e43 Update black.json5 2019-03-17 20:46:14 +09:00
syuilo
182ca5d434 10.96.0 2019-03-16 21:30:31 +09:00
syuilo
facde9a75d Increase chart limit 2019-03-16 21:02:17 +09:00
syuilo
41385640b9 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-16 18:17:15 +09:00
syuilo
7bad9db32e Update interval 2019-03-16 18:16:28 +09:00
MeiMei
af66f0a497 Fix #3504 (#4513) 2019-03-16 18:00:23 +09:00
MeiMei
95e1b80f41 updatePersonを試行した時点でもlastFetchedAtを更新する (#4510) 2019-03-16 09:55:19 +09:00
MeiMei
556e2eba95 Resolve #1727 (#4512) 2019-03-16 09:54:40 +09:00
syuilo
efe530cb17 Update CONTRIBUTING.md 2019-03-16 02:53:35 +09:00
syuilo
34e7c99283 Increase display instances limit 2019-03-16 01:36:10 +09:00
syuilo
4157ea8bc3 Add icons 🎨 2019-03-16 01:34:21 +09:00
syuilo
550517bbf3 Resolve #4506 2019-03-16 01:20:13 +09:00
syuilo
eb910cd8a1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-16 01:08:34 +09:00
syuilo
75131c4e8a Fix bug 2019-03-16 01:08:26 +09:00
MeiMei
ee29ab95be Fix users/search (#4505) 2019-03-15 22:22:59 +09:00
syuilo
e97951fc51 10.95.0 2019-03-15 13:54:08 +09:00
syuilo
dfabdef60f Resolve #4501 2019-03-15 13:51:23 +09:00
syuilo
5a87763193 New Crowdin translations (#4481)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)

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

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (English)
2019-03-15 13:48:46 +09:00
syuilo
6bb90f56fa ジョブを一覧できるように 2019-03-15 13:48:17 +09:00
syuilo
c883ae1350 🎨 2019-03-15 13:14:50 +09:00
syuilo
09e25e6a02 Better queue chart 2019-03-15 13:09:19 +09:00
syuilo
bf5d43054b Fix bug 2019-03-15 13:09:05 +09:00
MeiMei
63b3c65691 ファビコンが保存されないのを修正 (#4500)
* Fix ファビコン保存されない

* Fix meta
2019-03-15 12:40:10 +09:00
syuilo
f193da7f67 🎨 2019-03-15 02:17:10 +09:00
syuilo
40f38c2c0a Improve queue page 2019-03-15 01:56:40 +09:00
syuilo
db439ef804 🎨 2019-03-15 01:43:11 +09:00
MeiMei
56eb896a03 Accept Article object (#4499) 2019-03-15 00:23:24 +09:00
Acid Chicken (硫酸鶏)
68d43e43b6 Fix hashtag style 2019-03-15 00:03:24 +09:00
Acid Chicken (硫酸鶏)
c60517e49a Follow #3676 2019-03-14 22:18:31 +09:00
Acid Chicken (硫酸鶏)
3f59d261f2 Follow #3676 2019-03-14 22:18:10 +09:00
Acid Chicken (硫酸鶏)
4068d220e5 Follow #3676 2019-03-14 22:17:26 +09:00
syuilo
18968e7208 Fix bug 2019-03-14 21:51:33 +09:00
Acid Chicken (硫酸鶏)
38656103c0 Add angle bracket covered url syntax to mfm (#4483)
* Add angle bracket covered url syntax to mfm

* Fix path

* Fix match

* Fix index
2019-03-14 21:23:15 +09:00
syuilo
0f65b1bcc5 10.94.0 2019-03-14 16:40:06 +09:00
syuilo
a628821834 Improve readability 2019-03-14 16:35:07 +09:00
syuilo
6ceff60c1e Faviconを可変にするなど 2019-03-14 16:30:51 +09:00
syuilo
d762a6ce58 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-14 15:19:09 +09:00
syuilo
75a8037a46 Fix #4489 2019-03-14 15:19:02 +09:00
MeiMei
1179920790 unFollowAll on suspend (#4490)
* unFollowAll on suspend

* use services

* silent
2019-03-14 15:16:07 +09:00
Acid Chicken (硫酸鶏)
b323a160e3 Follow #3676 2019-03-14 01:20:25 +09:00
syuilo
b157e9535e Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-13 23:27:21 +09:00
syuilo
7668475bd6 🎨 2019-03-13 23:27:11 +09:00
syuilo
8bda2a1fb7 Refactor 2019-03-13 23:04:17 +09:00
Acid Chicken (硫酸鶏)
b092086b5b Add languages (#4452) 2019-03-13 14:22:55 +09:00
MeiMei
69a0d9034f Fix #4486 (#4487) 2019-03-13 11:21:16 +09:00
syuilo
f5be8fd313 10.93.1 2019-03-13 09:34:14 +09:00
syuilo
7f835d7f76 🎨 2019-03-13 09:32:10 +09:00
syuilo
ddbb7c5993 🎨 2019-03-13 09:26:38 +09:00
syuilo
00a3fe39e8 Update dependencies 🚀 2019-03-13 09:19:48 +09:00
syuilo
7537fb88d4 Refactor 2019-03-13 00:14:44 +09:00
syuilo
a81bc71a1e Resolve #4454 2019-03-13 00:13:56 +09:00
MeiMei
0a0aa0e2db Fix #4484 (#4485)
* Fix #4484

* import order
2019-03-12 23:38:11 +09:00
syuilo
c56b94ae96 Add type annotation to avoid type error 2019-03-12 23:31:18 +09:00
syuilo
e90712706d Add icons 🎨 2019-03-12 23:30:44 +09:00
syuilo
eb0623331f 🎨 2019-03-12 23:14:18 +09:00
MeiMei
d15bd59109 Fix queue charts (#4482) 2019-03-12 21:53:36 +09:00
syuilo
60e0b19372 10.93.0 2019-03-12 17:24:42 +09:00
syuilo
922eb937ff 🎨 2019-03-12 17:20:40 +09:00
syuilo
87573284f1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-12 17:11:33 +09:00
syuilo
a91c585f55 Add queue chart 2019-03-12 17:11:06 +09:00
syuilo
953ea21d5e New Crowdin translations (#4479)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)
2019-03-12 16:43:13 +09:00
syuilo
ecb00968bc Fix bug 2019-03-12 16:42:56 +09:00
syuilo
50ad8adb2d Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-12 13:26:33 +09:00
syuilo
16878caf09 Update doc 2019-03-12 13:26:26 +09:00
syuilo
5bc30c5493 Add log 2019-03-12 13:12:49 +09:00
MeiMei
85d89cf4c4 embedプレイヤーを閉じれるように (#4402) 2019-03-12 13:12:34 +09:00
syuilo
db693f598b シェアページを統合 2019-03-12 13:02:16 +09:00
syuilo
0494c770a1 Better share template 2019-03-12 12:59:26 +09:00
syuilo
c473b62aed Follow latest Web Share Target specification 2019-03-12 12:55:43 +09:00
syuilo
f19ac5320e Remove unnecessary checking 2019-03-12 12:46:01 +09:00
syuilo
612e3aafbc activeなジョブ数のカウント方法を分けた
https://github.com/syuilo/misskey/issues/4470#issuecomment-471827030
2019-03-12 12:31:01 +09:00
syuilo
0e97fec451 Fix typo 2019-03-12 10:46:25 +09:00
syuilo
e8c8626ee4 New Crowdin translations (#4469)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Czech)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Korean)
2019-03-12 10:41:05 +09:00
syuilo
d89e0f07f8 Update issue templates 2019-03-12 10:38:30 +09:00
syuilo
e7f81a42ce Resolve #4470 2019-03-12 10:35:17 +09:00
Acid Chicken (硫酸鶏)
ac614148b8 Update README.md [AUTOGEN] (#4477) 2019-03-12 09:50:42 +09:00
MeiMei
5eb02b4901 Resolve #4458 (#4476) 2019-03-12 09:50:20 +09:00
Acid Chicken (硫酸鶏)
65631525f6 Update README.md [AUTOGEN] (#4474) 2019-03-12 09:49:58 +09:00
syuilo
969435cfe9 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-12 00:34:30 +09:00
syuilo
c932f7a25b Resolve #1736 2019-03-12 00:34:19 +09:00
MeiMei
42d164dc57 log failed job (#4472) 2019-03-11 22:45:07 +09:00
syuilo
a7e60f80bd Refactor: Extract downloadTextFile function 2019-03-11 20:23:29 +09:00
syuilo
3dd5f313b7 Add icons 🎨 2019-03-11 20:12:44 +09:00
syuilo
883962c393 Add icon 🎨 2019-03-11 20:07:27 +09:00
syuilo
8a30ff1c76 🌎 A federated blogging platform 🚀 2019-03-11 20:03:02 +09:00
syuilo
e47c354916 Refactor 2019-03-11 19:57:50 +09:00
syuilo
496f42805d リストをインポートしたときにプロキシアカウントがフォローするように修正 2019-03-11 19:51:58 +09:00
syuilo
c3d34bda37 Resolve #4259 2019-03-11 19:43:58 +09:00
syuilo
bb6ede2b8f 10.92.4 2019-03-11 13:59:27 +09:00
syuilo
822400a1ba New Crowdin translations (#4461)
* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Czech)
2019-03-11 13:57:21 +09:00
syuilo
e3e08843f1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-11 09:59:14 +09:00
syuilo
ce0d4f77fa リストのエクスポートをできるように
#4259
2019-03-11 09:59:07 +09:00
Acid Chicken (硫酸鶏)
94fdb4e974 Update README.md [AUTOGEN] (#4467) 2019-03-11 08:13:02 +09:00
MeiMei
4d425fc8a4 Use proxy instead of weserv for url-preview images (#4466) 2019-03-11 01:03:09 +09:00
MeiMei
c6cdfa2f5a Ignore 4xx references in AP (#4463)
* Ignore 4xx references

* remove unnecessary comment
2019-03-10 22:27:25 +09:00
syuilo
0fff2e4f16 Remove debug code 2019-03-10 19:20:25 +09:00
syuilo
80a2172715 Resolve #4462 2019-03-10 19:16:33 +09:00
syuilo
5a0a297634 Improve redis config 2019-03-09 23:44:54 +09:00
syuilo
948a133b7b Fix log 2019-03-09 18:15:44 +09:00
syuilo
2ee826c958 10.92.3 2019-03-09 10:21:45 +09:00
syuilo
539409faf8 Better logs 2019-03-09 10:18:59 +09:00
syuilo
606e46e4d7 Make info 2019-03-09 10:10:24 +09:00
syuilo
a179cfd69a Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-03-09 10:09:12 +09:00
syuilo
d8379253d4 Rename 2019-03-09 10:09:04 +09:00
MeiMei
c3344fbd68 To retry AP deliver queue (#4457) 2019-03-09 08:57:55 +09:00
syuilo
4cebd6e84a Increase job attempts limit a little 2019-03-08 21:43:17 +09:00
syuilo
90fbf9dbb0 Add missing semicolon 2019-03-08 21:38:23 +09:00
syuilo
d365b9f634 Fix bug 2019-03-08 21:30:12 +09:00
179 changed files with 2750 additions and 832 deletions

View File

@@ -1,31 +0,0 @@
---
name: 🐛 Bug Report (🖥Client specific)
about: Create a report to help us improve
title: ''
labels: ⚠bug?, 🖥Client
assignees: ''
---
## Summary
<!-- Tell us what the bug is -->
## Expected Behavior
<!--- Tell us what should happen -->
## Actual Behavior
<!--- Tell us what happens instead of the expected behavior -->
## Steps to Reproduce
1.
2.
3.
## Environment
<!-- Tell us where on the platform it happens -->
<!-- e.g. desktop or mobile version, your browser, your OS -->

View File

@@ -1,31 +0,0 @@
---
name: 🐛 Bug Report (⚙Server specific)
about: Create a report to help us improve
title: ''
labels: ⚠bug?, ⚙Server
assignees: ''
---
## Summary
<!-- Tell us what the bug is -->
## Expected Behavior
<!--- Tell us what should happen -->
## Actual Behavior
<!--- Tell us what happens instead of the expected behavior -->
## Steps to Reproduce
1.
2.
3.
## Environment
<!-- Tell us where on the platform it happens -->
<!-- e.g. your Node.js version, your OS -->

View File

@@ -1,12 +0,0 @@
---
name: ✨ Feature Request (🖥Client specific)
about: Suggest an idea for this project
title: ''
labels: ✨Feature, 🖥Client
assignees: ''
---
## Summary
<!-- Tell us what the suggestion is -->

View File

@@ -1,12 +0,0 @@
---
name: ✨ Feature Request (⚙Server specific)
about: Suggest an idea for this project
title: ''
labels: ✨Feature, ⚙Server
assignees: ''
---
## Summary
<!-- Tell us what the suggestion is -->

View File

@@ -1,6 +1,95 @@
ChangeLog
=========
If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`)
10.98.2
----------
* 他のインスタンスから添付画像が見れない問題を修正
10.98.1
----------
* ドライブのファイルのサムネイルが表示されない問題を修正
* APでカスタム絵文字を送る時に常にimage/pngで送っている問題を修正
* いくらいじってもページリロードするとmisskeyのテーマがdark(future)になっちゃう問題を修正
10.98.0
----------
* ドライブのファイルダウンロード時に元のファイル名を尊重するように
* ドライブで画像以外のファイルを分かりやすく表示するように
* TwemojiのCDNを変更
* モバイルで通知の設定がない問題を修正
* デザインの調整
10.97.2
----------
* ビルド時に警告が出ないように修正
10.97.1
----------
* デザインの調整
10.97.0
----------
* リアクションに絵文字やカスタム絵文字を使えるように
* 不明なリアクションのフォールバックに star を使えるように
* デザインの調整
10.96.0
----------
* 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように
* updatePersonを試行した時点でもlastFetchedAtを更新するように
* 管理画面でリモートインスタンスの登録日時を表示
* ユーザーサジェストが機能しなくなっていた問題を修正
* 最近使ったハッシュタグ表示が機能していない問題を修正
* バグ修正
* デザインの調整
10.95.0
----------
* ジョブを一覧できるように
* MFMでURLを明示する構文の追加
* Articleタイプのアクティビティを受け入れるように
* 凍結されたユーザーをサジェストしないように
* ファビコンが保存されないのを修正
* キューのジョブクリアの動作を修正
* デザインの調整
10.94.0
----------
* Faviconを設定できるように
* アカウントを凍結したときすべてのフォローを解除するように
* シェアページが機能していない問題を修正
* インスタンスブロックをしていてもRenote等すると取得されてしまう問題を修正
* デザインの調整
10.93.1
----------
* データのエクスポートとインポートの動作を修正
* デザインの調整
10.93.0
----------
* フォローリストをインポートできるように
* embedプレイヤーを閉じれるように
* リストをインポートしたときにプロキシアカウントがフォローするように修正
* Web Share Targetの動作を修正
* おすすめアンケートのチョイスを修正
* デザインの調整
10.92.4
----------
* リストのエクスポートをできるように
* ジョブキューウィジェットを追加
* URLプレビューのサムネイルが表示されないことがある問題を修正
10.92.3
----------
* 管理画面の各種ジョブ数がおかしい問題を修正
* ジョブキューの動作を調整
10.92.2
----------
* 管理画面で各種ジョブ数を一覧できるように

View File

@@ -46,6 +46,9 @@ Convert な(na) to にゃ(nya)
Revert Nyaize
## Code style
### Use semicolon
To avoid ASI Hazard
### Don't use `export default`
Bad:
``` ts

View File

@@ -101,6 +101,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
----------------------------------------------------------------
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1?token-time=2145916800&token-hash=HGkZJ7s4bSaQVoOJ5q30mTWHTxDLiw1LuyaogKPLy24%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" width="100"></td>
@@ -108,6 +109,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
@@ -137,9 +139,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3?token-time=2145916800&token-hash=-iJszBqgYBhsM5qMdA1knf9wvprhEfESzKfR2oh7mIA%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="@Hekovic@gyutte.site" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="Hekovic" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td>
@@ -147,14 +149,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
</tr><tr>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
<td><a href="https://www.patreon.com/user?u=18072312">@Hekovic@gyutte.site</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Thu, 07 Mar 2019 11:30:05 UTC
**Last updated:** Fri, 22 Mar 2019 09:29:06 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@@ -118,7 +118,7 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
4. `NODE_ENV=production npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。
なにか問題が発生した場合は、`npm run clean`または`npm run cleanall`すると直る場合があります。
----------------------------------------------------------------

View File

@@ -2,12 +2,12 @@
meta:
lang: "Čeština"
common:
misskey: "⭐ ve fediverse"
about-title: "⭐ ve fediverse."
about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve Fediverse (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?"
misskey: "⭐ ve fedivesmíru"
about-title: "⭐ ve fedivesmíru."
about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve fedivesmíru (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?"
intro:
title: "Co je Misskey?"
about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala."
about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fedivesmír“ nebo „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala."
features: "Vlastnosti"
rich-contents: "Příspěvky"
rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování to je jen několik věcí, co můžete dělat s Misskey!"
@@ -190,6 +190,7 @@ common:
remain-deleted-note: "I nadále zobrazovat odstraněné příspěvky"
sound: "Zvuk"
enable-sounds: "Povolit zvuk"
enable-sounds-desc: "Přehrát zvuk, například při odeslání nebo přijetí příspěvku, či zprávy. Toto nastavení je uloženo v prohlížeči."
volume: "Hlasitost"
test: "Test"
update: "Aktualizace Misskey"
@@ -299,9 +300,11 @@ common/views/pages/explore.vue:
recently-updated-users: "Nedávno aktívni uživatelé"
recently-registered-users: "Nedávno registrovaní uživatelé"
popular-tags: "Populární tagy"
federated: "Z fediverse"
federated: "Z fedivesmíru"
explore: "Prozkoumat {host}"
common/views/components/url-preview.vue:
enable-player: "Otevřít v přehrávači"
disable-player: "Zavřít přehrávač"
common/views/components/user-list.vue:
no-users: "Žádní uživatelé"
common/views/components/games/reversi/reversi.vue:
@@ -412,12 +415,15 @@ common/views/components/nav.vue:
feedback: "Zpětná vazba"
common/views/components/note-menu.vue:
mention: "Zmínění"
detail: "Více"
copy-content: "Zkopírovat obsah"
copy-link: "Zkopírovat odkaz"
favorite: "Přidat do oblíbených"
unfavorite: "Odebrat z oblízených"
watch: "Sledovat"
unwatch: "Přestat sledovat"
pin: "Připnout"
unpin: "Odepnout"
delete: "Odstranit"
delete-confirm: "Opravdu chcete smazat tento příspěvek?"
remote: "Ukázat originální poznámku"
@@ -554,10 +560,12 @@ common/views/components/profile-editor.vue:
email-verified: "Váš e-mail byl ověřen"
email-not-verified: "Váš email není potvrzen. Prosím zkontrolujte si svou schránku."
export: "Exportovat"
import: "Importovat"
export-targets:
following-list: "Seznam sledujících"
mute-list: "Seznam ztlumených uživatelů"
blocking-list: "Seznam blokovaných uživatelů"
user-lists: "Seznamy"
enter-password: "Prosím, zadejte Vaše heslo"
danger-zone: "Nebezpečná zóna"
delete-account: "Smazat účet"
@@ -694,6 +702,7 @@ desktop/views/components/note.vue:
renote: "Renote"
add-reaction: "Přidat reakci"
undo-reaction: "Odebrat reakci"
detail: "Více"
private: "Tento příspěvek je soukromý"
deleted: "Tento příspěvek byl odstraněn"
desktop/views/components/notes.vue:
@@ -827,7 +836,7 @@ admin/views/index.vue:
emoji: "Emoji"
moderators: "Moderátoři"
users: "Uživatelé"
federation: "Z fediversu"
federation: "Federovaná"
announcements: "Oznámení"
hashtags: "Hashtagy"
queue: "Fronta úloh"
@@ -839,9 +848,7 @@ admin/views/dashboard.vue:
drive: "Disk"
instances: "Instance"
this-instance: "Tato instance"
federated: "Z fediversu"
admin/views/queue.vue:
operation: "Akce"
federated: "Federovaná"
admin/views/abuse.vue:
details: "Popis"
remove-report: "Odstranit"
@@ -988,14 +995,15 @@ admin/views/announcements.vue:
admin/views/hashtags.vue:
hided-tags: "Skryté tagy"
admin/views/federation.vue:
federation: "Z fediversu"
instance: "Instance"
host: "Hostitel"
notes: "Poznámky"
users: "Uživatelé"
caught-at: "Vytvořeno"
status: "Status"
latest-request-received-at: "Poslední požadavek přijat"
block: "Blokován"
instances: "Instance"
instances: "Federovaná"
states:
all: "Všechny"
blocked: "Blokován"
@@ -1029,8 +1037,6 @@ desktop/views/pages/selectdrive.vue:
desktop/views/pages/search.vue:
not-available: "Vyhledávání je vypnuté pro tuto instanci."
not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
desktop/views/pages/share.vue:
share-with: "Sdílet na {name}"
desktop/views/pages/tag.vue:
no-posts-found: "Nebyly nalezeny žádné příspěvky s \"{q}\"."
desktop/views/pages/user-list.users.vue:

View File

@@ -339,6 +339,9 @@ common/views/components/profile-editor.vue:
banner: "Banner"
save: "Speichern"
export: "Exportieren"
import: "Importieren"
export-targets:
user-lists: "Listen"
enter-password: "Bitte Passwort eingeben"
common/views/widgets/broadcast.vue:
fetching: "Laden"

View File

@@ -114,7 +114,7 @@ common:
a: "What are you doing?"
b: "What's happening?"
c: "Whats on your mind?"
d: "Would you post any words?"
d: "What do you want to say?"
e: "Write here"
f: "Waiting for your writing."
settings: "Settings"
@@ -169,9 +169,9 @@ common:
deck-column-align-flexible: "Flexible"
deck-column-width: "Deck column width"
deck-column-width-narrow: "Narrow"
deck-column-width-narrower: "Somewhat narrow"
deck-column-width-narrower: "Narrower"
deck-column-width-normal: "Regular"
deck-column-width-wider: "Somewhat wide"
deck-column-width-wider: "Slightly wide"
deck-column-width-wide: "Wide"
use-shadow: "Use shadows in the UI"
rounded-corners: "Round the corners of the UI"
@@ -285,7 +285,7 @@ auth/views/form.vue:
account-read: "View account information."
account-write: "Modify account information."
note-write: "Post."
like-write: "React to posts."
like-write: "Express yourself about this post."
following-write: "Follow and unfollow."
drive-read: "Read your drive."
drive-write: "Upload/delete files in your drive."
@@ -304,7 +304,7 @@ auth/views/index.vue:
error: "Session does not exist."
sign-in: "Please sign in."
common/views/pages/explore.vue:
verified-users: "Verified accounts"
verified-users: "Official accounts"
popular-users: "Popular users"
recently-updated-users: "Recently active users"
recently-registered-users: "Users who joined recently"
@@ -314,6 +314,7 @@ common/views/pages/explore.vue:
users-info: "Currently, {users} users are registered here"
common/views/components/url-preview.vue:
enable-player: "Enable playback"
disable-player: "Close the player"
common/views/components/user-list.vue:
no-users: "There are no users."
common/views/components/games/reversi/reversi.vue:
@@ -647,12 +648,16 @@ common/views/components/profile-editor.vue:
email-verified: "Your email has been verified."
email-not-verified: "Email address is not confirmed. Please check your inbox."
export: "Export"
import: "Import"
export-and-import: "Export and Import"
export-targets:
all-notes: "All posted Notes"
following-list: "List of followers"
mute-list: "List of muted accounts"
blocking-list: "List of blocked accounts"
user-lists: "Lists"
export-requested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive."
import-requested: "You have initiated an import. This may take quite some time."
enter-password: "Please enter your password"
danger-zone: "Cautious options"
delete-account: "Remove the account"
@@ -1052,7 +1057,7 @@ admin/views/dashboard.vue:
this-instance: "This instance"
federated: "Federated"
admin/views/queue.vue:
operation: "Action(s)"
title: "Queue"
remove-all-jobs: "Clear all queued jobs"
admin/views/abuse.vue:
title: "Abuse"
@@ -1108,6 +1113,7 @@ admin/views/instance.vue:
disable-local-timeline: "Disable the Local Timeline"
disable-global-timeline: "Disable global timeline"
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
enable-emoji-reaction: "Enable pictograms for reactions"
invite: "Invite"
save: "Save"
saved: "Saved"
@@ -1270,12 +1276,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue:
hided-tags: "Hidden Tags"
admin/views/federation.vue:
federation: "Federation"
instance: "Instance"
host: "Host"
notes: "Notes"
users: "Users"
following: "Following"
followers: "Followers"
caught-at: "Created at"
status: "Statuses"
latest-request-sent-at: "Time of last request sent"
latest-request-received-at: "Last request received at"
@@ -1284,7 +1291,7 @@ admin/views/federation.vue:
block: "Block"
marked-as-closed: "Marked as closed"
lookup: "Look up"
instances: "Instances"
instances: "Federated"
instance-not-registered: "The instance has not been discovered"
sort: "Sort by"
sorts:
@@ -1347,8 +1354,6 @@ desktop/views/pages/selectdrive.vue:
desktop/views/pages/search.vue:
not-available: "Search feature is turned off in the settings for this instance."
not-found: "No posts were found for '{q}'"
desktop/views/pages/share.vue:
share-with: "Share on {name}"
desktop/views/pages/tag.vue:
no-posts-found: "No posts contains \"{q}\" found."
desktop/views/pages/user-list.users.vue:

View File

@@ -103,6 +103,32 @@ common:
tags: "Etiquetas"
blocking: "Bloquear"
password: "Contraseña"
use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo"
line-width: "Grosor de línea"
line-width-thick: "Grosor"
font-size: "Tamaño del texto"
font-size-x-small: "Muy pequeño"
font-size-small: "Pequeño"
font-size-medium: "Normal"
font-size-large: "Grande"
font-size-x-large: "Muy grande"
deck-column-align: "Alineamiento de las columnas"
deck-column-align-center: "Centrar"
deck-column-align-left: "Izquierda"
deck-column-align-flexible: "Flexible"
deck-column-width: "Ancho de las columnas"
deck-column-width-narrow: "Estrecho"
deck-column-width-narrower: "Un poco estrecho"
deck-column-width-normal: "Normal"
deck-column-width-wider: "Un poco ancho"
deck-column-width-wide: "Ancho"
use-shadow: "Usar sombras en la Interfaz de Usuario"
rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario"
circle-icons: "Usar iconos circulares"
contrasted-acct: "Añadir contraste al nombre de usuario"
wallpaper: "Fondo de pantalla"
choose-wallpaper: "Escoge un fondo de pantalla"
navbar-position-left: "Izquierda"
search: "Buscar"
delete: "eliminar"
loading: "cargando"
@@ -395,9 +421,11 @@ common/views/components/profile-editor.vue:
save: "Guardar"
email-address: "Correo electrónico"
export: "Exportar"
import: "Importar"
export-targets:
mute-list: "Silenciar"
blocking-list: "Bloquear"
user-lists: "Listas"
enter-password: "Escribe una contraseña"
common/views/components/user-list-editor.vue:
users: "Usuarios"
@@ -787,11 +815,11 @@ admin/views/announcements.vue:
remove: "eliminar"
add: "Agregar"
admin/views/federation.vue:
instance: "Instancia"
host: "Host"
following: "Siguiendo"
status: "Estado"
block: "Bloquear"
instances: "Instancia"
states:
all: "Todo"
blocked: "Bloquear"

View File

@@ -522,11 +522,13 @@ common/views/components/profile-editor.vue:
email-verified: "Ladresse du courrier électronique a été vérifiée."
email-not-verified: "Adresse de courriel nest pas confirmée. Veuillez vérifier votre boite de réception."
export: "Exporter"
import: "Importer"
export-targets:
all-notes: "Toutes les notes publiées"
following-list: "Liste des abonnements"
mute-list: "Liste des comptes mis en sourdine"
blocking-list: "Liste des comptes bloqués"
user-lists: "Listes"
export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
enter-password: "Veuillez saisir votre mot de passe"
danger-zone: "Zone de danger"
@@ -925,7 +927,6 @@ admin/views/dashboard.vue:
this-instance: "Cette instance"
federated: "Fédérées"
admin/views/queue.vue:
operation: "Action(s)"
remove-all-jobs: "Enlever toutes les tâches en attente"
admin/views/abuse.vue:
title: "Abus"
@@ -1141,12 +1142,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue:
hided-tags: "Tags cachés"
admin/views/federation.vue:
federation: "Fédération"
instance: "Instance"
host: "Hôte"
notes: "Notes"
users: "Utilisateur·rice·s"
following: "Abonnements"
followers: "Abonné·e·s"
caught-at: "Créé le"
status: "Statuts"
latest-request-sent-at: "Dernière requête envoyée"
latest-request-received-at: "Dernière requête reçue"
@@ -1154,7 +1156,7 @@ admin/views/federation.vue:
block: "Bloquer"
marked-as-closed: "Marquées comme fermées"
lookup: "Recherche"
instances: "Instances"
instances: "Fédérées"
sort: "Trier par"
sorts:
caughtAtAsc: "Date dinscription (Ascendant)"
@@ -1207,8 +1209,6 @@ desktop/views/pages/selectdrive.vue:
desktop/views/pages/search.vue:
not-available: "La fonction de recherche est désactivée dans les paramètres de linstance."
not-found: "Aucune publication trouvée pour « {q} »."
desktop/views/pages/share.vue:
share-with: "Partager avec {name}"
desktop/views/pages/tag.vue:
no-posts-found: "Aucune publication contenant « {q} » na été trouvée."
desktop/views/pages/user-list.users.vue:

View File

@@ -14,6 +14,7 @@ const merge = (...args) => args.reduce((a, c) => ({
}), {});
const languages = [
'cs-CZ',
'de-DE',
'en-US',
'es-ES',
@@ -24,9 +25,11 @@ const languages = [
'nl-NL',
'pl-PL',
'zh-CN',
'zh-TW',
];
const primaries = {
'en': 'US',
'ja': 'JP',
'zh': 'CN',
};

View File

@@ -334,6 +334,7 @@ common/views/pages/explore.vue:
common/views/components/url-preview.vue:
enable-player: "プレイヤーを開く"
disable-player: "プレイヤーを閉じる"
common/views/components/user-list.vue:
no-users: "ユーザーがいません"
@@ -701,12 +702,16 @@ common/views/components/profile-editor.vue:
email-verified: "メールアドレスが確認されました"
email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
export: "エクスポート"
import: "インポート"
export-and-import: "エクスポートとインポート"
export-targets:
all-notes: "すべての投稿データ"
following-list: "フォロー"
mute-list: "ミュート"
blocking-list: "ブロック"
user-lists: "リスト"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
import-requested: "インポートをリクエストしました。これには時間がかかる場合があります。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
@@ -920,12 +925,6 @@ desktop/views/input-dialog.vue:
cancel: "キャンセル"
ok: "決定"
desktop/views/components/messaging-room-window.vue:
title: "メッセージ:"
desktop/views/components/messaging-window.vue:
title: "メッセージ"
desktop/views/components/note-detail.vue:
private: "この投稿は非公開です"
deleted: "この投稿は削除されました"
@@ -1095,7 +1094,7 @@ desktop/views/components/timeline.vue:
hybrid: "ソーシャル"
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
messages: "ダイレクト投稿"
list: "リスト"
hashtag: "ハッシュタグ"
add-tag-timeline: "ハッシュタグを追加"
@@ -1175,7 +1174,7 @@ admin/views/dashboard.vue:
federated: "連合"
admin/views/queue.vue:
operation: "操作"
title: "キュー"
remove-all-jobs: "すべてのジョブをクリア"
admin/views/abuse.vue:
@@ -1233,6 +1232,8 @@ admin/views/instance.vue:
disable-local-timeline: "ローカルタイムラインを無効にする"
disable-global-timeline: "グローバルタイムラインを無効にする"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
enable-emoji-reaction: "リアクションに絵文字を使えるようにする"
use-star-for-reaction-fallback: "不明なリアクションのフォールバックに star を使う"
invite: "招待"
save: "保存"
saved: "保存しました"
@@ -1403,12 +1404,13 @@ admin/views/hashtags.vue:
hided-tags: "Hidden Tags"
admin/views/federation.vue:
federation: "連合"
instance: "インスタンス"
host: "ホスト"
notes: "投稿"
users: "ユーザー"
following: "フォロー中"
followers: "フォロワー"
caught-at: "登録日時"
status: "ステータス"
latest-request-sent-at: "直近のリクエスト送信"
latest-request-received-at: "直近のリクエスト受信"
@@ -1417,7 +1419,7 @@ admin/views/federation.vue:
block: "ブロック"
marked-as-closed: "閉鎖されているとマーク"
lookup: "照会"
instances: "インスタンス"
instances: "連合"
instance-not-registered: "そのインスタンスは登録されていません"
sort: "ソート"
sorts:
@@ -1486,9 +1488,6 @@ desktop/views/pages/search.vue:
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{q}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{name}で共有"
desktop/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
@@ -1530,9 +1529,6 @@ desktop/views/pages/user/user.timeline.vue:
with-media: "メディア"
my-posts: "私の投稿"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
title: "通知"
@@ -1680,7 +1676,7 @@ mobile/views/pages/home.vue:
hybrid: "ソーシャル"
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
messages: "ダイレクト投稿"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"

View File

@@ -476,10 +476,12 @@ common/views/components/profile-editor.vue:
email-verified: "このメールアドレスOKや"
email-not-verified: "メールアドレスが確認されとらん。メールボックスもっぺん見てくれへん?"
export: "エクスポート"
import: "インポート"
export-targets:
following-list: "フォロー"
mute-list: "ミュート"
blocking-list: "ブロック"
user-lists: "リスト"
enter-password: "パスワードを入れてや"
common/views/components/user-list-editor.vue:
users: "ユーザー"
@@ -858,8 +860,6 @@ admin/views/dashboard.vue:
instances: "インスタンス"
this-instance: "ワイのインスタンス"
federated: "連合"
admin/views/queue.vue:
operation: "操作"
admin/views/abuse.vue:
details: "もっと"
remove-report: "削除"
@@ -988,7 +988,7 @@ admin/views/announcements.vue:
add: "増やす"
saved: "保存したで!"
admin/views/federation.vue:
federation: "連合"
instance: "インスタンス"
host: "ホスト"
notes: "投稿"
users: "ユーザー"
@@ -997,7 +997,7 @@ admin/views/federation.vue:
status: "ステータス"
block: "ブロック"
lookup: "照会"
instances: "インスタンス"
instances: "連合"
states:
all: "すべて"
blocked: "ブロック"

View File

@@ -314,6 +314,7 @@ common/views/pages/explore.vue:
users-info: "현재 {users} 사용자가 등록되어 있습니다"
common/views/components/url-preview.vue:
enable-player: "플레이어 열기"
disable-player: "플레이어 닫기"
common/views/components/user-list.vue:
no-users: "사용자가 없습니다"
common/views/components/games/reversi/reversi.vue:
@@ -647,12 +648,16 @@ common/views/components/profile-editor.vue:
email-verified: "매일 주소가 확인되었습니다"
email-not-verified: "메일 주소가 확인되지 않았습니다. 받은 편지함을 확인하여 주시기 바랍니다."
export: "내보내기"
import: "가져오기"
export-and-import: "내보내기와 가져오기"
export-targets:
all-notes: "모든 글 데이터"
following-list: "팔로잉"
mute-list: "뮤트"
blocking-list: "차단"
user-lists: "리스트"
export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다."
import-requested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
enter-password: "비밀번호를 입력하여 주십시오"
danger-zone: "위험한 설정"
delete-account: "계정 삭제"
@@ -1052,7 +1057,7 @@ admin/views/dashboard.vue:
this-instance: "이 인스턴스"
federated: "연합"
admin/views/queue.vue:
operation: "동작"
title: ""
remove-all-jobs: "모든 작업 제거"
admin/views/abuse.vue:
title: "스팸 신고"
@@ -1270,12 +1275,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue:
hided-tags: "Hidden Tags"
admin/views/federation.vue:
federation: "연합"
instance: "인스턴스"
host: "호스트"
notes: "글"
users: "사용자"
following: "팔로우 중"
followers: "팔로워"
caught-at: "등록 날짜"
status: "상태"
latest-request-sent-at: "마지막으로 요청을 전송한 시간"
latest-request-received-at: "마지막으로 요청을 받은 시간"
@@ -1284,7 +1290,7 @@ admin/views/federation.vue:
block: "차단"
marked-as-closed: "폐쇄된 것으로 표시"
lookup: "조회"
instances: "인스턴스"
instances: "연합"
instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다"
sort: "정렬"
sorts:
@@ -1347,8 +1353,6 @@ desktop/views/pages/selectdrive.vue:
desktop/views/pages/search.vue:
not-available: "검색 기능은 인스턴스 설정에서 비활성화되어 있습니다."
not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다."
desktop/views/pages/share.vue:
share-with: "{name}(으)로 공유"
desktop/views/pages/tag.vue:
no-posts-found: "해시태그 \"{q}\"가 붙은 글을 찾을 수 없습니다."
desktop/views/pages/user-list.users.vue:

View File

@@ -196,6 +196,7 @@ common/views/components/profile-editor.vue:
banner: "Omslagfoto"
export-targets:
following-list: "Volgend"
user-lists: "Lijsten"
enter-password: "Voer het wachtwoord in"
common/views/components/user-list-editor.vue:
users: "Gebruiker"

View File

@@ -187,6 +187,7 @@ common/views/components/profile-editor.vue:
save: "Lagre"
export-targets:
following-list: "Følger"
user-lists: "Lister"
common/views/components/user-list-editor.vue:
users: "Bruker"
common/views/widgets/broadcast.vue:

View File

@@ -121,8 +121,35 @@ common:
other: "Inne"
appearance: "Wygląd"
behavior: "Zachowanie"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
note-visibility: "Widoczność wpisów"
web-search-engine: "Wyszukiwarka internetowa"
line-width: "Szerokości linii"
line-width-thin: "Cienka"
line-width-normal: "Normalna"
line-width-thick: "Gruba"
font-size: "Rozmiar tekstu"
font-size-x-small: "Małe"
font-size-medium: "Normalna"
font-size-large: "Trochę duży"
font-size-x-large: "Duży"
deck-column-align-center: "Po środku"
deck-column-align-left: "Z lewej"
deck-column-align-flexible: "Elastyczne"
deck-column-width: "Szerokość kolumn w talii"
deck-column-width-narrow: "Wąska"
deck-column-width-narrower: "Trochę wąska"
deck-column-width-normal: "Normalna"
deck-column-width-wider: "Trochę szerokie"
deck-column-width-wide: "Szeroka"
wallpaper: "Tapeta"
choose-wallpaper: "Wybierz tapetę"
timeline: "Oś czasu"
sound: "Dźwięk"
test: "Test"
update: "Aktualizacja Misskey"
version: "Wersja:"
navbar-position-left: "Z lewej"
search: "Szukaj"
delete: "Usuń"
loading: "Ładowanie"
@@ -474,10 +501,12 @@ common/views/components/profile-editor.vue:
email-address: "Adres e-mail"
email-verified: "Twój adres e-mail został zweryfikowany."
export: "Eksportuj"
import: "Importuj"
export-targets:
following-list: "Śledzeni"
mute-list: "Wycisz"
blocking-list: "Zablokuj"
user-lists: "Listy"
enter-password: "Wprowadź hasło"
common/views/components/user-list-editor.vue:
users: "Użytkownicy"
@@ -918,13 +947,14 @@ admin/views/announcements.vue:
are-you-sure: "Usunąć \"$1\"?"
removed: "Usunięto"
admin/views/federation.vue:
instance: "Instancja"
notes: "Wpis"
users: "Użytkownicy"
following: "Śledzisz"
followers: "Śledzący"
caught-at: "Utworzono"
status: "Stan"
block: "Zablokuj"
instances: "Instancja"
sort: "Sortuj"
states:
all: "Wszyscy"

View File

@@ -314,6 +314,7 @@ common/views/pages/explore.vue:
users-info: "当前有{users}个注册用户"
common/views/components/url-preview.vue:
enable-player: "打开播放器"
disable-player: "关闭播放器"
common/views/components/user-list.vue:
no-users: "无用户"
common/views/components/games/reversi/reversi.vue:
@@ -647,12 +648,16 @@ common/views/components/profile-editor.vue:
email-verified: "电子邮件地址已验证"
email-not-verified: "邮件地址尚未验证。 请检查您的邮箱。"
export: "导出"
import: "导入"
export-and-import: "导出/导入"
export-targets:
all-notes: "所有发帖"
following-list: "关注列表"
mute-list: "屏蔽列表"
blocking-list: "黑名单"
user-lists: "列表"
export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。"
import-requested: "导入请求已提交。这可能需要花一点时间。"
enter-password: "请输入您的密码"
danger-zone: "危险选项"
delete-account: "删除帐户"
@@ -1052,7 +1057,7 @@ admin/views/dashboard.vue:
this-instance: "此实例"
federated: "联合"
admin/views/queue.vue:
operation: "操作"
title: "队列"
remove-all-jobs: "清除所有作业"
admin/views/abuse.vue:
title: "举报垃圾信息"
@@ -1270,12 +1275,13 @@ admin/views/announcements.vue:
admin/views/hashtags.vue:
hided-tags: "隐藏标签"
admin/views/federation.vue:
federation: "联合"
instance: ""
host: "主机名"
notes: "帖子"
users: "用户"
following: "正在关注"
followers: "关注者"
caught-at: "注册日期"
status: "状态"
latest-request-sent-at: "上次发送的请求"
latest-request-received-at: "上次收到的请求"
@@ -1284,7 +1290,7 @@ admin/views/federation.vue:
block: "拉黑"
marked-as-closed: "标记为已关闭"
lookup: "查询"
instances: "实例"
instances: "联合"
instance-not-registered: "实例未注册"
sort: "排序"
sorts:
@@ -1347,8 +1353,6 @@ desktop/views/pages/selectdrive.vue:
desktop/views/pages/search.vue:
not-available: "在此实例的设置中关闭搜索功能。"
not-found: "没有找到“{q}”的帖子"
desktop/views/pages/share.vue:
share-with: "共享{name}"
desktop/views/pages/tag.vue:
no-posts-found: "没有找到带有主题标签“{q}”的帖子"
desktop/views/pages/user-list.users.vue:

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.92.2",
"version": "10.98.2",
"codename": "nighthike",
"repository": {
"type": "git",
@@ -46,7 +46,6 @@
"@types/gulp-uglify": "3.0.6",
"@types/gulp-util": "3.0.34",
"@types/is-root": "1.0.0",
"@types/is-svg": "3.0.0",
"@types/is-url": "1.2.28",
"@types/js-yaml": "3.12.0",
"@types/jsdom": "12.2.3",
@@ -78,6 +77,7 @@
"@types/qrcode": "1.3.0",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.10",
"@types/rename": "1.0.1",
"@types/request": "2.48.1",
"@types/request-promise-native": "1.0.15",
"@types/request-stats": "3.0.0",
@@ -96,7 +96,7 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.5.0",
"apexcharts": "3.6.2",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@@ -108,21 +108,22 @@
"chai-http": "4.2.1",
"chalk": "2.4.2",
"commander": "2.19.0",
"content-disposition": "0.5.3",
"crc-32": "1.2.0",
"css-loader": "2.1.0",
"css-loader": "2.1.1",
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-equal": "1.0.1",
"deepcopy": "0.6.3",
"diskusage": "1.0.0",
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.3.1",
"elasticsearch": "15.4.1",
"emojilib": "2.4.0",
"escape-regexp": "0.0.1",
"eslint": "5.15.0",
"eslint": "5.15.1",
"eslint-plugin-vue": "5.2.2",
"eventemitter3": "3.1.0",
"feed": "2.0.2",
"feed": "2.0.4",
"file-type": "10.9.0",
"fuckadblock": "3.2.1",
"gulp": "4.0.0",
@@ -131,20 +132,20 @@
"gulp-mocha": "6.0.0",
"gulp-rename": "1.4.0",
"gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.4",
"gulp-sourcemaps": "2.6.5",
"gulp-stylus": "2.7.0",
"gulp-tslint": "8.1.3",
"gulp-tslint": "8.1.4",
"gulp-typescript": "5.0.0",
"gulp-uglify": "3.0.1",
"gulp-uglify": "3.0.2",
"gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "3.5.21",
"http-signature": "1.2.0",
"insert-text-at-cursor": "0.1.2",
"is-root": "2.0.0",
"is-svg": "3.0.0",
"js-yaml": "3.12.1",
"jsdom": "13.2.0",
"is-svg": "4.0.0",
"js-yaml": "3.12.2",
"jsdom": "14.0.0",
"json5": "2.1.0",
"json5-loader": "1.0.1",
"katex": "0.10.1",
@@ -189,13 +190,14 @@
"pug": "2.0.3",
"punycode": "2.1.1",
"qrcode": "1.3.3",
"randomcolor": "0.5.3",
"ratelimiter": "3.2.0",
"randomcolor": "0.5.4",
"ratelimiter": "3.3.0",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.1.10",
"redis": "2.8.0",
"rename": "1.0.4",
"request": "2.88.0",
"request-promise-native": "1.0.5",
"request-promise-native": "1.0.7",
"request-stats": "3.0.0",
"rimraf": "2.6.3",
"rndstr": "1.0.0",
@@ -210,14 +212,14 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
"systeminformation": "4.0.14",
"systeminformation": "4.0.16",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3",
"textarea-caret": "3.1.0",
"tinycolor2": "1.4.1",
"tmp": "0.0.33",
"ts-loader": "5.3.3",
"ts-node": "8.0.2",
"ts-node": "8.0.3",
"tslint": "5.13.1",
"tslint-sonarts": "1.9.0",
"typescript": "3.3.3333",
@@ -232,7 +234,7 @@
"vue-color": "2.7.0",
"vue-content-loading": "1.5.3",
"vue-cropperjs": "3.0.0",
"vue-i18n": "8.8.2",
"vue-i18n": "8.9.0",
"vue-js-modal": "1.3.28",
"vue-json-pretty": "1.4.1",
"vue-loader": "15.7.0",
@@ -241,9 +243,9 @@
"vue-router": "3.0.2",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.12",
"vue-svg-inline-loader": "1.2.13",
"vue-template-compiler": "2.6.8",
"vuedraggable": "2.18.1",
"vuedraggable": "2.19.2",
"vuewordcloud": "18.7.11",
"vuex": "3.1.0",
"vuex-persistedstate": "2.5.4",
@@ -252,7 +254,7 @@
"webpack": "4.28.4",
"webpack-cli": "3.2.3",
"websocket": "1.0.28",
"ws": "6.1.4",
"ws": "6.2.0",
"xev": "2.0.1"
}
}

View File

@@ -8,7 +8,7 @@ declare module 'koa-slow' {
function slow(options?: ISlowOptions): Middleware;
namespace slow { } // Hack
namespace slow {} // Hack
export = slow;
}

View File

@@ -181,7 +181,12 @@ export default Vue.extend({
},
grid: {
clipMarkers: false,
borderColor: 'rgba(0, 0, 0, 0.1)'
borderColor: 'rgba(0, 0, 0, 0.1)',
xaxis: {
lines: {
show: true,
}
},
},
stroke: {
curve: 'straight',
@@ -240,6 +245,7 @@ export default Vue.extend({
federationInstancesChart(total: boolean): any {
return {
series: [{
name: 'Instances',
data: this.format(total
? this.stats.federation.instance.total
: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))

View File

@@ -0,0 +1,196 @@
<template>
<div class="mzxlfysy">
<div>
<header>
<span><fa :icon="faInbox"/> In</span>
<span v-if="latestStats">{{ latestStats.inbox.activeSincePrevTick | number }} / {{ latestStats.inbox.delayed | number }}</span>
</header>
<div ref="in"></div>
</div>
<div>
<header>
<span><fa :icon="faPaperPlane"/> Out</span>
<span v-if="latestStats">{{ latestStats.deliver.activeSincePrevTick | number }} / {{ latestStats.deliver.delayed | number }}</span>
</header>
<div ref="out"></div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faInbox } from '@fortawesome/free-solid-svg-icons';
import { faPaperPlane } from '@fortawesome/free-regular-svg-icons';
import ApexCharts from 'apexcharts';
const limit = 150;
export default Vue.extend({
data() {
return {
stats: [],
inChart: null,
outChart: null,
faInbox, faPaperPlane
};
},
computed: {
latestStats(): any {
return this.stats[this.stats.length - 1];
}
},
watch: {
stats(stats) {
this.inChart.updateSeries([{
data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
}]);
this.outChart.updateSeries([{
data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting }))
}, {
data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
}]);
}
},
mounted() {
const chartOpts = {
chart: {
type: 'area',
height: 200,
animations: {
dynamicAnimation: {
enabled: false
}
},
toolbar: {
show: false
},
zoom: {
enabled: false
}
},
dataLabels: {
enabled: false
},
grid: {
clipMarkers: false,
borderColor: 'rgba(0, 0, 0, 0.1)'
},
stroke: {
curve: 'straight',
width: 2
},
tooltip: {
enabled: false
},
legend: {
show: false
},
colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'],
series: [{ data: [] }, { data: [] }, { data: [] }, { data: [] }] as any,
xaxis: {
type: 'numeric',
labels: {
show: false
},
tooltip: {
enabled: false
}
},
yaxis: {
show: false,
min: 0,
}
};
this.inChart = new ApexCharts(this.$refs.in, chartOpts);
this.outChart = new ApexCharts(this.$refs.out, chartOpts);
this.inChart.render();
this.outChart.render();
const connection = this.$root.stream.useSharedConnection('queueStats');
connection.on('stats', this.onStats);
connection.on('statsLog', this.onStatsLog);
connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
length: limit
});
this.$once('hook:beforeDestroy', () => {
connection.dispose();
this.inChart.destroy();
this.outChart.destroy();
});
},
methods: {
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > limit) this.stats.shift();
},
onStatsLog(statsLog) {
for (const stats of statsLog.reverse()) {
this.onStats(stats);
}
}
}
});
</script>
<style lang="stylus" scoped>
.mzxlfysy
display flex
> div
display block
flex 1
padding 20px 12px 0 12px
box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
background var(--face)
border-radius 8px
&:first-child
margin-right 16px
> header
display flex
padding 0 8px
margin-bottom -16px
color var(--adminDashboardCardFg)
font-size 14px
> span
&:last-child
margin-left auto
opacity 0.7
> span
opacity 0.7
> div
margin-bottom -10px
@media (max-width 1000px)
display block
margin-bottom 26px
> div
&:first-child
margin-right 0
margin-bottom 26px
</style>

View File

@@ -73,6 +73,10 @@
<x-charts ref="charts"/>
</div>
<div class="queue">
<x-queue/>
</div>
<div class="cpu-memory">
<x-cpu-memory :connection="connection"/>
</div>
@@ -86,9 +90,10 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
import XCpuMemory from "./cpu-memory.vue";
import XCharts from "./charts.vue";
import XApLog from "./ap-log.vue";
import XCpuMemory from "./dashboard.cpu-memory.vue";
import XQueue from "./dashboard.queue-charts.vue";
import XCharts from "./dashboard.charts.vue";
import XApLog from "./dashboard.ap-log.vue";
import { faDatabase } from '@fortawesome/free-solid-svg-icons';
import MarqueeText from 'vue-marquee-text-component';
import randomColor from 'randomcolor';
@@ -98,6 +103,7 @@ export default Vue.extend({
components: {
XCpuMemory,
XQueue,
XCharts,
XApLog,
MarqueeText
@@ -274,6 +280,9 @@ export default Vue.extend({
> .charts
margin-bottom 16px
> .queue
margin-bottom 16px
> .cpu-memory
margin-bottom 16px

View File

@@ -1,43 +1,58 @@
<template>
<div>
<ui-card>
<template #title><fa :icon="faTerminal"/> {{ $t('federation') }}</template>
<template #title><fa :icon="faTerminal"/> {{ $t('instance') }}</template>
<section class="fit-top">
<ui-input class="target" v-model="target" type="text" @enter="showInstance()">
<span>{{ $t('host') }}</span>
<template #prefix><fa :icon="faServer"/></template>
</ui-input>
<ui-button @click="showInstance()"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
<div class="instance" v-if="instance">
<ui-input :value="instance.host" type="text" readonly>
<span>{{ $t('host') }}</span>
</ui-input>
<ui-horizon-group inputs>
<ui-input :value="instance.host" type="text" readonly>
<span>{{ $t('host') }}</span>
<template #prefix><fa :icon="faServer"/></template>
</ui-input>
<ui-input :value="instance.caughtAt | date" type="text" readonly>
<span>{{ $t('caught-at') }}</span>
<template #prefix><fa :icon="faCrosshairs"/></template>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.notesCount | number" type="text" readonly>
<span>{{ $t('notes') }}</span>
<template #prefix><fa :icon="faEnvelopeOpenText"/></template>
</ui-input>
<ui-input :value="instance.usersCount | number" type="text" readonly>
<span>{{ $t('users') }}</span>
<template #prefix><fa :icon="faUsers"/></template>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.followingCount | number" type="text" readonly>
<span>{{ $t('following') }}</span>
<template #prefix><fa :icon="faCaretDown"/></template>
</ui-input>
<ui-input :value="instance.followersCount | number" type="text" readonly>
<span>{{ $t('followers') }}</span>
<template #prefix><fa :icon="faCaretUp"/></template>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.latestRequestSentAt" type="text" readonly>
<ui-input :value="instance.latestRequestSentAt | date" type="text" readonly>
<span>{{ $t('latest-request-sent-at') }}</span>
<template #prefix><fa :icon="faPaperPlane"/></template>
</ui-input>
<ui-input :value="instance.latestStatus" type="text" readonly>
<span>{{ $t('status') }}</span>
<template #prefix><fa :icon="faTrafficLight"/></template>
</ui-input>
</ui-horizon-group>
<ui-input :value="instance.latestRequestReceivedAt" type="text" readonly>
<ui-input :value="instance.latestRequestReceivedAt | date" type="text" readonly>
<span>{{ $t('latest-request-received-at') }}</span>
<template #prefix><fa :icon="faInbox"/></template>
</ui-input>
<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
<ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()">{{ $t('marked-as-closed') }}</ui-switch>
@@ -133,7 +148,8 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons';
import { faPaperPlane } from '@fortawesome/free-regular-svg-icons';
import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faTrafficLight, faInbox } from '@fortawesome/free-solid-svg-icons';
import ApexCharts from 'apexcharts';
import * as tinycolor from 'tinycolor2';
@@ -144,19 +160,23 @@ const negate = arr => arr.map(x => -x);
export default Vue.extend({
i18n: i18n('admin/views/federation.vue'),
filters: {
date: v => v ? new Date(v).toLocaleString() : 'N/A'
},
data() {
return {
instance: null,
target: null,
sort: '+lastCommunicatedAt',
state: 'all',
limit: 50,
limit: 100,
instances: [],
chart: null,
chartSrc: 'requests',
chartSpan: 'hour',
chartInstance: null,
faGlobe, faTerminal, faSearch, faMinusCircle, faServer
faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faPaperPlane, faTrafficLight, faInbox
};
},

View File

@@ -6,6 +6,7 @@
<ui-input :value="host" readonly>{{ $t('host') }}</ui-input>
<ui-input v-model="name">{{ $t('instance-name') }}</ui-input>
<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea>
<ui-input v-model="iconUrl"><template #icon><fa icon="link"/></template>{{ $t('icon-url') }}</ui-input>
<ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input>
<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input>
<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input>
@@ -24,6 +25,8 @@
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch>
</section>
<section class="fit-bottom">
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
@@ -154,9 +157,12 @@ export default Vue.extend({
disableRegistration: false,
disableLocalTimeline: false,
disableGlobalTimeline: false,
enableEmojiReaction: true,
useStarForReactionFallback: false,
mascotImageUrl: null,
bannerUrl: null,
errorImageUrl: null,
iconUrl: null,
name: null,
description: null,
languages: null,
@@ -204,9 +210,12 @@ export default Vue.extend({
this.disableRegistration = meta.disableRegistration;
this.disableLocalTimeline = meta.disableLocalTimeline;
this.disableGlobalTimeline = meta.disableGlobalTimeline;
this.enableEmojiReaction = meta.enableEmojiReaction;
this.useStarForReactionFallback = meta.useStarForReactionFallback;
this.mascotImageUrl = meta.mascotImageUrl;
this.bannerUrl = meta.bannerUrl;
this.errorImageUrl = meta.errorImageUrl;
this.iconUrl = meta.iconUrl;
this.name = meta.name;
this.description = meta.description;
this.languages = meta.langs.join(' ');
@@ -264,9 +273,12 @@ export default Vue.extend({
disableRegistration: this.disableRegistration,
disableLocalTimeline: this.disableLocalTimeline,
disableGlobalTimeline: this.disableGlobalTimeline,
enableEmojiReaction: this.enableEmojiReaction,
useStarForReactionFallback: this.useStarForReactionFallback,
mascotImageUrl: this.mascotImageUrl,
bannerUrl: this.bannerUrl,
errorImageUrl: this.errorImageUrl,
iconUrl: this.iconUrl,
name: this.name,
description: this.description,
langs: this.languages.split(' '),

View File

@@ -4,7 +4,7 @@
<template #title><fa :icon="faStream"/> {{ $t('logs') }}</template>
<section class="fit-top">
<ui-horizon-group inputs>
<ui-input v-model="domain" debounce>
<ui-input v-model="domain" :debounce="true">
<span>{{ $t('domain') }}</span>
</ui-input>
<ui-select v-model="level">

View File

@@ -1,68 +1,258 @@
<template>
<div>
<ui-card>
<template #title>{{ $t('operation') }}</template>
<section>
<header>Deliver</header>
<ui-horizon-group inputs v-if="stats">
<ui-input :value="stats.deliver.waiting | number" type="text" readonly>
<span>Waiting</span>
<template #title><fa :icon="faChartBar"/> {{ $t('title') }}</template>
<section class="wptihjuy">
<header><fa :icon="faPaperPlane"/> Deliver</header>
<ui-info warn v-if="latestStats && latestStats.deliver.waiting > 0">The queue is jammed.</ui-info>
<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
<ui-input :value="latestStats.deliver.activeSincePrevTick | number" type="text" readonly>
<span>Process</span>
<template #prefix><fa :icon="fasPlayCircle"/></template>
<template #suffix>jobs/tick</template>
</ui-input>
<ui-input :value="stats.deliver.delayed | number" type="text" readonly>
<span>Delayed</span>
</ui-input>
<ui-input :value="stats.deliver.active | number" type="text" readonly>
<ui-input :value="latestStats.deliver.active | number" type="text" readonly>
<span>Active</span>
<template #prefix><fa :icon="farPlayCircle"/></template>
<template #suffix>jobs</template>
</ui-input>
<ui-input :value="latestStats.deliver.waiting | number" type="text" readonly>
<span>Waiting</span>
<template #prefix><fa :icon="faStopCircle"/></template>
<template #suffix>jobs</template>
</ui-input>
<ui-input :value="latestStats.deliver.delayed | number" type="text" readonly>
<span>Delayed</span>
<template #prefix><fa :icon="faStopwatch"/></template>
<template #suffix>jobs</template>
</ui-input>
</ui-horizon-group>
<div ref="deliverChart" class="chart"></div>
</section>
<section>
<header>Inbox</header>
<ui-horizon-group inputs v-if="stats">
<ui-input :value="stats.inbox.waiting | number" type="text" readonly>
<span>Waiting</span>
<section class="wptihjuy">
<header><fa :icon="faInbox"/> Inbox</header>
<ui-info warn v-if="latestStats && latestStats.inbox.waiting > 0">The queue is jammed.</ui-info>
<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
<ui-input :value="latestStats.inbox.activeSincePrevTick | number" type="text" readonly>
<span>Process</span>
<template #prefix><fa :icon="fasPlayCircle"/></template>
<template #suffix>jobs/tick</template>
</ui-input>
<ui-input :value="stats.inbox.delayed | number" type="text" readonly>
<span>Delayed</span>
</ui-input>
<ui-input :value="stats.inbox.active | number" type="text" readonly>
<ui-input :value="latestStats.inbox.active | number" type="text" readonly>
<span>Active</span>
<template #prefix><fa :icon="farPlayCircle"/></template>
<template #suffix>jobs</template>
</ui-input>
<ui-input :value="latestStats.inbox.waiting | number" type="text" readonly>
<span>Waiting</span>
<template #prefix><fa :icon="faStopCircle"/></template>
<template #suffix>jobs</template>
</ui-input>
<ui-input :value="latestStats.inbox.delayed | number" type="text" readonly>
<span>Delayed</span>
<template #prefix><fa :icon="faStopwatch"/></template>
<template #suffix>jobs</template>
</ui-input>
</ui-horizon-group>
<div ref="inboxChart" class="chart"></div>
</section>
<section>
<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button>
</section>
</ui-card>
<ui-card>
<template #title><fa :icon="faTasks"/> {{ $t('jobs') }}</template>
<section class="fit-top">
<ui-horizon-group inputs>
<ui-select v-model="domain">
<template #label>{{ $t('queue') }}</template>
<option value="deliver">{{ $t('domains.deliver') }}</option>
<option value="inbox">{{ $t('domains.inbox') }}</option>
</ui-select>
<ui-select v-model="state">
<template #label>{{ $t('state') }}</template>
<option value="delayed">{{ $t('states.delayed') }}</option>
</ui-select>
</ui-horizon-group>
<sequential-entrance animation="entranceFromTop" delay="25">
<div class="xvvuvgsv" v-for="job in jobs">
<b>{{ job.id }}</b>
<template v-if="domain === 'deliver'">
<span>{{ job.data.to }}</span>
</template>
<template v-if="domain === 'inbox'">
<span>{{ job.activity.id }}</span>
</template>
</div>
</sequential-entrance>
<ui-info v-if="jobs.length == jobsLimit">{{ $t('result-is-truncated', { n: jobsLimit }) }}</ui-info>
</section>
</ui-card>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
import ApexCharts from 'apexcharts';
import * as tinycolor from 'tinycolor2';
import { faTasks, faInbox, faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons';
import { faPaperPlane, faStopCircle, faPlayCircle as farPlayCircle, faChartBar } from '@fortawesome/free-regular-svg-icons';
const limit = 200;
export default Vue.extend({
i18n: i18n('admin/views/queue.vue'),
data() {
return {
stats: null
stats: [],
deliverChart: null,
inboxChart: null,
jobs: [],
jobsLimit: 50,
domain: 'deliver',
state: 'delayed',
faTasks, faPaperPlane, faInbox, faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle, faChartBar
};
},
created() {
const fetchStats = () => {
this.$root.api('admin/queue/stats', {}, true).then(stats => {
this.stats = stats;
});
};
computed: {
latestStats(): any {
return this.stats[this.stats.length - 1];
}
},
fetchStats();
watch: {
stats(stats) {
this.inboxChart.updateSeries([{
name: 'Process',
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick }))
}, {
name: 'Active',
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
}, {
name: 'Waiting',
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting }))
}, {
name: 'Delayed',
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
}]);
this.deliverChart.updateSeries([{
name: 'Process',
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick }))
}, {
name: 'Active',
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
}, {
name: 'Waiting',
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting }))
}, {
name: 'Delayed',
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
}]);
},
const clock = setInterval(fetchStats, 1000);
domain() {
this.jobs = [];
this.fetchJobs();
},
state() {
this.jobs = [];
this.fetchJobs();
},
},
mounted() {
this.fetchJobs();
const chartOpts = id => ({
chart: {
id,
group: 'queue',
type: 'area',
height: 200,
animations: {
dynamicAnimation: {
enabled: false
}
},
toolbar: {
show: false
},
zoom: {
enabled: false
}
},
dataLabels: {
enabled: false
},
grid: {
clipMarkers: false,
borderColor: 'rgba(0, 0, 0, 0.1)',
xaxis: {
lines: {
show: true,
}
},
},
stroke: {
curve: 'straight',
width: 2
},
tooltip: {
enabled: false
},
legend: {
labels: {
colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
},
},
series: [] as any,
colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'],
xaxis: {
type: 'numeric',
labels: {
show: false
},
tooltip: {
enabled: false
}
},
yaxis: {
show: false,
min: 0,
}
});
this.inboxChart = new ApexCharts(this.$refs.inboxChart, chartOpts('a'));
this.deliverChart = new ApexCharts(this.$refs.deliverChart, chartOpts('b'));
this.inboxChart.render();
this.deliverChart.render();
const connection = this.$root.stream.useSharedConnection('queueStats');
connection.on('stats', this.onStats);
connection.on('statsLog', this.onStatsLog);
connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
length: limit
});
this.$once('hook:beforeDestroy', () => {
clearInterval(clock);
connection.dispose();
this.inboxChart.destroy();
this.deliverChart.destroy();
});
},
@@ -83,6 +273,39 @@ export default Vue.extend({
});
});
},
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > limit) this.stats.shift();
},
onStatsLog(statsLog) {
for (const stats of statsLog.reverse()) {
this.onStats(stats);
}
},
fetchJobs() {
this.$root.api('admin/queue/jobs', {
domain: this.domain,
state: this.state,
limit: this.jobsLimit
}).then(jobs => {
this.jobs = jobs;
});
},
}
});
</script>
<style lang="stylus" scoped>
.wptihjuy
> .chart
min-height 200px !important
margin 0 -8px
.xvvuvgsv
> b
margin-right 16px
</style>

View File

@@ -69,7 +69,7 @@ export default Vue.extend({
},
plotOptions: {
bar: {
columnWidth: '90%'
columnWidth: '80%'
}
},
grid: {

View File

@@ -30,6 +30,7 @@
import Vue from 'vue';
import * as emojilib from 'emojilib';
import contains from '../../../common/scripts/contains';
import { twemojiBase } from '../../../../../misc/twemoji-base';
type EmojiDef = {
emoji: string;
@@ -54,7 +55,7 @@ const emjdb: EmojiDef[] = lib.map((x: any) => ({
emoji: x[1].char,
name: x[0],
aliasOf: null,
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
}));
for (const x of lib as any) {
@@ -64,7 +65,7 @@ for (const x of lib as any) {
emoji: x[1].char,
name: k,
aliasOf: x[0],
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
});
}
}

View File

@@ -0,0 +1,177 @@
<template>
<div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`">
<img
:src="file.url"
:alt="file.name"
:title="file.name"
v-if="detail && is === 'image'"/>
<video
:src="file.url"
ref="volumectrl"
preload="metadata"
controls
v-else-if="detail && is === 'video'"/>
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
<audio
:src="file.url"
ref="volumectrl"
preload="metadata"
controls
v-else-if="detail && is === 'audio'"/>
<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/>
<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/>
<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/>
<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/>
<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/>
<fa :icon="faFile" class="icon" v-else/>
<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import anime from 'animejs';
import {
faFile,
faFileAlt,
faFileImage,
faMusic,
faFileVideo,
faFileCsv,
faFilePdf,
faFileArchive,
faFilm
} from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
props: {
file: {
type: Object,
required: true
},
fit: {
type: String,
required: true
},
detail: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isContextmenuShowing: false,
isDragging: false,
faFile,
faFileAlt,
faFileImage,
faMusic,
faFileVideo,
faFileCsv,
faFilePdf,
faFileArchive,
faFilm
};
},
computed: {
is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' {
if (this.file.type.startsWith('image/')) return 'image';
if (this.file.type.startsWith('video/')) return 'video';
if (this.file.type === 'audio/midi') return 'midi';
if (this.file.type.startsWith('audio/')) return 'audio';
if (this.file.type.endsWith('/csv')) return 'csv';
if (this.file.type.endsWith('/pdf')) return 'pdf';
if (this.file.type.startsWith('text/')) return 'textfile';
if ([
"application/zip",
"application/x-cpio",
"application/x-bzip",
"application/x-bzip2",
"application/java-archive",
"application/x-rar-compressed",
"application/x-tar",
"application/gzip",
"application/x-7z-compressed"
].some(e => e === this.file.type)) return 'archive';
return 'unknown';
},
isThumbnailAvailable(): boolean {
return this.file.thumbnailUrl
? this.file.thumbnailUrl.endsWith('?thumbnail')
? (this.is === 'image' || this.is === 'video')
: true
: false;
},
background(): string {
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
? `rgb(${this.file.properties.avgColor.join(',')})`
: 'transparent';
}
},
mounted() {
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume;
},
methods: {
onThumbnailLoaded() {
if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) {
anime({
targets: this.$refs.thumbnail,
backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`,
duration: 100,
easing: 'linear'
});
}
},
volumechange() {
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume });
}
}
});
</script>
<style lang="stylus" scoped>
.zdjebgpv
display flex
> img,
> .icon
pointer-events none
> img
height 100%
width 100%
margin auto
object-fit cover
> .icon
height 65%
width 65%
margin auto
> video,
> audio
width 100%
> .icon-sub
position absolute
width 30%
height auto
margin 0
right 4%
bottom 4%
&.detail
> .icon
height 100px
margin 16px auto
</style>

View File

@@ -10,6 +10,7 @@ import Vue from 'vue';
// スクリプトサイズがデカい
//import { lib } from 'emojilib';
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
import { twemojiBase } from '../../../../../misc/twemoji-base';
export default Vue.extend({
props: {
@@ -29,7 +30,11 @@ export default Vue.extend({
customEmojis: {
required: false,
default: () => []
}
},
isReaction: {
type: Boolean,
default: false
},
},
data() {
@@ -46,7 +51,7 @@ export default Vue.extend({
},
useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis;
return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
}
},
@@ -73,7 +78,7 @@ export default Vue.extend({
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
codes = codes.filter(x => x && x.length);
this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`;
this.url = `${twemojiBase}/2/svg/${codes.join('-')}.svg`;
}
}
});

View File

@@ -1,19 +1,5 @@
<template>
<span class="mk-reaction-icon">
<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" :alt="$t('@.reactions.like')">
<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" :alt="$t('@.reactions.love')">
<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" :alt="$t('@.reactions.laugh')">
<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" :alt="$t('@.reactions.hmm')">
<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" :alt="$t('@.reactions.surprise')">
<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" :alt="$t('@.reactions.congrats')">
<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" :alt="$t('@.reactions.angry')">
<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" :alt="$t('@.reactions.confused')">
<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" :alt="$t('@.reactions.rip')">
<template v-if="reaction == 'pudding'">
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" :alt="$t('@.reactions.pudding')">
<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" :alt="$t('@.reactions.pudding')">
</template>
</span>
<mk-emoji :emoji="str.startsWith(':') ? null : str" :name="str.startsWith(':') ? str.substr(1, str.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true"/>
</template>
<script lang="ts">
@@ -21,7 +7,35 @@ import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n(),
props: ['reaction']
props: {
reaction: {
type: String,
required: true
},
},
data() {
return {
customEmojis: (this.$root.getMetaSync() || { emojis: [] }).emojis || []
};
},
computed: {
str(): any {
switch (this.reaction) {
case 'like': return '👍';
case 'love': return '❤';
case 'laugh': return '😆';
case 'hmm': return '🤔';
case 'surprise': return '😮';
case 'congrats': return '🎉';
case 'angry': return '💢';
case 'confused': return '😥';
case 'rip': return '😇';
case 'pudding': return (this.$store.getters.isSignedIn && this.$store.state.settings.iLikeSushi) ? '🍣' : '🍮';
case 'star': return '⭐';
default: return this.reaction;
}
},
},
});
</script>

View File

@@ -3,7 +3,7 @@
<div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
<p v-if="!$root.isMobile">{{ title }}</p>
<div ref="buttons" :class="{ showFocus }">
<div class="buttons" ref="buttons" :class="{ showFocus }">
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
@@ -15,6 +15,9 @@
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
</div>
<div v-if="enableEmojiReaction" class="text">
<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>
</div>
</div>
</template>
@@ -23,6 +26,7 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import anime from 'animejs';
import { emojiRegex } from '../../../../../misc/emoji-regex';
export default Vue.extend({
i18n: i18n('common/views/components/reaction-picker.vue'),
@@ -56,6 +60,8 @@ export default Vue.extend({
data() {
return {
title: this.$t('choose-reaction'),
text: null,
enableEmojiReaction: false,
focus: null
};
},
@@ -94,6 +100,10 @@ export default Vue.extend({
},
mounted() {
this.$root.getMeta().then(meta => {
this.enableEmojiReaction = meta.enableEmojiReaction;
});
this.$nextTick(() => {
this.focus = 0;
@@ -143,6 +153,17 @@ export default Vue.extend({
});
},
reactText() {
if (!this.text) return;
this.react(this.text);
},
tryReactText() {
if (!this.text) return;
if (!this.text.match(emojiRegex)) return;
this.reactText();
},
onMouseover(e) {
this.title = e.target.title;
},
@@ -256,9 +277,9 @@ export default Vue.extend({
color var(--popupFg)
border-bottom solid var(--lineWidth) var(--faceDivider)
> div
padding 4px
width 240px
> .buttons
padding 4px 4px 8px 4px
width 216px
text-align center
&.showFocus
@@ -283,6 +304,9 @@ export default Vue.extend({
font-size 24px
border-radius 2px
> *
height 1em
&:hover
background var(--reactionPickerButtonHoverBg)
@@ -290,4 +314,29 @@ export default Vue.extend({
background var(--primary)
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
> .text
width 216px
padding 0 8px 8px 8px
> input
width 100%
padding 10px
margin 0
text-align center
font-size 16px
color var(--desktopPostFormTextareaFg)
background var(--desktopPostFormTextareaBg)
outline none
border solid 1px var(--primaryAlpha01)
border-radius 4px
transition border-color .2s ease
&:hover
border-color var(--primaryAlpha02)
transition border-color .1s ease
&:focus
border-color var(--primaryAlpha05)
transition border-color 0s ease
</style>

View File

@@ -136,12 +136,8 @@ export default Vue.extend({
&:hover
background var(--reactionViewerButtonHoverBg)
> .mk-reaction-icon
font-size 1.4em
> span
font-size 1.1em
line-height 32px
vertical-align middle
color var(--text)
</style>

View File

@@ -93,12 +93,17 @@ export default Vue.extend({
},
plotOptions: {
bar: {
columnWidth: '90%'
columnWidth: '80%'
}
},
grid: {
clipMarkers: false,
borderColor: 'rgba(0, 0, 0, 0.1)'
borderColor: 'rgba(0, 0, 0, 0.1)',
xaxis: {
lines: {
show: true,
}
},
},
tooltip: {
shared: true,

View File

@@ -51,12 +51,12 @@
<template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template>
</ui-input>
<ui-button @click="save(true)">{{ $t('save') }}</ui-button>
<ui-button @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
</ui-form>
</section>
<section>
<header>{{ $t('advanced') }}</header>
<header><fa :icon="faCogs"/> {{ $t('advanced') }}</header>
<div>
<ui-switch v-model="isCat" @change="save(false)">{{ $t('is-cat') }}</ui-switch>
@@ -66,7 +66,7 @@
</section>
<section>
<header>{{ $t('privacy') }}</header>
<header><fa :icon="faUnlockAlt"/> {{ $t('privacy') }}</header>
<div>
<ui-switch v-model="isLocked" @change="save(false)">{{ $t('is-locked') }}</ui-switch>
@@ -76,7 +76,7 @@
</section>
<section v-if="enableEmail">
<header>{{ $t('email') }}</header>
<header><fa :icon="faEnvelope"/> {{ $t('email') }}</header>
<div>
<template v-if="$store.state.i.email != null">
@@ -84,12 +84,12 @@
<ui-info v-else warn>{{ $t('email-not-verified') }}</ui-info>
</template>
<ui-input v-model="email" type="email"><span>{{ $t('email-address') }}</span></ui-input>
<ui-button @click="updateEmail()">{{ $t('save') }}</ui-button>
<ui-button @click="updateEmail()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
</div>
</section>
<section>
<header>{{ $t('export') }}</header>
<header><fa :icon="faBoxes"/> {{ $t('export-and-import') }}</header>
<div>
<ui-select v-model="exportTarget">
@@ -97,8 +97,12 @@
<option value="following">{{ $t('export-targets.following-list') }}</option>
<option value="mute">{{ $t('export-targets.mute-list') }}</option>
<option value="blocking">{{ $t('export-targets.blocking-list') }}</option>
<option value="user-lists">{{ $t('export-targets.user-lists') }}</option>
</ui-select>
<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button>
<ui-horizon-group class="fit-bottom">
<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button>
<ui-button @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><fa :icon="faUpload"/> {{ $t('import') }}</ui-button>
</ui-horizon-group>
</div>
</section>
@@ -118,7 +122,8 @@ import { apiUrl, host } from '../../../../config';
import { toUnicode } from 'punycode';
import langmap from 'langmap';
import { unique } from '../../../../../../prelude/array';
import { faDownload } from '@fortawesome/free-solid-svg-icons';
import { faDownload, faUpload, faUnlockAlt, faBoxes, faCogs } from '@fortawesome/free-solid-svg-icons';
import { faSave, faEnvelope } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/profile-editor.vue'),
@@ -147,7 +152,7 @@ export default Vue.extend({
avatarUploading: false,
bannerUploading: false,
exportTarget: 'notes',
faDownload
faDownload, faUpload, faSave, faEnvelope, faUnlockAlt, faBoxes, faCogs
};
},
@@ -284,6 +289,7 @@ export default Vue.extend({
this.exportTarget == 'following' ? 'i/export-following' :
this.exportTarget == 'mute' ? 'i/export-mute' :
this.exportTarget == 'blocking' ? 'i/export-blocking' :
this.exportTarget == 'user-lists' ? 'i/export-user-lists' :
null, {});
this.$root.dialog({
@@ -292,6 +298,22 @@ export default Vue.extend({
});
},
doImport() {
this.$chooseDriveFile().then(file => {
this.$root.api(
this.exportTarget == 'following' ? 'i/import-following' :
this.exportTarget == 'user-lists' ? 'i/import-user-lists' :
null, {
fileId: file.id
});
this.$root.dialog({
type: 'info',
text: this.$t('import-requested')
});
});
},
async deleteAccount() {
const { canceled: canceled, result: password } = await this.$root.dialog({
title: this.$t('enter-password'),

View File

@@ -159,7 +159,7 @@
</template>
<template v-if="page == null || page == 'notification'">
<x-notification v-show="page == 'notification'"/>
<x-notification/>
</template>
<template v-if="page == null || page == 'drive'">

View File

@@ -366,9 +366,6 @@ root(fill)
&[type='file']
display none
&[type='number']
text-align right
> .prefix
> .suffix
display block

View File

@@ -3,12 +3,14 @@
<ol v-if="uploads.length > 0">
<li v-for="ctx in uploads" :key="ctx.id">
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
<p class="status">
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
</p>
<div class="top">
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
<p class="status">
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
</p>
</div>
<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress>
<div class="progress initing" v-if="ctx.progress == undefined"></div>
<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div>
@@ -116,12 +118,17 @@ export default Vue.extend({
list-style none
> li
display block
display grid
margin 8px 0 0 0
padding 0
height 36px
width: 100%
box-shadow 0 -1px 0 var(--primaryAlpha01)
border-top solid 8px transparent
grid-template-columns 36px calc(100% - 44px)
grid-template-rows 1fr 8px
column-gap 8px
box-sizing content-box
&:first-child
margin 0
@@ -130,68 +137,62 @@ export default Vue.extend({
> .img
display block
position absolute
top 0
left 0
width 36px
height 36px
background-size cover
background-position center center
grid-column 1 / 2
grid-row 1 / 3
> .name
display block
position absolute
top 0
left 44px
margin 0
padding 0
max-width 256px
font-size 0.8em
color var(--primaryAlpha07)
white-space nowrap
text-overflow ellipsis
overflow hidden
> [data-icon]
margin-right 4px
> .status
display block
position absolute
top 0
right 0
margin 0
padding 0
font-size 0.8em
> .initing
color var(--primaryAlpha05)
> .kb
color var(--primaryAlpha05)
> .percentage
display inline-block
width 48px
text-align right
> .top
display flex
grid-column 2 / 3
grid-row 1 / 2
> .name
display block
padding 0 8px 0 0
margin 0
font-size 0.8em
color var(--primaryAlpha07)
white-space nowrap
text-overflow ellipsis
overflow hidden
flex-shrink 1
&:after
content '%'
> [data-icon]
margin-right 4px
> .status
display block
margin 0 0 0 auto
padding 0
font-size 0.8em
flex-shrink 0
> .initing
color var(--primaryAlpha05)
> .kb
color var(--primaryAlpha05)
> .percentage
display inline-block
width 48px
text-align right
color var(--primaryAlpha07)
&:after
content '%'
> progress
display block
position absolute
bottom 0
right 0
margin 0
width calc(100% - 44px)
height 8px
background transparent
border none
border-radius 4px
overflow hidden
grid-column 2 / 3
grid-row 2 / 3
z-index 2
&::-webkit-progress-value
background var(--primary)
@@ -201,12 +202,6 @@ export default Vue.extend({
> .progress
display block
position absolute
bottom 0
right 0
margin 0
width calc(100% - 44px)
height 8px
border none
border-radius 4px
background linear-gradient(
@@ -221,6 +216,9 @@ export default Vue.extend({
)
background-size 32px 32px
animation bg 1.5s linear infinite
grid-column 2 / 3
grid-row 2 / 3
z-index 1
&.initing
opacity 0.3

View File

@@ -1,5 +1,6 @@
<template>
<div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
<button class="disablePlayer" @click="playerEnabled = false" :title="$t('disable-player')"><fa icon="times"/></button>
<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
</div>
<div v-else-if="tweetUrl && detail" class="twitter">
@@ -126,6 +127,22 @@ export default Vue.extend({
position relative
width 100%
> button
position absolute
top -1.5em
right 0
font-size 1em
width 1.5em
height 1.5em
padding 0
margin 0
color var(--text)
background rgba(128, 128, 128, 0.2)
opacity 0.7
&:hover
opacity 0.9
> iframe
height 100%
left 0

View File

@@ -172,7 +172,7 @@ export default Vue.extend({
},
plotOptions: {
bar: {
columnWidth: '90%'
columnWidth: '80%'
}
},
grid: {

View File

@@ -26,6 +26,7 @@
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option>
</select>

View File

@@ -3,7 +3,7 @@
<h1>{{ $t('share-with', { name }) }}</h1>
<div>
<mk-signin v-if="!$store.getters.isSignedIn"/>
<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/>
<mk-post-form v-else-if="!posted" :initial-text="template" :instant="true" @posted="posted = true"/>
<p v-if="posted" class="posted"><fa icon="check"/></p>
</div>
<ui-button class="close" v-if="posted" @click="close">{{ $t('@.close') }}</ui-button>
@@ -20,9 +20,21 @@ export default Vue.extend({
return {
name: null,
posted: false,
text: new URLSearchParams(location.search).get('text')
text: new URLSearchParams(location.search).get('text'),
url: new URLSearchParams(location.search).get('url'),
title: new URLSearchParams(location.search).get('title'),
};
},
computed: {
template(): string {
let t = '';
if (this.title && this.url) t += `【[${this.title}](${this.url})】\n`;
if (this.title && !this.url) t += `${this.title}\n`;
if (this.text) t += `${this.text}\n`;
if (!this.title && this.url) t += `${this.url}`;
return t.trim();
}
},
methods: {
close() {
window.close();

View File

@@ -31,3 +31,4 @@ Vue.component('mkw-version', wVersion);
Vue.component('mkw-hashtags', wHashtags);
Vue.component('mkw-instance', wInstance);
Vue.component('mkw-post-form', wPostForm);
Vue.component('mkw-queue', () => import('./queue.vue').then(m => m.default));

View File

@@ -0,0 +1,170 @@
<template>
<div>
<ui-container :show-header="!props.compact">
<template #header><fa :icon="faTasks"/>Queue</template>
<div class="mntrproz">
<div>
<b>In</b>
<span v-if="latestStats">{{ latestStats.inbox.activeSincePrevTick | number }} / {{ latestStats.inbox.delayed | number }}</span>
<div ref="in"></div>
</div>
<div>
<b>Out</b>
<span v-if="latestStats">{{ latestStats.deliver.activeSincePrevTick | number }} / {{ latestStats.deliver.delayed | number }}</span>
<div ref="out"></div>
</div>
</div>
</ui-container>
</div>
</template>
<script lang="ts">
import define from '../../define-widget';
import { faTasks } from '@fortawesome/free-solid-svg-icons';
import ApexCharts from 'apexcharts';
export default define({
name: 'queue',
props: () => ({
compact: false
})
}).extend({
data() {
return {
stats: [],
inChart: null,
outChart: null,
faTasks
};
},
watch: {
stats(stats) {
this.inChart.updateSeries([{
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick }))
}, {
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
}, {
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting }))
}, {
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
}]);
this.outChart.updateSeries([{
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick }))
}, {
type: 'area',
data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
}, {
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting }))
}, {
type: 'line',
data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
}]);
}
},
computed: {
latestStats(): any {
return this.stats[this.stats.length - 1];
}
},
mounted() {
const chartOpts = {
chart: {
type: 'area',
height: 70,
animations: {
dynamicAnimation: {
enabled: false
}
},
sparkline: {
enabled: true,
}
},
tooltip: {
enabled: false
},
stroke: {
curve: 'straight',
width: 1
},
colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'],
series: [{ data: [] }, { data: [] }, { data: [] }, { data: [] }] as any,
yaxis: {
min: 0,
}
};
this.inChart = new ApexCharts(this.$refs.in, chartOpts);
this.outChart = new ApexCharts(this.$refs.out, chartOpts);
this.inChart.render();
this.outChart.render();
const connection = this.$root.stream.useSharedConnection('queueStats');
connection.on('stats', this.onStats);
connection.on('statsLog', this.onStatsLog);
connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
length: 50
});
this.$once('hook:beforeDestroy', () => {
connection.dispose();
this.inChart.destroy();
this.outChart.destroy();
});
},
methods: {
func() {
this.props.compact = !this.props.compact;
this.save();
},
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift();
},
onStatsLog(statsLog) {
for (const stats of statsLog.reverse()) {
this.onStats(stats);
}
}
}
});
</script>
<style lang="stylus" scoped>
.mntrproz
display flex
padding 4px
> div
width 50%
padding 4px
> b
display block
font-size 12px
color var(--text)
> span
position absolute
top 4px
right 4px
opacity 0.7
font-size 12px
color var(--text)
</style>

View File

@@ -18,7 +18,7 @@ import MkSelectDrive from './views/pages/selectdrive.vue';
import MkDrive from './views/pages/drive.vue';
import MkMessagingRoom from './views/pages/messaging-room.vue';
import MkReversi from './views/pages/games/reversi.vue';
import MkShare from './views/pages/share.vue';
import MkShare from '../common/views/pages/share.vue';
import MkFollow from '../common/views/pages/follow.vue';
import MkNotFound from '../common/views/pages/not-found.vue';
import MkSettings from './views/pages/settings.vue';

View File

@@ -6,7 +6,7 @@
<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p>
</template>
<template v-else-if="item.type == 'link'">
<a :href="item.href" :target="item.target" @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
<a :href="item.href" :target="item.target" @click="click(item)" :download="item.download"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
</template>
<template v-else-if="item.type == 'nest'">
<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p>

View File

@@ -3,7 +3,7 @@
<template #header><fa icon="crop"/>{{ title }}</template>
<div class="body">
<vue-cropper ref="cropper"
:src="image.url"
:src="imageUrl"
:view-mode="1"
:aspect-ratio="aspectRatio"
:container-style="{ width: '100%', 'max-height': '400px' }"
@@ -21,6 +21,7 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import VueCropper from 'vue-cropperjs';
import * as url from '../../../../../prelude/url';
export default Vue.extend({
i18n: i18n('desktop/views/components/crop-window.vue'),
@@ -41,6 +42,13 @@ export default Vue.extend({
required: true
}
},
computed: {
imageUrl() {
return `/proxy/?${url.query({
url: this.image.url
})}`;
},
},
methods: {
ok() {
(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => {

View File

@@ -21,9 +21,9 @@
<img src="/assets/label-red.svg"/>
<p>{{ $t('nsfw') }}</p>
</div>
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
</div>
<x-file-thumbnail class="thumbnail" :file="file" fit="contain"/>
<p class="name">
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
@@ -34,14 +34,18 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import anime from 'animejs';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
import updateAvatar from '../../api/update-avatar';
import updateBanner from '../../api/update-banner';
import { appendQuery } from '../../../../../prelude/url';
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/drive.file.vue'),
props: ['file'],
components: {
XFileThumbnail
},
data() {
return {
isContextmenuShowing: false,
@@ -57,11 +61,6 @@ export default Vue.extend({
},
title(): string {
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
},
background(): string {
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
? `rgb(${this.file.properties.avgColor.join(',')})`
: 'transparent';
}
},
methods: {
@@ -88,9 +87,10 @@ export default Vue.extend({
action: this.copyUrl
}, {
type: 'link',
href: `${this.file.url}?download`,
href: appendQuery(this.file.url, 'download'),
text: this.$t('contextmenu.download'),
icon: 'download',
download: this.file.name
}, null, {
type: 'item',
text: this.$t('@.delete'),
@@ -205,7 +205,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
.gvfdktuvdgwhmztnuekzkswkjygptfcv
padding 8px 0 0 0
height 180px
min-height 180px
border-radius 4px
&, *
@@ -254,6 +254,9 @@ export default Vue.extend({
> .name
color var(--primaryForeground)
> .thumbnail
color var(--primaryForeground)
&[data-is-contextmenu-showing]
&:after
content ""
@@ -319,18 +322,7 @@ export default Vue.extend({
width 128px
height 128px
margin auto
> img
display block
position absolute
top 0
left 0
right 0
bottom 0
margin auto
max-width 128px
max-height 128px
pointer-events none
color var(--driveFileIcon)
> .name
display block

View File

@@ -1,6 +1,6 @@
<template>
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
<template #header><fa icon="comments"/> {{ $t('title') }} <mk-user-name :user="user"/></template>
<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name :user="user"/></template>
<x-messaging-room :user="user" :class="$style.content"/>
</mk-window>
</template>
@@ -12,7 +12,7 @@ import { url } from '../../../config';
import getAcct from '../../../../../misc/acct/render';
export default Vue.extend({
i18n: i18n('desktop/views/components/messaging-room-window.vue'),
i18n: i18n(),
components: {
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
},

View File

@@ -1,6 +1,6 @@
<template>
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</template>
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template>
<x-messaging :class="$style.content" @navigate="navigate"/>
</mk-window>
</template>
@@ -11,7 +11,7 @@ import i18n from '../../../i18n';
import MkMessagingRoomWindow from './messaging-room-window.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/messaging-window.vue'),
i18n: i18n(),
components: {
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
},

View File

@@ -480,7 +480,7 @@ export default Vue.extend({
});
if (this.text && this.text != '') {
const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag);
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
}

View File

@@ -27,6 +27,7 @@
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option>
</select>

View File

@@ -12,7 +12,7 @@ import parseAcct from '../../../../../misc/acct/parse';
import getUserName from '../../../../../misc/get-user-name';
export default Vue.extend({
i18n: i18n('.vue'),
i18n: i18n(),
components: {
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
},
@@ -51,7 +51,7 @@ export default Vue.extend({
this.user = user;
this.fetching = false;
document.title = `メッセージ: ${getUserName(this.user)}`;
document.title = this.$t('@.messaging') + ': ' + getUserName(this.user);
Progress.done();
});

View File

@@ -1,66 +0,0 @@
<template>
<div class="pptjhabgjtt7kwskbfv4y3uml6fpuhmr">
<h1>{{ this.$t('share-with', { name }) }}</h1>
<div>
<mk-signin v-if="!$store.getters.isSignedIn"/>
<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/>
<p v-if="posted" class="posted"><fa icon="check"/></p>
</div>
<button v-if="posted" class="ui button" @click="close">{{ $t('@.close') }}</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('desktop/views/pages/share.vue'),
data() {
return {
name: null,
posted: false,
text: new URLSearchParams(location.search).get('text')
};
},
methods: {
close() {
window.close();
}
},
mounted() {
this.$root.getMeta().then(meta => {
this.name = meta.name;
});
}
});
</script>
<style lang="stylus" scoped>
.pptjhabgjtt7kwskbfv4y3uml6fpuhmr
padding 16px
> h1
margin 0 0 8px 0
color #555
font-size 20px
text-align center
> div
max-width 500px
margin 0 auto
background #fff
border solid 1px rgba(#000, 0.1)
border-radius 6px
overflow hidden
> .posted
display block
margin 0
padding 64px
text-align center
> button
display block
margin 16px auto
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="mkw-messaging">
<ui-container :show-header="props.design == 0">
<template #header><fa icon="comments"/>{{ $t('title') }}</template>
<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template>
<template #func><button @click="add"><fa icon="plus"/></button></template>
<x-messaging ref="index" compact @navigate="navigate"/>
@@ -21,7 +21,7 @@ export default define({
design: 0
})
}).extend({
i18n: i18n('desktop/views/widgets/messaging.vue'),
i18n: i18n(''),
components: {
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
},

View File

@@ -16,11 +16,11 @@ import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update';
import MiOS from './mios';
import { version, codename, lang, locale } from './config';
import { builtinThemes, applyTheme, darkTheme } from './theme';
import { builtinThemes, applyTheme, futureTheme } from './theme';
import Dialog from './common/views/components/dialog.vue';
if (localStorage.getItem('theme') == null) {
applyTheme(darkTheme);
applyTheme(futureTheme);
}
//#region FontAwesome

View File

@@ -26,7 +26,7 @@ import MkUserLists from './views/pages/user-lists.vue';
import MkUserList from './views/pages/user-list.vue';
import MkReversi from './views/pages/games/reversi.vue';
import MkTag from './views/pages/tag.vue';
import MkShare from './views/pages/share.vue';
import MkShare from '../common/views/pages/share.vue';
import MkFollow from '../common/views/pages/follow.vue';
import MkNotFound from '../common/views/pages/not-found.vue';

View File

@@ -1,11 +1,7 @@
<template>
<div class="pyvicwrksnfyhpfgkjwqknuururpaztw">
<div class="preview">
<img v-if="kind == 'image'" ref="img"
:src="file.url"
:alt="file.name"
:title="file.name"
:style="style">
<x-file-thumbnail class="preview" :file="file" fit="cover" :detail="true"/>
<template v-if="kind != 'image'"><fa icon="file"/></template>
<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height">
<span class="size">
@@ -38,7 +34,7 @@
<div class="menu">
<div>
<ui-input readonly :value="file.url">URL</ui-input>
<ui-button link :href="`${file.url}?download`" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
<ui-button link :href="dlUrl" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button>
@@ -61,11 +57,17 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import { gcd } from '../../../../../prelude/math';
import { appendQuery } from '../../../../../prelude/url';
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({
i18n: i18n('mobile/views/components/drive.file-detail.vue'),
props: ['file'],
components: {
XFileThumbnail
},
data() {
return {
gcd,
@@ -86,6 +88,10 @@ export default Vue.extend({
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
} : {};
},
dlUrl(): string {
return appendQuery(this.file.url, 'download');
}
},
@@ -142,8 +148,7 @@ export default Vue.extend({
padding 8px
background var(--bg)
> img
display block
> .preview
max-width 100%
max-height 300px
margin 0 auto

View File

@@ -1,7 +1,7 @@
<template>
<a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
<div class="container">
<div class="thumbnail" :style="thumbnail"></div>
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
<div class="body">
<p class="name">
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
@@ -26,9 +26,14 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({
i18n: i18n('mobile/views/components/drive.file.vue'),
props: ['file'],
components: {
XFileThumbnail
},
data() {
return {
isSelected: false
@@ -37,12 +42,6 @@ export default Vue.extend({
computed: {
browser(): any {
return this.$parent;
},
thumbnail(): any {
return {
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
'background-image': `url(${this.file.thumbnailUrl})`
};
}
},
created() {
@@ -74,9 +73,12 @@ export default Vue.extend({
pointer-events none
> .container
display grid
max-width 500px
margin 0 auto
padding 16px
grid-template-columns 64px 1fr
grid-column-gap 10px
&:after
content ""
@@ -84,18 +86,13 @@ export default Vue.extend({
clear both
> .thumbnail
display block
float left
width 64px
height 64px
background-size cover
background-position center center
color var(--driveFileIcon)
> .body
display block
float left
width calc(100% - 74px)
margin-left 10px
word-break break-all
> .name
display block
@@ -104,8 +101,7 @@ export default Vue.extend({
font-size 0.9em
font-weight bold
color var(--text)
text-overflow ellipsis
overflow-wrap break-word
word-break break-word
> .ext
opacity 0.5
@@ -154,6 +150,6 @@ export default Vue.extend({
background var(--primary)
&, *
color #fff !important
color var(--primaryForeground) !important
</style>

View File

@@ -367,7 +367,7 @@ export default Vue.extend({
});
if (this.text && this.text != '') {
const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag);
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
}

View File

@@ -19,6 +19,7 @@
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="version">{{ $t('@.widgets.version') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option>
<option value="queue">{{ $t('@.widgets.queue') }}</option>
<option value="memo">{{ $t('@.widgets.memo') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option>

View File

@@ -49,7 +49,7 @@ const defaultDeviceSettings = {
roundedCorners: true,
reduceMotion: false,
darkmode: true,
darkTheme: 'dark',
darkTheme: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
lightTheme: 'light',
lineWidth: 1,
fontSize: 0,

View File

@@ -10,26 +10,26 @@ export type Theme = {
props: { [key: string]: string };
};
export const lightTheme: Theme = require('../theme/light.json5');
export const darkTheme: Theme = require('../theme/dark.json5');
export const pinkTheme: Theme = require('../theme/pink.json5');
export const blackTheme: Theme = require('../theme/black.json5');
export const halloweenTheme: Theme = require('../theme/halloween.json5');
export const cafeTheme: Theme = require('../theme/cafe.json5');
export const japaneseSushiSetTheme: Theme = require('../theme/japanese-sushi-set.json5');
export const gruvboxDarkTheme: Theme = require('../theme/gruvbox-dark.json5');
export const monokaiTheme: Theme = require('../theme/monokai.json5');
export const colorfulTheme: Theme = require('../theme/colorful.json5');
export const rainyTheme: Theme = require('../theme/rainy.json5');
export const mauveTheme: Theme = require('../theme/mauve.json5');
export const grayTheme: Theme = require('../theme/gray.json5');
export const tweetDeckTheme: Theme = require('../theme/tweet-deck.json5');
export const lightTheme: Theme = require('../themes/light.json5');
export const darkTheme: Theme = require('../themes/dark.json5');
export const lavenderTheme: Theme = require('../themes/lavender.json5');
export const futureTheme: Theme = require('../themes/future.json5');
export const halloweenTheme: Theme = require('../themes/halloween.json5');
export const cafeTheme: Theme = require('../themes/cafe.json5');
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
export const monokaiTheme: Theme = require('../themes/monokai.json5');
export const colorfulTheme: Theme = require('../themes/colorful.json5');
export const rainyTheme: Theme = require('../themes/rainy.json5');
export const mauveTheme: Theme = require('../themes/mauve.json5');
export const grayTheme: Theme = require('../themes/gray.json5');
export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5');
export const builtinThemes = [
lightTheme,
darkTheme,
pinkTheme,
blackTheme,
lavenderTheme,
futureTheme,
halloweenTheme,
cafeTheme,
japaneseSushiSetTheme,

View File

@@ -43,6 +43,11 @@
}
],
"share_target": {
"url_template": "share?text=【{title}】%0A{text}%0A{url}"
"action": "/share/",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
}

View File

@@ -1,20 +0,0 @@
{
id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
name: 'Future',
author: 'syuilo',
base: 'dark',
vars: {
primary: 'rgb(94, 158, 185)',
secondary: 'rgb(22, 24, 30)',
text: 'rgb(214, 218, 224)',
},
props: {
renoteGradient: '#0a2d3c',
renoteText: '$primary',
quoteBorder: '$primary',
},
}

View File

@@ -153,6 +153,8 @@
messagingRoomMessageBg: '$secondary',
messagingRoomMessageFg: '#fff',
driveFileIcon: '$text',
formButtonBorder: 'rgba(255, 255, 255, 0.1)',
formButtonHoverBg: ':alpha<0.2<$primary',
formButtonHoverBorder: ':alpha<0.5<$primary',

View File

@@ -0,0 +1,39 @@
{
id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
name: 'Future',
author: 'syuilo',
desc: 'Sci-fi flavored',
base: 'dark',
vars: {
c0: '#0e0e0e',
c1: 'rgb(255, 105, 78)',
c2: 'rgb(99, 197, 210)',
c4: 'rgb(253, 254, 214)',
c3: 'rgb(204, 254, 253)',
primary: '$c1',
secondary: '#191919',
text: '$c3',
},
props: {
bg: '$c0',
noteText: '$c4',
noteHeaderAcct: ':alpha<0.65<$c4',
noteHeaderInfo: ':alpha<0.5<$c4',
subNoteText: ':alpha<0.7<$c4',
renoteGradient: 'rgba(0, 0, 0, 0)',
renoteText: '$c2',
quoteBorder: '$c2',
mfmHashtag: '$c1',
mfmUrl: '$c2',
mfmLink: '$c2',
mfmMention: '$c1',
mfmMentionForeground: '#fff',
notificationIndicator: '$c2',
link: '$c2',
desktopHeaderBg: '$secondary',
},
}

View File

@@ -153,6 +153,8 @@
messagingRoomMessageBg: '#eee',
messagingRoomMessageFg: '#333',
driveFileIcon: '$text',
formButtonBorder: 'rgba(0, 0, 0, 0.1)',
formButtonHoverBg: ':alpha<0.12<$primary',
formButtonHoverBorder: ':alpha<0.3<$primary',
@@ -179,7 +181,7 @@
desktopTimelineSrcHover: ':darken<7<$text',
desktopWindowTitle: '$text',
desktopWindowShadow: 'rgba(0, 0, 0, 0.2)',
desktopDriveBg: '#fff',
desktopDriveBg: '@bg',
desktopDriveFolderBg: ':lighten<31<$primary',
desktopDriveFolderHoverBg: ':lighten<27<$primary',
desktopDriveFolderActiveBg: ':lighten<25<$primary',

View File

@@ -19,6 +19,8 @@ export type Source = {
host: string;
port: number;
pass: string;
db?: number;
prefix?: string;
};
elasticsearch: {
host: string;

View File

@@ -0,0 +1,61 @@
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
import { deliverQueue, inboxQueue } from '../queue';
const ev = new Xev();
const interval = 3000;
/**
* Report queue stats regularly
*/
export default function() {
const log = new Deque<any>();
ev.on('requestQueueStatsLog', x => {
ev.emit(`queueStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50));
});
let activeDeliverJobs = 0;
let activeInboxJobs = 0;
deliverQueue.on('global:active', () => {
activeDeliverJobs++;
});
inboxQueue.on('global:active', () => {
activeInboxJobs++;
});
async function tick() {
const deliverJobCounts = await deliverQueue.getJobCounts();
const inboxJobCounts = await inboxQueue.getJobCounts();
const stats = {
deliver: {
activeSincePrevTick: activeDeliverJobs,
active: deliverJobCounts.active,
waiting: deliverJobCounts.waiting,
delayed: deliverJobCounts.delayed
},
inbox: {
activeSincePrevTick: activeInboxJobs,
active: inboxJobCounts.active,
waiting: inboxJobCounts.waiting,
delayed: inboxJobCounts.delayed
}
};
ev.emit('queueStats', stats);
log.unshift(stats);
if (log.length > 200) log.pop();
activeDeliverJobs = 0;
activeInboxJobs = 0;
}
tick();
setInterval(tick, interval);
}

View File

@@ -5,6 +5,8 @@ export default config.redis ? redis.createClient(
config.redis.port,
config.redis.host,
{
auth_pass: config.redis.pass
auth_pass: config.redis.pass,
prefix: config.redis.prefix,
db: config.redis.db || 0
}
) : null;

View File

@@ -16,6 +16,7 @@ import Xev from 'xev';
import Logger from './services/logger';
import serverStats from './daemons/server-stats';
import notesStats from './daemons/notes-stats';
import queueStats from './daemons/queue-stats';
import loadConfig from './config/load';
import { Config } from './config/types';
import { lessThan } from './prelude/array';
@@ -50,6 +51,7 @@ function main() {
if (program.daemons) {
serverStats();
notesStats();
queueStats();
}
}

View File

@@ -1,5 +1,6 @@
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
import { URL } from 'url';
import { urlRegex } from './prelude';
export function fromHtml(html: string): string {
if (html == null) return null;
@@ -14,7 +15,7 @@ export function fromHtml(html: string): string {
return text.trim();
function getText(node: any) {
function getText(node: any): string {
if (node.nodeName == '#text') return node.value;
if (node.childNodes) {
@@ -38,10 +39,11 @@ export function fromHtml(html: string): string {
const txt = getText(node);
const rel = node.attrs.find((x: any) => x.name == 'rel');
const href = node.attrs.find((x: any) => x.name == 'href');
const isHashtag = rel && rel.value.match('tag') !== null;
// ハッシュタグ / hrefがない / txtがURL
if ((rel && rel.value.match('tag') !== null) || !href || href.value == txt) {
text += txt;
if (isHashtag || !href || href.value == txt) {
text += isHashtag || txt.match(urlRegex) ? txt : `<${txt}>`;
// メンション
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
const part = txt.split('@');

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import * as A from '../prelude/array';
import * as S from '../prelude/string';
import { MfmForest, MfmTree } from './types';
import { MfmForest, MfmTree } from './prelude';
import { createTree, createLeaf } from '../prelude/tree';
function isEmptyTextTree(t: MfmTree): boolean {

View File

@@ -1,5 +1,5 @@
import { mfmLanguage } from './language';
import { MfmForest } from './types';
import { MfmForest } from './prelude';
import { normalize } from './normalize';
export function parse(source: string): MfmForest {

View File

@@ -35,3 +35,5 @@ export function createLeaf(type: string, props: any): MfmTree {
export function createTree(type: string, children: MfmForest, props: any): MfmTree {
return T.createTree({ type, props }, children);
}
export const urlRegex = /^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/;

View File

@@ -2,7 +2,7 @@ import { JSDOM } from 'jsdom';
import config from '../config';
import { INote } from '../models/note';
import { intersperse } from '../prelude/array';
import { MfmForest, MfmTree } from './types';
import { MfmForest, MfmTree } from './prelude';
export function toHtml(tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) {
if (tokens == null) {

View File

@@ -1,5 +1,5 @@
import * as fs from 'fs';
import * as isSvg from 'is-svg';
import isSvg from 'is-svg';
export default function(path: string) {
try {

View File

@@ -0,0 +1,6 @@
const cd = require('content-disposition');
export function contentDisposition(type: 'inline' | 'attachment', filename: string): string {
const fallback = filename.replace(/[^\w.-]/g, '_');
return cd(filename, { type, fallback });
}

27
src/misc/convert-host.ts Normal file
View File

@@ -0,0 +1,27 @@
import config from '../config';
import { toUnicode, toASCII } from 'punycode';
import { URL } from 'url';
export function getFullApAccount(username: string, host: string) {
return host ? `${username}@${toApHost(host)}` : `${username}@${toApHost(config.host)}`;
}
export function isSelfHost(host: string) {
if (host == null) return true;
return toApHost(config.host) === toApHost(host);
}
export function extractDbHost(uri: string) {
const url = new URL(uri);
return toDbHost(url.hostname);
}
export function toDbHost(host: string) {
if (host == null) return null;
return toUnicode(host.toLowerCase());
}
export function toApHost(host: string) {
if (host == null) return null;
return toASCII(host.toLowerCase());
}

10
src/misc/create-temp.ts Normal file
View File

@@ -0,0 +1,10 @@
import * as tmp from 'tmp';
export function createTemp(): Promise<[string, any]> {
return new Promise<[string, any]>((res, rej) => {
tmp.file((e, path, fd, cleanup) => {
if (e) return rej(e);
res([path, cleanup]);
});
});
}

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