Compare commits

..

119 Commits

Author SHA1 Message Date
syuilo
8fe153c7c1 12.11.0 2020-02-16 22:53:35 +09:00
syuilo
36a8720fbb New Crowdin translations (#5948)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

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

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)
2020-02-16 22:53:16 +09:00
syuilo
9cbfdc94d9 Clean up 2020-02-16 22:46:51 +09:00
syuilo
091923764d Implement image dialog 2020-02-16 22:46:18 +09:00
syuilo
dc39caed1e Resolve #5942 2020-02-16 22:15:49 +09:00
syuilo
bcd7d1f007 Update CHANGELOG.md 2020-02-16 21:11:44 +09:00
syuilo
40d4dc0474 Refactor 2020-02-16 21:11:27 +09:00
syuilo
02ac30c0d0 Resolve #5958 2020-02-16 21:10:52 +09:00
syuilo
518bc92673 Clean up 2020-02-16 21:05:17 +09:00
syuilo
a5b92e316c Refactor: Extract scroll utility functions 2020-02-16 20:58:41 +09:00
syuilo
828c7b66a0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-16 20:54:33 +09:00
syuilo
93474eaa06 2回目以降の読み込みは30個までフェッチするように 2020-02-16 20:54:25 +09:00
okpierre
237f366aa2 Update notification (#5956)
* Add icon for messaging

This will add icon within messaging

* Update messaging-room.message.vue

Link to missing icon

* Update notification.vue

fix renote icon in notification

https://github.com/syuilo/misskey/issues/5955
2020-02-16 20:21:05 +09:00
fuyu
714bcf28d5 テーマの切り替え時に時計の色が変わるように (#5959)
* テーマの切り替え時に時計の色が変わるように

* ディレイを追加
2020-02-16 20:20:02 +09:00
syuilo
420eeb4d68 Clean up 2020-02-15 23:42:52 +09:00
syuilo
bc6daf4a2e Update CHANGELOG.md 2020-02-15 23:16:50 +09:00
syuilo
6f7832c09b API doc 2020-02-15 23:13:59 +09:00
syuilo
bef67fa275 Update trend.ts 2020-02-15 23:01:41 +09:00
syuilo
05d7198667 🎨 2020-02-15 22:51:46 +09:00
syuilo
df0bfc14e5 🎨 2020-02-15 22:21:35 +09:00
syuilo
3f28f7451f Prefetch aicons 2020-02-15 21:56:21 +09:00
tamaina
dbb9199d6f Fix widget bg (#5952) 2020-02-15 21:42:45 +09:00
syuilo
72cb3b03af Update CHANGELOG.md 2020-02-15 21:39:59 +09:00
syuilo
d0085f00ed Fix #5950 2020-02-15 21:39:38 +09:00
syuilo
43734f027b Refactoring 2020-02-15 21:33:32 +09:00
syuilo
bb903cab40 🎨 2020-02-15 18:47:50 +09:00
syuilo
92f765bc47 Update sequential-entrance.vue 2020-02-15 18:42:43 +09:00
syuilo
742889a035 Update sequential-entrance.vue 2020-02-15 18:39:45 +09:00
syuilo
24453ebcc3 Improve banner animation performance 2020-02-15 17:44:26 +09:00
syuilo
8b8ab1bf5c Update CHANGELOG.md 2020-02-15 17:33:51 +09:00
syuilo
e9bc9b8675 Fix bug 2020-02-15 17:31:45 +09:00
syuilo
eeaa27c7ca Improve usability 2020-02-15 09:22:16 +09:00
syuilo
ccea1755fc なんか 2020-02-15 09:10:49 +09:00
syuilo
c32a5d602b 🎨 2020-02-15 08:52:21 +09:00
syuilo
2a04f2ca4d Improve follow-requests page 2020-02-15 08:42:21 +09:00
syuilo
37c80e8ef5 Improve wallpaper feature 2020-02-15 08:29:59 +09:00
syuilo
1dce62e42a 🎨 2020-02-15 07:54:20 +09:00
syuilo
ec222378c4 12.10.0 2020-02-15 04:32:48 +09:00
syuilo
ac930a1c6a New Crowdin translations (#5947)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)
2020-02-15 04:30:54 +09:00
syuilo
5ccd5ad56e Add driveFiles index 2020-02-15 04:29:57 +09:00
syuilo
67da90530b Use em 2020-02-15 04:02:32 +09:00
syuilo
b7f3753615 Use em 2020-02-15 03:57:43 +09:00
syuilo
d21d38509c Font size setting 2020-02-15 03:44:40 +09:00
syuilo
a59e1c0345 Use em 2020-02-15 03:25:54 +09:00
syuilo
7ab613b394 Use em 2020-02-15 03:24:09 +09:00
syuilo
c00ab0fbe2 Api key setting 2020-02-15 03:18:33 +09:00
syuilo
87451b1223 Add header clock 2020-02-15 02:54:42 +09:00
syuilo
d2b61229a3 Improve usability 2020-02-15 02:39:27 +09:00
syuilo
980584020a 🎨 2020-02-15 02:26:45 +09:00
syuilo
a43d0dafa5 🎨 2020-02-15 02:20:10 +09:00
syuilo
d5c1e7e579 Resolve #5928 2020-02-15 02:16:11 +09:00
syuilo
55bdf0d618 🎨 2020-02-15 01:40:38 +09:00
syuilo
44f7c13ad4 ✌️ 2020-02-15 01:39:14 +09:00
syuilo
7bd1a3c8ac Update CHANGELOG.md 2020-02-15 01:33:58 +09:00
Xeltica
4f1981df03 サイドバーメニューからアカウントを作成できるように (#5910)
* メニューからアカウントを作成できるようにした

* i18n

* Update signup.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-15 01:33:08 +09:00
syuilo
8689a998aa New Crowdin translations (#5938)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2020-02-15 01:19:53 +09:00
syuilo
079bb8d722 🎨 2020-02-15 01:18:48 +09:00
syuilo
65c0b6c7da Resolve #5939 2020-02-15 01:03:59 +09:00
syuilo
84958af4ce Improve doc page 2020-02-15 00:27:53 +09:00
syuilo
c53b59914b doc 2020-02-15 00:17:21 +09:00
syuilo
8ffd9ab2d9 Create reaction.ja-JP.md 2020-02-15 00:11:01 +09:00
syuilo
0305caf504 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-15 00:02:10 +09:00
syuilo
3012e4ffe0 Update CHANGELOG.md 2020-02-15 00:00:53 +09:00
MeiMei
585f3c3d3e Fix Oops! (#5945) 2020-02-14 23:59:54 +09:00
syuilo
f1d07dfbed Fix favicon provide 2020-02-14 23:55:31 +09:00
syuilo
cddfc55110 ✌️ 2020-02-14 23:55:13 +09:00
fuyu
a2ce072ae7 アナログ時計ウィジェットを移植 (#5944)
* アナログ時計ウィジェットを移植

* analog-clock -> clock
2020-02-14 23:31:24 +09:00
MeiMei
439563c5d6 サムネイルをJPEGで生成するように (#5941) 2020-02-14 11:40:45 +09:00
syuilo
962617b4f4 Update CHANGELOG.md 2020-02-14 01:28:15 +09:00
syuilo
4a202f0f7e 12.9.0 2020-02-14 01:26:28 +09:00
syuilo
6e6b12519a Refactor 2020-02-14 01:24:05 +09:00
syuilo
f5f7654f4b Improve custom emoji managemant 2020-02-14 01:09:39 +09:00
syuilo
5ac4c48ad1 動きのあるMFMを無効にするオプション 2020-02-13 23:20:12 +09:00
syuilo
7e18fd18b0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 23:11:14 +09:00
syuilo
fb30c479ea Update CHANGELOG.md 2020-02-13 23:11:11 +09:00
DW
f40dcbfe13 Fix for CASCADE DELETE not being federated (#5812)
* Fix for CASCADE DELETE not being federated

* Use JOIN to get user

* fix typo
2020-02-13 23:08:33 +09:00
syuilo
8755b5f353 Fix bug 2020-02-13 22:48:56 +09:00
syuilo
691482bb28 Update dependencies 🚀 2020-02-13 22:39:23 +09:00
syuilo
4248bb8ce0 New Crowdin translations (#5931)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)
2020-02-13 22:35:22 +09:00
syuilo
a5653e33d3 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 22:13:35 +09:00
syuilo
072dc1c7e6 Clean up 2020-02-13 22:13:31 +09:00
syuilo
d76fceae85 Fix #5935 2020-02-13 22:13:24 +09:00
okpierre
86107b2710 Fix For Messaging Icon (#5933)
* Add icon for messaging

This will add icon within messaging

* Update messaging-room.message.vue

Link to missing icon
2020-02-13 15:53:02 +09:00
syuilo
a473768bef 🎨 2020-02-13 11:54:12 +09:00
syuilo
f7fe13a177 i18n 2020-02-13 11:46:02 +09:00
syuilo
acd29d22eb Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 11:45:02 +09:00
syuilo
c228155514 Fix #5930 2020-02-13 11:44:58 +09:00
syuilo
b601b98d5c New Crowdin translations (#5929)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)
2020-02-13 06:02:36 +09:00
syuilo
1cd3419688 Refactor 2020-02-13 05:08:01 +09:00
syuilo
6f5f233bb5 12.8.0 2020-02-13 03:13:30 +09:00
syuilo
d33492cd49 New Crowdin translations (#5926)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2020-02-13 03:12:58 +09:00
syuilo
83ad9f369f OSネイティブの絵文字を使用オプション 2020-02-13 03:11:37 +09:00
syuilo
3a78b62520 Update about-misskey.vue 2020-02-13 03:01:39 +09:00
syuilo
2479f75d8a インスタンス情報ページとMisskey情報ページを分離するなど 2020-02-13 02:48:52 +09:00
syuilo
f0d187f71e Add crowdin link 2020-02-13 02:35:50 +09:00
syuilo
056942391a Update CHANGELOG.md 2020-02-13 02:18:33 +09:00
syuilo
2feef81516 グループ招待の通知とか
Resolve #5880
Resolve #5927
2020-02-13 02:17:54 +09:00
Satsuki Yanagi
037d4b581b フランス語と関西弁を有効に (#5925) 2020-02-12 22:40:35 +09:00
syuilo
da4cf6fdb4 New Crowdin translations (#5924)
* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2020-02-12 22:40:18 +09:00
syuilo
848bcd5a63 Update CHANGELOG.md 2020-02-12 08:05:26 +09:00
syuilo
7b60b6c6dc New Crowdin translations (#5921)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Kannada)

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

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (Dutch)

* 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 (Norwegian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Kannada)
2020-02-12 08:00:07 +09:00
syuilo
fbc801d1da 言語切り替え 2020-02-12 07:12:58 +09:00
syuilo
2f18f82e3d 🎨 2020-02-12 06:43:22 +09:00
tamaina
c2d5a96bb6 翻訳の抜けを修正その2 (#5893)
* Missing translation

* use npx

* ✌️

* Update ja-JP.yml

* Update signup.vue

* Update ja-JP.yml

* Update messaging-room.vue

* Update ja-JP.yml

* Update signup.vue

* Update ja-JP.yml

* Update signin.vue

* Update ja-JP.yml

* Update index.vue

* Update ja-JP.yml

* Update signup.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-12 06:27:11 +09:00
syuilo
46575d4f04 Fix #5919 2020-02-12 06:19:31 +09:00
syuilo
5e7be93980 Resolve #5907 2020-02-12 06:01:59 +09:00
syuilo
3f749c6540 良い感じに 2020-02-12 05:21:08 +09:00
syuilo
2a7d4ee866 Update sequential-entrance.vue 2020-02-12 03:31:46 +09:00
syuilo
083095ded6 Update sequential-entrance.vue 2020-02-12 03:31:20 +09:00
syuilo
7207663f37 🎨 2020-02-12 02:56:42 +09:00
syuilo
66b49c909a 🎨 2020-02-12 02:55:35 +09:00
syuilo
ab469aa243 Update CHANGELOG.md 2020-02-12 02:52:45 +09:00
syuilo
afa4563e1e Update paging.ts 2020-02-12 02:52:43 +09:00
syuilo
320b3d8617 🎨 2020-02-12 02:52:37 +09:00
syuilo
7493429b4d 🎨 2020-02-12 02:35:03 +09:00
syuilo
d70f7a717b Fix #5918 2020-02-12 01:01:17 +09:00
tamaina
4ab38b7894 タイムラインを放置すると先頭の投稿が見えなくなるのを修正 Fix #5903 (#5913)
* seqent fix

* comment

* ✌️
2020-02-12 00:38:29 +09:00
syuilo
2068407be0 Update app.vue 2020-02-12 00:31:48 +09:00
tamaina
10a7369fec サーバーから切断されましたのダイアログは時間をおいて表示するように (#5916)
* timeout disconnect dialog

* 70ms

* 150ms
2020-02-11 22:57:09 +09:00
360 changed files with 3589 additions and 1166 deletions

View File

@@ -1,6 +1,60 @@
ChangeLog
=========
12.11.0 (2020/02/16)
-------------------
### ✨Improvements
* 投稿詳細ページで前後の投稿を見れるように
* 自分のfollowersートはRenoteできるように
* 画像ダイアログを実装
* フォロー申請ページの調整
* 壁紙設定の強化
* 画面が狭い状態でMisskeyを起動した場合でも、画面幅が広がったときにウィジェットを表示するように
* 「もっと読み込む」したときの読み込み量を増量
### 🐛Fixes
* 認証なしでグローバルTLにアクセスすると妙なエラーが返る問題を修正
* API docが見れない問題を修正
* 画面右上に当たり判定があるのを修正
12.10.0 (2020/02/15)
-------------------
### ✨Improvements
* アンテナの受信ソースにグループを指定できるように
* 時計ウィジェットを追加
* ログアウトせずに新しいアカウントを追加できるように
* フォントサイズを設定できるように
* APIキー設定を実装
### 🐛Fixes
* v12アップデート後にトップページアクセスでOops!になっちゃうのを修正
* drive/files APIのパフォーマンスを改善
12.9.0 (2020/02/14)
-------------------
### ✨Improvements
* カスタム絵文字の管理を強化
* 動きのあるMFMを無効にするオプションを追加
### 🐛Fixes
* タイムラインに自分の返信と自分への返信と投稿者自身への返信以外の返信が含まれている問題を修正
* グループがない状態でグループチャットを開始しようとするとフリーズする問題を修正
* 通知インジケーターがずれる問題を修正
* AP: 投稿を削除したときに関係する投稿の削除アクティビティが連合されない問題を修正
12.8.0 (2020/02/13)
--------------------
### ✨Improvements
* タイムラインなどを遡っているときは新しいアイテムが来てもスクロールしないように
* 表示言語を切り替えられるように
* グループに招待されたときの通知を追加
* フランス語と関西弁を有効に
* OSネイティブの絵文字を使用オプションを追加
### 🐛Fixes
* リストを追加するとエラーが出る問題を修正
* タイムラインを放置すると先頭の投稿が見えなくなるのを修正
12.7.1 (2020/02/11)
--------------------
### 🐛Fixes

View File

@@ -1 +1,2 @@
---
_lang_: "Čeština"

View File

@@ -1 +1,2 @@
---
_lang_: "Dansk"

View File

@@ -1 +1,39 @@
---
_lang_: "Deutsch"
monthAndDay: "{day}/{month}"
search: "Suchen"
notifications: "Benachrichtigungen"
username: "Benutzername"
password: "Passwort"
fetchingAsApObject: "Aus Fediverse holen"
ok: "OK"
gotIt: "Verstanden!"
cancel: "Abbrechen"
enterUsername: "Benutzername eingeben"
renotedBy: "Renote von {user}"
noNotes: "Keine Notizen"
noNotifications: "Keine Benachrichtigungen"
instance: "Instanz"
settings: "Einstellungen"
profile: "Profil"
timeline: "Zeitleiste"
noAccountDescription: "Keine Selbsteinführung"
login: "Einloggen"
loggingIn: "Einloggen in bearbeitung"
logout: "Ausloggen"
signup: "Registrieren"
uploading: "Upload läuft"
save: "Speichern"
users: "Benutzer"
addUser: "Benutzer hinzufügen"
copyUsername: "Benutzernamen kopieren"
selectUser: "Benutzer wählen"
instances: "Instanz"
mutedUsers: "Stummgestellte Benutzer"
blockedUsers: "Blockierte Benutzer"
noUsers: "Keine Benutzer"
_widgets:
notifications: "Benachrichtigungen"
timeline: "Zeitleiste"
_profile:
username: "Benutzername"

View File

@@ -1,21 +1,6 @@
---
_ago:
unknown: "Unknown"
future: "Future"
justNow: "Just now"
secondsAgo: "{n}s ago"
minutesAgo: "{n}m ago"
hoursAgo: "{n}h ago"
daysAgo: "{n}d ago"
weeksAgo: "{n}week(s) ago"
monthsAgo: "{n}month(s) ago"
yearsAgo: "{n}year(s) ago"
_time:
second: "s"
minute: "m"
hour: "h"
day: "d"
introMisskey: "Welcome! Misskey is an open source distributed microblogging service.\nCreate \"notes\" to share what's happening or to tell everyone about you📡\nThen send \"reactions\" to respond quickly to everyone's notes👍\nLet's explore a new world🚀"
_lang_: "English"
introMisskey: "Welcome! Misskey is an open source and also decentralized microblogging service.\nWrite the \"notes\" to share what is happening now, or send out your own words to everyone 📡\nWith the \"reactions\", you can add your feelings to everyone's notes faster than anyone 👍\nLet's explore the new world 🚀"
monthAndDay: "{month}/{day}"
search: "Search"
notifications: "Notifications"
@@ -84,7 +69,7 @@ enterListName: "List name"
privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility"
follow: "Following"
follow: "Follow"
followRequest: "Request follow"
followRequests: "Follow requests"
unfollow: "Unfollow"
@@ -127,7 +112,7 @@ flagAsBot: "This account is a bot"
flagAsCat: "This account is a cat"
autoAcceptFollowed: "Automatically approve follow requests from users you're following"
addAcount: "Add Account"
loginFailed: "Sign in failure"
loginFailed: "Failed to sign in"
showOnRemote: "View on remote instance"
general: "General"
wallpaper: "Wallpaper"
@@ -264,12 +249,12 @@ instanceDescription: "Instance description"
maintainerName: "Maintainer"
maintainerEmail: "Maintainer email"
tosUrl: "Terms of Service URL"
thisYear: "This year"
thisMonth: "This month"
thisYear: "Year"
thisMonth: "Month"
today: "Today"
dayX: "{day} days"
monthX: "{month} months"
yearX: "{year} years"
dayX: "{day}"
monthX: "{month}"
yearX: "{year} /"
pages: "Pages"
integration: "Integration"
connectSerice: "Connect"
@@ -326,6 +311,7 @@ aboutMisskey: "About Misskey"
aboutMisskeyText: "Misskey is an open-source software developed by syuilo since 2014."
misskeyMembers: "It is currently developed an maintained by the members listed below:"
misskeySource: "Source code is available here:"
misskeyTranslation: "Help us with your contribution to translate Misskey:"
misskeyDonate: "Help us to keep improving the software by donating here:"
morePatrons: "We really appreciate the support of many other helpers not listed here. Thank you! 🥰"
patrons: "Backers"
@@ -370,12 +356,68 @@ members: "Members"
transfer: "Transfer"
messagingWithUser: "Messaging with other user"
messagingWithGroup: "Messaging within group"
title: "Title"
text: "Text"
enable: "Enable"
next: "Next"
retype: "Enter again"
noteOf: "{user}'s notes"
inviteToGroup: "Invite to group"
maxNoteTextLength: "Character limit of the note"
quoteAttached: "Quoted"
quoteQuestion: "Do you want to append a quote?"
noMessagesYet: "No messages yet"
newMessageExists: "You've got a new message"
onlyOneFileCanBeAttached: "You can only attach one file to a message"
signinRequired: "Please sign in"
invitationCode: "Invitation code"
checking: "Checking"
available: "Available"
unavailable: "Not available"
usernameInvalidFormat: "letters, numbers and _ are acceptable."
tooShort: "Too short"
tooLong: "Too long"
weakPassword: "Weak password"
normalPassword: "Good password"
strongPassword: "Strong password"
passwordMatched: "Matched"
passwordNotMatched: "Doesn't match"
signinWith: "Sign in with {x}"
tapSecurityKey: "Tap your security key"
or: "Or"
uiLanguage: "UI display language"
groupInvited: "Invited to group"
aboutX: "About {x}"
useOsNativeEmojis: "Use the OS native Emojis"
noGroups: "No groups"
joinOrCreateGroup: "Get invited to join the groups or you can create your own group."
noHistory: "No history items"
disableAnimatedMfm: "Disable MFM which has animations"
doing: "On my way"
category: "Category"
tags: "Tags"
docSource: "Source of this document"
createAccount: "Create account"
existingAcount: "Existing accounts"
regenerate: "Regenerate"
fontSize: "Font size"
noFollowRequests: "You don't have any pending follow requests"
_ago:
unknown: "Unknown"
future: "Future"
justNow: "Just now"
secondsAgo: "{n}s ago"
minutesAgo: "{n}m ago"
hoursAgo: "{n}h ago"
daysAgo: "{n}d ago"
weeksAgo: "{n}week(s) ago"
monthsAgo: "{n}month(s) ago"
yearsAgo: "{n}year(s) ago"
_time:
second: "s"
minute: "m"
hour: "h"
day: "d"
_tutorial:
title: "How to use Misskey"
step1_1: "Welcome!"
@@ -386,7 +428,7 @@ _tutorial:
step3_1: "Finished setting up your profile?"
step3_2: "The next step is to post a note. You can do this by pressing a pencil icon on the screen."
step3_3: "Fill in the modal and press the button on the right top to post."
step3_4: "Have nothing to say? Try \"I just started Misskey!\""
step3_4: "Have nothing to say? Try \"just setting up my msky\"!"
step4_1: "Finished posting your first note?"
step4_2: "Hurray! Now your first note is displayed on your timeline."
step5_1: "Now, let's try making your timeline more lively by following other people."
@@ -443,6 +485,7 @@ _antennaSources:
homeTimeline: "Notes from following users"
users: "Notes from specific users"
userList: "Notes from specific list"
userGroup: "Notes from users in the specified group"
_weekday:
sunday: "Sunday"
monday: "Monday"

View File

@@ -1,21 +1,6 @@
---
_ago:
unknown: "Desconocido"
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundos"
minutesAgo: "Hace {n} minutos"
hoursAgo: "Hace {n} horas"
daysAgo: "Hace {n} días"
weeksAgo: "Hace {n} semanas"
monthsAgo: "Hace {n} meses"
yearsAgo: "Hace {n} años"
_time:
second: "Segundos"
minute: "Minutos"
hour: "Horas"
day: "Días"
introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentralizado de código abierto. Escribe \"notas\" para compartir lo que te ocurre ahora o para contar sobre ti a todos. 📡\nCon la función de \"reacciones\", puedes también añadir una reacción rápida a las notas de todos.👍\nExplora un nuevo mundo.🚀"
_lang_: "Español"
introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentralizado de código abierto.\nEscribe \"notas\" para compartir lo que te ocurre ahora o para contar sobre ti a todos 📡\nCon la función de \"reacciones\", puedes también añadir una reacción rápida a las notas de todos 👍\nExplora un nuevo mundo 🚀"
monthAndDay: "{day}/{month}"
search: "Buscar"
notifications: "Notificaciones"
@@ -326,6 +311,7 @@ aboutMisskey: "Sobre Misskey"
aboutMisskeyText: "Misskey es un software de código abierto, desarrollado por syuilo desde el 2014"
misskeyMembers: "Es creado y mantenido por los miembros aquí listados:"
misskeySource: "El código fuente está disponible aquí:"
misskeyTranslation: "Ayúdanos con tu contribución para traducir Misskey:"
misskeyDonate: "Puedes contribuir al desarrollo de Misskey donando aquí:"
morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰"
patrons: "Patrocinadores"
@@ -370,12 +356,68 @@ members: "Miembros"
transfer: "Transferir"
messagingWithUser: "Chatear con usuario"
messagingWithGroup: "Chatear en grupo"
title: "Título"
text: "Texto"
enable: "Activar"
next: "Siguiente"
retype: "Intentar de nuevo"
noteOf: "Notas de {user}"
inviteToGroup: "Invitar al grupo"
maxNoteTextLength: "Límite de caracteres en una nota"
quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?"
noMessagesYet: "Aún no hay chat"
newMessageExists: "Tienes un mensaje nuevo"
onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje"
signinRequired: "Iniciar sesión"
invitationCode: "Código de invitación"
checking: "Comprobando"
available: "Disponible"
unavailable: "No disponible"
usernameInvalidFormat: "utiliza letras, números y/o -."
tooShort: "Demasiado corto"
tooLong: "Demasiado largo"
weakPassword: "Contraseña débil"
normalPassword: "Buena contraseña"
strongPassword: "Muy buena contraseña"
passwordMatched: "Correcto"
passwordNotMatched: "Las contraseñas no son las mismas"
signinWith: "Inicie sesión con {x}"
tapSecurityKey: "Toque la clave de seguridad"
or: "O"
uiLanguage: "Idioma de visualización de la interfaz"
groupInvited: "Invitado al grupo"
aboutX: "Acerca de {x}"
useOsNativeEmojis: "Usa los emojis nativos de la plataforma"
noGroups: "Sin grupos"
joinOrCreateGroup: "Obtenga una invitación para unirse al grupos o puede crear su propio grupo."
noHistory: "No hay datos en el historial"
disableAnimatedMfm: "Deshabilitar MFM que tiene animaciones"
doing: "Voy en camino"
category: "Categoría"
tags: "Etiqueta"
docSource: "Fuente de este documento"
createAccount: "Crear cuenta"
existingAcount: "Cuentas existentes"
regenerate: "Regenerar"
fontSize: "Tamaño de la letra"
noFollowRequests: "No hay solicitudes de seguimiento"
_ago:
unknown: "Desconocido"
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundos"
minutesAgo: "Hace {n} minutos"
hoursAgo: "Hace {n} horas"
daysAgo: "Hace {n} días"
weeksAgo: "Hace {n} semanas"
monthsAgo: "Hace {n} meses"
yearsAgo: "Hace {n} años"
_time:
second: "Segundos"
minute: "Minutos"
hour: "Horas"
day: "Días"
_tutorial:
title: "Cómo usar Misskey"
step1_1: "Bienvenido"
@@ -443,6 +485,7 @@ _antennaSources:
homeTimeline: "Notas de los usuarios que sigues"
users: "Notas de un usuario o varios"
userList: "Notas de los usuarios de una lista"
userGroup: "Notas de los usuarios de una grupo"
_weekday:
sunday: "Domingo"
monday: "Lunes"

View File

@@ -1 +1,853 @@
---
_lang_: "Français"
introMisskey: "Bienvenue! Misskey est un service de microblogage décentralisé open source.\nÉcrivez des «notes» pour partager ce qui vous arrive maintenant ou pour parler de vous à tout le monde 📡\nAvec la fonction «réactions», vous pouvez également ajouter une réaction rapide aux notes de chacun 👍\nExplorez un nouveau monde 🚀"
monthAndDay: "{day}/{month}"
search: "Rechercher"
notifications: "Notifications"
username: "Nom d'utilisateur·rice"
password: "Mot de passe"
fetchingAsApObject: "Récupération depuis le fédiverse"
ok: "D'accord"
gotIt: "J'ai compris !"
cancel: "Annuler"
enterUsername: "Entrer un nom d'utilisateur·rice"
renotedBy: "Renoté par {user}"
noNotes: "Pas de notes"
noNotifications: "Pas de notifications"
instance: "Instance"
settings: "Paramètres"
profile: "Profil"
timeline: "Fil d'actualité"
noAccountDescription: "L'utilisateur·rice n'a pas renseigné de présentation sur son profil"
login: "Se connecter"
loggingIn: "Connexion en cours"
logout: "Se déconnecter"
signup: "S'enregistrer"
uploading: "Envoi en cours"
save: "Enregistrer"
users: "Utilisateur·rice·s"
addUser: "Ajouter un·e utilisateur·rice"
favorite: "Ajouter aux favoris"
favorites: "Favoris"
unfavorite: "Retirer des favoris"
pin: "Épingler sur le profil"
unpin: "Désépingler"
copyContent: "Copier le contenu"
copyLink: "Copier le lien"
delete: "Supprimer"
addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message"
copyUsername: "Copier le nom d'utilisateur"
reply: "Répondre"
loadMore: "Voir plus"
youGotNewFollower: "Vous a suivi"
receiveFollowRequest: "Demande de suivi reçue"
followRequestAccepted: "Suivre la demande acceptée"
mentions: "Mentions"
directNotes: "Messages directs"
importAndExport: "Import et export"
import: "Importer"
export: "Exporter"
files: "Fichier·s"
download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\" ? Les notes avec ce fichier joint seront aussi supprimées."
unfollowConfirm: "Êtes-vous sûr·e ne plus vouloir suivre {name} ?"
exportRequested: "Vous avez demandé une exportation. Cela pourrait prendre un peu de temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes"
noLists: "Aucune liste"
note: "Note"
notes: "Notes"
following: "Abonnements"
followers: "Abonné·e·s"
followsYou: "Votre abonné"
createList: "Créer une liste"
manageLists: "Gérer les listes"
error: "Une erreur est survenue"
retry: "Réessayer"
enterListName: "Nom de la liste"
privacy: "Vie privée"
makeFollowManuallyApprove: "Demandes dabonnements requiert lapprobation"
defaultNoteVisibility: "Visibilité par défaut"
follow: "Abonnement"
followRequest: "Demande dabonnement"
followRequests: "Demandes dabonnement"
unfollow: "Se désabonner"
followRequestPending: "En attente dapprobation"
enterEmoji: "ou entrez un émoji"
renote: "Renote"
unrenote: "Annuler Renote"
quote: "Citation"
pinnedNote: "Note épinglée"
you: "Vous"
clickToShow: "Cliquer pour afficher"
sensitive: "Contenu sensible"
add: "Ajouter"
reaction: "Réactions"
reactionSettingDescription: "Personnaliser les émojis à afficher dans le sélecteur de réactions, délimités par les sauts de ligne."
rememberNoteVisibility: "Se souvenir de la visibilité des notes"
renameFile: "Renommer le ficher"
attachCancel: "Enlever le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Enlever le marquage comme sensible"
enterFileName: "Entrer le nom du fichier"
mute: "Mettre en sourdine"
unmute: "Enlever la sourdine"
block: "Bloquer"
unblock: "Débloquer"
suspend: "Suspendre"
unsuspend: "Annuler la suspension"
blockConfirm: "Désirez-vous bloquer ce compte ?"
unblockConfirm: "Désirez-vous débloquer ce compte ?"
suspendConfirm: "Désirez-vous suspendre ce compte ?"
unsuspendConfirm: "Désirez-vous annuler la suspension de ce compte ?"
selectList: "Sélectionner une liste"
customEmojis: "Émojis personnalisés"
emojiName: "Nom de lémoji"
emojiUrl: "URL de lémoji"
addEmoji: "Ajouter un émoji"
cacheRemoteFiles: "Mettre en cache des fichiers distants"
cacheRemoteFilesDescription: "Quand ce paramètre est désactivé, les fichiers distants sont chargés directement de l'instance distante. Désactiver cela diminuera l'utilisation du stockage mais augmentera le trafic parce les miniatures ne seront pas générées."
flagAsBot: "Ce compte est un robot"
flagAsCat: "Ce compte est un chat"
autoAcceptFollowed: "Approuver automatiquement les abonnements des utilisateurs abonné·e·s"
addAcount: "Ajouter un compte"
loginFailed: "Échec de la connexion"
showOnRemote: "Voir sur l'instance distante"
general: "Général"
wallpaper: "Arrière plan"
removeWallpaper: "Supprimer l'arrière plan"
searchWith: "Recherche : {q}"
youHaveNoLists: "Vous n'avez aucune liste"
followConfirm: "Désirez-vous suivre {name} ?"
proxyAccount: "Compte proxy"
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions, comme un·e abonné·e distant pour les utilisateurs d'autres instances.\nExemple : quand un·e utilisateur·rice distant·e est ajouté·e à une liste, ses notes ne serait pas visibles sur l'instance si personne ne le·la suit. Le compte proxy va donc le·la suivre pour que ses notes soient acheminées."
host: "Hôte"
selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Correspondant·e"
annotation: "Commentaires"
federation: "Fédération"
instances: "Instance"
registeredAt: "Premier contact le"
latestRequestSentAt: "Dernière requête envoyée"
latestRequestReceivedAt: "Dernière requête reçue"
latestStatus: "Dernière statut"
storageUsage: "Stockage utilisé"
charts: "Graphiques"
perHour: "par heure"
perDay: "par jour"
stopActivityDelivery: "Arrêter l'envoi d'activités"
blockThisInstance: "Bloquer cette instnce"
operations: "Opérations"
software: "Logiciel"
version: "Version"
metadata: "Métadonnées"
withNFiles: "{n} fichier(s)"
monitor: "Écran de contrôle"
jobQueue: "File dattente"
cpuAndMemory: "Processeur et mémoire"
network: "Réseau"
disk: "Disque"
instanceInfo: "Informations sur l'instance"
statistics: "Statistiques"
clearQueue: "Vider la file d'attente"
clearQueueConfirmTitle: "Êtes-vous sûr·e de vouloir vider la file d'attente ?"
clearQueueConfirmText: "Les notes non distribuées ne seront pas livrées. Normalement, vous n'avez PAS besoin d'effectuer cette opération."
clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider le cache de fichiers distants ?"
blockedInstances: "Instances bloquées"
blockedInstancesDescription: "Listez les instance que vous désirez bloquer, une par ligne. Ces instances bloquées ne seront pas capable d'interagir avec cette instance."
muteAndBlock: "Masqués / Bloqués"
mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s"
noUsers: "Il n'y a aucun utilisateur·rice"
editProfile: "Modifier votre profil"
noteDeleteConfirm: "Confirmez-vous la suppression de cette note ?"
pinLimitExceeded: "Je ne peux plus épingler"
intro: "L'installation de Misskey est terminée! Créons le compte administrateur."
done: "Terminé"
processing: "Traitement en cours"
preview: "Prévisualisation"
noCustomEmojis: "Il a pas démoji"
customEmojisOfRemote: "Émojis l'instance distante"
noJobs: "Il n'y a aucune tâche planifiée"
federating: "En cours de fédération"
blocked: "Bloqué"
suspended: "Suspendu"
all: "Tous"
subscribing: "Abonné"
publishing: "Publié"
notResponding: "Ne répond pas"
instanceFollowing: "Abonnements une instance"
instanceFollowers: "Abonné·e·s de l'instance"
instanceUsers: "Utilisateur·e·s de l'instance"
changePassword: "Modifier votre mot de passe"
security: "Sécurité"
retypedNotMatch: "Les saisies ne correspondent pas."
currentPassword: "Mot de passe actuel"
newPassword: "Nouveau mot de passe"
newPasswordRetype: "Nouveau mot de passe (répéter)"
attachFile: "Joindre un fichier"
more: "Plus !"
featured: "Surlignage"
usernameOrUserId: "Nom d'utilisateur ou ID utilisateur"
noSuchUser: "Utilisateur non trouvé"
lookup: "Recherche"
announcements: "Annonces"
imageUrl: "URL de limage"
remove: "Supprimer"
removed: "Supprimé"
removeAreYouSure: "Supprimer «{x}» ?"
saved: "Enregistré"
messaging: "Discuter"
upload: "Téléchargez"
fromDrive: "Depuis le Drive"
fromUrl: "De l'URL"
explore: "Découvrir"
games: "Jeux de Misskey"
messageRead: "Lus"
recentUsedEmojis: "Emoji récemment utilisé"
noMoreHistory: "Plus d'histoire passée"
startMessaging: "Commencer à écrire un discutez"
nUsersRead: "{n} personnes ont lu"
agreeTo: "D'accord {0}"
tos: "Conditions d'utilisation"
start: "Commencer"
home: "Principal"
remoteUserCaution: "Les informations sont incomplètes en raison de l'utilisateur distant."
activity: "Activités"
images: "Images"
birthday: "Date de naissance"
yearsOld: "{age} ans"
registeredDate: "Date de création"
location: "Localisation"
theme: "Thème"
lightThemes: "Thème lumineux"
darkThemes: "Thème sombre"
drive: "Drive"
selectFile: "Choisir le fichier"
selectFiles: "Choisir le fichiers"
renameFolder: "Renommer le dossier"
createFolder: "Créer un dossier"
deleteFolder: "Supprimer le dossier"
addFile: "Ajoutez un fichier"
emptyDrive: "Le Drive est vide"
emptyFolder: "Le dossier est vide"
copyUrl: "Copier lURL"
rename: "Renommer"
avatar: "Avatar"
banner: "Bannière"
nsfw: "Contenu sensible"
disconnectedFromServer: "Déconnecté du serveur"
reloadConfirm: "Voulez-vous recharger?"
watch: "Surveiller"
unwatch: "Ne plus surveiller"
accept: "Autoriser"
reject: "Refuser"
instanceName: "Nom de linstance"
instanceDescription: "Description de linstance"
maintainerName: "Nom d'administrateur"
maintainerEmail: "Email de l'administrateur"
tosUrl: "URL des conditions d'utilisation"
thisYear: "Cette année"
thisMonth: "Ce mois-ci"
today: "Aujourd'hui"
dayX: "{day} jour"
monthX: "{month} mois"
yearX: "{year} année"
pages: "Pages"
integration: "Intégrations"
connectSerice: "Connecter"
disconnectSerice: "Déconnecter"
enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Si vous désactivez ces le fils, les administrateurs et les modérateurs pourront toujours y accéder pour plus de commodité."
registration: "S'inscrire"
enableRegistration: "Autoriser nimporte qui à senregistrés"
invite: "Inviter"
proxyRemoteFiles: "Proxy fichiers distants"
proxyRemoteFilesDescription: "Si vous activez ce paramètre, les fichiers distants non stockés ou supprimés en raison d'une capacité excédentaire seront affichés via un proxy local et généreront une miniature. Cela n'affectera pas le stockage du serveur."
driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local"
driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant"
inMb: "en mégaoctets"
iconUrl: "URL de l'image de l'icône"
bannerUrl: "URL de l'image de la bannière"
basicInfo: "Informations basiques"
pinnedUsers: "Utilisateur·rice épinglé·e"
pinnedUsersDescription: "Décrivez les utilisateurs que vous souhaitez définir sur la page \"Découvrir\" séparés par une nouvelle ligne"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Activation de reCAPTCHA"
recaptchaSiteKey: "Clé du site"
recaptchaSecretKey: "Clé secrète"
antennas: "Antenne"
manageAntennas: "Gestion d'antenne"
name: "Nom"
antennaSource: "Recevoir la source"
antennaKeywords: "Mots clés entrants"
antennaKeywordsDescription: "Lorsqu'il est séparé par un espace, il devient une spécification ET, et lorsqu'il est séparé par un saut de ligne, il devient une spécification OU."
notifyAntenna: "Notifier les nouvelles notes"
withFileAntenna: "Notes uniquement avec fichiers joints"
serviceworker: "ServiceWorker"
enableServiceworker: "Activer ServiceWorker"
antennaUsersDescription: "Spécifiez les noms d'utilisateurs séparés par des sauts de ligne"
caseSensitive: "Sensible à la casse"
withReplies: "Y compris répondres"
connectedTo: "Vous êtes connectés aux services suivants"
notesAndReplies: "Notes et Répondres"
withFiles: "Avec fichiers joints"
silence: "Mettre en masquer"
silenceConfirm: "Mettre l'utilisateur sous masquer ?"
unsilenceConfirm: "Voulez-vous annuler le masquer ?"
popularUsers: "Utilisateur·rice·s populaires"
recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment"
recentlyRegisteredUsers: "Utilisateur·rice·s récemment enregistrés"
recentlyDiscoveredUsers: "Utilisateur·rice·s récemment découverts"
exploreUsersCount: "Il y a {count} utilisateur·rice·s"
exploreFediverse: "Explorer le Fédiverse"
popularTags: "Mots-clés populaires"
userList: "Listes"
about: "Informations"
aboutMisskey: "À propos de Misskey"
aboutMisskeyText: "Misskey est un logiciel open source, développé par syuilo depuis 2014."
misskeyMembers: "Il est développé et maintenu par les membres répertoriés ici:"
misskeySource: "Le code source est disponible ici:"
misskeyTranslation: "Aidez-nous avec votre contribution à traduire Misskey:"
misskeyDonate: "Vous pouvez contribuer au développement de Misskey en faisant un don ici:"
morePatrons: "Nous apprécions vraiment le soutien de nombreux autres les soutiens non répertoriés ici. Merci beaucoup à tous! 🥰"
patrons: "Supporteurs"
administrator: "Administrateur"
token: "Jeton"
twoStepAuthentication: "Authentification à deux facteurs"
moderator: "Modérateurs"
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
securityKey: "Clé de sécurité"
securityKeyName: "Nom de la clé"
registerSecurityKey: "Sinscrire la clé de sécurité"
lastUsed: "Dernier utilisé"
unregister: "Se désinscrire"
passwordLessLogin: "Connectez-vous sans mot de passe"
resetPassword: "Réinitialiser mot de passe"
newPasswordIs: "Votre nouveau mot de passe est \"{password}\""
post: "Notes"
posted: "Publié !"
autoReloadWhenDisconnected: "Rechargement automatique lorsque le serveur se déconnecte"
autoNoteWatch: "Surveiller automatique pour les notes"
autoNoteWatchDescription: "Soyez informé des notes auxquelles vous avez réagi ou répondu."
reduceUiAnimation: "Réduire l'animation de l'interface"
share: "Partager"
notFound: "Non trouvé"
notFoundDescription: "Aucune page ne correspond à l'URL spécifiée."
uploadFolder: "Emplacement de téléversement par défaut"
cacheClear: "Vider le cache"
markAsReadAllNotifications: "Marquer toutes les notifications comme lues"
markAsReadAllUnreadNotes: "Marquer toutes les notes comme lues"
markAsReadAllTalkMessages: "Marquer toutes les discutez comme lues"
help: "Aide"
inputMessageHere: "Tapez ici votre message"
close: "Fermer"
group: "Groupe"
groups: "Groupes"
createGroup: "Créer un groupe"
ownedGroups: "Groupe propriétaire"
joinedGroups: "Membre dans les groupes"
invites: "Inviter"
groupName: "Nom du groupe"
members: "Membres"
transfer: "Transférer"
messagingWithUser: "Discutez avec les utilisateurs"
messagingWithGroup: "Discuter en groupe"
title: "Titre"
text: "Texte"
enable: "Activer"
next: "Suivant"
retype: "Retapez"
noteOf: "{user} notes"
inviteToGroup: "Inviter au groupe"
maxNoteTextLength: "Limite de note caractères"
quoteAttached: "Avec citation"
quoteQuestion: "Souhaitez-vous ajoutez une citation ?"
noMessagesYet: "Pas encore discuté"
newMessageExists: "Vous avez un nouveau message"
onlyOneFileCanBeAttached: "Vous ne pouvez joindre qu'un seul fichier au message"
signinRequired: "Veuillez vous connecter"
invitationCode: "Code dinvitation"
checking: "Vérification"
available: "Disponible"
unavailable: "Non disponible"
usernameInvalidFormat: "Vous pouvez utiliser des lettres, des nombres et _"
tooShort: "Est trop court"
tooLong: "Est trop long"
weakPassword: "Faible mot de passe"
normalPassword: "Bon mot de passe"
strongPassword: "Fort mot de passe"
passwordMatched: "Correcte"
passwordNotMatched: "Ne correspond pas"
signinWith: "Connectez-vous avec {x}"
tapSecurityKey: "Touchez la clé de sécurité"
or: "OU"
uiLanguage: "Langue d'affichage de l'interface"
groupInvited: "Invité au groupe"
aboutX: "À propos de {x}"
useOsNativeEmojis: "Utilisez les emojis natifs de la plateforme"
noGroups: "Pas de groupes"
joinOrCreateGroup: "Soyez invité à rejoindre les groupes ou vous pouvez créer votre propre groupe."
noHistory: "Pas d'historique"
disableAnimatedMfm: "Désactiver MFM qui a des animations"
doing: "Attends une seconde"
category: "Catégories"
tags: "Étiquettes"
docSource: "Source de ce document"
createAccount: "Créer compte"
existingAcount: "Comptes existants"
regenerate: "Régénérer"
fontSize: "Taille de la police"
noFollowRequests: "Vous n'avez aucune demandes d'abonnement en attente"
_ago:
unknown: "Inconnu"
future: "Futur"
justNow: "à linstant"
secondsAgo: "Il y a {n}s"
minutesAgo: "Il y a {n}min"
hoursAgo: "Il y a {n} heures"
daysAgo: "Il y a {n} jours"
weeksAgo: "Il y a {n} semaines"
monthsAgo: "Il y a {n} mois"
yearsAgo: "Il y a {n} ans"
_time:
second: "s"
minute: "min"
hour: "h"
day: "j"
_tutorial:
title: "Comment utiliser Misskey"
step1_1: "Bienvenue,"
_2fa:
alreadyRegistered: "Cette étape à déjà été complétée"
registerDevice: "Sinscrire l'appareil"
registerKey: "Sinscrire la clé"
step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
step2: "Ensuite, scannez le code QR affiché avec l'application."
step3: "Entrez le jeton affiché sur l'application et vous avez terminé."
step4: "Lorsque vous vous connectez, entrez le jeton de la même manière."
securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion avec non seulement la clé de sécurité matérielle qui prend en charge FIDO2, mais également l'authentification par empreinte digitale ou PIN sur votre appareil."
_permissions:
"read:account": "Afficher les informations du compte"
"write:account": "Mettre à jour les informations de votre compte"
"read:blocks": "Voir les blocs"
"write:blocks": "Écrire des blocs"
"read:drive": "Parcourir le Drive"
"write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris"
"write:favorites": "Écrire des favoris"
"read:following": "Voir les informations de l'abonné"
"write:following": "Abonnements/Se désabonner"
"read:messaging": "Cherche à discuter"
"write:messaging": "Contrôler le discuter"
"read:mutes": "Voir les comptes muets"
"write:mutes": "Gérer les comptes muets"
"write:notes": "Créer / supprimer des notes"
"read:notifications": "Afficher les notifications"
"write:notifications": "Gérer vos notifications"
"read:reactions": "Lire les réactions"
"write:reactions": "Gérer vos réactions"
"write:votes": "Voter"
"read:pages": "Afficher la page"
"write:pages": "Mettre à jour les Pages"
"read:page-likes": "Lire les favoris sur les Pages"
"write:page-likes": "Mettre à jour les favoris sur les Pages"
"read:user-groups": "Voir les groupes d'utilisateur·rice·s"
"write:user-groups": "Éditer les groupes des utilisateur·rice·s"
_auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
permissionAsk: "Cette application nécessite les autorisations suivantes "
_antennaSources:
all: "Toutes les notes"
homeTimeline: "Notes de l'utilisateur auquel je m'abonne"
users: "Notes des un ou plusieurs utilisateurs spécifiés"
userList: "Notes pour les utilisateurs de la liste spécifiée"
userGroup: "Notes pour les utilisateurs de la groupe spécifiée"
_weekday:
sunday: "Dimanche"
monday: "Lundi"
tuesday: "Mardi"
wednesday: "Mercredi"
thursday: "Jeudi"
friday: "Vendredi"
saturday: "Samedi"
_widgets:
memo: "Note collante"
notifications: "Notifications"
timeline: "Fil d'actualité"
calendar: "Calendrier"
trends: "Tendances"
clock: "Horloge"
rss: "Lecteur de flux RSS"
_cw:
hide: "Masquer"
show: "Voir plus"
chars: "{count} caractères"
files: "{count} fichiers"
poll: "Sondage"
_poll:
noOnlyOneChoice: "Au moins 2 réponses nécéssaires"
choiceN: "Choix {n}"
noMore: "Vous ne pouvez pas en ajouter davantage"
canMultipleVote: "Autoriser le multi-choix"
expiration: "Fin du sondage"
infinite: "Illimité"
at: "Choisir une date"
after: "Chosir une durée"
deadlineDate: "Date de fin"
deadlineTime: "Heure de fin"
duration: "Durée"
votesCount: "{n} votes"
totalVotes: "{n} votes au total"
vote: "Voter"
showResult: "Voir les résultats"
voted: "Déjà voté"
closed: "Terminé"
remainingDays: "{d} jours, {h} heures restantes"
remainingHours: "{h} heures et {m} minutes restantes"
remainingMinutes: "{m} minutes et {s} secondes restantes"
remainingSeconds: "{s} secondes restantes"
_visibility:
public: "Public"
publicDescription: "Publier à tou·te·s les utilisateur·rice·s"
home: "Principal"
homeDescription: "Publier sur le fil principal uniquement"
followers: "Abonné·e·s"
followersDescription: "Publier à vos abonné·e·s uniquement"
specified: "Direct"
specifiedDescription: "Publier uniquement aux utilisateur·rice·s mentionné·e·s"
localOnly: "Local seulement"
_postForm:
replyPlaceholder: "Répondre à cette note ..."
quotePlaceholder: "Citez cette note ..."
_placeholders:
a: "Qu'est-ce qu'il se passe ?"
b: "Quoi de neuf ?"
c: "Quavez-vous en tête ?"
d: "Désirez-vous publier quelques mots ?"
e: "Écrivez ici"
f: "En attente de vos écrits ..."
_profile:
name: "Nom"
username: "Nom d'utilisateur·rice"
description: "À propos de moi"
youCanIncludeHashtags: "Vous pouvez également inclure des hashtags."
metadata: "Informations complémentaires"
metadataLabel: "Étiquette"
metadataContent: "Contenu"
_exportOrImport:
allNotes: "Toutes les notes"
followingList: "Abonnements"
muteList: "Mettre en sourdine"
blockingList: "Bloquer"
userLists: "Listes"
_charts:
federationInstancesIncDec: "Variation du nombre d'instances"
federationInstancesTotal: "Nombre d'instances au total"
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Nombre d'utilsateur·rice·s au total"
activeUsers: "Utilisateur·rice·s actif·ve·s"
notesIncDec: "Variation du nombre d'notes"
localNotesIncDec: "Variation du nombre de notes local"
remoteNotesIncDec: "Variation du nombre dnotes distant"
notesTotal: "Nombre d'notes au total"
filesIncDec: "Variation du nombre de fichiers"
filesTotal: "Nombre de fichiers au total"
storageUsageIncDec: "Variation de l'utilisation du stockage"
storageUsageTotal: "Utilisation totale du stockage"
_instanceCharts:
requests: "Requêtes"
users: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Somme du nombre d'utilisateur·rice·s accumulés"
notes: "Variation du nombre d'notes"
notesTotal: "Somme du nombre dnotes accumulés"
ff: "Variation des abonné·e·s"
ffTotal: "Somme du nombre d'abonnements accumulés"
cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "Somme de la taille du cache accumulé"
files: "Variation du nombre de fichiers"
filesTotal: "Somme du nombre de fichiers accumulés"
_timelines:
home: "Principal"
local: "Local"
social: "Social"
global: "Global"
_pages:
newPage: "Créer une page"
editPage: "Modifier une page"
readPage: "Voir la source"
page-created: "Page a été créée !"
page-updated: "A mis à jour la page"
name-already-exists: "Une page portant le même nom existe déjà"
title-invalid-name: "LURL de la page spécifiée nest pas valide"
text-invalid-name: "Assurez-vous qu'il n'est pas vide"
editThisPage: "Éditer cette page"
viewSource: "Afficher la source"
viewPage: "Afficher la page"
like: "Favori"
unlike: "Je nfavoris pas"
liked-pages: "Pages favoris"
my-pages: "Mes pages"
inspector: "Inspecteur"
content: "Bloc de page"
variables: "Variables"
variables-info: "Vous pouvez créer une page dynamique à l'aide de variables. En tapant le <b>{nom de variable}</b> dans le texte, vous pouvez y incorporer la valeur de la variable. Par exemple, si dans le texte <b>Bonjour {chose} monde!</b> la valeur de la variable (chose) est <b>ai</b>, le texte devient est <b>Bonjour ai monde!</b>."
variables-info2: "L'évaluation des variables (le calcul des valeurs) se fait de haut en bas, donc l'variable ne peut pas se référer à une autre qui est en dessous. Par exemple, lorsque les variables <b>A、B、C</b> sont définies, <b>C</b> peut faire référence à <b>A</b> ou <b>B</b>, mais <b>A</b> ne peut pas faire référence à <b>B</b> ou <b>C</b>."
variables-info3: "Pour recevoir une entrée utilisateur, ajoutez un bloc \"Entrée\" sur la page et définissez le nom des variables que vous souhaitez stocker dans le champ \"Nom de la variable\" (les variables seront créées automatiquement). Les actions seront exécutées en fonction de l'entrée utilisateur de ces variables."
variables-info4: "Les fonctions vous permettent d'organiser le processus de calcul des valeurs sous une forme réutilisable. Pour créer une fonction, créez une variable de type \"fonction\". Une fonction peut avoir un slot (argument) et sa valeur peut être utilisée comme variable dans la fonction. Il existe également une fonction qui prend une fonction comme argument dans la norme AiScript (appelée fonction d'ordre supérieur). En plus des fonctions prédéfinies, elles peuvent être définies instantanément dans ces emplacements de fonction d'ordre supérieur."
more-details: "Description"
title: "Titre"
url: "URL de page"
summary: "Résumé de page"
alignCenter: "Centrée"
hide-title-when-pinned: "Masquer le titre de la page lorsque celle-ci est épinglée au profil"
font: "Police de caractères"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
set-eye-catching-image: "Définir une image attirante"
remove-eye-catching-image: "Supprimer une image attirante"
chooseBlock: "Ajouter un bloc"
selectType: "Choisir un type"
enterVariableName: "Veuillez entrer un nom de variable"
the-variable-name-is-already-used: "Cette variable est déjà utilisée"
content-blocks: "Contenu"
input-blocks: "Entrée"
special-blocks: "Spécial"
post-from-post-form: "Publier ce contenu"
posted-from-post-form: "Publié !"
blocks:
text: "Texte"
textarea: "Zone de texte"
section: "Section"
image: "Images"
button: "Bouton"
if: "Si"
_if:
variable: "Variables"
post: "Formulaire à publier"
_post:
text: "Contenu"
textInput: "Entrée de textuelle"
_textInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
textareaInput: "Entrée de textuelle multiligne"
_textareaInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
numberInput: "Entrée numérique"
_numberInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
switch: "Basculer"
_switch:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
counter: "Compteur"
_counter:
name: "Nom de la variable"
text: "Titre"
inc: "Augmenter le chiffre"
_button:
text: "Titre"
colored: "Coloré"
action: "L'opération lorsque le bouton sera pressé"
_action:
dialog: "Afficher une fenêtre de dialogue"
_dialog:
content: "Contenu"
resetRandom: "Réinitialiser le nombre aléatoire"
pushEvent: "Envoyer un évènement"
_pushEvent:
event: "Nom de l'évènement"
message: "Message à afficher lorsque appuyé"
variable: "Variable à envoyer"
no-variable: "Rien"
radioButton: "Choix"
_radioButton:
name: "Nom de la variable"
title: "Titre"
values: "Choix séparés par des sauts de ligne"
default: "Valeur par défaut"
script:
categories:
flow: "Contrôle"
logical: "Opération logique"
operation: "Calculer"
comparison: "Comparer"
random: "Aléatoire"
value: "Valeur"
fn: "Fonction"
text: "Manipulation de texte"
convert: "Convertir"
list: "Listes"
blocks:
text: "Texte"
multiLineText: "Texte (Multi-lignes)"
textList: "Liste de texte"
_textList:
info: "Veuillez séparer chacun avec une nouvelle ligne"
strLen: "Longueur d'un texte"
_strLen:
arg1: "Texte"
strPick: "Extraire un caractère"
_strPick:
arg1: "Texte"
arg2: "Position du joueur"
strReplace: "Remplacement de texte"
_strReplace:
arg1: "Texte"
arg2: "Avant le remplacement"
arg3: "Après le remplacement"
strReverse: "Inverser le texte"
_strReverse:
arg1: "Texte"
join: "Concaténer du texte"
_join:
arg1: "Listes"
arg2: "Séparateur"
add: "Ajouter"
_add:
arg1: "A"
arg2: "B"
subtract: "Soustraire"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplier par"
_multiply:
arg1: "A"
arg2: "B"
divide: "Diviser par"
_divide:
arg1: "A"
arg2: "B"
mod: "Reste"
_mod:
arg1: "A"
arg2: "B"
round: "Décimal rond"
_round:
arg1: "Numérique"
eq: "A et B sont équivalents"
_eq:
arg1: "A"
arg2: "B"
notEq: "A et B sont différents"
_notEq:
arg1: "A"
arg2: "B"
and: "A et B"
_and:
arg1: "A"
arg2: "B"
or: "A ou B"
_or:
arg1: "A"
arg2: "B"
lt: "A est plus petit que B"
_lt:
arg1: "A"
arg2: "B"
gt: "A est supérieur à B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "A est plus petit ou égal à B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: "A est supérieur ou égal à B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Branche"
_if:
arg1: "Si"
arg2: "Si"
arg3: "Sinon"
not: "Nier"
_not:
arg1: "Nier"
random: "Aléatoire"
_random:
arg1: "Probabilité"
rannum: "Nombre aléatoire"
_rannum:
arg1: "Minimum"
arg2: "Maximum"
randomPick: "Sélectionner au hasard dans la liste"
_randomPick:
arg1: "Listes"
dailyRandom: "Aléatoire (Quotidien pour chaque utilisateur)"
_dailyRandom:
arg1: "Probabilité"
dailyRannum: "Numéros aléatoires (Quotidien pour chaque utilisateur)"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque utilisateur)"
_dailyRandomPick:
arg1: "Listes"
seedRandom: "Aléatoire (graine)"
_seedRandom:
arg1: "Graine"
arg2: "Probabilité"
seedRannum: "Nombre aléatoire (Graine)"
_seedRannum:
arg1: "Graine"
arg2: "Minimum"
arg3: "Maximum"
seedRandomPick: "Sélectionné au hasard dans la liste (graine)"
_seedRandomPick:
arg1: "Graine"
arg2: "Listes"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour chaque utilisateur)"
_DRPWPM:
arg1: "Liste de texte"
pick: "Sélectionner dans la liste"
_pick:
arg1: "Listes"
arg2: "Position"
listLen: "Longueur de la liste"
_listLen:
arg1: "Listes"
number: "Numérique"
stringToNumber: "Convertir du texte en numérique"
_stringToNumber:
arg1: "Texte"
numberToString: "Convertir du numérique en texte"
_numberToString:
arg1: "Numérique"
splitStrByLine: "Séparer le texte par lignes"
_splitStrByLine:
arg1: "Texte"
ref: "Variables"
fn: "Fonction"
_fn:
slots: "Slots"
slots-info: "Veuillez délimiter chaque slot par un saut de ligne"
arg1: "Sortie"
for: "Répéter"
_for:
arg1: "Compter"
arg2: "Action"
typeError: "Le slot {slot} accepte \"{expect}\" mais a \"{actual}\" !"
thereIsEmptySlot: "Slot {slot} est vide !"
types:
string: "Texte"
number: "Numérique"
boolean: "Marqueur"
array: "Listes"
stringArray: "Liste de texte"
emptySlot: "Slot vide"
enviromentVariables: "Variables d'environnement"
pageVariables: "Élément de page"
argVariables: "Entrée slot"

View File

@@ -19,9 +19,9 @@ const languages = [
//'de-DE',
'en-US',
'es-ES',
//'fr-FR',
'fr-FR',
'ja-JP',
//'ja-KS',
'ja-KS',
'ko-KR',
//'nl-NL',
//'pl-PL',

View File

@@ -1,20 +1,4 @@
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_lang_: "日本語"
introMisskey: "ようこそMisskeyは、オープンソースの分散型マイクロブログサービスです。\n「ート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日"
@@ -327,6 +311,7 @@ aboutMisskey: "Misskeyについて"
aboutMisskeyText: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。"
misskeyMembers: "現在以下のメンバーによって開発・メンテナンスされています:"
misskeySource: "ソースコードはここで公開されています:"
misskeyTranslation: "Misskeyの翻訳にご協力をお願いします:"
misskeyDonate: "Misskeyに寄付をして開発をサポートできます:"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
patrons: "支援者"
@@ -371,12 +356,71 @@ members: "メンバー"
transfer: "譲渡"
messagingWithUser: "ユーザーとチャット"
messagingWithGroup: "グループでチャット"
title: "タイトル"
text: "テキスト"
enable: "有効にする"
next: "次"
retype: "再入力"
noteOf: "{user}のノート"
inviteToGroup: "グループに招待"
maxNoteTextLength: "ノートの文字数制限"
quoteAttached: "引用付き"
quoteQuestion: "引用として添付しますか?"
noMessagesYet: "まだチャットはありません"
newMessageExists: "新しいメッセージがあります"
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
signinRequired: "ログインしてください"
invitationCode: "招待コード"
checking: "確認しています"
available: "利用できます"
unavailable: "利用できません"
usernameInvalidFormat: "a~z、A~Z、0~9、_が使えます"
tooShort: "短すぎます"
tooLong: "長すぎます"
weakPassword: "弱いパスワード"
normalPassword: "普通のパスワード"
strongPassword: "強いパスワード"
passwordMatched: "一致しました"
passwordNotMatched: "一致していません"
signinWith: "{x}でログイン"
tapSecurityKey: "セキュリティーキーにタッチ"
or: "もしくは"
uiLanguage: "UIの表示言語"
groupInvited: "グループに招待されました"
aboutX: "{x}について"
useOsNativeEmojis: "OSネイティブの絵文字を使用"
noGroups: "グループがありません"
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
noHistory: "履歴はありません"
disableAnimatedMfm: "動きのあるMFMを無効にする"
doing: "やっています"
category: "カテゴリ"
tags: "タグ"
docSource: "このドキュメントのソース"
createAccount: "アカウントを作成"
existingAcount: "既存のアカウント"
regenerate: "再生成"
fontSize: "フォントサイズ"
noFollowRequests: "フォロー申請はありません"
openImageInNewTab: "画像を新しいタブで開く"
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_tutorial:
title: "Misskeyの使い方"
@@ -449,6 +493,7 @@ _antennaSources:
homeTimeline: "フォローしているユーザーのノート"
users: "指定した一人または複数のユーザーのノート"
userList: "指定したリストのユーザーのノート"
userGroup: "指定したグループのユーザーのノート"
_weekday:
sunday: "日曜日"

View File

@@ -1,20 +1,5 @@
---
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_lang_: "日本語 (関西弁)"
introMisskey: "ようこそMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作成しぃ、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素はよ反応を追加することもできます✌\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日"
search: "探す"
@@ -124,6 +109,22 @@ aboutMisskey: "Misskeyってなんや"
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
close: "さいなら"
joinedGroups: "参加しとるグループ"
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_2fa:
alreadyRegistered: "もう設定終わっとるわ"
_auth:

2
locales/kn-IN.yml Normal file
View File

@@ -0,0 +1,2 @@
---
_lang_: "ಕನ್ನಡ"

View File

@@ -1,20 +1,5 @@
---
_ago:
unknown: "알 수 없음"
future: "미래"
justNow: "방금 전"
secondsAgo: "{n}초 전"
minutesAgo: "{n}분 전"
hoursAgo: "{n}시간 전"
daysAgo: "{n}일 전"
weeksAgo: "{n}주 전"
monthsAgo: "{n}개월 전"
yearsAgo: "{n}년 전"
_time:
second: "초"
minute: "분"
hour: "시간"
day: "일"
_lang_: "한국어"
introMisskey: "환영합니다! Misskey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
monthAndDay: "{month}월 {day}일"
search: "검색"
@@ -268,7 +253,7 @@ thisYear: "올해"
thisMonth: "이번 달"
today: "오늘"
dayX: "{day}일"
monthX: "{month}월"
monthX: "{month}월"
yearX: "{year}년"
pages: "페이지"
integration: "연동"
@@ -323,9 +308,10 @@ popularTags: "인기 태그"
userList: "리스트"
about: "정보"
aboutMisskey: "Misskey에 대하여"
aboutMisskeyText: "Misskey는 syuilo에 의해 2014년부터 개발 오픈 소스 소프트웨어입니다."
misskeyMembers: "현재는 아래 멤버들에 의해 개발 및 유지보수 되고 있습니다."
misskeySource: "소스 코드는 여기에서 보실 수 있습니다:"
aboutMisskeyText: "Misskey는 syuilo에 의해 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
misskeyMembers: "현재는 아래 멤버들에 의해 개발 및 유지보수 되고 있습니다:"
misskeySource: "소스코드는 여기에 공개되어 있습니다:"
misskeyTranslation: "Misskey의 번역을 함께해 주시길 부탁드립니다:"
misskeyDonate: "Misskey에 기부하심으로써 개발에 도움을 주실 수 있습니다:"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자들"
@@ -370,12 +356,68 @@ members: "멤버"
transfer: "양도"
messagingWithUser: "유저와 대화하기"
messagingWithGroup: "그룹끼리 대화하기"
title: "제목"
text: "텍스트"
enable: "사용"
next: "다음"
retype: "다시 입력"
noteOf: "{user}의 노트"
inviteToGroup: "그룹에 초대하기"
maxNoteTextLength: "노트의 문자 수 제한"
quoteAttached: "인용함"
quoteQuestion: "인용해서 작성하시겠습니까?"
noMessagesYet: "아직 대화가 없습니다"
newMessageExists: "새 메시지가 있습니다"
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
signinRequired: "로그인 해주세요"
invitationCode: "초대 코드"
checking: "확인하는 중입니다"
available: "사용 가능합니다"
unavailable: "사용할 수 없습니다"
usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다"
tooShort: "너무 짧습니다"
tooLong: "너무 깁니다"
weakPassword: "약한 비밀번호"
normalPassword: "좋은 비밀번호"
strongPassword: "강한 비밀번호"
passwordMatched: "일치합니다"
passwordNotMatched: "일치하지 않습니다"
signinWith: "{x}로 로그인"
tapSecurityKey: "보안 키를 터치"
or: "혹은"
uiLanguage: "UI 표시 언어"
groupInvited: "그룹에 초대되었습니다"
aboutX: "{x}에 대하여"
useOsNativeEmojis: "OS 기본 이모지를 사용"
noGroups: "그룹이 없습니다"
joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을 만들어 보세요."
noHistory: "기록이 없습니다"
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
doing: "잠시만요"
category: "카테고리"
tags: "태그"
docSource: "이 문서의 소스"
createAccount: "계정 만들기"
existingAcount: "기존 계정"
regenerate: "다시 생성"
fontSize: "글자 크기"
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
_ago:
unknown: "알 수 없음"
future: "미래"
justNow: "방금 전"
secondsAgo: "{n}초 전"
minutesAgo: "{n}분 전"
hoursAgo: "{n}시간 전"
daysAgo: "{n}일 전"
weeksAgo: "{n}주 전"
monthsAgo: "{n}개월 전"
yearsAgo: "{n}년 전"
_time:
second: "초"
minute: "분"
hour: "시간"
day: "일"
_tutorial:
title: "Misskey의 사용 방법"
step1_1: "환영합니다!"
@@ -407,7 +449,7 @@ _2fa:
step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다."
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 시큐리티 키 혹은 휴대전화의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
_permissions:
"read:account": "계정의 정보를 봅니다"
"write:account": "계정의 정보를 변경합니다"
@@ -443,6 +485,7 @@ _antennaSources:
homeTimeline: "팔로우중인 유저의 노트"
users: "지정한 한 명 혹은 여러 명의 유저의 노트"
userList: "지정한 리스트에 속한 유저의 노트"
userGroup: "지정한 그룹에 속한 유저의 노트"
_weekday:
sunday: "일요일"
monday: "월요일"

View File

@@ -1 +1,2 @@
---
_lang_: "Nederlands"

View File

@@ -1 +1,2 @@
---
_lang_: "Norsk Bokmål"

View File

@@ -1 +1,2 @@
---
_lang_: "język polski"

View File

@@ -1 +1,2 @@
---
_lang_: "Português"

View File

@@ -1 +1,2 @@
---
_lang_: "Русский язык"

View File

@@ -1,20 +1,5 @@
---
_ago:
unknown: "未知"
future: "未来"
justNow: "最近"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}小时前"
daysAgo: "{n}日前"
weeksAgo: "{n}周前"
monthsAgo: "{n}月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "小时"
day: "日"
_lang_: "中文(简体)"
introMisskey: "欢迎Misskey是一个开源的分散型SNS服务。\n通过「帖子」来分享现在发生的事情吧📡\n「反应」功能可以让你快速的对大家的「帖子」来表达感情👍\n一起来探索新的世界吧🚀"
monthAndDay: "{month}月 {day}日"
search: "搜索"
@@ -136,18 +121,23 @@ searchWith: "搜索:{q}"
youHaveNoLists: "列表为空"
followConfirm: "你确定要关注{name}吗?"
proxyAccount: "代理账户"
proxyAccountDescription: "代理帐户是在某些情况下充当用户的远程关注者的帐户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该实例,因此将代之以代理帐户。"
host: "主机名"
selectUser: "选择用户"
recipient: "收件人"
annotation: "注解"
federation: "联合"
instances: "实例"
registeredAt: "初次观察"
latestRequestSentAt: "上次发送的请求"
latestRequestReceivedAt: "上次收到的请求"
latestStatus: "最后状态"
storageUsage: "已用存储"
charts: "图表"
perHour: "每小时"
perDay: "每天"
stopActivityDelivery: "停止发送活动"
blockThisInstance: "阻止此实例"
operations: "操作"
software: "软件"
version: "版本"
@@ -162,6 +152,7 @@ instanceInfo: "实例情报"
statistics: "统计"
clearQueue: "清除队列"
clearQueueConfirmTitle: "确定清除队列?"
clearQueueConfirmText: "未送达的帖子将不会送达。 通常,您不需要这样做。"
clearCachedFiles: "清除缓存"
clearCachedFilesConfirm: "确定要清除缓存文件?"
blockedInstances: "被阻拦的实例"
@@ -288,6 +279,7 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
recaptchaSiteKey: "网站密钥"
recaptchaSecretKey: "reCAPTCHA 密钥"
antennas: "天线"
name: "名称"
antennaKeywordsDescription: "使用空格分隔会产生AND规范并且使用换行符分隔会产生OR规范"
serviceworker: "ServiceWorker"
@@ -350,12 +342,30 @@ invites: "邀请"
groupName: "群组名"
members: "成员"
transfer: "转让"
title: "标题"
text: "文本"
enable: "启用"
next: "下一个"
retype: "重新输入"
noteOf: "{user}的帖子"
inviteToGroup: "群组邀请"
maxNoteTextLength: "帖子的字数限制"
_ago:
unknown: "未知"
future: "未来"
justNow: "最近"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}小时前"
daysAgo: "{n}日前"
weeksAgo: "{n}周前"
monthsAgo: "{n}月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "小时"
day: "日"
_tutorial:
title: "Misskey的使用方法"
step1_1: "欢迎!"

View File

@@ -1 +1,2 @@
---
_lang_: "中文(繁体)"

View File

@@ -0,0 +1,38 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userGroupInvitation1581526429287 implements MigrationInterface {
name = 'userGroupInvitation1581526429287'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE TABLE "user_group_invitation" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_160c63ec02bf23f6a5c5e8140d6" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE INDEX "IDX_bfbc6305547539369fe73eb144" ON "user_group_invitation" ("userId") `, undefined);
await queryRunner.query(`CREATE INDEX "IDX_5cc8c468090e129857e9fecce5" ON "user_group_invitation" ("userGroupId") `, undefined);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e9793f65f504e5a31fbaedbf2f" ON "user_group_invitation" ("userId", "userGroupId") `, undefined);
await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`, undefined);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined);
await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined);
await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined);
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_bfbc6305547539369fe73eb144a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_5cc8c468090e129857e9fecce5a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_5cc8c468090e129857e9fecce5a"`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_bfbc6305547539369fe73eb144a"`, undefined);
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined);
await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted')`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined);
await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO "notification_type_enum"`, undefined);
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_e9793f65f504e5a31fbaedbf2f"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_5cc8c468090e129857e9fecce5"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_bfbc6305547539369fe73eb144"`, undefined);
await queryRunner.query(`DROP TABLE "user_group_invitation"`, undefined);
}
}

View File

@@ -0,0 +1,28 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userGroupAntenna1581695816408 implements MigrationInterface {
name = 'userGroupAntenna1581695816408'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "antenna" ADD "userGroupJoiningId" character varying(32)`, undefined);
await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum" RENAME TO "antenna_src_enum_old"`, undefined);
await queryRunner.query(`CREATE TYPE "antenna_src_enum" AS ENUM('home', 'all', 'users', 'list', 'group')`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum" USING "src"::"text"::"antenna_src_enum"`, undefined);
await queryRunner.query(`DROP TYPE "antenna_src_enum_old"`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "users"`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" ADD "users" character varying(1024) array NOT NULL DEFAULT '{}'::varchar[]`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb" FOREIGN KEY ("userGroupJoiningId") REFERENCES "user_group_joining"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb"`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "users"`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" ADD "users" character varying array NOT NULL DEFAULT '{}'`, undefined);
await queryRunner.query(`CREATE TYPE "antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list')`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum_old" USING "src"::"text"::"antenna_src_enum_old"`, undefined);
await queryRunner.query(`DROP TYPE "antenna_src_enum"`, undefined);
await queryRunner.query(`ALTER TYPE "antenna_src_enum_old" RENAME TO "antenna_src_enum"`, undefined);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "userGroupJoiningId"`, undefined);
}
}

View File

@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class driveUserFolderIdIndex1581708415836 implements MigrationInterface {
name = 'driveUserFolderIdIndex1581708415836'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE INDEX "IDX_55720b33a61a7c806a8215b825" ON "drive_file" ("userId", "folderId", "id") `, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`DROP INDEX "IDX_55720b33a61a7c806a8215b825"`, undefined);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.7.1",
"version": "12.11.0",
"codename": "indigo",
"repository": {
"type": "git",
@@ -30,16 +30,16 @@
"lodash": "^4.17.13"
},
"dependencies": {
"@elastic/elasticsearch": "7.5.0",
"@fortawesome/fontawesome-svg-core": "1.2.26",
"@fortawesome/free-brands-svg-icons": "5.12.0",
"@fortawesome/free-regular-svg-icons": "5.12.0",
"@fortawesome/free-solid-svg-icons": "5.12.0",
"@elastic/elasticsearch": "7.6.0",
"@fortawesome/fontawesome-svg-core": "1.2.27",
"@fortawesome/free-brands-svg-icons": "5.12.1",
"@fortawesome/free-regular-svg-icons": "5.12.1",
"@fortawesome/free-solid-svg-icons": "5.12.1",
"@fortawesome/vue-fontawesome": "0.1.9",
"@juggle/resize-observer": "3.0.2",
"@koa/cors": "3.0.0",
"@koa/multer": "2.0.2",
"@koa/router": "8.0.6",
"@koa/router": "8.0.8",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.0",
"@types/cbor": "5.0.0",
@@ -54,7 +54,7 @@
"@types/js-yaml": "3.12.2",
"@types/jsdom": "12.2.4",
"@types/katex": "0.11.0",
"@types/koa": "2.11.0",
"@types/koa": "2.11.1",
"@types/koa-bodyparser": "4.3.0",
"@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0",
@@ -69,7 +69,7 @@
"@types/lolex": "5.1.0",
"@types/markdown-it": "0.0.9",
"@types/mocha": "7.0.1",
"@types/node": "13.7.0",
"@types/node": "13.7.1",
"@types/nodemailer": "6.4.0",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
@@ -80,7 +80,7 @@
"@types/qrcode": "1.3.4",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.14",
"@types/redis": "2.8.15",
"@types/rename": "1.0.1",
"@types/request": "2.48.4",
"@types/request-promise-native": "1.0.17",
@@ -95,18 +95,18 @@
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.7",
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.3",
"@types/webpack": "4.41.6",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "1.0.0",
"@types/ws": "7.2.1",
"@typescript-eslint/parser": "2.18.0",
"@typescript-eslint/parser": "2.19.2",
"agentkeepalive": "4.1.0",
"animejs": "3.1.0",
"apexcharts": "3.15.3",
"apexcharts": "3.15.6",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.610.0",
"aws-sdk": "2.617.0",
"bcryptjs": "2.4.3",
"bull": "3.12.1",
"cafy": "15.2.1",
@@ -115,7 +115,7 @@
"chalk": "3.0.0",
"chart.js": "2.9.3",
"cli-highlight": "2.1.4",
"commander": "4.1.0",
"commander": "4.1.1",
"content-disposition": "0.5.3",
"crc-32": "1.2.0",
"css-loader": "3.4.2",
@@ -128,7 +128,7 @@
"eventemitter3": "4.0.0",
"feed": "4.1.0",
"fibers": "4.0.2",
"file-type": "13.1.2",
"file-type": "14.1.2",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"gulp": "4.0.2",
@@ -144,12 +144,12 @@
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.0",
"http-signature": "1.3.1",
"https-proxy-agent": "4.0.0",
"https-proxy-agent": "5.0.0",
"insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.2.1",
"js-yaml": "3.13.1",
"jsdom": "16.0.1",
"jsdom": "16.1.0",
"json5": "2.1.1",
"json5-loader": "3.0.0",
"jsrsasign": "8.0.12",
@@ -198,16 +198,16 @@
"randomcolor": "0.5.4",
"ratelimiter": "3.4.0",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.3.0",
"redis": "2.8.0",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"request": "2.88.0",
"request": "2.88.2",
"request-promise-native": "1.0.8",
"request-stats": "3.0.0",
"require-all": "3.0.0",
"rimraf": "3.0.1",
"rimraf": "3.0.2",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.25.0",
@@ -221,7 +221,7 @@
"style-loader": "1.1.3",
"summaly": "2.3.1",
"syslog-pro": "1.0.0",
"systeminformation": "4.21.1",
"systeminformation": "4.21.2",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.3.4",
"textarea-caret": "3.1.0",
@@ -245,7 +245,7 @@
"vue-cropperjs": "4.0.1",
"vue-i18n": "8.15.3",
"vue-json-pretty": "1.6.3",
"vue-loader": "15.8.3",
"vue-loader": "15.9.0",
"vue-marquee-text-component": "1.1.1",
"vue-meta": "2.3.2",
"vue-prism-component": "1.1.1",
@@ -256,10 +256,10 @@
"vue-template-compiler": "2.6.11",
"vuedraggable": "2.23.2",
"vuex": "3.1.2",
"vuex-persistedstate": "2.7.0",
"vuex-persistedstate": "2.7.1",
"web-push": "3.4.3",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack": "4.41.6",
"webpack-cli": "3.3.11",
"websocket": "1.0.31",
"ws": "7.2.1",
"xev": "2.0.1"

View File

@@ -25,6 +25,7 @@
<input type="search" :placeholder="$t('search')" v-model="searchQuery" v-autocomplete="{ model: 'searchQuery' }" :disabled="searchWait" @keypress="searchKeypress"/>
</div>
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
<x-clock v-if="isDesktop" class="clock"/>
</div>
</header>
@@ -57,13 +58,13 @@
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.pendingReceivedFollowRequestsCount"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
</router-link>
<div class="divider"></div>
<router-link class="item" active-class="active" to="/featured">
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
@@ -90,7 +91,7 @@
</nav>
</transition>
<div class="contents" ref="contents">
<div class="contents" ref="contents" :class="{ wallpaper }">
<main ref="main">
<div class="content">
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
@@ -107,7 +108,7 @@
<div class="widgets">
<div ref="widgets" :class="{ edit: widgetsEditMode }">
<template v-if="enableWidgets && $store.getters.isSignedIn">
<template v-if="isDesktop && $store.getters.isSignedIn">
<template v-if="widgetsEditMode">
<mk-button primary @click="addWidget" class="add"><fa :icon="faPlus"/></mk-button>
<x-draggable
@@ -136,7 +137,7 @@
</div>
<div class="buttons">
<button v-if="$store.getters.isSignedIn" class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.state.i.hasUnreadSpecifiedNotes || $store.state.i.pendingReceivedFollowRequestsCount || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button home _button" :disabled="$route.path === '/'" @click="$router.push('/')"><fa :icon="faHome"/></button>
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
@@ -157,15 +158,18 @@ import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regul
import { ResizeObserver } from '@juggle/resize-observer';
import { v4 as uuid } from 'uuid';
import i18n from './i18n';
import { host } from './config';
import { host, instanceName } from './config';
import { search } from './scripts/search';
import contains from './scripts/contains';
import MkToast from './components/toast.vue';
const DESKTOP_THRESHOLD = 1100;
export default Vue.extend({
i18n,
components: {
XClock: () => import('./components/header-clock.vue').then(m => m.default),
XNotifications: () => import('./components/notifications.vue').then(m => m.default),
MkButton: () => import('./components/ui/button.vue').then(m => m.default),
XDraggable: () => import('vuedraggable'),
@@ -184,9 +188,10 @@ export default Vue.extend({
searchQuery: '',
searchWait: false,
widgetsEditMode: false,
enableWidgets: window.innerWidth >= 1100,
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
canBack: false,
disconnectedDialog: null as Promise<void> | null,
wallpaper: localStorage.getItem('wallpaper') != null,
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer
};
},
@@ -224,6 +229,10 @@ export default Vue.extend({
el.removeEventListener('mousedown', this.onMousedown);
}
}
},
isDesktop() {
if (this.isDesktop) this.adjustWidgetsWidth();
}
},
@@ -247,11 +256,15 @@ export default Vue.extend({
}
this.$root.stream.on('_disconnected_', () => {
if (!this.disconnectedDialog) {
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
if (this.disconnectedDialog) return;
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
setTimeout(() => {
if (this.$root.stream.state !== 'reconnecting') return;
this.disconnectedDialog = this.$root.dialog({
type: 'warning',
showCancelButton: true,
@@ -263,22 +276,12 @@ export default Vue.extend({
}
this.disconnectedDialog = null;
});
}
}, 150)
});
},
mounted() {
// https://stackoverflow.com/questions/33891709/when-flexbox-items-wrap-in-column-mode-container-does-not-grow-its-width
if (this.enableWidgets) {
const adjustWidgetsWidth = () => {
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
if (lastChild == null) return;
const width = lastChild.offsetLeft + 300;
this.$refs.widgets.style.width = width + 'px';
};
setInterval(adjustWidgetsWidth, 1000);
}
if (this.isDesktop) this.adjustWidgetsWidth();
const adjustTitlePosition = () => {
this.$refs.title.style.left = (this.$refs.main.getBoundingClientRect().left - this.$refs.nav.offsetWidth) + 'px';
@@ -289,13 +292,32 @@ export default Vue.extend({
const ro = new ResizeObserver((entries, observer) => {
adjustTitlePosition();
});
ro.observe(this.$refs.contents);
window.addEventListener('resize', adjustTitlePosition);
window.addEventListener('resize', adjustTitlePosition, { passive: true });
if (!this.isDesktop) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
}, { passive: true });
}
},
methods: {
adjustWidgetsWidth() {
// https://stackoverflow.com/questions/33891709/when-flexbox-items-wrap-in-column-mode-container-does-not-grow-its-width
const adjust = () => {
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
if (lastChild == null) return;
const width = lastChild.offsetLeft + 300 + 16;
this.$refs.widgets.style.width = width + 'px';
};
setInterval(adjust, 1000);
setTimeout(adjust, 100);
},
help() {
this.$router.push('/docs/keyboard-shortcut');
},
@@ -344,7 +366,7 @@ export default Vue.extend({
const accountItems = accounts.map(account => ({
type: 'user',
user: account,
action: () => { this.switchAccount(account) }
action: () => { this.switchAccount(account); }
}));
this.$root.menu({
@@ -358,12 +380,28 @@ export default Vue.extend({
text: this.$t('settings'),
to: '/my/settings',
icon: faCog,
}, null, {
}, null, ...accountItems, {
type: 'item',
text: this.$t('addAcount'),
icon: faPlus,
action: () => { this.addAcount() },
}], ...accountItems],
text: this.$t('addAcount'),
action: () => {
this.$root.menu({
items: [{
type: 'item',
text: this.$t('existingAcount'),
action: () => { this.addAcount(); },
}, {
type: 'item',
text: this.$t('createAccount'),
action: () => { this.createAccount(); },
}],
align: 'left',
fixed: true,
width: 240,
source: ev.currentTarget || ev.target,
});
},
}]],
align: 'left',
fixed: true,
width: 240,
@@ -477,9 +515,14 @@ export default Vue.extend({
icon: faQuestionCircle,
}, {
type: 'link',
text: this.$t('about'),
text: this.$t('aboutX', { x: instanceName || host }),
to: '/about',
icon: faInfoCircle,
}, {
type: 'link',
text: this.$t('aboutMisskey'),
to: '/about-misskey',
icon: faInfoCircle,
}],
align: 'left',
fixed: true,
@@ -498,9 +541,25 @@ export default Vue.extend({
});
},
async switchAccount(account) {
const token = this.$store.state.device.accounts.find(x => x.id === account.id).token;
this.$root.api('i', {}, token).then(i => {
async createAccount() {
this.$root.new(await import('./components/signup-dialog.vue').then(m => m.default)).$once('signup', res => {
this.$store.dispatch('addAcount', res);
this.switchAccountWithToken(res.i);
});
},
async switchAccount(account: any) {
const token = this.$store.state.device.accounts.find((x: any) => x.id === account.id).token;
this.switchAccountWithToken(token);
},
switchAccountWithToken(token: string) {
this.$root.dialog({
type: 'waiting',
iconOnly: true
});
this.$root.api('i', {}, token).then((i: any) => {
this.$store.dispatch('switchAccount', {
...i,
token: token
@@ -547,6 +606,7 @@ export default Vue.extend({
'calendar',
'rss',
'trends',
'clock'
];
this.$root.menu({
@@ -744,10 +804,9 @@ export default Vue.extend({
position: relative;
> input {
$margin: 8px;
width: 200px;
width: 220px;
box-sizing: border-box;
margin-right: $margin;
margin-right: 8px;
padding: 0 12px 0 42px;
font-size: 1rem;
line-height: 38px;
@@ -778,6 +837,10 @@ export default Vue.extend({
border-radius: 100%;
font-size: 16px;
}
> .clock {
margin-left: 8px;
}
}
}
@@ -925,6 +988,10 @@ export default Vue.extend({
margin: 0 auto;
min-width: 0;
&.wallpaper {
background: var(--wallpaperOverlay);
}
> main {
width: $main-width;
min-width: $main-width;

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Misskey API</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/api.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View File

@@ -0,0 +1,156 @@
<template>
<svg class="mbcofsoe" viewBox="0 0 10 10" preserveAspectRatio="none">
<circle v-for="(angle, i) in graduations"
:cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
:cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
:r="i % 5 == 0 ? 0.125 : 0.05"
:fill="i % 5 == 0 ? majorGraduationColor : minorGraduationColor"
:key="i"/>
<line
:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
:y1="5 + (Math.cos(sAngle) * (sHandLengthRatio * handsTailLength))"
:x2="5 + (Math.sin(sAngle) * ((sHandLengthRatio * 5) - handsPadding))"
:y2="5 - (Math.cos(sAngle) * ((sHandLengthRatio * 5) - handsPadding))"
:stroke="sHandColor"
stroke-width="0.05"/>
<line
:x1="5 - (Math.sin(mAngle) * (mHandLengthRatio * handsTailLength))"
:y1="5 + (Math.cos(mAngle) * (mHandLengthRatio * handsTailLength))"
:x2="5 + (Math.sin(mAngle) * ((mHandLengthRatio * 5) - handsPadding))"
:y2="5 - (Math.cos(mAngle) * ((mHandLengthRatio * 5) - handsPadding))"
:stroke="mHandColor"
stroke-width="0.1"/>
<line
:x1="5 - (Math.sin(hAngle) * (hHandLengthRatio * handsTailLength))"
:y1="5 + (Math.cos(hAngle) * (hHandLengthRatio * handsTailLength))"
:x2="5 + (Math.sin(hAngle) * ((hHandLengthRatio * 5) - handsPadding))"
:y2="5 - (Math.cos(hAngle) * ((hHandLengthRatio * 5) - handsPadding))"
:stroke="hHandColor"
stroke-width="0.1"/>
</svg>
</template>
<script lang="ts">
import Vue from 'vue';
import * as tinycolor from 'tinycolor2';
export default Vue.extend({
props: {
smooth: {
type: Boolean,
default: false
}
},
data() {
return {
now: new Date(),
enabled: true,
graduationsPadding: 0.5,
handsPadding: 1,
handsTailLength: 0.7,
hHandLengthRatio: 0.75,
mHandLengthRatio: 1,
sHandLengthRatio: 1,
computedStyle: getComputedStyle(document.documentElement)
};
},
computed: {
dark(): boolean {
return tinycolor(this.computedStyle.getPropertyValue('--bg')).isDark();
},
majorGraduationColor(): string {
return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
},
minorGraduationColor(): string {
return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
},
sHandColor(): string {
return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
},
mHandColor(): string {
return tinycolor(this.computedStyle.getPropertyValue('--fg')).toHexString();
},
hHandColor(): string {
return tinycolor(this.computedStyle.getPropertyValue('--accent')).toHexString();
},
ms(): number {
return this.now.getMilliseconds() * (this.smooth ? 1 : 0);
},
s(): number {
return this.now.getSeconds();
},
m(): number {
return this.now.getMinutes();
},
h(): number {
return this.now.getHours();
},
hAngle(): number {
return Math.PI * (this.h % 12 + (this.m + (this.s + this.ms / 1000) / 60) / 60) / 6;
},
mAngle(): number {
return Math.PI * (this.m + (this.s + this.ms / 1000) / 60) / 30;
},
sAngle(): number {
return Math.PI * (this.s + this.ms / 1000) / 30;
},
graduations(): any {
const angles = [];
for (let i = 0; i < 60; i++) {
const angle = Math.PI * i / 30;
angles.push(angle);
}
return angles;
}
},
mounted() {
const update = () => {
if (this.enabled) {
this.tick();
requestAnimationFrame(update);
}
};
update();
this.$store.subscribe((mutation, state) => {
if (mutation.type !== 'device/set') return;
if (mutation?.payload?.key !== 'theme') return;
setTimeout(() => {
this.computedStyle = getComputedStyle(document.documentElement);
}, 250);
});
},
beforeDestroy() {
this.enabled = false;
},
methods: {
tick() {
this.now = new Date();
}
}
});
</script>
<style lang="scss" scoped>
.mbcofsoe {
display: block;
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="swhvrteh" @contextmenu.prevent="() => {}">
<ol class="users" ref="suggests" v-if="type === 'user'">
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1" class="user">
<img class="avatar" :src="user.avatarUrl" alt=""/>
<img class="avatar" :src="user.avatarUrl"/>
<span class="name">
<mk-user-name :user="user" :key="user.id"/>
</span>
@@ -18,7 +18,7 @@
<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else>{{ emoji.emoji }}</span>
<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
@@ -130,8 +130,8 @@ export default Vue.extend({
return (this.$refs.suggests as Element).children;
},
useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis;
useOsNativeEmojis(): boolean {
return this.$store.state.device.useOsNativeEmojis;
}
},

View File

@@ -12,7 +12,7 @@
preload="metadata"
controls
v-else-if="detail && is === 'video'"/>
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
<img :src="file.thumbnailUrl" @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'"/>

View File

@@ -23,13 +23,13 @@
<x-folder v-for="folder in folders" :key="folder.id" class="folder" :folder="folder"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<mk-button v-if="moreFolders">{{ $t('@.load-more') }}</mk-button>
<mk-button v-if="moreFolders">{{ $t('loadMore') }}</mk-button>
</div>
<div class="files" ref="filesContainer" v-if="files.length > 0">
<x-file v-for="file in files" :key="file.id" class="file" :file="file" :select-mode="selectMode"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('@.load-more') }}</mk-button>
<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('loadMore') }}</mk-button>
</div>
<div class="empty" v-if="files.length == 0 && folders.length == 0 && !fetching">
<p v-if="draghover">{{ $t('empty-draghover') }}</p>

View File

@@ -1,7 +1,7 @@
<template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt"/>
<img v-else-if="char && !useOsDefaultEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/>
<span v-else-if="char && useOsDefaultEmojis">{{ char }}</span>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/>
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
<span v-else>:{{ name }}:</span>
</template>
@@ -53,8 +53,8 @@ export default Vue.extend({
return this.customEmoji ? `:${this.customEmoji.name}:` : this.char;
},
useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
useOsNativeEmojis(): boolean {
return this.$store.state.device.useOsNativeEmojis && !this.isReaction;
},
ce() {

View File

@@ -1,6 +1,6 @@
<template>
<div class="mjndxjcg _panel">
<img src="https://xn--931a.moe/assets/error.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/error.png" class="_ghost"/>
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
</div>
@@ -45,8 +45,6 @@ export default Vue.extend({
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="mk-google">
<input type="search" v-model="query" :placeholder="q">
<button @click="search"><fa icon="search"/> {{ $t('@.search') }}</button>
<button @click="search"><fa icon="search"/> {{ $t('search') }}</button>
</div>
</template>

View File

@@ -0,0 +1,101 @@
<template>
<div class="eqryymyo">
<div class="header">
<time ref="time" class="_ghost">
<span class="yyyymmdd">{{ yyyy }}/{{ mm }}/{{ dd }}</span>
<br>
<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span>
</time>
</div>
<div class="content _panel _ghost">
<mk-clock/>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import MkClock from './analog-clock.vue';
export default Vue.extend({
components: {
MkClock
},
data() {
return {
now: new Date(),
clock: null
};
},
computed: {
yyyy(): number {
return this.now.getFullYear();
},
mm(): string {
return ('0' + (this.now.getMonth() + 1)).slice(-2);
},
dd(): string {
return ('0' + this.now.getDate()).slice(-2);
},
hh(): string {
return ('0' + this.now.getHours()).slice(-2);
},
nn(): string {
return ('0' + this.now.getMinutes()).slice(-2);
}
},
mounted() {
this.tick();
this.clock = setInterval(this.tick, 1000);
},
beforeDestroy() {
clearInterval(this.clock);
},
methods: {
tick() {
this.now = new Date();
}
}
});
</script>
<style lang="scss" scoped>
.eqryymyo {
display: inline-block;
overflow: visible;
> .header {
padding: 0 12px;
padding-top: 4px;
text-align: center;
font-size: 12px;
font-family: Lucida Console, Courier, monospace;
&:hover + .content {
opacity: 1;
}
> time {
display: table-cell;
vertical-align: middle;
height: 48px;
> .yyyymmdd {
opacity: 0.7;
}
}
}
> .content {
opacity: 0;
display: block;
position: absolute;
top: auto;
right: 0;
margin: 16px 0 0 0;
padding: 16px;
width: 230px;
transition: opacity 0.2s ease;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<x-modal ref="modal" @closed="() => { $emit('closed'); destroyDom(); }">
<img class="xubzgfga" ref="img" :src="image.url" :alt="image.name" :title="image.name" @click="close" tabindex="-1"/>
</x-modal>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../i18n';
import XModal from './modal.vue';
export default Vue.extend({
i18n,
components: {
XModal,
},
props: {
image: {
type: Object,
required: true
},
},
mounted() {
this.$nextTick(() => {
this.$refs.img.focus();
});
},
methods: {
close() {
this.$refs.modal.close();
},
}
});
</script>
<style lang="scss" scoped>
.xubzgfga {
position: fixed;
z-index: 2;
top: 0;
right: 0;
bottom: 0;
left: 0;
max-width: 100%;
max-height: 100%;
margin: auto;
cursor: zoom-out;
image-orientation: from-image;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="yxspomdl">
<div class="yxspomdl" :class="{ inline }">
<div class="ring"></div>
</div>
</template>
@@ -8,6 +8,13 @@
import Vue from 'vue';
export default Vue.extend({
props: {
inline: {
type: Boolean,
required: false,
default: false
}
}
});
</script>
@@ -25,22 +32,31 @@ export default Vue.extend({
padding: 32px;
text-align: center;
&.inline {
display: inline;
padding: 0;
> .ring:after {
width: 32px;
height: 32px;
}
}
> .ring {
display: inline-block;
width: 80px;
height: 80px;
opacity: 0.7;
vertical-align: middle;
}
> .ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
box-sizing: border-box;
width: 48px;
height: 48px;
border-radius: 50%;
border: solid 6px;
border-color: var(--fg) transparent transparent transparent;
border: solid 4px;
border-color: currentColor transparent transparent transparent;
animation: ring 0.5s linear infinite;
}
}

View File

@@ -20,6 +20,7 @@ import Vue from 'vue';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import { getStaticImageUrl } from '../scripts/get-static-image-url';
import ImageViewer from './image-viewer.vue';
export default Vue.extend({
i18n,
@@ -60,7 +61,16 @@ export default Vue.extend({
},
methods: {
onClick() {
window.open(this.image.url, '_blank');
if (this.$store.state.device.imageNewTab) {
window.open(this.image.url, '_blank');
} else {
const viewer = this.$root.new(ImageViewer, {
image: this.image
});
this.$once('hook:beforeDestroy', () => {
viewer.close();
});
}
}
}
});

View File

@@ -125,11 +125,13 @@ export default Vue.extend({
> .item {
display: block;
position: relative;
padding: 8px 16px;
width: 100%;
box-sizing: border-box;
white-space: nowrap;
font-size: 0.9em;
line-height: 20px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -82,10 +82,10 @@ export default Vue.component('misskey-flavored-markdown', {
attrs: {
style: `display: inline-block; font-size: 150%;`
},
directives: [this.$store.state.settings.disableAnimatedMfm ? {} : {
directives: [this.$store.state.device.animatedMfm ? {
name: 'animate-css',
value: { classes: 'tada', iteration: 'infinite' }
}]
}: {}]
}, genEl(token.children));
}
@@ -110,10 +110,10 @@ export default Vue.component('misskey-flavored-markdown', {
attrs: {
style: 'display: inline-block;'
},
directives: [this.$store.state.settings.disableAnimatedMfm ? {} : {
directives: [this.$store.state.device.animatedMfm ? {
name: 'animate-css',
value: { classes: 'rubberBand', iteration: 'infinite' }
}]
} : {}]
}, genEl(token.children));
}
@@ -122,9 +122,8 @@ export default Vue.component('misskey-flavored-markdown', {
token.node.props.attr == 'left' ? 'reverse' :
token.node.props.attr == 'alternate' ? 'alternate' :
'normal';
const style = (this.$store.state.settings.disableAnimatedMfm)
? ''
: `animation: spin 1.5s linear infinite; animation-direction: ${direction};`;
const style = this.$store.state.device.animatedMfm
? `animation: spin 1.5s linear infinite; animation-direction: ${direction};` : '';
return (createElement as any)('span', {
attrs: {
style: 'display: inline-block;' + style
@@ -135,7 +134,7 @@ export default Vue.component('misskey-flavored-markdown', {
case 'jump': {
return (createElement as any)('span', {
attrs: {
style: (this.$store.state.settings.disableAnimatedMfm) ? 'display: inline-block;' : 'display: inline-block; animation: jump 0.75s linear infinite;'
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: jump 0.75s linear infinite;' : 'display: inline-block;'
},
}, genEl(token.children));
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="mk-modal">
<div class="mk-modal" v-hotkey.global="keymap">
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
</transition>
@@ -20,6 +20,13 @@ export default Vue.extend({
show: true,
};
},
computed: {
keymap(): any {
return {
'esc': this.close,
};
},
},
methods: {
close() {
this.show = false;

View File

@@ -77,23 +77,19 @@ export default Vue.extend({
> .admin,
> .moderator {
margin-right: 0.5em;
color: var(--badge);
}
> .username {
margin: 0 .5em 0 0;
overflow: hidden;
text-overflow: ellipsis;
color: var(--noteHeaderAcct);
}
> .info {
margin-left: auto;
font-size: 0.9em;
> * {
color: var(--noteHeaderInfo);
}
> .mobile {
margin-right: 8px;
}

View File

@@ -58,7 +58,7 @@
<template v-else><fa :icon="faReply"/></template>
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
</button>
<button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" class="button _button" ref="renoteButton">
<button v-if="canRenote" @click="renote()" class="button _button" ref="renoteButton">
<fa :icon="faRetweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
</button>
<button v-else class="button _button">
@@ -190,16 +190,16 @@ export default Vue.extend({
return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId);
},
canRenote(): boolean {
return ['public', 'home'].includes(this.appearNote.visibility) || this.isMyNote;
},
reactionsCount(): number {
return this.appearNote.reactions
? sum(Object.values(this.appearNote.reactions))
: 0;
},
title(): string {
return '';
},
urls(): string[] {
if (this.appearNote.text) {
const ast = parse(this.appearNote.text);
@@ -863,7 +863,7 @@ export default Vue.extend({
}
&:hover {
color: var(--mkykhqkw);
color: var(--fgHighlighted);
}
> .count {

View File

@@ -1,38 +1,45 @@
<template>
<div class="mk-notes" v-size="[{ max: 500 }]">
<div class="empty" v-if="empty">
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
<div>{{ $t('noNotes') }}</div>
</div>
<mk-error v-if="error" @retry="init()"/>
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
<div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</div>
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
<x-note :note="note" :detail="detail" :key="note.id"/>
</x-list>
<footer v-if="more">
<button @click="fetchMore()" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" class="_buttonPrimary">
<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
</button>
</footer>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import paging from '../scripts/paging';
import XNote from './note.vue';
import XList from './date-separated-list.vue';
import MkButton from './ui/button.vue';
export default Vue.extend({
i18n,
components: {
XNote, XList
XNote, XList, MkButton
},
mixins: [
@@ -63,16 +70,14 @@ export default Vue.extend({
}
},
data() {
return {
faSpinner
};
},
computed: {
notes(): any[] {
return this.extract ? this.extract(this.items) : this.items;
},
reversed(): boolean {
return this.pagination.reversed;
}
},
methods: {
@@ -94,42 +99,28 @@ export default Vue.extend({
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
> .notes {
> ::v-deep * {
> ::v-deep *:not(:last-child) {
margin-bottom: var(--marginFull);
}
}
&.max-width_500px {
> .notes {
> ::v-deep * {
> ::v-deep *:not(:last-child) {
margin-bottom: var(--marginHalf);
}
}
}
> footer {
text-align: center;
&:empty {
display: none;
}
> button {
margin: 0;
padding: 16px;
width: 100%;
border-radius: var(--radius);
&:disabled {
opacity: 0.7;
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;
height: 48px;
width: 100%;
}
}
</style>

View File

@@ -6,6 +6,7 @@
<fa :icon="faPlus" v-if="notification.type === 'follow'"/>
<fa :icon="faClock" v-if="notification.type === 'receiveFollowRequest'"/>
<fa :icon="faCheck" v-if="notification.type === 'followRequestAccepted'"/>
<fa :icon="faIdCardAlt" v-if="notification.type === 'groupInvited'"/>
<fa :icon="faRetweet" v-if="notification.type === 'renote'"/>
<fa :icon="faReply" v-if="notification.type === 'reply'"/>
<fa :icon="faAt" v-if="notification.type === 'mention'"/>
@@ -40,13 +41,14 @@
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><mk-follow-button :user="notification.user" :full="true"/></div></span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $t('groupInvited') }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $t('reject') }}</button></div></span>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faClock } from '@fortawesome/free-regular-svg-icons';
import getNoteSummary from '../../misc/get-note-summary';
import XReactionIcon from './reaction-icon.vue';
@@ -78,7 +80,8 @@ export default Vue.extend({
return {
getNoteSummary,
followRequestDone: false,
faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck
groupInviteDone: false,
faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck
};
},
methods: {
@@ -90,6 +93,18 @@ export default Vue.extend({
this.followRequestDone = true;
this.$root.api('following/requests/reject', { userId: this.notification.user.id });
},
acceptGroupInvitation() {
this.groupInviteDone = true;
this.$root.api('users/groups/invitations/accept', { invitationId: this.notification.invitation.id });
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
rejectGroupInvitation() {
this.groupInviteDone = true;
this.$root.api('users/groups/invitations/reject', { invitationId: this.notification.invitation.id });
},
}
});
</script>
@@ -149,12 +164,12 @@ export default Vue.extend({
height: 100%;
}
&.follow, &.followRequestAccepted, &.receiveFollowRequest {
&.follow, &.followRequestAccepted, &.receiveFollowRequest, &.groupInvited {
padding: 3px;
background: #36aed2;
}
&.retweet {
&.renote {
padding: 3px;
background: #36d298;
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="lzyxtsnt">
<img v-if="image" :src="image.url" alt=""/>
<img v-if="image" :src="image.url"/>
</div>
</template>

View File

@@ -21,7 +21,7 @@
<div class="form">
<x-note-preview class="preview" v-if="reply" :note="reply"/>
<x-note-preview class="preview" v-if="renote" :note="renote"/>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('quoteAttached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
<div v-if="visibility === 'specified'" class="to-specified">
<span style="margin-right: 8px;">{{ $t('recipient') }}</span>
<div class="visibleUsers">
@@ -445,7 +445,7 @@ export default Vue.extend({
this.$root.dialog({
type: 'info',
text: this.$t('@.post-form.quote-question'),
text: this.$t('quoteQuestion'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) {
@@ -610,6 +610,7 @@ export default Vue.extend({
right: 0;
> .text-count {
opacity: 0.7;
line-height: 66px;
@media (max-width: 500px) {

View File

@@ -13,7 +13,7 @@
mode="out-in"
appear
>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction"><x-reaction-icon :reaction="reaction"/></button>
</transition-group>
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>

View File

@@ -1,12 +1,10 @@
<template>
<transition-group v-if="$store.state.device.animation"
name="staggered-fade"
class="uupnnhew"
:data-direction="direction"
:data-reversed="reversed ? 'true' : 'false'"
name="staggered"
tag="div"
:css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
mode="out-in"
appear
>
<slot></slot>
@@ -37,40 +35,46 @@ export default Vue.extend({
default: false
}
},
i: 0,
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
let index = this.$options.i;
const delay = this.delay * index;
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
this.$options.i++;
},
enter(el, done) {
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateY(0px)';
el.addEventListener('transitionend', () => {
el.style.transition = '';
this.$options.i--;
done();
}, { once: true });
});
},
leave(el) {
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
},
focus() {
this.$slots.default[0].elm.focus();
}
}
},
});
</script>
<style lang="scss">
.staggered-fade-move {
transition: transform 0.7s !important;
.staggered-move {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) !important;
}
.uupnnhew[data-direction="up"] {
.staggered-enter {
opacity: 0;
transform: translateY(64px);
}
}
.uupnnhew[data-direction="down"] {
.staggered-enter {
opacity: 0;
transform: translateY(-64px);
}
}
.uupnnhew[data-reversed="true"] {
@for $i from 1 through 30 {
.staggered-enter-active:nth-last-child(#{$i}) {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
}
}
}
.uupnnhew[data-reversed="false"] {
@for $i from 1 through 30 {
.staggered-enter-active:nth-child(#{$i}) {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
}
}
}
</style>

View File

@@ -12,15 +12,15 @@
<template #prefix><fa :icon="faLock"/></template>
</mk-input>
<mk-button type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</mk-button>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="['fab', 'twitter']"/> {{ $t('signin-with-twitter') }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="['fab', 'github']"/> {{ $t('signin-with-github') }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="['fab', 'discord']"/> {{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="faTwitter"/> {{ $t('signinWith', { x: 'Twitter' }) }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="faGithub"/> {{ $t('signinWith', { x: 'GitHub' }) }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="faDiscord"/> {{ $t('signinWith', { x: 'Discord' }) }}</a></p>
</div>
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
<p>{{ $t('tap-key') }}</p>
<p>{{ $t('tapSecurityKey') }}</p>
<mk-button @click="queryKey" v-if="!queryingKey">
{{ $t('@.error.retry') }}
{{ $t('retry') }}
</mk-button>
</div>
<div class="or-hr" v-if="user && user.securityKeys">
@@ -46,6 +46,7 @@
import Vue from 'vue';
import { toUnicode } from 'punycode';
import { faLock, faGavel } from '@fortawesome/free-solid-svg-icons';
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue';
import i18n from '../i18n';
@@ -86,7 +87,7 @@ export default Vue.extend({
credential: null,
challengeData: null,
queryingKey: false,
faLock, faGavel
faLock, faGavel, faTwitter, faDiscord, faGithub
};
},

View File

@@ -1,7 +1,7 @@
<template>
<x-window @closed="() => { $emit('closed'); destroyDom(); }">
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }">
<template #header>{{ $t('signup') }}</template>
<x-signup/>
<x-signup :auto-set="autoSet" @signup="onSignup"/>
</x-window>
</template>
@@ -18,5 +18,20 @@ export default Vue.extend({
XSignup,
XWindow,
},
props: {
autoSet: {
type: Boolean,
required: false,
default: false,
}
},
methods: {
onSignup(res) {
this.$emit('signup', res);
this.$refs.window.close();
}
}
});
</script>

View File

@@ -2,9 +2,8 @@
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<template v-if="meta">
<mk-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<span>{{ $t('invitation-code') }}</span>
<template #prefix><fa icon="id-card-alt"/></template>
<template #desc v-html="this.$t('invitation-info').replace('{}', 'mailto:' + meta.maintainerEmail)"></template>
<span>{{ $t('invitationCode') }}</span>
<template #prefix><fa :icon="faKey"/></template>
</mk-input>
<mk-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
<span>{{ $t('username') }}</span>
@@ -15,26 +14,26 @@
<span v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('available') }}</span>
<span v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('unavailable') }}</span>
<span v-if="usernameState == 'error'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('error') }}</span>
<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('invalid-format') }}</span>
<span v-if="usernameState == 'min-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('too-short') }}</span>
<span v-if="usernameState == 'max-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('too-long') }}</span>
<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('usernameInvalidFormat') }}</span>
<span v-if="usernameState == 'min-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('tooShort') }}</span>
<span v-if="usernameState == 'max-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('tooLong') }}</span>
</template>
</mk-input>
<mk-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword">
<span>{{ $t('password') }}</span>
<template #prefix><fa :icon="faLock"/></template>
<template #desc>
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('weak-password') }}</p>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('normal-password') }}</p>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('strong-password') }}</p>
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('weakPassword') }}</p>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('normalPassword') }}</p>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('strongPassword') }}</p>
</template>
</mk-input>
<mk-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
<span>{{ $t('password') }} ({{ $t('retype') }})</span>
<template #prefix><fa :icon="faLock"/></template>
<template #desc>
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('password-matched') }}</p>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('password-not-matched') }}</p>
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('passwordMatched') }}</p>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('passwordNotMatched') }}</p>
</template>
</mk-input>
<mk-switch v-model="ToSAgreement" v-if="meta.tosUrl">
@@ -50,7 +49,7 @@
<script lang="ts">
import Vue from 'vue';
import { faLock, faExclamationTriangle, faSpinner, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faLock, faExclamationTriangle, faSpinner, faCheck, faKey } from '@fortawesome/free-solid-svg-icons';
const getPasswordStrength = require('syuilo-password-strength');
import { toUnicode } from 'punycode';
import i18n from '../i18n';
@@ -81,7 +80,15 @@ export default Vue.extend({
passwordRetypeState: null,
submitting: false,
ToSAgreement: false,
faLock, faExclamationTriangle, faSpinner, faCheck
faLock, faExclamationTriangle, faSpinner, faCheck, faKey
}
},
props: {
autoSet: {
type: Boolean,
required: false,
default: false,
}
},
@@ -98,6 +105,15 @@ export default Vue.extend({
}
},
created() {
if (this.autoSet) {
this.$once('signup', res => {
localStorage.setItem('i', res.i);
location.reload();
});
}
},
mounted() {
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
@@ -167,15 +183,14 @@ export default Vue.extend({
username: this.username,
password: this.password
}).then(res => {
localStorage.setItem('i', res.i);
location.href = '/';
this.$emit('signup', res);
});
}).catch(() => {
this.submitting = false;
this.$root.dialog({
type: 'error',
text: this.$t('some-error')
text: this.$t('error')
});
if (this.meta.enableRecaptcha) {

View File

@@ -48,7 +48,7 @@ export default Vue.extend({
ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) :
ago >= -1 ? this.$t('_ago.justNow') :
ago < -1 ? this.$t('_ago.future') :
this.$t('@.time.unknown'));
this.$t('_ago.unknown'));
}
},
created() {

View File

@@ -105,7 +105,7 @@ export default Vue.extend({
padding: 8px 14px;
text-align: center;
font-weight: normal;
font-size: 14px;
font-size: 0.9em;
line-height: 24px;
box-shadow: none;
text-decoration: none;

View File

@@ -239,6 +239,10 @@ export default Vue.extend({
position: relative;
margin: 32px 0;
&:not(.inline):first-child {
margin-top: 8px;
}
> .icon {
position: absolute;
top: 0;

View File

@@ -5,9 +5,9 @@
<slot name="empty"></slot>
</div>
<div class="more" v-if="more" key="_more_">
<mk-button :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</div>
</sequential-entrance>
@@ -15,7 +15,6 @@
<script lang="ts">
import Vue from 'vue';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import MkButton from './button.vue';
import paging from '../../scripts/paging';
@@ -37,12 +36,6 @@ export default Vue.extend({
default: true
}
},
data() {
return {
faSpinner
};
},
});
</script>
@@ -55,5 +48,12 @@ export default Vue.extend({
margin-bottom: 8px;
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;
height: 48px;
min-width: 150px;
}
}
</style>

View File

@@ -77,6 +77,10 @@ export default Vue.extend({
position: relative;
margin: 32px 0;
&:not(.inline):first-child {
margin-top: 8px;
}
> .icon {
position: absolute;
top: 0;

View File

@@ -129,7 +129,6 @@ export default Vue.extend({
> .label {
margin-left: 8px;
display: block;
font-size: 16px;
cursor: pointer;
transition: inherit;
color: var(--fg);

View File

@@ -85,6 +85,10 @@ export default Vue.extend({
margin: 42px 0 32px 0;
position: relative;
&:first-child {
margin-top: 16px;
}
&:last-child {
margin-bottom: 0;
}

View File

@@ -23,7 +23,7 @@
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
</div>
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching">
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('loading') : $t('loadMore') }}
</button>
</div>
</mk-container>

View File

@@ -3,7 +3,7 @@ declare const _VERSION_: string;
declare const _ENV_: string;
const address = new URL(location.href);
const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement;
const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content;
export const host = address.host;
export const hostname = address.hostname;
@@ -15,4 +15,4 @@ export const langs = _LANGS_;
export const locale = JSON.parse(localStorage.getItem('locale'));
export const version = _VERSION_;
export const env = _ENV_;
export const instanceName = siteName && siteName.content ? siteName.content : 'Misskey';
export const instanceName = siteName === 'Misskey' ? null : siteName;

View File

@@ -61,20 +61,22 @@ if (localStorage.getItem('theme') == null) {
}
//#region Detect the user language
let lang = null;
let lang = localStorage.getItem('lang');
if (langs.map(x => x[0]).includes(navigator.language)) {
lang = navigator.language;
} else {
lang = langs.map(x => x[0]).find(x => x.split('-')[0] == navigator.language);
if (lang == null) {
if (langs.map(x => x[0]).includes(navigator.language)) {
lang = navigator.language;
} else {
lang = langs.map(x => x[0]).find(x => x.split('-')[0] == navigator.language);
if (lang == null) {
// Fallback
lang = 'en-US';
if (lang == null) {
// Fallback
lang = 'en-US';
}
}
}
localStorage.setItem('lang', lang);
localStorage.setItem('lang', lang);
}
//#endregion
// Detect the user agent
@@ -134,8 +136,6 @@ document.body.innerHTML = '<div id="app"></div>';
const os = new MiOS();
os.init(async () => {
if (os.store.state.settings.wallpaper) document.documentElement.style.backgroundImage = `url(${os.store.state.settings.wallpaper})`;
if ('Notification' in window && os.store.getters.isSignedIn) {
// 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') {
@@ -147,7 +147,7 @@ os.init(async () => {
store: os.store,
metaInfo: {
title: null,
titleTemplate: title => title ? `${title} | ${instanceName}` : instanceName
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
},
data() {
return {

View File

@@ -42,9 +42,9 @@ export default class MiOS extends EventEmitter {
* @param callback A function that call when initialized
*/
@autobind
public async init(_callback) {
const callback = () => {
_callback();
public async init(callback) {
const finish = () => {
callback();
this.store.dispatch('instance/fetch').then(() => {
// Init service worker
@@ -59,7 +59,7 @@ export default class MiOS extends EventEmitter {
let me = null;
// Return when not signed in
if (token == null) {
if (token == null || token === 'null') {
return done();
}
@@ -104,7 +104,7 @@ export default class MiOS extends EventEmitter {
this.initStream();
// Finish init
callback();
finish();
};
// キャッシュがあったとき
@@ -133,7 +133,7 @@ export default class MiOS extends EventEmitter {
this.initStream();
// Finish init
callback();
finish();
}
});
}
@@ -227,7 +227,6 @@ export default class MiOS extends EventEmitter {
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
alert(locale['common']['my-token-regenerated']);
this.signout();
});
}

View File

@@ -0,0 +1,79 @@
<template>
<div class="znqjceqz">
<portal to="title">🍀 {{ $t('aboutMisskey') }}</portal>
<section class="_card">
<div class="_title">🍀 {{ $t('aboutMisskey') }}</div>
<div class="_content">
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
<div>🛠 {{ $t('misskeyMembers') }}</div>
<ul class="members">
<li><mk-link url="https://github.com/syuilo" class="at">@syuilo</mk-link></li>
<li><mk-link url="https://github.com/AyaMorisawa" class="at">@AyaMorisawa</mk-link></li>
<li><mk-link url="https://github.com/mei23" class="at">@mei23</mk-link></li>
<li><mk-link url="https://github.com/acid-chicken" class="at">@acid-chicken</mk-link></li>
<li><mk-link url="https://github.com/tamaina" class="at">@tamaina</mk-link></li>
<li><mk-link url="https://github.com/rinsuki" class="at">@rinsuki</mk-link></li>
</ul>
<div style="margin-top: 1em;">📦 {{ $t('misskeySource') }}</div>
<mk-url url="https://github.com/syuilo/misskey"/>
<div style="margin-top: 1em;">🌏 {{ $t('misskeyTranslation') }}</div>
<mk-url url="https://crowdin.com/project/misskey"/>
<div style="margin-top: 1em;">💴 {{ $t('misskeyDonate') }}</div>
<mk-url url="https://www.patreon.com/syuilo"/>
</div>
<div class="_content">
<span><mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</span>
<ul>
<li>Gargron</li>
<li>Satsuki Yanagi</li>
<li>noellabo</li>
<li>naga_rus</li>
<li>Melilot</li>
<li>AureoleArk</li>
<li>Peter G.</li>
<li>motcha</li>
<li>Atsuko Tominaga</li>
<li>dansup</li>
<li>Nokotaro Takeda</li>
<li>YUKIMOCHI</li>
<li>nanami kan</li>
<li>Hekovic</li>
<li>wara</li>
<li>Takashi Shibuya</li>
<li>Noizeman</li>
</ul>
<span>{{ $t('morePatrons') }}</span>
</div>
</section>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { version } from '../config';
import i18n from '../i18n';
import MkLink from '../components/link.vue';
export default Vue.extend({
i18n,
components: {
MkLink
},
metaInfo() {
return {
title: this.$t('aboutMisskey') as string
};
},
data() {
return {
version,
faInfoCircle
}
},
});
</script>

View File

@@ -6,7 +6,7 @@
<section class="_card info" v-if="meta">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('instanceInfo') }}</div>
<div class="_content" v-if="meta.description">
<div>{{ meta.description }}</div>
<div v-html="meta.description"></div>
</div>
<div class="_content table">
<div><b>{{ $t('administrator') }}</b><span>{{ meta.maintainerName }}</span></div>
@@ -20,49 +20,6 @@
<div><b>Misskey</b><span>v{{ version }}</span></div>
</div>
</section>
<section class="_card aboutMisskey">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('aboutMisskey') }}</div>
<div class="_content">
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
<div>{{ $t('misskeyMembers') }}</div>
<span class="members">
<a href="https://github.com/syuilo" target="_blank" class="_link">@syuilo</a>
<a href="https://github.com/AyaMorisawa" target="_blank" class="_link">@AyaMorisawa</a>
<a href="https://github.com/mei23" target="_blank" class="_link">@mei23</a>
<a href="https://github.com/acid-chicken" target="_blank" class="_link">@acid-chicken</a>
<a href="https://github.com/tamaina" target="_blank" class="_link">@tamaina</a>
<a href="https://github.com/rinsuki" target="_blank" class="_link">@rinsuki</a>
</span>
<div style="margin-top: 1em;">{{ $t('misskeySource') }}</div>
<mk-url url="https://github.com/syuilo/misskey"/>
<div style="margin-top: 1em;">{{ $t('misskeyDonate') }}</div>
<mk-url url="https://www.patreon.com/syuilo"/>
</div>
<div class="_content">
<span><mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</span>
<ul>
<li>Gargron</li>
<li>Satsuki Yanagi</li>
<li>noellabo</li>
<li>naga_rus</li>
<li>Melilot</li>
<li>AureoleArk</li>
<li>Peter G.</li>
<li>motcha</li>
<li>Atsuko Tominaga</li>
<li>dansup</li>
<li>Nokotaro Takeda</li>
<li>YUKIMOCHI</li>
<li>nanami kan</li>
<li>Hekovic</li>
<li>wara</li>
<li>Takashi Shibuya</li>
<li>Noizeman</li>
</ul>
<span>{{ $t('morePatrons') }}</span>
</div>
</section>
</div>
</template>
@@ -117,15 +74,5 @@ export default Vue.extend({
}
}
}
> .aboutMisskey {
> ._content {
> .members {
> a {
margin-right: 0.5em;
}
}
}
}
}
</style>

View File

@@ -8,7 +8,7 @@
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content">
<mfm :text="announcement.text"/>
<img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt=""/>
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
</div>
<div class="_footer" v-if="$store.getters.isSignedIn && !announcement.isRead">
<mk-button @click="read(announcement)" primary><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>

View File

@@ -3,9 +3,13 @@
<portal to="icon"><fa :icon="faFileAlt"/></portal>
<portal to="title">{{ title }}</portal>
<main class="_card">
<div class="_title"><fa :icon="faFileAlt"/> {{ title }}</div>
<div class="_content">
<div v-html="body" class="qyqbqfal"></div>
</div>
<div class="_footer">
<mk-link :url="`https://github.com/syuilo/misskey/blob/master/src/docs/${doc}.ja-JP.md`" class="at">{{ $t('docSource') }}</mk-link>
</div>
</main>
</div>
</template>
@@ -14,19 +18,27 @@
import Vue from 'vue';
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
import MarkdownIt from 'markdown-it';
import i18n from '../i18n';
import { url, lang } from '../config';
import MkLink from '../components/link.vue';
const markdown = MarkdownIt({
html: true
});
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.title,
};
},
components: {
MkLink
},
props: {
doc: {
type: String,

View File

@@ -1,27 +1,40 @@
<template>
<mk-pagination :pagination="pagination" #default="{items}" class="mk-follow-requests" ref="list">
<div class="user _panel" v-for="(req, i) in items" :key="req.id">
<mk-avatar class="avatar" :user="req.follower"/>
<div class="body">
<div class="name">
<router-link class="name" :to="req.follower | userPage" v-user-preview="req.follower.id"><mk-user-name :user="req.follower"/></router-link>
<p class="acct">@{{ req.follower | acct }}</p>
<div>
<portal to="icon"><fa :icon="faUserClock"/></portal>
<portal to="title">{{ $t('followRequests') }}</portal>
<mk-pagination :pagination="pagination" class="mk-follow-requests" ref="list">
<template #empty>
<div class="tkdrhpxr">
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
<div>{{ $t('noFollowRequests') }}</div>
</div>
<div class="description" v-if="req.follower.description" :title="req.follower.description">
<mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$store.state.i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
</template>
<template #default="{items}">
<div class="user _panel" v-for="req in items" :key="req.id">
<mk-avatar class="avatar" :user="req.follower"/>
<div class="body">
<div class="name">
<router-link class="name" :to="req.follower | userPage" v-user-preview="req.follower.id"><mk-user-name :user="req.follower"/></router-link>
<p class="acct">@{{ req.follower | acct }}</p>
</div>
<div class="description" v-if="req.follower.description" :title="req.follower.description">
<mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$store.state.i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
</div>
<div class="actions">
<button class="_button" @click="accept(req.follower)"><fa :icon="faCheck"/></button>
<button class="_button" @click="reject(req.follower)"><fa :icon="faTimes"/></button>
</div>
</div>
</div>
<div class="actions">
<button class="_button" @click="accept(req.follower)"><fa :icon="faCheck"/></button>
<button class="_button" @click="reject(req.follower)"><fa :icon="faTimes"/></button>
</div>
</div>
</div>
</mk-pagination>
</template>
</mk-pagination>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';
import { faUserClock, faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';
import MkPagination from '../components/ui/pagination.vue';
export default Vue.extend({
@@ -41,7 +54,7 @@ export default Vue.extend({
endpoint: 'following/requests/list',
limit: 10,
},
faCheck, faTimes
faCheck, faTimes, faUserClock
};
},
@@ -62,6 +75,18 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-follow-requests {
.tkdrhpxr {
padding: 32px;
text-align: center;
> img {
vertical-align: bottom;
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
}
}
> .user {
display: flex;
padding: 16px;

View File

@@ -62,7 +62,9 @@ export default Vue.extend({
},
signup() {
this.$root.new(XSignupDialog);
this.$root.new(XSignupDialog, {
autoSet: true
});
}
}
});

View File

@@ -10,7 +10,7 @@
import Vue from 'vue';
import XSetup from './index.welcome.setup.vue';
import XEntrance from './index.welcome.entrance.vue';
import { getInstanceName } from '../scripts/get-instance-name';
import { instanceName } from '../config';
export default Vue.extend({
components: {
@@ -20,7 +20,7 @@ export default Vue.extend({
data() {
return {
instanceName: getInstanceName(),
instanceName: instanceName || 'Misskey',
}
},

View File

@@ -5,7 +5,7 @@
<mk-button @click="add()" primary style="margin: 0 auto 16px auto;"><fa :icon="faPlus"/> {{ $t('add') }}</mk-button>
<section class="_card announcements">
<div class="_content announcement" v-for="announcement in announcements">
<mk-input v-model="announcement.title" style="margin-top: 8px;">
<mk-input v-model="announcement.title">
<span>{{ $t('title') }}</span>
</mk-input>
<mk-textarea v-model="announcement.text">

View File

@@ -2,10 +2,10 @@
<div class="mk-instance-emojis">
<portal to="icon"><fa :icon="faLaugh"/></portal>
<portal to="title">{{ $t('customEmojis') }}</portal>
<section class="_card local">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojis') }}</div>
<div class="_content">
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
<mk-pagination :pagination="pagination" class="emojis" ref="emojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
@@ -13,20 +13,30 @@
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>
<span class="info">
<b class="category">{{ emoji.category }}</b>
<span class="aliases">{{ emoji.aliases.join(' ') }}</span>
</span>
</div>
</div>
</template>
</mk-pagination>
</div>
<div class="_footer">
<mk-button inline primary @click="add()"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button>
<div class="_content" v-if="selected">
<mk-input v-model="name"><span>{{ $t('name') }}</span></mk-input>
<mk-input v-model="category" :datalist="categories"><span>{{ $t('category') }}</span></mk-input>
<mk-input v-model="aliases"><span>{{ $t('tags') }}</span></mk-input>
<mk-button inline primary @click="update"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
<mk-button inline :disabled="selected == null" @click="del()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</mk-button>
</div>
<div class="_footer">
<mk-button inline primary @click="add"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button>
</div>
</section>
<section class="_card remote">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojisOfRemote') }}</div>
<div class="_content">
<mk-input v-model="host" :debounce="true" style="margin-top: 0;"><span>{{ $t('host') }}</span></mk-input>
<mk-input v-model="host" :debounce="true"><span>{{ $t('host') }}</span></mk-input>
<mk-pagination :pagination="remotePagination" class="emojis" ref="remoteEmojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
@@ -34,7 +44,7 @@
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>
<span class="host">{{ emoji.host }}</span>
<span class="info">{{ emoji.host }}</span>
</div>
</div>
</template>
@@ -49,12 +59,13 @@
<script lang="ts">
import Vue from 'vue';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { faPlus, faSave } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt, faLaugh } from '@fortawesome/free-regular-svg-icons';
import MkButton from '../../components/ui/button.vue';
import MkInput from '../../components/ui/input.vue';
import MkPagination from '../../components/ui/pagination.vue';
import { apiUrl } from '../../config';
import { selectFile } from '../../scripts/select-file';
import { unique } from '../../../prelude/array';
export default Vue.extend({
metaInfo() {
@@ -71,9 +82,11 @@ export default Vue.extend({
data() {
return {
name: null,
selected: null,
selectedRemote: null,
name: null,
category: null,
aliases: null,
host: '',
pagination: {
endpoint: 'admin/emoji/list',
@@ -86,52 +99,48 @@ export default Vue.extend({
host: this.host ? this.host : null
})
},
faTrashAlt, faPlus, faLaugh
faTrashAlt, faPlus, faLaugh, faSave
}
},
computed: {
categories() {
if (this.$store.state.instance.meta) {
return unique(this.$store.state.instance.meta.emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
} else {
return [];
}
}
},
watch: {
host() {
this.$refs.remoteEmojis.reload();
},
selected() {
this.name = this.selected ? this.selected.name : null;
this.category = this.selected ? this.selected.category : null;
this.aliases = this.selected ? this.selected.aliases.join(' ') : null;
}
},
methods: {
async add() {
const { canceled: canceled, result: name } = await this.$root.dialog({
title: this.$t('emojiName'),
input: true
});
if (canceled) return;
this.name = name;
(this.$refs.file as any).click();
},
onChangeFile() {
const [file] = Array.from((this.$refs.file as any).files);
if (file == null) return;
const data = new FormData();
data.append('file', file);
data.append('name', this.name);
data.append('i', this.$store.state.i.token);
async add(e) {
const files = await selectFile(this, e.currentTarget || e.target, null, true);
const dialog = this.$root.dialog({
type: 'waiting',
text: this.$t('uploading') + '...',
text: this.$t('doing') + '...',
showOkButton: false,
showCancelButton: false,
cancelableByBgClick: false
});
fetch(apiUrl + '/admin/emoji/add', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(f => {
Promise.all(files.map(file => this.$root.api('admin/emoji/add', {
fileId: file.id,
})))
.then(() => {
this.$refs.emojis.reload();
this.$root.dialog({
type: 'success',
@@ -143,6 +152,22 @@ export default Vue.extend({
});
},
async update() {
await this.$root.api('admin/emoji/update', {
id: this.selected.id,
name: this.name,
category: this.category,
aliases: this.aliases.split(' '),
});
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$refs.emojis.reload();
},
async del() {
const { canceled } = await this.$root.dialog({
type: 'warning',
@@ -207,6 +232,18 @@ export default Vue.extend({
> .name {
display: block;
}
> .info {
opacity: 0.5;
> .category {
margin-right: 16px;
}
> .aliases {
font-style: oblique;
}
}
}
}
}
@@ -241,7 +278,7 @@ export default Vue.extend({
display: block;
}
> .host {
> .info {
opacity: 0.5;
}
}

View File

@@ -6,7 +6,7 @@
<section class="_card info">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
<div class="_content">
<mk-input v-model="name" style="margin-top: 8px;">{{ $t('instanceName') }}</mk-input>
<mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
<mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
<mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
<mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
@@ -43,8 +43,6 @@
<div class="_content">
<mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
<template v-if="enableRecaptcha">
<mk-info>{{ $t('recaptcha-info') }}</mk-info>
<mk-info warn>{{ $t('recaptcha-info2') }}</mk-info>
<mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
<mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
</template>
@@ -61,12 +59,11 @@
<section class="_card">
<div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
<div class="_content">
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></mk-switch>
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
<template v-if="enableServiceWorker">
<mk-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></mk-info>
<mk-horizon-group inputs class="fit-bottom">
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>{{ $t('vapid-publickey') }}</mk-input>
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>{{ $t('vapid-privatekey') }}</mk-input>
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
</mk-horizon-group>
</template>
</div>
@@ -78,7 +75,7 @@
<section class="_card">
<div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
<div class="_content">
<mk-textarea v-model="pinnedUsers" style="margin-top: 0;">
<mk-textarea v-model="pinnedUsers">
<template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
</mk-textarea>
</div>
@@ -111,7 +108,7 @@
<section class="_card">
<div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
<div class="_content">
<mk-textarea v-model="blockedHosts" style="margin-top: 0;">
<mk-textarea v-model="blockedHosts">
<template #desc>{{ $t('blockedInstancesDescription') }}</template>
</mk-textarea>
</div>

View File

@@ -6,7 +6,7 @@
<section class="_card lookup">
<div class="_title"><fa :icon="faSearch"/> {{ $t('lookup') }}</div>
<div class="_content">
<mk-input class="target" v-model="target" type="text" @enter="showUser()" style="margin-top: 0;">
<mk-input class="target" v-model="target" type="text" @enter="showUser()">
<span>{{ $t('usernameOrUserId') }}</span>
</mk-input>
<mk-button @click="showUser()" primary><fa :icon="faSearch"/> {{ $t('lookup') }}</mk-button>

View File

@@ -100,7 +100,7 @@ export default Vue.extend({
const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
const name = this.$store.state.settings.pasteDialog
? await this.$root.dialog({
title: this.$t('@.post-form.enter-file-name'),
title: this.$t('enterFileName'),
input: {
default: formatted
},

View File

@@ -3,8 +3,8 @@
<mk-avatar class="avatar" :user="message.user"/>
<div class="content">
<div class="balloon _panel" :data-no-text="message.text == null">
<button class="delete-button" v-if="isMe" :title="$t('@.delete')" @click="del">
<img src="/assets/desktop/remove.png" alt="Delete"/>
<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
<img src="/assets/remove.png" alt="Delete"/>
</button>
<div class="content" v-if="!message.isDeleted">
<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
@@ -230,7 +230,7 @@ export default Vue.extend({
> footer {
display: block;
margin: 2px 0 0 0;
font-size: 10px;
font-size: 0.65em;
> .read {
margin: 0 8px;

View File

@@ -14,10 +14,10 @@
<div class="body">
<mk-loading v-if="fetching"/>
<p class="empty" v-if="!fetching && messages.length == 0"><fa icon="info-circle"/>{{ user ? $t('not-talked-user') : $t('not-talked-group') }}</p>
<p class="empty" v-if="!fetching && messages.length == 0"><fa :icon="faInfoCircle"/>{{ $t('noMessagesYet') }}</p>
<p class="no-history" v-if="!fetching && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('noMoreHistory') }}</p>
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('loading') : $t('loadMore') }}
</button>
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
<x-message :message="message" :is-group="group != null" :key="message.id"/>
@@ -26,7 +26,7 @@
<footer>
<transition name="fade">
<div class="new-message" v-show="showIndicator">
<button class="_buttonPrimary" @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('new-message') }}</button>
<button class="_buttonPrimary" @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('newMessageExists') }}</button>
</div>
</transition>
<x-form v-if="!fetching" :user="user" :group="group" ref="form"/>
@@ -36,7 +36,7 @@
<script lang="ts">
import Vue from 'vue';
import { faArrowCircleDown, faFlag, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faArrowCircleDown, faFlag, faUsers, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import XList from '../components/date-separated-list.vue';
import XMessage from './messaging-room.message.vue';
@@ -64,7 +64,7 @@ export default Vue.extend({
connection: null,
showIndicator: false,
timer: null,
faArrowCircleDown, faFlag, faUsers
faArrowCircleDown, faFlag, faUsers, faInfoCircle
};
},
@@ -139,7 +139,7 @@ export default Vue.extend({
} else if (e.dataTransfer.files.length > 1) {
this.$root.dialog({
type: 'error',
text: this.$t('only-one-file-attached')
text: this.$t('onlyOneFileCanBeAttached')
});
return;
}

View File

@@ -31,7 +31,10 @@
</div>
</router-link>
</sequential-entrance>
<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
<div class="no-history" v-if="!fetching && messages.length == 0">
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
<div>{{ $t('noHistory') }}</div>
</div>
<mk-loading v-if="fetching"/>
</div>
</template>
@@ -139,6 +142,14 @@ export default Vue.extend({
async startGroup() {
const groups1 = await this.$root.api('users/groups/owned');
const groups2 = await this.$root.api('users/groups/joined');
if (groups1.length === 0 && groups2.length === 0) {
this.$root.dialog({
type: 'warning',
title: this.$t('noGroups'),
text: this.$t('joinOrCreateGroup'),
});
return;
}
const { canceled, result: group } = await this.$root.dialog({
type: null,
title: this.$t('group'),
@@ -277,24 +288,18 @@ export default Vue.extend({
}
> .no-history {
margin: 0;
padding: 2em 1em;
padding: 32px;
text-align: center;
color: #999;
font-weight: 500;
> img {
vertical-align: bottom;
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
}
}
@media (max-width: 400px) {
> .search {
> .result {
> .users {
> li {
padding: 8px 16px;
}
}
}
}
> .history {
> .message {
&:not([data-is-me]):not([data-is-read]) {
@@ -306,7 +311,7 @@ export default Vue.extend({
> div {
padding: 16px;
font-size: 14px;
font-size: 0.9em;
> .avatar {
margin: 0 12px 0 0;

View File

@@ -2,7 +2,7 @@
<div class="shaynizk _card">
<div class="_title" v-if="antenna.name">{{ antenna.name }}</div>
<div class="_content body">
<mk-input v-model="name" style="margin-top: 8px;">
<mk-input v-model="name">
<span>{{ $t('name') }}</span>
</mk-input>
<mk-select v-model="src">
@@ -11,12 +11,17 @@
<option value="home">{{ $t('_antennaSources.homeTimeline') }}</option>
<option value="users">{{ $t('_antennaSources.users') }}</option>
<option value="list">{{ $t('_antennaSources.userList') }}</option>
<option value="group">{{ $t('_antennaSources.userGroup') }}</option>
</mk-select>
<mk-select v-model="userListId" v-if="src === 'list'">
<template #label>{{ $t('userList') }}</template>
<option v-for="list in userLists" :value="list.id" :key="list.id">{{ list.name }}</option>
</mk-select>
<mk-textarea v-model="users" v-if="src === 'users'">
<mk-select v-model="userGroupId" v-else-if="src === 'group'">
<template #label>{{ $t('userGroup') }}</template>
<option v-for="group in userGroups" :value="group.id" :key="group.id">{{ group.name }}</option>
</mk-select>
<mk-textarea v-model="users" v-else-if="src === 'users'">
<span>{{ $t('users') }}</span>
<template #desc>{{ $t('antennaUsersDescription') }} <button class="_textButton" @click="addUser">{{ $t('addUser') }}</button></template>
</mk-textarea>
@@ -67,6 +72,7 @@ export default Vue.extend({
name: '',
src: '',
userListId: null,
userGroupId: null,
users: '',
keywords: '',
caseSensitive: false,
@@ -74,6 +80,7 @@ export default Vue.extend({
withFile: false,
notify: false,
userLists: null,
userGroups: null,
faSave, faTrash
};
},
@@ -83,6 +90,13 @@ export default Vue.extend({
if (this.src === 'list' && this.userLists === null) {
this.userLists = await this.$root.api('users/lists/list');
}
if (this.src === 'group' && this.userGroups === null) {
const groups1 = await this.$root.api('users/groups/owned');
const groups2 = await this.$root.api('users/groups/joined');
this.userGroups = [...groups1, ...groups2];
}
}
},
@@ -90,6 +104,7 @@ export default Vue.extend({
this.name = this.antenna.name;
this.src = this.antenna.src;
this.userListId = this.antenna.userListId;
this.userGroupId = this.antenna.userGroupId;
this.users = this.antenna.users.join('\n');
this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
this.caseSensitive = this.antenna.caseSensitive;
@@ -105,6 +120,7 @@ export default Vue.extend({
name: this.name,
src: this.src,
userListId: this.userListId,
userGroupId: this.userGroupId,
withReplies: this.withReplies,
withFile: this.withFile,
notify: this.notify,
@@ -119,6 +135,7 @@ export default Vue.extend({
name: this.name,
src: this.src,
userListId: this.userListId,
userGroupId: this.userGroupId,
withReplies: this.withReplies,
withFile: this.withFile,
notify: this.notify,

View File

@@ -50,6 +50,7 @@ export default Vue.extend({
name: '',
src: 'all',
userListId: null,
userGroupId: null,
users: [],
keywords: [],
withReplies: false,

View File

@@ -17,13 +17,13 @@
<mk-container :body-togglable="true">
<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
<mk-pagination :pagination="invitePagination" #default="{items}" ref="invites">
<div class="_frame" v-for="invite in items" :key="invite.id">
<div class="_title">{{ invite.group.name }}</div>
<div class="_content"><mk-avatars :user-ids="invite.group.userIds"/></div>
<mk-pagination :pagination="invitationPagination" #default="{items}" ref="invitations">
<div class="_frame" v-for="invitation in items" :key="invitation.id">
<div class="_title">{{ invitation.group.name }}</div>
<div class="_content"><mk-avatars :user-ids="invitation.group.userIds"/></div>
<div class="_footer">
<mk-button @click="acceptInvite(invite)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button>
<mk-button @click="rejectInvite(invite)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button>
<mk-button @click="acceptInvite(invitation)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button>
<mk-button @click="rejectInvite(invitation)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button>
</div>
</div>
</mk-pagination>
@@ -73,7 +73,7 @@ export default Vue.extend({
endpoint: 'users/groups/joined',
limit: 10,
},
invitePagination: {
invitationPagination: {
endpoint: 'i/user-group-invites',
limit: 10,
},
@@ -95,23 +95,23 @@ export default Vue.extend({
iconOnly: true, autoClose: true
});
},
acceptInvite(invite) {
acceptInvite(invitation) {
this.$root.api('users/groups/invitations/accept', {
inviteId: invite.id
invitationId: invitation.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$refs.invites.reload();
this.$refs.invitations.reload();
this.$refs.joined.reload();
});
},
rejectInvite(invite) {
rejectInvite(invitation) {
this.$root.api('users/groups/invitations/reject', {
inviteId: invite.id
invitationId: invitation.id
}).then(() => {
this.$refs.invites.reload();
this.$refs.invitations.reload();
});
}
}

View File

@@ -5,7 +5,7 @@
<section class="_card">
<div class="_content">
<img src="https://xn--931a.moe/assets/not-found.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/not-found.png" class="_ghost"/>
<div>{{ $t('notFoundDescription') }}</div>
</div>
</section>
@@ -45,8 +45,6 @@ export default Vue.extend({
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
}

View File

@@ -4,9 +4,19 @@
<portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal>
<transition name="zoom" mode="out-in">
<x-note v-if="note" :note="note" :key="note.id" :detail="true"/>
<div v-else-if="error">
<mk-error @retry="fetch()"/>
<div v-if="note">
<mk-button v-if="hasNext && !showNext" @click="showNext = true" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></mk-button>
<x-notes v-if="showNext" ref="next" :pagination="next"/>
<hr v-if="showNext"/>
<x-note :note="note" :key="note.id" :detail="true"/>
<div v-if="error">
<mk-error @retry="fetch()"/>
</div>
<mk-button v-if="hasPrev && !showPrev" @click="showPrev = true" primary style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></mk-button>
<hr v-if="showPrev"/>
<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
</div>
</transition>
</div>
@@ -14,9 +24,12 @@
<script lang="ts">
import Vue from 'vue';
import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import Progress from '../scripts/loading';
import XNote from '../components/note.vue';
import XNotes from '../components/notes.vue';
import MkButton from '../components/ui/button.vue';
export default Vue.extend({
i18n,
@@ -26,12 +39,36 @@ export default Vue.extend({
};
},
components: {
XNote
XNote,
XNotes,
MkButton,
},
data() {
return {
note: null,
hasPrev: false,
hasNext: false,
showPrev: false,
showNext: false,
error: null,
prev: {
endpoint: 'users/notes',
limit: 10,
params: init => ({
userId: this.note.userId,
untilId: this.note.id,
})
},
next: {
reversed: true,
endpoint: 'users/notes',
limit: 10,
params: init => ({
userId: this.note.userId,
sinceId: this.note.id,
})
},
faChevronUp, faChevronDown
};
},
watch: {
@@ -46,7 +83,22 @@ export default Vue.extend({
this.$root.api('notes/show', {
noteId: this.$route.params.note
}).then(note => {
this.note = note;
Promise.all([
this.$root.api('users/notes', {
userId: note.userId,
untilId: note.id,
limit: 1,
}),
this.$root.api('users/notes', {
userId: note.userId,
sinceId: note.id,
limit: 1,
}),
]).then(([prev, next]) => {
this.hasPrev = prev.length !== 0;
this.hasNext = next.length !== 0;
this.note = note;
});
}).catch(e => {
this.error = e;
}).finally(() => {

View File

@@ -0,0 +1,46 @@
<template>
<section class="_card">
<div class="_title"><fa :icon="faKey"/> API</div>
<div class="_content">
<mk-input :value="$store.state.i.token" readonly>
<span>{{ $t('token') }}</span>
</mk-input>
<mk-button @click="regenerateToken"><fa :icon="faSyncAlt"/> {{ $t('regenerate') }}</mk-button>
</div>
</section>
</template>
<script lang="ts">
import Vue from 'vue';
import { faKey, faSyncAlt } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../i18n';
import MkButton from '../../components/ui/button.vue';
import MkInput from '../../components/ui/input.vue';
export default Vue.extend({
i18n,
components: {
MkButton, MkInput
},
data() {
return {
faKey, faSyncAlt
};
},
methods: {
regenerateToken() {
this.$root.dialog({
title: this.$t('password'),
input: {
type: 'password'
}
}).then(({ canceled, result: password }) => {
if (canceled) return;
this.$root.api('i/regenerate_token', {
password: password
});
});
},
}
});
</script>

View File

@@ -2,12 +2,8 @@
<section class="_card">
<div class="_title"><fa :icon="faCog"/> {{ $t('general') }}</div>
<div class="_content">
<mk-input type="file" @change="onWallpaperChange" style="margin-top: 0;">
<span>{{ $t('wallpaper') }}</span>
<template #icon><fa :icon="faImage"/></template>
<template #desc v-if="wallpaperUploading">{{ $t('uploading') }}<mk-ellipsis/></template>
</mk-input>
<mk-button primary :disabled="$store.state.settings.wallpaper == null" @click="delWallpaper()">{{ $t('removeWallpaper') }}</mk-button>
<mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button>
<mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button>
</div>
<div class="_content">
<mk-switch v-model="autoReload">
@@ -23,10 +19,28 @@
<mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button>
</div>
<div class="_content">
<mk-switch v-model="reduceAnimation">
{{ $t('reduceUiAnimation') }}
<mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch>
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
<mk-switch v-model="useOsNativeEmojis">
{{ $t('useOsNativeEmojis') }}
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
</mk-switch>
</div>
<div class="_content">
<mk-select v-model="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</mk-select>
</div>
<div class="_content">
<div>{{ $t('fontSize') }}</div>
<mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio>
<mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio>
</div>
</section>
</template>
@@ -36,8 +50,11 @@ import { faImage, faCog } from '@fortawesome/free-solid-svg-icons';
import MkInput from '../../components/ui/input.vue';
import MkButton from '../../components/ui/button.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkSelect from '../../components/ui/select.vue';
import MkRadio from '../../components/ui/radio.vue';
import i18n from '../../i18n';
import { apiUrl } from '../../config';
import { langs } from '../../config';
import { selectFile } from '../../scripts/select-file';
export default Vue.extend({
i18n,
@@ -46,21 +63,21 @@ export default Vue.extend({
MkInput,
MkButton,
MkSwitch,
MkSelect,
MkRadio,
},
data() {
return {
wallpaperUploading: false,
langs,
lang: localStorage.getItem('lang'),
fontSize: localStorage.getItem('fontSize'),
wallpaper: localStorage.getItem('wallpaper'),
faImage, faCog
}
},
computed: {
wallpaper: {
get() { return this.$store.state.settings.wallpaper; },
set(value) { this.$store.dispatch('settings/set', { key: 'wallpaper', value }); }
},
autoReload: {
get() { return this.$store.state.device.autoReload; },
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
@@ -70,40 +87,56 @@ export default Vue.extend({
get() { return !this.$store.state.device.animation; },
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
},
disableAnimatedMfm: {
get() { return !this.$store.state.device.animatedMfm; },
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
},
useOsNativeEmojis: {
get() { return this.$store.state.device.useOsNativeEmojis; },
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
},
imageNewTab: {
get() { return this.$store.state.device.imageNewTab; },
set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); }
},
},
watch: {
lang() {
localStorage.setItem('lang', this.lang);
localStorage.removeItem('locale');
location.reload();
},
fontSize() {
if (this.fontSize == null) {
localStorage.removeItem('fontSize');
} else {
localStorage.setItem('fontSize', this.fontSize);
}
location.reload();
},
wallpaper() {
if (this.wallpaper == null) {
localStorage.removeItem('wallpaper');
} else {
localStorage.setItem('wallpaper', this.wallpaper);
}
location.reload();
}
},
methods: {
onWallpaperChange([file]) {
this.wallpaperUploading = true;
const data = new FormData();
data.append('file', file);
data.append('i', this.$store.state.i.token);
fetch(apiUrl + '/drive/files/create', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(f => {
this.wallpaper = f.url;
this.wallpaperUploading = false;
document.documentElement.style.backgroundImage = `url(${this.$store.state.settings.wallpaper})`;
})
.catch(e => {
this.wallpaperUploading = false;
this.$root.dialog({
type: 'error',
text: e
});
setWallpaper(e) {
selectFile(this, e.currentTarget || e.target, null, false).then(file => {
this.wallpaper = file.url;
});
},
delWallpaper() {
this.wallpaper = null;
document.documentElement.style.backgroundImage = 'none';
},
onChangeAutoWatch(v) {
this.$root.api('i/update', {
autoWatch: v

View File

@@ -2,8 +2,7 @@
<section class="_card">
<div class="_title"><fa :icon="faBoxes"/> {{ $t('importAndExport') }}</div>
<div class="_content">
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
<mk-select v-model="exportTarget" style="margin-top: 0;">
<mk-select v-model="exportTarget">
<option value="notes">{{ $t('_exportOrImport.allNotes') }}</option>
<option value="following">{{ $t('_exportOrImport.followingList') }}</option>
<option value="user-lists">{{ $t('_exportOrImport.userLists') }}</option>
@@ -13,6 +12,7 @@
<mk-button inline @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</mk-button>
<mk-button inline @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><fa :icon="faUpload"/> {{ $t('import') }}</mk-button>
</div>
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
</section>
</template>

View File

@@ -14,6 +14,7 @@
<x-security/>
<x-2fa/>
<x-integration/>
<x-api/>
<mk-button @click="cacheClear()" primary class="cacheClear">{{ $t('cacheClear') }}</mk-button>
<mk-button @click="$root.signout()" primary class="logout">{{ $t('logout') }}</mk-button>
@@ -34,6 +35,7 @@ import XSecurity from './security.vue';
import XTheme from './theme.vue';
import X2fa from './2fa.vue';
import XIntegration from './integration.vue';
import XApi from './api.vue';
import MkButton from '../../components/ui/button.vue';
export default Vue.extend({
@@ -55,6 +57,7 @@ export default Vue.extend({
XTheme,
X2fa,
XIntegration,
XApi,
MkButton,
},

View File

@@ -2,7 +2,7 @@
<section class="_card">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
<div class="_content">
<mk-textarea v-model="reactions" style="margin-top: 16px;">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
<mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
</div>
<div class="_footer">
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>

View File

@@ -35,7 +35,6 @@ export default Vue.extend({
data() {
return {
wallpaperUploading: false,
faPalette
}
},

View File

@@ -1,9 +1,15 @@
<template>
<x-notes ref="notes" :pagination="pagination" @before="before" @after="after"/>
<div>
<portal to="icon"><fa :icon="faHashtag"/></portal>
<portal to="title">{{ $route.params.tag }}</portal>
<x-notes ref="notes" :pagination="pagination" @before="before" @after="after"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faHashtag } from '@fortawesome/free-solid-svg-icons';
import Progress from '../scripts/loading';
import XNotes from '../components/notes.vue';
@@ -26,7 +32,8 @@ export default Vue.extend({
params: () => ({
tag: this.$route.params.tag,
})
}
},
faHashtag
};
},

View File

@@ -13,8 +13,8 @@
<mk-user-name class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')"><fa :icon="farBookmark"/></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
</div>
@@ -30,8 +30,8 @@
<mk-user-name :user="user" :nowrap="false" class="name"/>
<div class="bottom">
<span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')"><fa :icon="farBookmark"/></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
</div>
@@ -250,6 +250,7 @@ export default Vue.extend({
background-size: cover;
background-position: center;
box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
will-change: background-position;
}
> .fade {
@@ -380,7 +381,7 @@ export default Vue.extend({
> .description {
padding: 24px 24px 24px 154px;
font-size: 15px;
font-size: 0.95em;
@media (max-width: 500px) {
padding: 16px;
@@ -395,7 +396,7 @@ export default Vue.extend({
> .fields {
padding: 24px;
font-size: 14px;
font-size: 0.9em;
border-top: solid 1px var(--divider);
@media (max-width: 500px) {

View File

@@ -20,6 +20,7 @@ export const router = new VueRouter({
{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') },
{ path: '/featured', component: page('featured') },
{ path: '/docs', component: page('docs') },
{ path: '/docs/:doc', component: page('doc'), props: true },
@@ -62,11 +63,16 @@ export const router = new VueRouter({
],
// なんかHacky
// 通常の使い方をすると scroll メソッドの behavior を設定できないため、自前で window.scroll するようにする
// setTimeout しないと、アニメーション(トランジション)の関係でうまく動かない
scrollBehavior(to) {
window._scroll = () => { // さらにHacky
if (to.name === 'index') {
window.scroll({ top: indexScrollPos, behavior: 'instant' });
const i = setInterval(() => {
window.scroll({ top: indexScrollPos, behavior: 'instant' });
}, 10);
setTimeout(() => {
clearInterval(i);
}, 500);
} else {
window.scroll({ top: 0, behavior: 'instant' });
}

View File

@@ -1,8 +0,0 @@
export function getInstanceName() {
const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement;
if (siteName && siteName.content) {
return siteName.content;
}
return 'Misskey';
}

View File

@@ -1,15 +1,20 @@
import Vue from 'vue';
import { getScrollPosition, onScrollTop } from './scroll';
const SECOND_FETCH_LIMIT = 30;
export default (opts) => ({
data() {
return {
items: [],
queue: [],
offset: 0,
fetching: true,
moreFetching: false,
inited: false,
more: false,
backed: false,
isBackTop: false,
};
},
@@ -32,13 +37,17 @@ export default (opts) => ({
created() {
opts.displayLimit = opts.displayLimit || 30;
this.init();
this.$on('hook:activated', () => {
this.isBackTop = false;
});
this.$on('hook:deactivated', () => {
this.isBackTop = window.scrollY === 0;
});
},
methods: {
isScrollTop() {
return window.scrollY <= 8;
},
updateItem(i, item) {
Vue.set((this as any).items, i, item);
},
@@ -55,18 +64,18 @@ export default (opts) => ({
if (params && params.then) params = await params;
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;
await this.$root.api(endpoint, {
...params,
limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1,
...params
}).then(x => {
if (!this.pagination.noPaging && (x.length === (this.pagination.limit || 10) + 1)) {
x.pop();
this.items = x;
}).then(items => {
if (!this.pagination.noPaging && (items.length === (this.pagination.limit || 10) + 1)) {
items.pop();
this.items = this.pagination.reversed ? [...items].reverse() : items;
this.more = true;
} else {
this.items = x;
this.items = this.pagination.reversed ? [...items].reverse() : items;
this.more = false;
}
this.offset = x.length;
this.offset = items.length;
this.inited = true;
this.fetching = false;
if (opts.after) opts.after(this, null);
@@ -84,44 +93,51 @@ export default (opts) => ({
if (params && params.then) params = await params;
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;
await this.$root.api(endpoint, {
limit: (this.pagination.limit || 10) + 1,
...params,
limit: SECOND_FETCH_LIMIT + 1,
...(this.pagination.offsetMode ? {
offset: this.offset,
} : this.pagination.reversed ? {
sinceId: this.items[0].id,
} : {
untilId: this.items[this.items.length - 1].id,
}),
...params
}).then(x => {
if (x.length === (this.pagination.limit || 10) + 1) {
x.pop();
this.items = this.items.concat(x);
}).then(items => {
if (items.length === SECOND_FETCH_LIMIT + 1) {
items.pop();
this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);
this.more = true;
} else {
this.items = this.items.concat(x);
this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);
this.more = false;
}
this.offset += x.length;
this.offset += items.length;
this.moreFetching = false;
}, e => {
this.moreFetching = false;
});
},
prepend(item, silent = false) {
if (opts.onPrepend) {
const cancel = opts.onPrepend(this, item, silent);
if (cancel) return;
}
prepend(item) {
const isTop = this.isBackTop || (document.body.contains(this.$el) && (getScrollPosition(this.$el) === 0));
// Prepend the item
this.items.unshift(item);
if (isTop) {
// Prepend the item
this.items.unshift(item);
if (this.isScrollTop()) {
// オーバーフローしたら古い投稿は捨てる
// オーバーフローしたら古いアイテムは捨てる
if (this.items.length >= opts.displayLimit) {
this.items = this.items.slice(0, opts.displayLimit);
this.more = true;
}
} else {
this.queue.push(item);
onScrollTop(this.$el, () => {
for (const item of this.queue) {
this.prepend(item);
}
this.queue = [];
});
}
},

View File

@@ -2,7 +2,7 @@ export default ($root: any) => {
if ($root.$store.getters.isSignedIn) return;
$root.dialog({
title: $root.$t('@.signin-required'),
title: $root.$t('signinRequired'),
text: null
});

View File

@@ -0,0 +1,27 @@
export function getScrollContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
const style = window.getComputedStyle(el);
if (style.getPropertyValue('overflow') === 'auto') {
return el;
} else {
return getScrollContainer(el.parentElement);
}
}
export function getScrollPosition(el: Element | null): number {
const container = getScrollContainer(el);
return container == null ? window.scrollY : container.scrollTop;
}
export function onScrollTop(el: Element, cb) {
const container = getScrollContainer(el) || window;
const onScroll = ev => {
if (!document.body.contains(el)) return;
const pos = getScrollPosition(el);
if (pos === 0) {
cb();
container.removeEventListener('scroll', onscroll);
}
};
container.addEventListener('scroll', onScroll, { passive: true });
}

View File

@@ -1,8 +1,8 @@
import { faUpload, faCloud, faLink } from '@fortawesome/free-solid-svg-icons';
import { faUpload, faCloud } from '@fortawesome/free-solid-svg-icons';
import { selectDriveFile } from './select-drive-file';
import { apiUrl } from '../config';
export function selectFile(component: any, src: any, label: string, multiple = false) {
export function selectFile(component: any, src: any, label: string | null, multiple = false) {
return new Promise((res, rej) => {
const chooseFileFromPc = () => {
const input = document.createElement('input');
@@ -56,10 +56,10 @@ export function selectFile(component: any, src: any, label: string, multiple = f
};
component.$root.menu({
items: [{
items: [label ? {
text: label,
type: 'label'
}, {
} : undefined, {
text: component.$t('upload'),
icon: faUpload,
action: chooseFileFromPc

View File

@@ -9,7 +9,7 @@ import MiOS from '../mios';
*/
export default class Stream extends EventEmitter {
private stream: ReconnectingWebsocket;
public state: string;
public state: 'initializing' | 'reconnecting' | 'connected';
private sharedConnectionPools: Pool[] = [];
private sharedConnections: SharedConnection[] = [];
private nonSharedConnections: NonSharedConnection[] = [];

View File

@@ -13,7 +13,6 @@ const defaultSettings = {
defaultNoteLocalOnly: false,
uploadFolder: null,
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
wallpaper: null,
memo: null,
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
};
@@ -31,13 +30,15 @@ const defaultDeviceSettings = {
lang: null,
loadRawImages: false,
alwaysShowNsfw: false,
useOsDefaultEmojis: false,
useOsNativeEmojis: false,
autoReload: false,
accounts: [],
recentEmojis: [],
themes: [],
theme: 'light',
animation: true,
animatedMfm: true,
imageNewTab: false,
userData: {},
};

View File

@@ -58,6 +58,18 @@ html {
}
}
}
&.f-small {
font-size: 0.9em;
}
&.f-large {
font-size: 1.1em;
}
&.f-veryLarge {
font-size: 1.2em;
}
}
html.changing-theme {
@@ -116,6 +128,13 @@ a {
}
}
hr {
margin: var(--margin) 0 var(--margin) 0;
border: none;
height: 1px;
background: var(--divider);
}
#nprogress {
pointer-events: none;
position: absolute;
@@ -164,6 +183,19 @@ a {
}
}
._noSelect {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
._ghost {
&, * {
@extend ._noSelect;
pointer-events: none;
}
}
._button {
appearance: none;
padding: 0;
@@ -175,9 +207,7 @@ a {
font-size: 1em;
&, * {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
@extend ._noSelect;
}
* {

View File

@@ -9,8 +9,8 @@ export type Theme = {
props: { [key: string]: string };
};
export const lightTheme: Theme = require('./themes/light.json5');
export const darkTheme: Theme = require('./themes/dark.json5');
export const lightTheme: Theme = require('./themes/_light.json5');
export const darkTheme: Theme = require('./themes/_dark.json5');
export const builtinThemes = [
lightTheme,

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