Compare commits

...

178 Commits

Author SHA1 Message Date
86b0dfdd33 2.29.0 2018-06-07 06:16:11 +09:00
ab04f2fce0 ✌️ 2018-06-07 06:15:57 +09:00
be9f836b21 やった 2018-06-07 06:13:57 +09:00
818bc96aab 2.28.0 2018-06-07 05:15:05 +09:00
14d12c21f2 nanka iroiro 2018-06-07 05:14:37 +09:00
2053a041e5 Fix 2018-06-07 04:33:39 +09:00
0534a0a41e ✌️ 2018-06-07 04:31:49 +09:00
0ac5fdab49 2.27.3 2018-06-07 02:07:00 +09:00
39099909bf Fix firefox 2018-06-07 02:06:32 +09:00
999ce8e366 Improve scroll performance 2018-06-07 01:52:03 +09:00
8678e30cc8 2.27.2 2018-06-07 01:45:04 +09:00
8a59e9d9c8 Merge pull request #1677 from syuilo/l10n_master
New Crowdin translations
2018-06-07 01:44:26 +09:00
dddace9d6a New translations ja.yml (English) 2018-06-07 01:42:12 +09:00
388cb7db3a ✌️ 2018-06-07 01:41:05 +09:00
46b74b3e1c New translations ja.yml (Portuguese) 2018-06-07 01:22:17 +09:00
d53e80c88a New translations ja.yml (Korean) 2018-06-07 01:22:14 +09:00
d8a8f36676 New translations ja.yml (Polish) 2018-06-07 01:22:12 +09:00
dafdbbf552 New translations ja.yml (Chinese Simplified) 2018-06-07 01:22:10 +09:00
52bc52293b New translations ja.yml (Italian) 2018-06-07 01:22:08 +09:00
0733aefb64 New translations ja.yml (Russian) 2018-06-07 01:22:06 +09:00
aac6dec5da New translations ja.yml (English) 2018-06-07 01:22:03 +09:00
d44c59ea3e New translations ja.yml (Spanish) 2018-06-07 01:22:01 +09:00
9b3c3881c4 New translations ja.yml (German) 2018-06-07 01:21:59 +09:00
cdd722dca0 New translations ja.yml (French) 2018-06-07 01:21:57 +09:00
9ad7a80496 i18n 2018-06-07 01:17:29 +09:00
b85597b15d Fix glitch 2018-06-07 01:00:38 +09:00
ebb98d975b ✌️ 2018-06-07 00:53:31 +09:00
c1b320710b 2.27.1 2018-06-06 19:37:15 +09:00
1201794bef Revert "fix: validate post's text with Ctrl+Enter on PC."
This reverts commit fac6868305.
2018-06-06 19:34:25 +09:00
dc58c9bd2f 2.27.0 2018-06-06 19:24:34 +09:00
9787da7240 Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-06 19:22:48 +09:00
b0f989dbac Deckにウィジェットを置けるように 2018-06-06 19:22:45 +09:00
a0ec6b8ea7 Merge pull request #1678 from 2vg/patch-2
fix: validate post's text with Ctrl+Enter on PC.
2018-06-06 09:50:17 +09:00
fac6868305 fix: validate post's text with Ctrl+Enter on PC. 2018-06-06 09:46:33 +09:00
ed8fa59639 Update README.md 2018-06-06 05:39:05 +09:00
e8edda01a9 2.26.2 2018-06-06 05:29:45 +09:00
380a369eca oops 2018-06-06 05:29:31 +09:00
781fffee42 2.26.1 2018-06-06 05:18:46 +09:00
69b5de3346 wip 2018-06-06 05:18:08 +09:00
0d8c83f27c wip 2018-06-06 04:00:48 +09:00
8ca58de2ba wip 2018-06-06 03:12:06 +09:00
d8cd24fab0 wip 2018-06-06 03:03:56 +09:00
f918081168 wip 2018-06-06 02:48:26 +09:00
f88fb9bc1d wip 2018-06-06 01:54:36 +09:00
062fbd7d27 wip 2018-06-06 01:54:24 +09:00
6b6af008d0 New translations ja.yml (Portuguese) 2018-06-05 23:23:22 +09:00
4d35def548 New translations ja.yml (Korean) 2018-06-05 23:23:20 +09:00
b369d6bd5c New translations ja.yml (Polish) 2018-06-05 23:23:17 +09:00
63dfe2726c New translations ja.yml (Chinese Simplified) 2018-06-05 23:23:15 +09:00
1002d29cc2 New translations ja.yml (Italian) 2018-06-05 23:23:13 +09:00
868240666a New translations ja.yml (Russian) 2018-06-05 23:23:11 +09:00
02a88fdc9c New translations ja.yml (English) 2018-06-05 23:23:08 +09:00
bc4adf7107 New translations ja.yml (Spanish) 2018-06-05 23:23:06 +09:00
bd67785802 New translations ja.yml (German) 2018-06-05 23:23:03 +09:00
68c90e8ebe New translations ja.yml (French) 2018-06-05 23:23:01 +09:00
64519a9fd4 2.26.0 2018-06-05 23:22:37 +09:00
d21da0211c Merge pull request #1676 from syuilo/deck
Deck (wip)
2018-06-05 23:21:53 +09:00
2e919b788f wip 2018-06-05 23:19:04 +09:00
2d2056f2bd Merge pull request #1675 from 2vg/patch-1
fix: validate post's text on mobile client.
2018-06-05 23:03:26 +09:00
334dabc1de fix: validate post's text on mobile client. 2018-06-05 23:00:03 +09:00
dfa2c951d6 wip 2018-06-05 22:54:03 +09:00
e28d1c7569 wip 2018-06-05 21:44:02 +09:00
9ce0f96de3 wip 2018-06-05 21:36:21 +09:00
a408b19bbe Update README.md 2018-06-05 15:09:21 +09:00
f9a17b8021 Merge pull request #1674 from syuilo/l10n_master
New Crowdin translations
2018-06-04 23:22:34 +09:00
5eeb200913 New translations ja.yml (English) 2018-06-04 20:07:56 +09:00
f87981eeee 2.25.2 2018-06-04 19:59:07 +09:00
761ae807db Fix bug 2018-06-03 19:53:57 +09:00
643a0e6b13 New translations ja.yml (Portuguese) 2018-06-03 19:41:36 +09:00
e7e5f76e9e New translations ja.yml (Korean) 2018-06-03 19:41:34 +09:00
247acd81a9 New translations ja.yml (Polish) 2018-06-03 19:41:32 +09:00
a2457a6ac4 New translations ja.yml (Chinese Simplified) 2018-06-03 19:41:30 +09:00
af7a320493 New translations ja.yml (Italian) 2018-06-03 19:41:28 +09:00
4dd8b7e85d New translations ja.yml (Russian) 2018-06-03 19:41:26 +09:00
3a4392af40 New translations ja.yml (English) 2018-06-03 19:41:23 +09:00
44f70f0009 New translations ja.yml (Spanish) 2018-06-03 19:41:20 +09:00
238c4cf181 New translations ja.yml (German) 2018-06-03 19:41:18 +09:00
9171c49d85 New translations ja.yml (French) 2018-06-03 19:41:16 +09:00
5e967e24ff ✌️ 2018-06-03 19:39:02 +09:00
70ac07d60e 🎨 2018-06-03 07:34:34 +09:00
81ee670dc2 New translations ja.yml (Polish) 2018-06-03 05:32:01 +09:00
faf215685b New translations ja.yml (Polish) 2018-06-03 05:21:18 +09:00
255c07d1ab 2.25.1 2018-06-03 04:51:58 +09:00
83e9711274 New translations ja.yml (English) 2018-06-02 23:02:02 +09:00
0aa9201770 2.25.0 2018-06-02 19:23:17 +09:00
534e43f72d Fix bug 2018-06-02 19:19:58 +09:00
8f50080647 Merge pull request #1673 from syuilo/l10n_master
New Crowdin translations
2018-06-02 17:05:18 +09:00
cdc70875e5 New translations ja.yml (English) 2018-06-02 16:51:10 +09:00
e6962d6fab New translations ja.yml (English) 2018-06-02 16:41:22 +09:00
3703563939 New translations ja.yml (Portuguese) 2018-06-02 16:31:39 +09:00
e81b145735 New translations ja.yml (Korean) 2018-06-02 16:31:37 +09:00
7f4145ee56 New translations ja.yml (Polish) 2018-06-02 16:31:35 +09:00
3967cf40b3 New translations ja.yml (Chinese Simplified) 2018-06-02 16:31:33 +09:00
84b0d56c4c New translations ja.yml (Italian) 2018-06-02 16:31:32 +09:00
e081d8d4ca New translations ja.yml (Russian) 2018-06-02 16:31:30 +09:00
b6ad7149d8 New translations ja.yml (English) 2018-06-02 16:31:28 +09:00
0f36f60cb4 New translations ja.yml (Spanish) 2018-06-02 16:31:26 +09:00
1284eef9e2 New translations ja.yml (German) 2018-06-02 16:31:24 +09:00
dec264ee6a New translations ja.yml (French) 2018-06-02 16:31:22 +09:00
e25e1d88d6 Merge pull request #1671 from syuilo/locked-account
Locked account
2018-06-02 16:28:08 +09:00
60a7f7f146 wip 2018-06-02 16:27:24 +09:00
897f7a031d wip 2018-06-02 16:13:32 +09:00
4feff8835c wip 2018-06-02 16:01:32 +09:00
8dfd892b71 wip 2018-06-02 15:51:43 +09:00
9e8cfd76c8 wip 2018-06-02 13:40:28 +09:00
a6a4bb6599 wip 2018-06-02 13:36:29 +09:00
5ca8a0d886 wip 2018-06-02 13:34:53 +09:00
6840496791 wip 2018-06-02 13:14:54 +09:00
0128831649 wip 2018-06-02 13:11:28 +09:00
56fa24e401 wip 2018-06-02 12:58:56 +09:00
e011870a60 Merge pull request #1672 from Angristan/patch-1
Update build docs
2018-06-02 03:54:10 +09:00
8d78ee08c1 Cover multiple cases 2018-06-01 20:52:47 +02:00
2752319e50 Update build docs
Fix #1474
2018-06-01 20:24:30 +02:00
a26c19cbd2 wip 2018-06-02 00:51:20 +09:00
f14571dc42 wip 2018-06-02 00:38:31 +09:00
484d17f53f wip 2018-06-02 00:15:17 +09:00
924119651a Merge branch 'master' into locked-account 2018-06-01 23:37:19 +09:00
c6d49dacbc Merge pull request #1670 from syuilo/l10n_master
New Crowdin translations
2018-06-01 23:36:22 +09:00
0be790fa31 wip 2018-06-01 21:55:27 +09:00
b7f6eb8290 New translations ja.yml (English) 2018-06-01 16:29:25 +09:00
f1bda0b2e1 New translations ja.yml (English) 2018-06-01 16:12:22 +09:00
bae44b4708 wip 2018-06-01 01:12:02 +09:00
35115607bc wip 2018-06-01 00:42:37 +09:00
51255bb446 wip 2018-05-31 22:56:02 +09:00
bd758a156e wip 2018-05-31 18:34:15 +09:00
51929fb607 typo 2018-05-31 18:11:28 +09:00
9599a31239 wip 2018-05-31 18:08:47 +09:00
9fdb125960 Merge pull request #1668 from syuilo/l10n_master
New Crowdin translations
2018-05-31 17:38:49 +09:00
534c0a6001 New translations ja.yml (English) 2018-05-31 17:31:23 +09:00
58bfcfda91 New translations ja.yml (English) 2018-05-31 17:21:36 +09:00
8d0802f05d New translations ja.yml (English) 2018-05-31 17:12:41 +09:00
5cd8c5d229 New translations ja.yml (English) 2018-05-31 17:02:48 +09:00
fa3c4436d9 New translations ja.yml (English) 2018-05-31 16:52:27 +09:00
d32d95918c 🎨 2018-05-31 16:44:11 +09:00
34899757d5 New translations ja.yml (English) 2018-05-31 16:41:32 +09:00
111dbdcd7f Fix 2018-05-31 16:38:05 +09:00
0c38509f1c New translations ja.yml (Polish) 2018-05-31 04:21:47 +09:00
652aa1f69b New translations ja.yml (German) 2018-05-31 00:33:22 +09:00
dc380c38da New translations ja.yml (German) 2018-05-31 00:23:33 +09:00
8555e04f50 New translations ja.yml (German) 2018-05-31 00:13:36 +09:00
c23bbf81f1 2.24.3 2018-05-30 23:08:26 +09:00
7dd7de8ff6 Update endpoints.ts 2018-05-30 23:05:24 +09:00
2ca8bafde3 Merge pull request #1664 from syuilo/l10n_master
New Crowdin translations
2018-05-30 22:08:38 +09:00
79f6c3f1ca New translations ja.yml (Portuguese) 2018-05-30 21:23:17 +09:00
fce0b315cf New translations ja.yml (Korean) 2018-05-30 21:23:14 +09:00
56c7a8f2e4 New translations ja.yml (Polish) 2018-05-30 21:23:10 +09:00
5ef2f157f2 New translations ja.yml (Chinese Simplified) 2018-05-30 21:23:08 +09:00
738afbe475 New translations ja.yml (Italian) 2018-05-30 21:23:06 +09:00
791a81a4c7 New translations ja.yml (Russian) 2018-05-30 21:23:03 +09:00
aa82d7a2c9 New translations ja.yml (English) 2018-05-30 21:23:01 +09:00
f57d2e54d2 New translations ja.yml (Spanish) 2018-05-30 21:22:58 +09:00
fea1a2e51b New translations ja.yml (German) 2018-05-30 21:22:55 +09:00
bda5347f1e New translations ja.yml (French) 2018-05-30 21:22:53 +09:00
98d9c37922 Merge pull request #1665 from m4sk1n/patch-1
fix typo
2018-05-30 21:21:59 +09:00
e3bde41a25 New translations ja.yml (Polish) 2018-05-30 21:14:46 +09:00
5fb2f7749d fix typo 2018-05-30 12:13:51 +00:00
a56bdf2372 New translations ja.yml (Polish) 2018-05-30 21:05:48 +09:00
9d991df32f New translations ja.yml (English) 2018-05-30 21:05:45 +09:00
c4a3f89d1c New translations ja.yml (English) 2018-05-30 20:51:48 +09:00
ea223bab51 Fix bug 2018-05-30 18:13:20 +09:00
dd94392317 2.24.2 2018-05-30 05:02:56 +09:00
baa2845916 Merge pull request #1663 from syuilo/l10n_master
New Crowdin translations
2018-05-30 05:02:17 +09:00
97ae4ea13e New translations ja.yml (English) 2018-05-30 05:00:58 +09:00
d1c5f0c70f New translations ja.yml (Portuguese) 2018-05-30 04:51:38 +09:00
95bff3005f New translations ja.yml (Korean) 2018-05-30 04:51:36 +09:00
c0b06496b1 New translations ja.yml (Polish) 2018-05-30 04:51:34 +09:00
2105e1f259 New translations ja.yml (Chinese Simplified) 2018-05-30 04:51:32 +09:00
e546414c2f New translations ja.yml (Italian) 2018-05-30 04:51:30 +09:00
1f4660a930 New translations ja.yml (Russian) 2018-05-30 04:51:28 +09:00
a2165c2e01 New translations ja.yml (English) 2018-05-30 04:51:26 +09:00
1af920739f New translations ja.yml (Spanish) 2018-05-30 04:51:24 +09:00
868e8228f0 New translations ja.yml (German) 2018-05-30 04:51:22 +09:00
2bbc74560d New translations ja.yml (French) 2018-05-30 04:51:20 +09:00
5d2caa456d 🎨 2018-05-30 04:46:50 +09:00
9069a99a15 wip 2018-05-30 04:45:27 +09:00
fa56a44d85 🎨 2018-05-30 04:07:23 +09:00
248acaee75 2.24.1 2018-05-30 00:16:03 +09:00
ef75f12abe Fix bug 2018-05-30 00:15:32 +09:00
122 changed files with 4655 additions and 824 deletions

View File

@ -20,9 +20,10 @@ ultimately sophisticated new type of mini-blog based SNS.
----------------------------------------------------------------
* Reactions
* User lists
* Cusromizable column view (known as MisskeyDeck)
* Private messages
* Mute
* Real time contents
* Streaming
* ActivityPub compatible
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).

View File

@ -3,16 +3,21 @@ const User = require('../built/models/user').default;
const args = process.argv.slice(2);
const userId = new mongo.ObjectID(args[0]);
const user = args[0];
console.log(`Suspending ${userId}...`);
const q = user.startsWith('@') ? {
username: user.split('@')[1],
host: user.split('@')[2] || null
} : { _id: new mongo.ObjectID(user) };
User.update({ _id: userId }, {
console.log(`Suspending ${user}...`);
User.update(q, {
$set: {
isSuspended: true
}
}).then(() => {
console.log(`Suspended ${userId}`);
console.log(`Suspended ${user}`);
}, e => {
console.error(e);
});

View File

@ -47,7 +47,14 @@ You need to generate config file via `npm run config` command.
*5.* Build Misskey
----------------------------------------------------------------
We need to use `node-gyp` to build the `crypto` module.
Build misskey with the following:
`npm run build`
If you're on Debian, you will need to install the `build-essential` package.
If you're still encountering errors about some modules, use node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`

View File

@ -36,39 +36,50 @@ common:
confused: "Verwirrt"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
b: "何かありましたか?"
c: "何をお考えですか?"
d: "言いたいことは?"
e: "ここに書いてください"
f: "あなたが書くのを待っています..."
a: "Was machst du gerade?"
b: "Was ist so passiert?"
c: "Was geht dir durch den Kopf?"
d: "Willst du etwas sagen?"
e: "Schreib hier etwas!"
f: "Warte darauf, das du schreibst."
delete: "Löschen"
loading: "Laden"
ok: "OK"
update-available: "Eine neue Version von Misskey ist verfügbar ({newer}, aktuell ist {current}). Lade die Seite neu um die aktuelle Version zu laden"
my-token-regenerated: "Dein Token wurde generiert. Du wirst jetzt abgemeldet."
widgets:
analog-clock: "アナログ時計"
profile: "プロフィール"
calendar: "カレンダー"
timemachine: "カレンダー(タイムマシン)"
activity: "アクティビティ"
rss: "RSSリーダー"
memo: "メモ"
trends: "トレンド"
photo-stream: "フォトストリーム"
slideshow: "スライドショー"
version: "バージョン"
analog-clock: "Analoge Uhr"
profile: "Profil"
calendar: "Kalender"
timemachine: "Kalender (Zeitmaschiene)"
activity: "Aktivitäten"
rss: "RSS Leser"
memo: "Notizen"
trends: "Trends"
photo-stream: "Bilder"
slideshow: "Diashow"
version: "Version"
broadcast: "ブロードキャスト"
notifications: "通知"
users: "おすすめユーザー"
polls: "投票"
notifications: "Benachrichtigungen"
users: "Empfohlene Benutzer"
polls: "Umfragen"
post-form: "投稿フォーム"
messaging: "メッセージ"
server: "サーバー情報"
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
messaging: "Nachrichten"
server: "Server-Info"
donation: "Spenden"
nav: "Navigation"
tips: "Tipps"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "Verbindung zum Server ist fehlgeschlagen"
description: "Es gibt entweder ein Problem mit deiner Internetverbindung, der Server ist nicht erreichbar oder wird gerade gewartet. Bitte versuche es später noch einmal."
@ -123,8 +134,8 @@ common/views/components/nav.vue:
common/views/components/note-menu.vue:
favorite: "Diese Anmerkung favorisieren"
pin: "An die Profilseite pinnen"
delete: "削除"
delete-confirm: "この投稿を削除しますか?"
delete: "Löschen"
delete-confirm: "Diesen Post löschen?"
remote: "Auf Quelle anzeigen"
common/views/components/poll.vue:
vote-to: "Stimme für '{}'"
@ -185,14 +196,14 @@ common/views/components/twitter-setting.vue:
common/views/components/uploader.vue:
waiting: "Warten"
common/views/components/visibility-chooser.vue:
public: "公開"
home: "ホーム"
public: "Öffentlich"
home: "Home"
home-desc: "ホームタイムラインにのみ公開"
followers: "フォロワー"
followers-desc: "自分のフォロワーにのみ公開"
specified: "ダイレクト"
specified-desc: "指定したユーザーにのみ公開"
private: "非公開"
followers: "Folgende"
followers-desc: "Nur für diejenigen sichtbar, die dir folgen"
specified: "Direkt"
specified-desc: "Poste nur für bestimmte Benutzer"
private: "Privat"
common/views/widgets/broadcast.vue:
fetching: "Laden"
no-broadcasts: "Keine Broadcasts"
@ -208,9 +219,9 @@ common/views/widgets/server.vue:
title: "Serverinformationen"
toggle: "Sicht umschalten"
common/views/widgets/memo.vue:
title: "メモ"
memo: "ここに書いて!"
save: "保存"
title: "Notizen"
memo: "Schreib hier!"
save: "Speichern"
desktop/views/components/activity.chart.vue:
total: "Schwarz ... komplett"
notes: "Blau ... Hinweise"
@ -249,29 +260,29 @@ desktop/views/components/drive.file.vue:
copy-url: "URL kopieren"
download: "Download"
else-files: "その他..."
set-as-avatar: "アイコンに設定"
set-as-banner: "バナーに設定"
open-in-app: "アプリで開く"
add-app: "アプリを追加"
rename-file: "ファイル名の変更"
input-new-file-name: "新しいファイル名を入力してください"
copied: "コピー完了"
copied-url-to-clipboard: "URLをクリップボードにコピーしました"
set-as-avatar: "Als Avatar festlegen"
set-as-banner: "Setze als Banner"
open-in-app: "In der App öffnen"
add-app: "App hinzufügen"
rename-file: "Datei umbennen"
input-new-file-name: "Geben Sie den neuen Dateinamen an"
copied: "Kopieren erfolgreich"
copied-url-to-clipboard: "URL wurde in die Zwischenablage kopiert"
desktop/views/components/drive.folder.vue:
unable-to-process: "操作を完了できません"
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
unhandled-error: "不明なエラー"
unable-to-process: "Der Vorgang konnte nicht beendet werden"
circular-reference-detected: "Das Zielverzeichnis ist ein Unterverzeichnis des Verzeichnisses welches du verschieben möchtest"
unhandled-error: "Unbekannter Fehler"
contextmenu:
move-to-this-folder: "このフォルダへ移動"
show-in-new-window: "新しいウィンドウで表示"
rename: "名前を変更"
rename-folder: "フォルダ名の変更"
input-new-folder-name: "新しいフォルダ名を入力してください"
move-to-this-folder: "Verschiebe in diesen Ordner"
show-in-new-window: "In einem neuen Fenster anzeigen"
rename: "Umbenennen"
rename-folder: "Ordner umbenennen"
input-new-folder-name: "Namen für neuen Ordner eingeben"
desktop/views/components/drive.nav-folder.vue:
drive: "ドライブ"
drive: "Laufwerk"
desktop/views/components/drive.vue:
search: "検索"
load-more: "もっと読み込む"
search: "Suchen"
load-more: "Mehr laden"
empty-draghover: "Herzlich Willkommen!"
empty-drive: "Dein Speicher ist leer"
empty-drive-description: "Du kannst rechts klicken und \"Datei hochladen\" auswählen oder eine Datei per Drag and Drop auf das Fenster ziehen."
@ -290,12 +301,14 @@ desktop/views/components/drive.vue:
upload: "Eine Datei hochladen"
url-upload: "Von einer URL hochladen"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
empty: "フォロワーはいないようです。"
empty: "Dir scheint niemand zu folgen."
desktop/views/components/following-window.vue:
following: "{} のフォロー"
desktop/views/components/following.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "Speicher"
favorites: "Favoriten"
lists: "Listen"
follow-requests: "フォロー申請"
customize: "Anpassen"
settings: "Einstellungen"
signout: "Ausloggen"
dark: "Verdunkeln"
desktop/views/components/ui.header.nav.vue:
home: "Home"
deck: "デッキ"
messaging: "Nachrichten"
game: "Spielen"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "Einen neuen Post erstellen"
desktop/views/components/ui.header.search.vue:
placeholder: "Suchen"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -5,7 +5,7 @@ meta:
common:
misskey: "A planet of fediverse"
about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
about: "Thanks for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within Fediverse (a universe where various social media platforms are organized) it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
time:
unknown: "unknown"
future: "future"
@ -45,8 +45,8 @@ common:
delete: "Delete"
loading: "Loading"
ok: "OK"
update-available: "A new version of Misskey is now available({newer}, current is {current}). Reload the page to apply the update."
my-token-regenerated: "Your token has been generated. You will now get logged out."
update-available: "A new version of Misskey is now available({newer}, the current version is {current}). Reload the page to apply updates."
my-token-regenerated: "Your token has been renewed so you will be signed out."
widgets:
analog-clock: "Analog clock"
profile: "Profile"
@ -69,9 +69,20 @@ common:
donation: "Donation"
nav: "Navigation"
tips: "Tips"
deck:
widgets: "Widgets"
home: "Home"
local: "Local"
global: "Global"
notifications: "Notifications"
list: "List"
swap-left: "Move Left"
swap-right: "Move Right"
remove: "Remove"
add-column: "Add a column"
common/views/components/connect-failed.vue:
title: "Unable to connect to the server"
description: "There is a problem either with your internet connection, or the server may be down or under maintenance. Please {try again} later."
description: "There is a problem either with your Internet connection, or the server may be down or under maintenance. Please {try again} later."
thanks: "Thank you for using Misskey."
troubleshoot: "Troubleshoot"
common/views/components/connect-failed.troubleshooter.vue:
@ -79,12 +90,12 @@ common/views/components/connect-failed.troubleshooter.vue:
network: "Network connection"
checking-network: "Checking network connection"
internet: "Internet connection"
checking-internet: "Checking internet connection"
checking-internet: "Checking Internet connection"
server: "Server connection"
checking-server: "Checking server connection"
finding: "Finding a problem"
no-network: "There is no Network connection"
no-network-desc: "Please make sure you are connected to the Network."
finding: "Searching for issues"
no-network: "No connection"
no-network-desc: "Please make sure you are connected to the network."
no-internet: "There is no Internet connection"
no-internet-desc: "Please make sure you are connected to the Internet."
no-server: "Unable to connect to the Misskey server"
@ -94,19 +105,19 @@ common/views/components/connect-failed.troubleshooter.vue:
flush: "Clean cache"
set-version: "Specify version"
common/views/components/messaging.vue:
search-user: "Find an user"
search-user: "Find a user"
you: "You"
no-history: "No history"
common/views/components/messaging-room.vue:
empty: "No conversations"
more: "More"
empty: "You haven't messaged this user"
more: "Read more"
no-history: "There is no more history"
resize-form: "Drag to resize"
new-message: "New message"
common/views/components/messaging-room.form.vue:
input-message-here: "Enter message here"
send: "Send"
attach-from-local: "Attach files from your pc"
attach-from-local: "Attach files from your PC"
attach-from-drive: "Attach files from your Drive"
common/views/components/messaging-room.message.vue:
is-read: "Read"
@ -138,7 +149,7 @@ common/views/components/poll-editor.vue:
choice-n: "Choice {}"
remove: "Remove this choice"
add: "+ Add a choice"
destroy: "Destroy this poll"
destroy: "Cancel this poll"
common/views/components/reaction-picker.vue:
choose-reaction: "Choose a reaction"
common/views/components/signin.vue:
@ -158,10 +169,10 @@ common/views/components/signup.vue:
too-long: "Please enter up to 20 characters."
password: "Password"
password-placeholder: "We recommend more than 8 characters."
weak-password: "Weak"
normal-password: "So so"
strong-password: "Strong"
retype: "Type again"
weak-password: "Weak password"
normal-password: "Fair password"
strong-password: "Strong password"
retype: "Re-enter"
retype-placeholder: "Confirm your password"
password-matched: "OK"
password-not-matched: "Doesn't match"
@ -178,9 +189,9 @@ common/views/components/stream-indicator.vue:
common/views/components/twitter-setting.vue:
description: "If you connect your Twitter account to your Misskey account, you will be able to see your Twitter account information on your profile and you can sign-in using Twitter."
connected-to: "You are connected to this Twitter account"
detail: "Detail..."
detail: "Details..."
reconnect: "Reconnect"
connect: "Link your twitter account"
connect: "Link your Twitter account"
disconnect: "Disconnect"
common/views/components/uploader.vue:
waiting: "Waiting"
@ -200,7 +211,7 @@ common/views/widgets/broadcast.vue:
next: "Next"
common/views/widgets/donation.vue:
title: "Donation"
text: "To keep Misskey up and running we spend money for our domain name, servers and so on.. We don't get any money from it, and we would really appreciate it if you could donate. If you're interested contact {}. Thank you for your contribution!"
text: "To keep Misskey up and running we spend money for our domain name, servers and so on. Since we don't get money from advertisements, we count on donations from all of you. If you're interested contact {}. Thank you for your contribution!"
common/views/widgets/photo-stream.vue:
title: "Photostream"
no-photos: "No photos"
@ -223,7 +234,7 @@ desktop/views/components/calendar.vue:
title: "{1} / {2}"
prev: "Previous month"
next: "Next month"
go: "Click to naviguate"
go: "Click to navigate"
desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Choosing files"
upload: "Upload files from your PC"
@ -290,19 +301,21 @@ desktop/views/components/drive.vue:
upload: "Upload a file"
url-upload: "Upload from a URL"
desktop/views/components/follow-button.vue:
unfollow: "Unfollow"
following: "Following"
follow: "Follow"
request-pending: "フォロー許可待ち"
follow-request: "Follow request"
desktop/views/components/followers-window.vue:
followers: "Followers of {}"
followers: "{}'s followers"
desktop/views/components/followers.vue:
empty: "Seems that you dont have any followers."
empty: "Seems like you dont have any followers."
desktop/views/components/following-window.vue:
following: "Following of {}"
following: "Following {}"
desktop/views/components/following.vue:
empty: "You dont follow anyone."
desktop/views/components/friends-maker.vue:
title: "Recommended users:"
empty: "Similar users werent found."
empty: "Couldn't find any recommended users."
fetching: "Loading…"
refresh: "More"
close: "Close"
@ -323,16 +336,16 @@ desktop/views/components/note-detail.vue:
more: "Load more conversations"
private: "this post is private"
deleted: "this post has been deleted"
reposted-by: "Renoted by {}"
reposted-by: "Reposted by {}"
location: "Location"
renote: "Renote"
renote: "Repost"
add-reaction: "Add a reaction"
desktop/views/components/notes.note.vue:
reposted-by: "Reposted by {}"
reply: "Reply"
renote: "Renote"
renote: "Repost"
add-reaction: "Add a reaction"
detail: "Show detail"
detail: "Show details"
private: "this post is private"
deleted: "this post has been deleted"
desktop/views/components/notes.vue:
@ -346,20 +359,20 @@ desktop/views/components/post-form.vue:
quote-placeholder: "Quote this note..."
submit: "Post"
reply: "Reply"
renote: "Renote"
renote: "Repost"
posted: "Posted!"
replied: "Replied!"
reposted: "Reposted!"
note-failed: "Failed to note"
reply-failed: "Failed to reply"
renote-failed: "Failed to renote"
renote-failed: "Failed to repost"
posting: "Posting"
attach-media-from-local: "Attach media from your pc"
attach-media-from-local: "Attach media from your PC"
attach-media-from-drive: "Attach media from your Drive"
attach-cancel: "Cancel attachment"
insert-a-kao: "v(‘ω’)v"
create-poll: "Create a poll"
text-remain: "{} chars remaining"
text-remain: "{} characters remaining"
desktop/views/components/post-form-window.vue:
note: "New note"
reply: "Reply"
@ -370,12 +383,12 @@ desktop/views/components/progress-dialog.vue:
desktop/views/components/renote-form.vue:
quote: "Quote..."
cancel: "Cancel"
renote: "Renote"
renote: "Repost"
reposting: "Reposting..."
success: "Reposted!"
failure: "Failed to Renote"
failure: "Repost failed"
desktop/views/components/renote-form-window.vue:
title: "Are you sure you want to renote this note?"
title: "Are you sure you want to repost this?"
desktop/views/components/settings-window.vue:
settings: "Settings"
desktop/views/components/settings.vue:
@ -405,22 +418,22 @@ desktop/views/components/settings.vue:
gradient-window-header: "Use gradients on window headers"
post-form-on-timeline: "Display post form at the top of the timeline"
show-reply-target: "Display reply target"
show-my-renotes: "Show my renote in the timeline"
show-renoted-my-notes: "Show renoted my post in the timeline"
show-my-renotes: "Show my reposts in the timeline"
show-renoted-my-notes: "Show my posts that have been shared in the timeline"
show-maps: "Show the map"
show-maps-desc: "Show the map of the location attached to the post."
show-maps-desc: "Automatically show the map of the location attached to the post."
sound: "Sound"
enable-sounds: "Enable sound"
enable-sounds-desc: "Play a sound when you received a post/message. This setting is stored in the browser."
enable-sounds-desc: "Play a sound when you receive a post/message. This setting is stored in the browser."
volume: "Volume"
test: "Test"
mobile: "Mobile"
disable-via-mobile: "Not mark the post as 'from mobile'"
disable-via-mobile: "Don't mark the post as 'from mobile'"
language: "Language"
pick-language: "Select a language"
recommended: "Recommended"
auto: "Auto"
specify-language: "Specify the language"
specify-language: "Specify language"
language-desc: "You need to reload the page for the changes to take effect."
cache: "Cache"
clean-cache: "Cleanup"
@ -438,11 +451,11 @@ desktop/views/components/settings.vue:
do-update: "Check for update"
update-settings: "Advanced settings"
prevent-update: "Postpone updates (not recommended)"
prevent-update-desc: "You may reflect updates even if you select this setting. This setting is valid only this device."
prevent-update-desc: "Even if you turn this setting on updates may apply. This setting is valid only for this device."
no-updates: "No updates available"
no-updates-desc: "Your Misskey is up to date."
update-available: "New version is available!"
update-available-desc: "To reload the page and updates are applied."
update-available-desc: "The updates will apply if you reload the page again."
advanced-settings: "Advanced"
debug-mode: "Enable the debug mode"
debug-mode-desc: "This setting is stored in the browser."
@ -476,8 +489,8 @@ desktop/views/components/settings.api.vue:
regenerate-token: "Regenerate the token"
token: "Token:"
enter-password: "Please enter the password"
desktop/views/components/settings.app.vue:
no-apps: "No authorized apps"
desktop/views/components/settings.apps.vue:
no-apps: "No linked applications"
desktop/views/components/settings.mute.vue:
no-users: "No muted users"
desktop/views/components/settings.password.vue:
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "Drive"
favorites: "Favorites"
lists: "Lists"
follow-requests: "Follow requests"
customize: "Customize"
settings: "Settings"
signout: "Sign out"
dark: "Fall in dark"
desktop/views/components/ui.header.nav.vue:
home: "Home"
deck: "Deck"
messaging: "Messages"
game: "Play Othello"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "Compose new Post"
desktop/views/components/ui.header.search.vue:
placeholder: "Search"
desktop/views/components/received-follow-requests-window.vue:
title: "Follow requests"
accept: "Accept"
reject: "Reject"
desktop/views/components/user-lists-window.vue:
title: "User lists"
create-list: "Create list"
desktop/views/components/user-preview.vue:
notes: "Posts"
@ -633,13 +653,13 @@ mobile/views/components/drive.vue:
load-more: "Load more"
nothing-in-drive: "Nothing"
folder-is-empty: "This folder is empty"
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
prompt: "What do you want to do? (Please enter a number): <1 → Upload a file | 2 → Upload a file from a URL | 3 → Create a folder | 4 → Change this folder's name | 5 → Move this folder | 6 → Delete this folder>"
deletion-alert: "Sorry! Deleting a folder is not yet implemented."
folder-name: "Folder name"
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
root-rename-alert: "You're in the root; it can't be renamed because it's not a folder. Navigate to a folder you want to rename and try again."
root-move-alert: "You're in the root; it can't be moved because it's not a folder. Navigate to a folder you want to move and try again."
url-prompt: "URL of file you want to upload"
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
uploading: "Upload requested. It may take some time for the upload to complete."
mobile/views/components/drive-file-detail.vue:
rename: "Rename"
mobile/views/components/drive-file-chooser.vue:
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "Following"
follow: "Follow"
unfollow: "Unfollow"
request-pending: "フォロー許可待ち"
follow-request: "Follow request"
mobile/views/components/friends-maker.vue:
title: "Let's follow users"
empty: "Featured user was not found."
@ -662,7 +684,7 @@ mobile/views/components/friends-maker.vue:
refresh: "See more"
close: "Close"
mobile/views/components/note.vue:
reposted-by: "Renoted by {}"
reposted-by: "Reposted by {}"
more: "See more"
less: "Hide"
private: "this post is private"
@ -671,7 +693,7 @@ mobile/views/components/note.vue:
mobile/views/components/note-detail.vue:
reply: "Reply"
reaction: "Reaction"
reposted-by: "Renoted by {}"
reposted-by: "Reposted by {}"
private: "this post is private"
deleted: "this post has been deleted"
location: "Location"
@ -693,11 +715,11 @@ mobile/views/components/post-form.vue:
add-visible-user: "Add a user"
submit: "Post"
reply: "Reply"
renote: "Renote"
renote: "Repost"
quote-placeholder: "Quote this post... (optional)"
reply-placeholder: "Reply to this note..."
cw-placeholder: "内容への注釈 (オプション)"
location-alert: "お使いの端末は位置情報に対応していません"
cw-placeholder: "Comments about content (optional)"
location-alert: "Your device does not support location services"
error: "Error"
username-prompt: "Enter user name"
mobile/views/components/sub-note-content.vue:
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "No notes"
load-more: "More"
mobile/views/components/ui.nav.vue:
home: "Home"
timeline: "Timeline"
notifications: "Notifications"
messaging: "Messages"
follow-requests: "Follow requests"
search: "Search"
drive: "Drive"
favorites: "Favorites"
user-lists: "Lists"
widgets: "Widgets"
game: "Games"
darkmode: "Dark mode"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "More"
mobile/views/pages/favorites.vue:
title: "Favorites"
mobile/views/pages/user-lists.vue:
title: "Lists"
enter-list-name: "Enter list name"
mobile/views/pages/drive.vue:
drive: "Drive"
more: "Load more"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "Messaging"
mobile/views/pages/messaging-room.vue:
messaging: "Messaging"
mobile/views/pages/received-follow-requests.vue:
title: "Follow requests"
accept: "Accept"
reject: "Reject"
mobile/views/pages/note.vue:
title: "Post"
prev: "Previous note"
@ -784,8 +815,8 @@ mobile/views/pages/settings.vue:
circle-icons: "Use circle icons"
timeline: "Timeline"
show-reply-target: "Show reply target"
show-my-renotes: "Show my renotes"
show-renoted-my-notes: "Show renoted my notes"
show-my-renotes: "Show my reposts"
show-renoted-my-notes: "Show my reposted posts"
post-style: "Post design"
post-style-standard: "Standard"
post-style-smart: "Smart"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "Impossible de se connecter au server."
description: "Il y a soit un problème avec votre connexion internet, soit le serveur est hors-ligne ou en maintenance. Veuillez {ressayer} plus tard."
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "Uploader un fichier"
url-upload: "Uploader d'un URL"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,8 +489,8 @@ desktop/views/components/settings.api.vue:
regenerate-token: "Regenerer le token"
token: "Token:"
enter-password: "Veuillez entrer le mot de passe"
desktop/views/components/settings.app.vue:
no-apps: "Aucune application authorisée"
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "Aucun utilisateurs mis en sourdine"
desktop/views/components/settings.password.vue:
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "Drive"
favorites: "Favorites"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "Modifications"
settings: "Réglages"
signout: "Déconnexion"
dark: "Fall in dark"
desktop/views/components/ui.header.nav.vue:
home: "Accueil"
deck: "デッキ"
messaging: "Messages"
game: "Jeux"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "Composer un nouveau post"
desktop/views/components/ui.header.search.vue:
placeholder: "Chercher"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "Suivre"
unfollow: "Ne plus suivre"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "Pas de notes"
load-more: "Afficher plus"
mobile/views/components/ui.nav.vue:
home: "Accueil"
timeline: "タイムライン"
notifications: "Notifications"
messaging: "Messages"
follow-requests: "フォロー申請"
search: "Rechercher"
drive: "Drive"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "Afficher plus"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "Drive"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "Messagerie"
mobile/views/pages/messaging-room.vue:
messaging: "Messagerie"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "Post"
prev: "Note précedante"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -76,6 +76,19 @@ common:
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
rename: "名前を変更"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -331,8 +344,10 @@ desktop/views/components/drive.vue:
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
@ -551,7 +566,7 @@ desktop/views/components/settings.api.vue:
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
@ -596,6 +611,7 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
@ -603,6 +619,7 @@ desktop/views/components/ui.header.account.vue:
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
@ -615,7 +632,13 @@ desktop/views/components/ui.header.post.vue:
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
@ -771,8 +794,10 @@ mobile/views/components/drive.file-detail.vue:
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
@ -838,12 +863,14 @@ mobile/views/components/timeline.vue:
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -863,6 +890,10 @@ mobile/views/components/users-list.vue:
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -884,6 +915,11 @@ mobile/views/pages/messaging.vue:
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -3,8 +3,8 @@ meta:
lang: "język polski"
divider: ""
common:
misskey: "A planet of fediverse"
about-title: "A ⭐ of fediverse."
misskey: "Planeta Fediwersum"
about-title: "⭐ Fediwersum"
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time:
unknown: "nieznany"
@ -36,19 +36,19 @@ common:
confused: "Zmieszany"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
b: "何かありましたか?"
c: "何をお考えですか?"
d: "言いたいことは?"
e: "ここに書いてください"
f: "あなたが書くのを待っています..."
a: "Co robisz?"
b: "Co się wydarzyło?"
c: "Co Ci chodzi po głowie?"
d: "Czy masz coś do powiedzenia?"
e: "Napisz coś tutaj!"
f: "Czekamy, aż coś napiszesz."
delete: "Usuń"
loading: "Ładowanie"
ok: "OK"
update-available: "Nowa wersja Misskey jest dostępna ({newer}, obecna to {current}). Odśwież stronę, aby zastosować aktualizację."
my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
widgets:
analog-clock: "アナログ時計"
analog-clock: "Zegar analogowy"
profile: "Profil"
calendar: "Kalendarz"
timemachine: "Kalendarz (wehikuł czasu)"
@ -69,6 +69,17 @@ common:
donation: "Dotacje"
nav: "Nawigacja"
tips: "Wskazówki"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "Nie udało się połączyć z serwerem"
description: "Wystąpił problem z Twoim połączeniem z Internetem, lub z serwerem. {Spróbuj ponownie} wkrótce."
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "Wyślij plik"
url-upload: "Wyślij z adresu URL"
desktop/views/components/follow-button.vue:
unfollow: "Przestań śledzić"
following: "フォロー中"
follow: "Śledź"
request-pending: "Oczekiwanie na pozwolenie"
follow-request: "Poproś o śledzenie"
desktop/views/components/followers-window.vue:
followers: "Śledzący"
desktop/views/components/followers.vue:
@ -344,7 +357,7 @@ desktop/views/components/notifications.vue:
desktop/views/components/post-form.vue:
reply-placeholder: "Odpowiedz na ten wpis…"
quote-placeholder: "Zacytuj ten wpis…"
submit: "投稿"
submit: "Wyślij"
reply: "Odpowiedz"
renote: "Udostępnienie"
posted: "Opublikowano!"
@ -427,7 +440,7 @@ desktop/views/components/settings.vue:
cache-warn: "Pamięć podręczna informacji o koncie/wpisów/odpowiedzi/wiadomości/ustawień przechowywanych w przeglądarce zostanie usunięta. Będziesz musiał odświeżyć stronę po wyczyszczeniu."
cache-cleared: "Wyczyszczono pamięć podręczną"
cache-cleared-desc: "Proszę odświeżyć stronę."
auto-watch: "投稿の自動ウォッチ"
auto-watch: "Automatycznie nasłuchuj"
auto-watch-desc: "Otrzymuj natychmiastowo informacje o wpisach/odpowiedziach/reakcjach."
about: "O Misskey"
operator: "Administrator instancji"
@ -470,13 +483,13 @@ desktop/views/components/settings.2fa.vue:
failed: "Nie udało się skonfigurować uwierzytelniania dwuetapowego, upewnij się że wprowadziłeś prawidłowy token."
info: "Od teraz, wprowadzaj token wyświetlany na urządzeniu przy każdym logowaniu do Misskey."
desktop/views/components/settings.api.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
intro: "Aby uzyskać dostęp do API, ustaw ten token jako klucz 'i' parametrów żądań."
caution: "Nie pokazuj tego tokenu osobom trzecim (nie wprowadzaj go nigdzie indziej), aby konto nie trafiło w niepowołane ręce."
regeneration-of-token: "W przypadku wycieku tokenu, możesz wygenerować nowy."
regenerate-token: "Wygeneruj nowy token"
token: "Token:"
enter-password: "Wprowadź hasło"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "Brak zautoryzowanych aplikacji"
desktop/views/components/settings.mute.vue:
no-users: "Brak wyciszonych użytkowników"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "Dysk"
favorites: "Ulubione"
lists: "Listy"
follow-requests: "Prośby o śledzenie"
customize: "Dostosuj"
settings: "Ustawienia"
signout: "Wyloguj się"
dark: "Sprowadź ciemność"
desktop/views/components/ui.header.nav.vue:
home: "Strona główna"
deck: "デッキ"
messaging: "Wiadomości"
game: "Gra"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "Utwórz nowy wpis"
desktop/views/components/ui.header.search.vue:
placeholder: "Szukaj"
desktop/views/components/received-follow-requests-window.vue:
title: "Poproś o śledzenie"
accept: "Zatwierdź"
reject: "Odmów"
desktop/views/components/user-lists-window.vue:
title: "Listy"
create-list: "Utwórz listę"
desktop/views/components/user-preview.vue:
notes: "Wpisy"
@ -545,8 +565,8 @@ desktop/views/components/window.vue:
popout: "Pop-out"
close: "Zamknij"
desktop/views/pages/welcome.vue:
about: "詳しく..."
gotit: "わかった"
about: "O Misskey"
gotit: "Rozumiem!"
signin: "Zaloguj się"
signup: "Zarejestruj się"
signin-button: "Zaloguj się"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "Śledź"
unfollow: "Przestań śledzić"
request-pending: "Oczekiwanie na pozwolenie"
follow-request: "Poproś o śledzenie"
mobile/views/components/friends-maker.vue:
title: "Zacznij śledzić ludzi takich jak Ty"
empty: "Nie znaleziono podobnych użytkowników."
@ -694,7 +716,7 @@ mobile/views/components/post-form.vue:
submit: "Wyślij"
reply: "Odpowiedz"
renote: "Udostępnij"
quote-placeholder: "この投稿を引用... (オプション)"
quote-placeholder: "Zacytuj ten wpis… (nieobowiązkowe)"
reply-placeholder: "Odpowiedź na ten wpis…"
cw-placeholder: "Treść ostrzeżenia (opcjonalnie)"
location-alert: "Twoje urządzenie nie pozwala na przekazywanie informacji o lokalizacji"
@ -709,15 +731,17 @@ mobile/views/components/timeline.vue:
empty: "Brak wpisów"
load-more: "Więcej"
mobile/views/components/ui.nav.vue:
home: "Strona główna"
timeline: "Oś czasu"
notifications: "Powiadomienia"
messaging: "Wiadomości"
follow-requests: "Prośby o śledzenie"
search: "Szukaj"
drive: "Dysk"
favorites: "お気に入り"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
favorites: "Ulubione"
user-lists: "Listy"
widgets: "Widżety"
game: "Gry"
darkmode: "Tryb ciemny"
settings: "Ustawienia"
about: "O Misskey"
mobile/views/components/user-timeline.vue:
@ -729,10 +753,13 @@ mobile/views/components/users-list.vue:
known: "Znasz"
load-more: "Więcej"
mobile/views/pages/favorites.vue:
title: "お気に入り"
title: "Ulubione"
mobile/views/pages/user-lists.vue:
title: "Listy"
enter-list-name: "Wprowadź nazwę listy"
mobile/views/pages/drive.vue:
drive: "Dysk"
more: "もっと見る"
more: "Załaduj więcej"
mobile/views/pages/followers.vue:
followers-of: "Śledzący {}"
mobile/views/pages/following.vue:
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "Wiadomości"
mobile/views/pages/messaging-room.vue:
messaging: "Wiadomości"
mobile/views/pages/received-follow-requests.vue:
title: "Prośby o śledzenie"
accept: "Zatwierdź"
reject: "Odmów"
mobile/views/pages/note.vue:
title: "Wpis"
prev: "Poprzedni wpis"
@ -788,7 +819,7 @@ mobile/views/pages/settings.vue:
show-renoted-my-notes: "Pokazuj udostępnienia moich wpisów"
post-style: "Styl wpisów"
post-style-standard: "Standardowy"
post-style-smart: "スマート"
post-style-smart: "Inteligentny"
behavior: "Zachowanie"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
disable-via-mobile: "Nie oznaczaj wpisów jako „wysłane z telefonu”"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -69,6 +69,17 @@ common:
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
deck:
widgets: "ウィジェット"
home: "ホーム"
local: "ローカル"
global: "グローバル"
notifications: "通知"
list: "リスト"
swap-left: "左に移動"
swap-right: "右に移動"
remove: "カラムを削除"
add-column: "カラムを追加"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@ -290,8 +301,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/follow-button.vue:
unfollow: "フォロー解除"
follow: "フォローする"
following: "フォロー"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue:
followers: "{} のフォロワー"
desktop/views/components/followers.vue:
@ -476,7 +489,7 @@ desktop/views/components/settings.api.vue:
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue:
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
@ -514,12 +527,14 @@ desktop/views/components/ui.header.account.vue:
drive: "ドライブ"
favorites: "お気に入り"
lists: "リスト"
follow-requests: "フォロー申請"
customize: "カスタマイズ"
settings: "設定"
signout: "サインアウト"
dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
messaging: "メッセージ"
game: "ゲーム"
desktop/views/components/ui.header.notifications.vue:
@ -528,7 +543,12 @@ desktop/views/components/ui.header.post.vue:
post: "新規投稿"
desktop/views/components/ui.header.search.vue:
placeholder: "検索"
desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
@ -653,8 +673,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)"
exif: "EXIF"
mobile/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
unfollow: "フォロー解除"
request-pending: "フォロー許可待ち"
follow-request: "フォロー申請"
mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。"
@ -709,12 +731,14 @@ mobile/views/components/timeline.vue:
empty: "投稿がありません"
load-more: "もっと"
mobile/views/components/ui.nav.vue:
home: "ホーム"
timeline: "タイムライン"
notifications: "通知"
messaging: "メッセージ"
follow-requests: "フォロー申請"
search: "検索"
drive: "ドライブ"
favorites: "お気に入り"
user-lists: "リスト"
widgets: "ウィジェット"
game: "ゲーム"
darkmode: "ダークモード"
@ -730,6 +754,9 @@ mobile/views/components/users-list.vue:
load-more: "もっと"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/drive.vue:
drive: "ドライブ"
more: "もっと見る"
@ -745,6 +772,10 @@ mobile/views/pages/messaging.vue:
messaging: "メッセージ"
mobile/views/pages/messaging-room.vue:
messaging: "メッセージ"
mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請"
accept: "承認"
reject: "拒否"
mobile/views/pages/note.vue:
title: "投稿"
prev: "前の投稿"

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "2.24.0",
"clientVersion": "1.0.6017",
"version": "2.29.0",
"clientVersion": "1.0.6195",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 512 512" width="512" height="512"><defs><clipPath id="_clipPath_P6eAE2OaBltOJ3gHGVajfqsOnfv4xIns"><rect width="512" height="512"/></clipPath></defs><g clip-path="url(#_clipPath_P6eAE2OaBltOJ3gHGVajfqsOnfv4xIns)"><clipPath id="_clipPath_P6q7MZAUp3XpQhVgs2GuAbegX9v4gkom"><rect x="0" y="0" width="512" height="512" transform="matrix(1,0,0,1,0,0)" fill="rgb(255,255,255)"/></clipPath><g clip-path="url(#_clipPath_P6q7MZAUp3XpQhVgs2GuAbegX9v4gkom)"><g id="Group"><g id="g4502"><g id="g5125"><g id="text4489"><path d=" M 190.093 359.243 C 167.923 359.32 148.881 345.963 139.9 330.409 C 135.104 323.615 125.617 321.198 125.482 330.409 L 125.482 372.939 C 125.482 390.026 119.253 404.799 106.794 417.258 C 94.69 429.362 79.917 435.413 62.474 435.413 C 45.387 435.413 30.614 429.362 18.155 417.258 C 6.052 404.799 0 390.026 0 372.939 L 0 139.061 C 0 125.89 3.738 113.965 11.213 103.285 C 19.045 92.25 29.012 84.596 41.116 80.325 C 47.879 77.833 54.999 76.587 62.474 76.587 C 81.697 76.587 97.716 84.062 110.531 99.013 C 117.295 106.489 121.211 110.405 122.279 110.761 C 122.279 110.761 173.043 172.145 174.467 173.213 C 175.891 174.281 180.073 182.446 190.093 182.446 C 200.112 182.446 204.829 174.281 206.253 173.213 C 207.676 172.145 258.44 110.761 258.44 110.761 C 258.796 111.117 262.534 107.201 269.654 99.013 C 282.825 84.062 299.022 76.587 318.245 76.587 C 325.364 76.587 332.484 77.833 339.603 80.325 C 351.707 84.596 361.496 92.25 368.972 103.285 C 376.803 113.965 380.719 125.89 380.719 139.061 L 380.719 372.939 C 380.719 390.026 374.489 404.799 362.03 417.258 C 349.927 429.362 335.154 435.413 317.711 435.413 C 300.624 435.413 285.851 429.362 273.391 417.258 C 261.288 404.799 255.237 390.026 255.237 372.939 L 255.237 330.409 C 254.184 318.802 243.925 326.116 240.285 330.409 C 230.674 348.208 212.262 359.167 190.093 359.243 Z M 457.535 184.448 Q 435.109 184.448 419.09 168.963 Q 403.605 152.944 403.605 130.518 Q 403.605 108.091 419.09 92.606 Q 435.109 76.587 457.535 76.587 Q 479.962 76.587 495.981 92.606 Q 512 108.091 512 130.518 Q 512 152.944 495.981 168.963 Q 479.962 184.448 457.535 184.448 Z M 458.069 195.128 Q 480.496 195.128 495.981 211.147 Q 512 227.166 512 249.592 L 512 381.482 Q 512 403.909 495.981 419.928 Q 480.496 435.413 458.069 435.413 Q 435.643 435.413 419.624 419.928 Q 403.605 403.909 403.605 381.482 L 403.605 249.592 Q 403.605 227.166 419.624 211.147 Q 435.643 195.128 458.069 195.128 Z " fill-rule="evenodd" fill="rgb(157,157,157)"/></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="1024px" height="512px" viewBox="0 256 1024 512" enable-background="new 0 256 1024 512" xml:space="preserve">
<polyline opacity="0.5" fill="none" stroke="#000000" stroke-width="34" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
896.5,608.5 800.5,416.5 704.5,608.5 608.5,416.5 512.5,608.5 416.5,416.5 320.5,608.5 224.5,416.5 128.5,608.5 "/>
</svg>

Before

Width:  |  Height:  |  Size: 646 B

View File

@ -3,6 +3,7 @@
<main v-if="$store.getters.isSignedIn">
<p class="fetching" v-if="fetching">読み込み中<mk-ellipsis/></p>
<x-form
class="form"
ref="form"
v-if="state == 'waiting'"
:session="session"
@ -26,7 +27,7 @@
<h1>サインインしてください</h1>
<mk-signin/>
</main>
<footer><img src="/assets/auth/logo.svg" alt="Misskey"/></footer>
<footer><img src="/assets/auth/icon.svg" alt="Misskey"/></footer>
</div>
</template>
@ -102,7 +103,7 @@ export default Vue.extend({
padding 32px
color #555
> div
> div:not(.form)
padding 64px
> h1
@ -143,8 +144,8 @@ export default Vue.extend({
> footer
> img
display block
width 64px
height 64px
margin 0 auto
width 32px
height 32px
margin 16px auto
</style>

View File

@ -9,9 +9,9 @@ export default function<T extends object>(data: {
widget: {
type: Object
},
isMobile: {
type: Boolean,
default: false
platform: {
type: String,
required: true
},
isCustomizeMode: {
type: Boolean,
@ -66,17 +66,10 @@ export default function<T extends object>(data: {
this.bakeProps();
if (this.isMobile) {
(this as any).api('i/update_mobile_home', {
id: this.id,
data: this.props
});
} else {
(this as any).api('i/update_home', {
id: this.id,
data: this.props
});
}
(this as any).api('i/update_widget', {
id: this.id,
data: this.props
});
}
}
});

View File

@ -1,5 +1,3 @@
import * as merge from 'object-assign-deep';
import Stream from './stream';
import StreamManager from './stream-manager';
import MiOS from '../../../mios';
@ -20,7 +18,7 @@ export class HomeStream extends Stream {
}, 1000 * 60);
// 自分の情報が更新されたとき
this.on('i_updated', i => {
this.on('meUpdated', i => {
if (os.debug) {
console.log('I updated:', i);
}
@ -60,25 +58,18 @@ export class HomeStream extends Stream {
});
this.on('home_updated', x => {
if (x.home) {
os.store.commit('settings/setHome', x.home);
} else {
os.store.commit('settings/setHomeWidget', {
id: x.id,
data: x.data
});
}
os.store.commit('settings/setHome', x);
});
this.on('mobile_home_updated', x => {
if (x.home) {
os.store.commit('settings/setMobileHome', x.home);
} else {
os.store.commit('settings/setMobileHomeWidget', {
id: x.id,
data: x.data
});
}
os.store.commit('settings/setMobileHome', x);
});
this.on('widgetUpdated', x => {
os.store.commit('settings/setWidget', {
id: x.id,
data: x.data
});
});
// トークンが再生成されたとき

View File

@ -1,12 +1,10 @@
<template>
<svg class="mk-analog-clock" viewBox="0 0 10 10" preserveAspectRatio="none">
<line v-for="angle, i in graduations"
:x1="5 + (Math.sin(angle) * (5 - graduationsPadding))"
:y1="5 - (Math.cos(angle) * (5 - graduationsPadding))"
:x2="5 + (Math.sin(angle) * ((5 - graduationsPadding) - (i % 5 == 0 ? longGraduationLength : shortGraduationLength)))"
:y2="5 - (Math.cos(angle) * ((5 - graduationsPadding) - (i % 5 == 0 ? longGraduationLength : shortGraduationLength)))"
:stroke="i % 5 == 0 ? longGraduationColor : shortGraduationColor"
stroke-width="0.05"/>
<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"/>
<line
:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
@ -43,14 +41,13 @@ export default Vue.extend({
default: false
}
},
data() {
return {
now: new Date(),
clock: null,
graduationsPadding: 0.5,
longGraduationLength: 0.3,
shortGraduationLength: 0.15,
handsPadding: 1,
handsTailLength: 0.7,
hHandLengthRatio: 0.75,
@ -58,11 +55,12 @@ export default Vue.extend({
sHandLengthRatio: 1
};
},
computed: {
longGraduationColor(): string {
majorGraduationColor(): string {
return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
},
shortGraduationColor(): string {
minorGraduationColor(): string {
return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
},
@ -106,12 +104,15 @@ export default Vue.extend({
return angles;
}
},
mounted() {
this.clock = setInterval(this.tick, 1000);
},
beforeDestroy() {
clearInterval(this.clock);
},
methods: {
tick() {
this.now = new Date();

View File

@ -1,6 +1,7 @@
import Vue from 'vue';
import analogClock from './analog-clock.vue';
import menu from './menu.vue';
import signin from './signin.vue';
import signup from './signup.vue';
import forkit from './forkit.vue';
@ -29,6 +30,7 @@ import Othello from './othello.vue';
import welcomeTimeline from './welcome-timeline.vue';
Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-menu', menu);
Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit);

View File

@ -0,0 +1,161 @@
<template>
<div class="mk-menu">
<div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact }" ref="popover">
<template v-for="item in items">
<div v-if="item == null"></div>
<button v-else @click="clicked(item.onClick)" v-html="item.content"></button>
</template>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as anime from 'animejs';
export default Vue.extend({
props: ['source', 'compact', 'items'],
mounted() {
this.$nextTick(() => {
const popover = this.$refs.popover as any;
const rect = this.source.getBoundingClientRect();
const width = popover.offsetWidth;
const height = popover.offsetHeight;
if (this.compact) {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
popover.style.left = (x - (width / 2)) + 'px';
popover.style.top = (y - (height / 2)) + 'px';
} else {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
popover.style.left = (x - (width / 2)) + 'px';
popover.style.top = y + 'px';
}
anime({
targets: this.$refs.backdrop,
opacity: 1,
duration: 100,
easing: 'linear'
});
anime({
targets: this.$refs.popover,
opacity: 1,
scale: [0.5, 1],
duration: 500
});
});
},
methods: {
clicked(fn) {
fn();
this.close();
},
close() {
(this.$refs.backdrop as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.backdrop,
opacity: 0,
duration: 200,
easing: 'linear'
});
(this.$refs.popover as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.popover,
opacity: 0,
scale: 0.5,
duration: 200,
easing: 'easeInBack',
complete: () => {
this.$emit('closed');
this.$destroy();
}
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
$border-color = rgba(27, 31, 35, 0.15)
.mk-menu
position initial
> .backdrop
position fixed
top 0
left 0
z-index 10000
width 100%
height 100%
background rgba(#000, 0.1)
opacity 0
> .popover
position absolute
z-index 10001
padding 8px 0
background #fff
border 1px solid $border-color
border-radius 4px
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
transform scale(0.5)
opacity 0
$balloon-size = 16px
&:not(.compact)
margin-top $balloon-size
transform-origin center -($balloon-size)
&:before
content ""
display block
position absolute
top -($balloon-size * 2)
left s('calc(50% - %s)', $balloon-size)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
border-bottom solid $balloon-size $border-color
&:after
content ""
display block
position absolute
top -($balloon-size * 2) + 1.5px
left s('calc(50% - %s)', $balloon-size)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
border-bottom solid $balloon-size #fff
> button
display block
padding 8px 16px
width 100%
&:hover
color $theme-color-foreground
background $theme-color
text-decoration none
&:active
color $theme-color-foreground
background darken($theme-color, 10%)
> div
margin 8px 0
height 1px
background #eee
</style>

View File

@ -1,55 +1,41 @@
<template>
<div class="mk-note-menu">
<div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact }" ref="popover">
<button @click="favorite">%i18n:@favorite%</button>
<button v-if="note.userId == $store.state.i.id" @click="pin">%i18n:@pin%</button>
<button v-if="note.userId == $store.state.i.id" @click="del">%i18n:@delete%</button>
<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a>
</div>
<div class="mk-note-menu" style="position:initial">
<mk-menu ref="menu" :source="source" :compact="compact" :items="items" @closed="$destroy"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as anime from 'animejs';
export default Vue.extend({
props: ['note', 'source', 'compact'],
mounted() {
this.$nextTick(() => {
const popover = this.$refs.popover as any;
const rect = this.source.getBoundingClientRect();
const width = popover.offsetWidth;
const height = popover.offsetHeight;
if (this.compact) {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
popover.style.left = (x - (width / 2)) + 'px';
popover.style.top = (y - (height / 2)) + 'px';
} else {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
popover.style.left = (x - (width / 2)) + 'px';
popover.style.top = y + 'px';
computed: {
items() {
const items = [];
items.push({
content: '%i18n:@favorite%',
onClick: this.favorite
});
if (this.note.userId == this.$store.state.i.id) {
items.push({
content: '%i18n:@pin%',
onClick: this.pin
});
items.push({
content: '%i18n:@delete%',
onClick: this.del
});
}
anime({
targets: this.$refs.backdrop,
opacity: 1,
duration: 100,
easing: 'linear'
});
anime({
targets: this.$refs.popover,
opacity: 1,
scale: [0.5, 1],
duration: 500
});
});
if (this.note.uri) {
items.push({
content: '%i18n:@remote%',
onClick: () => {
window.open(this.note.uri, '_blank');
}
});
}
return items;
}
},
methods: {
pin() {
@ -78,98 +64,8 @@ export default Vue.extend({
},
close() {
(this.$refs.backdrop as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.backdrop,
opacity: 0,
duration: 200,
easing: 'linear'
});
(this.$refs.popover as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.popover,
opacity: 0,
scale: 0.5,
duration: 200,
easing: 'easeInBack',
complete: () => this.$destroy()
});
this.$refs.menu.close();
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
$border-color = rgba(27, 31, 35, 0.15)
.mk-note-menu
position initial
> .backdrop
position fixed
top 0
left 0
z-index 10000
width 100%
height 100%
background rgba(#000, 0.1)
opacity 0
> .popover
position absolute
z-index 10001
padding 8px 0
background #fff
border 1px solid $border-color
border-radius 4px
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
transform scale(0.5)
opacity 0
$balloon-size = 16px
&:not(.compact)
margin-top $balloon-size
transform-origin center -($balloon-size)
&:before
content ""
display block
position absolute
top -($balloon-size * 2)
left s('calc(50% - %s)', $balloon-size)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
border-bottom solid $balloon-size $border-color
&:after
content ""
display block
position absolute
top -($balloon-size * 2) + 1.5px
left s('calc(50% - %s)', $balloon-size)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
border-bottom solid $balloon-size #fff
> button
> a
display block
padding 8px 16px
width 100%
&:hover
color $theme-color-foreground
background $theme-color
text-decoration none
&:active
color $theme-color-foreground
background darken($theme-color, 10%)
</style>

View File

@ -2,7 +2,7 @@
<div class="mkw-broadcast"
:data-found="broadcasts.length != 0"
:data-melt="props.design == 1"
:data-mobile="isMobile"
:data-mobile="platform == 'mobile'"
>
<div class="icon">
<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">

View File

@ -1,5 +1,5 @@
<template>
<div class="mkw-calendar" :data-special="special" :data-mobile="isMobile">
<div class="mkw-calendar" :data-special="special" :data-mobile="platform == 'mobile'">
<mk-widget-container :naked="props.design == 1" :show-header="false">
<div class="mkw-calendar--body">
<div class="calendar" :data-is-holiday="isHoliday">
@ -67,7 +67,7 @@ export default define({
},
methods: {
func() {
if (this.isMobile) return;
if (this.platform == 'mobile') return;
if (this.props.design == 2) {
this.props.design = 0;
} else {

View File

@ -1,5 +1,5 @@
<template>
<div class="mkw-donation" :data-mobile="isMobile">
<div class="mkw-donation" :data-mobile="platform == 'mobile'">
<article>
<h1>%fa:heart%%i18n:@title%</h1>
<p>

View File

@ -73,6 +73,7 @@ root(isDark)
background isDark ? #282c37 : #fff
border none
border-bottom solid 1px isDark ? #1c2023 : #eee
border-radius 0
> button
display block

View File

@ -4,7 +4,7 @@
<template slot="header">%fa:rss-square%RSS</template>
<button slot="func" title="設定" @click="setting">%fa:cog%</button>
<div class="mkw-rss--body" :data-mobile="isMobile">
<div class="mkw-rss--body" :data-mobile="platform == 'mobile'">
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div class="feed" v-else>
<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>

View File

@ -1,5 +1,5 @@
<template>
<div class="mkw-slideshow" :data-mobile="isMobile">
<div class="mkw-slideshow" :data-mobile="platform == 'mobile'">
<div @click="choose">
<p v-if="props.folder === undefined">
<template v-if="isCustomizeMode">フォルダを指定するにはカスタマイズモードを終了してください</template>

View File

@ -6,7 +6,7 @@ export default (os: OS) => opts => {
const o = opts || {};
if (o.renote) {
const vm = os.new(RenoteFormWindow, {
renote: o.renote
note: o.renote
});
document.body.appendChild(vm.$el);
} else {

View File

@ -23,6 +23,7 @@ import updateAvatar from './api/update-avatar';
import updateBanner from './api/update-banner';
import MkIndex from './views/pages/index.vue';
import MkDeck from './views/pages/deck/deck.vue';
import MkUser from './views/pages/user/user.vue';
import MkFavorites from './views/pages/favorites.vue';
import MkSelectDrive from './views/pages/selectdrive.vue';
@ -50,6 +51,7 @@ init(async (launch) => {
mode: 'history',
routes: [
{ path: '/', name: 'index', component: MkIndex },
{ path: '/deck', name: 'deck', component: MkDeck },
{ path: '/i/customize-home', component: MkHomeCustomize },
{ path: '/i/favorites', component: MkFavorites },
{ path: '/i/messaging/:user', component: MkMessagingRoom },

View File

@ -42,8 +42,3 @@ html
&:active
background-color $theme-color
body
display flex
flex-direction column
min-height 100%

View File

@ -62,7 +62,7 @@ export default Vue.extend({
onContextmenu(e) {
this.isContextmenuShowing = true;
contextmenu(e, [{
contextmenu((this as any).os)(e, [{
type: 'item',
text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%',

View File

@ -52,7 +52,7 @@ export default Vue.extend({
onContextmenu(e) {
this.isContextmenuShowing = true;
contextmenu(e, [{
contextmenu((this as any).os)(e, [{
type: 'item',
text: '%i18n:@contextmenu.move-to-this-folder%',
icon: '%fa:arrow-right%',

View File

@ -136,7 +136,7 @@ export default Vue.extend({
},
methods: {
onContextmenu(e) {
contextmenu(e, [{
contextmenu((this as any).os)(e, [{
type: 'item',
text: '%i18n:@contextmenu.create-folder%',
icon: '%fa:R folder%',

View File

@ -1,19 +1,16 @@
<template>
<button class="mk-follow-button"
:class="{ wait, follow: !user.isFollowing, unfollow: user.isFollowing, big: size == 'big' }"
:class="{ wait, active: u.isFollowing || u.hasPendingFollowRequestFromYou, big: size == 'big' }"
@click="onClick"
:disabled="wait"
:title="user.isFollowing ? '%i18n:@unfollow%' : '%i18n:@follow%'"
>
<template v-if="!wait && user.isFollowing">
<template v-if="size == 'compact'">%fa:minus%</template>
<template v-if="size == 'big'">%fa:minus%%i18n:@unfollow%</template>
<template v-if="!wait">
<template v-if="u.hasPendingFollowRequestFromYou">%fa:hourglass-half%<template v-if="size == 'big'"> %i18n:@request-pending%</template></template>
<template v-else-if="u.isFollowing">%fa:minus%<template v-if="size == 'big'"> %i18n:@following%</template></template>
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow-request%</template></template>
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow%</template></template>
</template>
<template v-if="!wait && !user.isFollowing">
<template v-if="size == 'compact'">%fa:plus%</template>
<template v-if="size == 'big'">%fa:plus%%i18n:@follow%</template>
</template>
<template v-if="wait">%fa:spinner .pulse .fw%</template>
<template v-else>%fa:spinner .pulse .fw%</template>
</button>
</template>
@ -34,6 +31,7 @@ export default Vue.extend({
data() {
return {
u: this.user,
wait: false,
connection: null,
connectionId: null
@ -56,39 +54,44 @@ export default Vue.extend({
methods: {
onFollow(user) {
if (user.id == this.user.id) {
if (user.id == this.u.id) {
this.user.isFollowing = user.isFollowing;
}
},
onUnfollow(user) {
if (user.id == this.user.id) {
if (user.id == this.u.id) {
this.user.isFollowing = user.isFollowing;
}
},
onClick() {
async onClick() {
this.wait = true;
if (this.user.isFollowing) {
(this as any).api('following/delete', {
userId: this.user.id
}).then(() => {
this.user.isFollowing = false;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
});
} else {
(this as any).api('following/create', {
userId: this.user.id
}).then(() => {
this.user.isFollowing = true;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
});
try {
if (this.u.isFollowing) {
this.u = await (this as any).api('following/delete', {
userId: this.u.id
});
} else {
if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) {
this.u = await (this as any).api('following/requests/cancel', {
userId: this.u.id
});
} else if (this.u.isLocked) {
this.u = await (this as any).api('following/create', {
userId: this.u.id
});
} else {
this.u = await (this as any).api('following/create', {
userId: this.user.id
});
}
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
@ -124,7 +127,7 @@ root(isDark)
border 2px solid rgba($theme-color, 0.3)
border-radius 8px
&.follow
&:not(.active)
color isDark ? #fff : #888
background isDark ? linear-gradient(to bottom, #313543 0%, #282c37 100%) : linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%)
border solid 1px isDark ? #1c2023 : #e2e2e2
@ -137,7 +140,7 @@ root(isDark)
background isDark ? #22262f : #ececec
border-color isDark ? #151a1d : #dcdcdc
&.unfollow
&.active
color $theme-color-foreground
background linear-gradient(to bottom, lighten($theme-color, 25%) 0%, lighten($theme-color, 10%) 100%)
border solid 1px lighten($theme-color, 15%)
@ -162,9 +165,6 @@ root(isDark)
height 38px
line-height 38px
i
margin-right 8px
.mk-follow-button[data-darkmode]
root(true)

View File

@ -47,7 +47,7 @@
:key="place"
>
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true"/>
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
</div>
</x-draggable>
<div class="main">
@ -60,12 +60,11 @@
</template>
<template v-else>
<div v-for="place in ['left', 'right']" :class="place">
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/>
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp" platform="desktop"/>
</div>
<div class="main">
<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
<mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/>
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<mk-timeline class="tl" cref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
</div>
</template>
</div>
@ -77,6 +76,50 @@ import Vue from 'vue';
import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid';
const defaultDesktopHomeWidgets = {
left: [
'profile',
'calendar',
'activity',
'rss',
'trends',
'photo-stream',
'version'
],
right: [
'broadcast',
'notifications',
'users',
'polls',
'server',
'donation',
'nav',
'tips'
]
};
//#region Construct home data
const _defaultDesktopHomeWidgets = [];
defaultDesktopHomeWidgets.left.forEach(widget => {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'left',
data: {}
});
});
defaultDesktopHomeWidgets.right.forEach(widget => {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'right',
data: {}
});
});
//#endregion
export default Vue.extend({
components: {
XDraggable
@ -104,7 +147,7 @@ export default Vue.extend({
computed: {
home(): any[] {
return this.$store.state.settings.home;
return this.$store.state.settings.home || [];
},
left(): any[] {
return this.home.filter(w => w.place == 'left');
@ -120,6 +163,16 @@ export default Vue.extend({
}
},
created() {
if (this.$store.state.settings.home == null) {
this.api('i/update_home', {
home: _defaultDesktopHomeWidgets
}).then(() => {
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
});
}
},
mounted() {
this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use();
@ -299,11 +352,18 @@ root(isDark)
width calc(100% - 275px * 2)
order 2
.mk-post-form
> .form
margin-bottom 16px
border solid 1px rgba(#000, 0.075)
border-radius 4px
@media (max-width 700px)
padding 0
> .tl
border none
border-radius 0
> *:not(.main)
width 275px
padding 16px 0 16px 0

View File

@ -463,7 +463,7 @@ root(isDark)
> .app
margin-right 8px
padding-right 8px
border-right solid 1px #eaeaea
border-right solid 1px isDark ? #1c2023 : #eaeaea
> .visibility
margin-left 8px
@ -552,7 +552,7 @@ root(isDark)
padding 2px 8px 2px 16px
font-size 90%
color #8d969e
background #edf0f3
background isDark ? #313543 : #edf0f3
border-radius 4px
&:before
@ -565,7 +565,7 @@ root(isDark)
width 8px
height 8px
margin auto 0
background #fff
background isDark ? #282c37 : #fff
border-radius 100%
&:hover

View File

@ -74,7 +74,7 @@ export default Vue.extend({
mounted() {
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
window.addEventListener('scroll', this.onScroll);
window.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {

View File

@ -5,6 +5,7 @@
<template v-for="(notification, i) in _notifications">
<div class="notification" :class="notification.type" :key="notification.id">
<mk-time :time="notification.createdAt"/>
<template v-if="notification.type == 'reaction'">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
@ -17,6 +18,7 @@
</router-link>
</div>
</template>
<template v-if="notification.type == 'renote'">
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
@ -28,6 +30,7 @@
</router-link>
</div>
</template>
<template v-if="notification.type == 'quote'">
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
@ -37,6 +40,7 @@
<router-link class="note-preview" :to="notification.note | notePage">{{ getNoteSummary(notification.note) }}</router-link>
</div>
</template>
<template v-if="notification.type == 'follow'">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
@ -45,6 +49,16 @@
</p>
</div>
</template>
<template v-if="notification.type == 'receiveFollowRequest'">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
<p>%fa:user-clock%
<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link>
</p>
</div>
</template>
<template v-if="notification.type == 'reply'">
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
@ -54,6 +68,7 @@
<router-link class="note-preview" :to="notification.note | notePage">{{ getNoteSummary(notification.note) }}</router-link>
</div>
</template>
<template v-if="notification.type == 'mention'">
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
@ -63,6 +78,7 @@
<a class="note-preview" :href="notification.note | notePage">{{ getNoteSummary(notification.note) }}</a>
</div>
</template>
<template v-if="notification.type == 'poll_vote'">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
@ -73,6 +89,7 @@
</div>
</template>
</div>
<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
<span>%fa:angle-up%{{ notification._datetext }}</span>
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
@ -189,7 +206,7 @@ root(isDark)
margin 0
padding 16px
overflow-wrap break-word
font-size 0.9em
font-size 12px
border-bottom solid 1px isDark ? #1c2023 : rgba(#000, 0.05)
&:last-child
@ -251,6 +268,10 @@ root(isDark)
.text p i
color #53c7ce
&.receiveFollowRequest
.text p i
color #888
&.reply, &.mention
.text p i
color #555

View File

@ -1,21 +1,23 @@
<template>
<mk-window ref="window" is-modal @closed="$destroy">
<span slot="header">
<span :class="$style.icon" v-if="geo">%fa:map-marker-alt%</span>
<mk-window class="mk-post-form-window" ref="window" is-modal @closed="$destroy">
<span slot="header" class="mk-post-form-window--header">
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
<span v-if="!reply">%i18n:@note%</span>
<span v-if="reply">%i18n:@reply%</span>
<span :class="$style.count" v-if="media.length != 0">{{ '%i18n:@attaches%'.replace('{}', media.length) }}</span>
<span :class="$style.count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
<span class="count" v-if="media.length != 0">{{ '%i18n:@attaches%'.replace('{}', media.length) }}</span>
<span class="count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
</span>
<mk-note-preview v-if="reply" :class="$style.notePreview" :note="reply"/>
<mk-post-form ref="form"
:reply="reply"
@posted="onPosted"
@change-uploadings="onChangeUploadings"
@change-attached-media="onChangeMedia"
@geo-attached="onGeoAttached"
@geo-dettached="onGeoDettached"/>
<div class="mk-post-form-window--body">
<mk-note-preview v-if="reply" class="notePreview" :note="reply"/>
<mk-post-form ref="form"
:reply="reply"
@posted="onPosted"
@change-uploadings="onChangeUploadings"
@change-attached-media="onChangeMedia"
@geo-attached="onGeoAttached"
@geo-dettached="onGeoDettached"/>
</div>
</mk-window>
</template>
@ -56,21 +58,33 @@ export default Vue.extend({
});
</script>
<style lang="stylus" module>
.icon
margin-right 8px
<style lang="stylus" scoped>
root(isDark)
.mk-post-form-window--header
.icon
margin-right 8px
.count
margin-left 8px
opacity 0.8
.count
margin-left 8px
opacity 0.8
&:before
content '('
&:before
content '('
&:after
content ')'
&:after
content ')'
.notePreview
margin 16px 22px 0 22px
.mk-post-form-window--body
.notePreview
if isDark
margin 16px 22px 0 22px
else
margin 16px 22px
.mk-post-form-window[data-darkmode]
root(true)
.mk-post-form-window:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,72 @@
<template>
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
<span slot="header">%fa:envelope R% %i18n:@title%</span>
<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
<div v-for="req in requests">
<router-link :key="req.id" :to="req.follower | userPage">{{ req.follower | userName }}</router-link>
<span>
<a @click="accept(req.follower)">%i18n:@accept%</a>|<a @click="reject(req.follower)">%i18n:@reject%</a>
</span>
</div>
</div>
</mk-window>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
data() {
return {
fetching: true,
requests: []
};
},
mounted() {
(this as any).api('following/requests/list').then(requests => {
this.fetching = false;
this.requests = requests;
});
},
methods: {
accept(user) {
(this as any).api('following/requests/accept', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
},
reject(user) {
(this as any).api('following/requests/reject', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
},
close() {
(this as any).$refs.window.close();
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
padding 16px
> button
margin-bottom 16px
> div
display flex
padding 16px
border solid 1px isDark ? #1c2023 : #eee
border-radius 4px
> span
margin 0 0 0 auto
.slpqaxdoxhvglersgjukmvizkqbmbokc[data-darkmode]
root(true)
.slpqaxdoxhvglersgjukmvizkqbmbokc:not([data-darkmode])
root(false)
</style>

View File

@ -23,7 +23,11 @@
</label>
<button class="ui primary" @click="save">%i18n:@save%</button>
<section>
<h2>その他</h2>
<h2>%i18n:@locked-account%</h2>
<mk-switch v-model="$store.state.i.isLocked" @change="onChangeIsLocked" text="%i18n:@is-locked%"/>
</section>
<section>
<h2>%i18n:@other%</h2>
<mk-switch v-model="$store.state.i.isBot" @change="onChangeIsBot" text="%i18n:@is-bot%"/>
<mk-switch v-model="$store.state.i.isCat" @change="onChangeIsCat" text="%i18n:@is-cat%"/>
</section>
@ -62,6 +66,11 @@ export default Vue.extend({
(this as any).apis.notify('プロフィールを更新しました');
});
},
onChangeIsLocked() {
(this as any).api('i/update', {
isLocked: this.$store.state.i.isLocked
});
},
onChangeIsBot() {
(this as any).api('i/update', {
isBot: this.$store.state.i.isBot

View File

@ -40,6 +40,8 @@
<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">%i18n:@customize%</button>
</div>
<div class="div">
<button class="ui" @click="updateWallpaper">%i18n:@choose-wallpaper%</button>
<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
<mk-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
@ -293,6 +295,20 @@ export default Vue.extend({
this.$router.push('/i/customize-home');
this.$emit('done');
},
updateWallpaper() {
(this as any).apis.chooseDriveFile({
multiple: false
}).then(file => {
(this as any).api('i/update', {
wallpaperId: file.id
});
});
},
deleteWallpaper() {
(this as any).api('i/update', {
wallpaperId: null
});
},
onChangeFetchOnScroll(v) {
this.$store.dispatch('settings/set', {
key: 'fetchOnScroll',

View File

@ -19,6 +19,9 @@
<li @click="list">
<p>%fa:list%<span>%i18n:@lists%</span>%fa:angle-right%</p>
</li>
<li @click="followRequests" v-if="$store.state.i.isLocked">
<p>%fa:envelope R%<span>%i18n:@follow-requests%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>%fa:angle-right%</p>
</li>
</ul>
<ul>
<li>
@ -46,6 +49,7 @@
<script lang="ts">
import Vue from 'vue';
import MkUserListsWindow from './user-lists-window.vue';
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
import MkSettingsWindow from './settings-window.vue';
import MkDriveWindow from './drive-window.vue';
import contains from '../../../common/scripts/contains';
@ -91,6 +95,10 @@ export default Vue.extend({
this.$router.push(`i/lists/${ list.id }`);
});
},
followRequests() {
this.close();
(this as any).os.new(MkFollowRequestsWindow);
},
settings() {
this.close();
(this as any).os.new(MkSettingsWindow);
@ -225,6 +233,16 @@ root(isDark)
> span:first-child
padding-left 22px
> span:nth-child(2)
> i
margin-left 4px
padding 2px 8px
font-size 90%
font-style normal
background $theme-color
color $theme-color-foreground
border-radius 8px
> [data-fa]:first-child
margin-right 6px
width 16px

View File

@ -8,6 +8,12 @@
<p>%i18n:@home%</p>
</router-link>
</li>
<li class="deck" :class="{ active: $route.name == 'deck' }">
<router-link to="/deck">
%fa:columns%
<p>%i18n:@deck% <small>(beta)</small></p>
</router-link>
</li>
<li class="messaging">
<a @click="messaging">
%fa:comments%

View File

@ -1,6 +1,6 @@
<template>
<div>
<x-header/>
<div class="mk-ui" :style="style">
<x-header class="header"/>
<div class="content">
<slot></slot>
</div>
@ -16,6 +16,15 @@ export default Vue.extend({
components: {
XHeader
},
computed: {
style(): any {
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
return {
backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null,
backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
};
}
},
mounted() {
document.addEventListener('keydown', this.onKeydown);
},
@ -35,3 +44,22 @@ export default Vue.extend({
});
</script>
<style lang="stylus" scoped>
.mk-ui
display flex
flex-direction column
flex 1
background-size cover
background-position center
background-attachment fixed
> .header
@media (max-width 1000px)
display none
> .content
display flex
flex-direction column
flex 1
overflow hidden
</style>

View File

@ -1,8 +1,8 @@
<template>
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
<span slot="header">%fa:list% リスト</span>
<span slot="header">%fa:list% %i18n:@title%</span>
<div data-id="6e4caea3-d8f9-4ab7-96de-ab67fe8d5c82" :data-darkmode="$store.state.device.darkmode">
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
<button class="ui" @click="add">%i18n:@create-list%</button>
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.title }}</a>
</div>
@ -60,10 +60,10 @@ root(isDark)
border solid 1px isDark ? #1c2023 : #eee
border-radius 4px
[data-id="6e4caea3-d8f9-4ab7-96de-ab67fe8d5c82"][data-darkmode]
.xkxvokkjlptzyewouewmceqcxhpgzprp[data-darkmode]
root(true)
[data-id="6e4caea3-d8f9-4ab7-96de-ab67fe8d5c82"]:not([data-darkmode])
.xkxvokkjlptzyewouewmceqcxhpgzprp:not([data-darkmode])
root(false)
</style>

View File

@ -36,7 +36,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
root(isDark)
background isDark ? #282C37 : #fff
border solid 1px rgba(#000, 0.075)
border solid 1px rgba(#000, isDark ? 0.2 : 0.075)
border-radius 6px
overflow hidden

View File

@ -0,0 +1,195 @@
<template>
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow }">
<header :class="{ indicate }">
<slot name="header"></slot>
<button ref="menu" @click="showMenu">%fa:caret-down%</button>
</header>
<div ref="body">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Menu from '../../../../common/views/components/menu.vue';
export default Vue.extend({
props: {
id: {
type: String,
required: false
},
name: {
type: String,
required: false
},
menu: {
type: Array,
required: false
},
naked: {
type: Boolean,
required: false,
default: false
},
narrow: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
indicate: false
};
},
provide() {
return {
column: this,
isScrollTop: this.isScrollTop,
indicate: v => this.indicate = v
};
},
mounted() {
this.$refs.body.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {
this.$refs.body.removeEventListener('scroll', this.onScroll);
},
methods: {
isScrollTop() {
return this.$refs.body.scrollTop == 0;
},
onScroll() {
if (this.isScrollTop()) {
this.$emit('top');
}
if (this.$store.state.settings.fetchOnScroll !== false) {
const current = this.$refs.body.scrollTop + this.$refs.body.clientHeight;
if (current > this.$refs.body.scrollHeight - 1) this.$emit('bottom');
}
},
showMenu() {
const items = [{
content: '%fa:pencil-alt% %i18n:common.deck.rename%',
onClick: () => {
(this as any).apis.input({
title: '%i18n:common.deck.rename%',
default: this.name,
allowEmpty: false
}).then(name => {
this.$store.dispatch('settings/renameDeckColumn', { id: this.id, name });
});
}
}, null, {
content: '%fa:arrow-left% %i18n:common.deck.swap-left%',
onClick: () => {
this.$store.dispatch('settings/swapLeftDeckColumn', this.id);
}
}, {
content: '%fa:arrow-right% %i18n:common.deck.swap-right%',
onClick: () => {
this.$store.dispatch('settings/swapRightDeckColumn', this.id);
}
}, null, {
content: '%fa:trash-alt R% %i18n:common.deck.remove%',
onClick: () => {
this.$store.dispatch('settings/removeDeckColumn', this.id);
}
}];
if (this.menu) {
items.unshift(null);
this.menu.reverse().forEach(i => items.unshift(i));
}
this.os.new(Menu, {
source: this.$refs.menu,
compact: false,
items
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
$header-height = 42px
flex 1
min-width 330px
max-width 330px
height 100%
background isDark ? #282C37 : #fff
border-radius 6px
box-shadow 0 2px 16px rgba(#000, 0.1)
overflow hidden
&.narrow
min-width 285px
max-width 285px
&.naked
background rgba(#000, isDark ? 0.25 : 0.1)
> header
background transparent
box-shadow none
if !isDark
> button
color #bbb
> header
z-index 1
line-height $header-height
padding 0 16px
font-size 14px
color isDark ? #e3e5e8 : #888
background isDark ? #313543 : #fff
box-shadow 0 1px rgba(#000, 0.15)
&.indicate
box-shadow 0 3px 0 0 $theme-color
> span
[data-fa]
margin-right 8px
> button
position absolute
top 0
right 0
width $header-height
line-height $header-height
color isDark ? #9baec8 : #ccc
&:hover
color isDark ? #b2c1d5 : #aaa
&:active
color isDark ? #b2c1d5 : #999
> div
height "calc(100% - %s)" % $header-height
overflow auto
overflow-x hidden
.dnpfarvgbnfmyzbdquhhzyxcmstpdqzs[data-darkmode]
root(true)
.dnpfarvgbnfmyzbdquhhzyxcmstpdqzs:not([data-darkmode])
root(false)
</style>

View File

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

View File

@ -0,0 +1,153 @@
<template>
<div class="fnlfosztlhtptnongximhlbykxblytcq">
<mk-avatar class="avatar" :user="note.user"/>
<div class="main">
<header>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
<span class="is-admin" v-if="note.user.isAdmin">%i18n:@admin%</span>
<span class="is-bot" v-if="note.user.isBot">%i18n:@bot%</span>
<span class="is-cat" v-if="note.user.isCat">%i18n:@cat%</span>
<span class="username"><mk-acct :user="note.user"/></span>
<div class="info">
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
<router-link class="created-at" :to="note | notePage">
<mk-time :time="note.createdAt"/>
</router-link>
<span class="visibility" v-if="note.visibility != 'public'">
<template v-if="note.visibility == 'home'">%fa:home%</template>
<template v-if="note.visibility == 'followers'">%fa:unlock%</template>
<template v-if="note.visibility == 'specified'">%fa:envelope%</template>
<template v-if="note.visibility == 'private'">%fa:lock%</template>
</span>
</div>
</header>
<div class="body">
<mk-sub-note-content class="text" :note="note"/>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
note: {
type: Object,
required: true
},
// TODO
truncate: {
type: Boolean,
default: true
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
display flex
padding 16px
font-size 10px
background isDark ? #21242d : #fcfcfc
&.smart
> .main
width 100%
> header
align-items center
> .avatar
flex-shrink 0
display block
margin 0 8px 0 0
width 38px
height 38px
border-radius 8px
> .main
flex 1
min-width 0
> header
display flex
align-items baseline
margin-bottom 2px
white-space nowrap
> .avatar
flex-shrink 0
margin-right 8px
width 18px
height 18px
border-radius 100%
> .name
display block
margin 0 0.5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #607073
font-size 1em
font-weight 700
text-align left
text-decoration none
text-overflow ellipsis
&:hover
text-decoration underline
> .is-admin
> .is-bot
> .is-cat
align-self center
margin 0 0.5em 0 0
padding 1px 5px
font-size 0.8em
color isDark ? #758188 : #aaa
border solid 1px isDark ? #57616f : #ddd
border-radius 3px
&.is-admin
border-color isDark ? #d42c41 : #f56a7b
color isDark ? #d42c41 : #f56a7b
> .username
text-align left
margin 0
color isDark ? #606984 : #d1d8da
> .info
margin-left auto
font-size 0.9em
> *
color isDark ? #606984 : #b2b8bb
> .mobile
margin-right 6px
> .visibility
margin-left 6px
> .body
> .text
margin 0
padding 0
color isDark ? #959ba7 : #717171
pre
max-height 120px
font-size 80%
.fnlfosztlhtptnongximhlbykxblytcq[data-darkmode]
root(true)
.fnlfosztlhtptnongximhlbykxblytcq:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,513 @@
<template>
<div class="zyjjkidcqjnlegkqebitfviomuqmseqk" :class="{ renote: isRenote }">
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
<x-sub :note="p.reply"/>
</div>
<div class="renote" v-if="isRenote">
<mk-avatar class="avatar" :user="note.user"/>
%fa:retweet%
<span>{{ '%i18n:@reposted-by%'.substr(0, '%i18n:@reposted-by%'.indexOf('{')) }}</span>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
<span>{{ '%i18n:@reposted-by%'.substr('%i18n:@reposted-by%'.indexOf('}') + 1) }}</span>
<mk-time :time="note.createdAt"/>
</div>
<article>
<mk-avatar class="avatar" :user="p.user"/>
<div class="main">
<header>
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
<span class="is-admin" v-if="p.user.isAdmin">admin</span>
<span class="is-bot" v-if="p.user.isBot">bot</span>
<span class="is-cat" v-if="p.user.isCat">cat</span>
<span class="username"><mk-acct :user="p.user"/></span>
<div class="info">
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
<router-link class="created-at" :to="p | notePage">
<mk-time :time="p.createdAt"/>
</router-link>
<span class="visibility" v-if="p.visibility != 'public'">
<template v-if="p.visibility == 'home'">%fa:home%</template>
<template v-if="p.visibility == 'followers'">%fa:unlock%</template>
<template v-if="p.visibility == 'specified'">%fa:envelope%</template>
<template v-if="p.visibility == 'private'">%fa:lock%</template>
</span>
</div>
</header>
<div class="body">
<p v-if="p.cw != null" class="cw">
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@less%' : '%i18n:@more%' }}</span>
</p>
<div class="content" v-show="p.cw == null || showContent">
<div class="text">
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
<a class="reply" v-if="p.reply">%fa:reply%</a>
<mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="$store.state.i"/>
<a class="rp" v-if="p.renote != null">RP:</a>
</div>
<div class="media" v-if="p.media.length > 0">
<mk-media-list :media-list="p.media"/>
</div>
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
<div class="tags" v-if="p.tags && p.tags.length > 0">
<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
</div>
<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
<div class="renote" v-if="p.renote">
<mk-note-preview :note="p.renote"/>
</div>
</div>
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
</div>
<footer>
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
<button @click="reply">
<template v-if="p.reply">%fa:reply-all%</template>
<template v-else>%fa:reply%</template>
</button>
<button @click="renote" title="Renote">%fa:retweet%</button>
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">%fa:plus%</button>
<button class="menu" @click="menu" ref="menuButton">%fa:ellipsis-h%</button>
</footer>
</div>
</article>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import parse from '../../../../../../text/parse';
import canHideText from '../../../../common/scripts/can-hide-text';
import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../../common/views/components/reaction-picker.vue';
import XSub from './deck.note.sub.vue';
export default Vue.extend({
components: {
XSub
},
props: ['note'],
data() {
return {
showContent: false,
connection: null,
connectionId: null
};
},
computed: {
isRenote(): boolean {
return (this.note.renote &&
this.note.text == null &&
this.note.mediaIds.length == 0 &&
this.note.poll == null);
},
p(): any {
return this.isRenote ? this.note.renote : this.note;
},
urls(): string[] {
if (this.p.text) {
const ast = parse(this.p.text);
return ast
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
.map(t => t.url);
} else {
return null;
}
}
},
created() {
if (this.$store.getters.isSignedIn) {
this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use();
}
},
mounted() {
this.capture(true);
if (this.$store.getters.isSignedIn) {
this.connection.on('_connected_', this.onStreamConnected);
}
},
beforeDestroy() {
this.decapture(true);
if (this.$store.getters.isSignedIn) {
this.connection.off('_connected_', this.onStreamConnected);
(this as any).os.stream.dispose(this.connectionId);
}
},
methods: {
canHideText,
capture(withHandler = false) {
if (this.$store.getters.isSignedIn) {
this.connection.send({
type: 'capture',
id: this.p.id
});
if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
}
},
decapture(withHandler = false) {
if (this.$store.getters.isSignedIn) {
this.connection.send({
type: 'decapture',
id: this.p.id
});
if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
}
},
onStreamConnected() {
this.capture();
},
onStreamNoteUpdated(data) {
const note = data.note;
if (note.id == this.note.id) {
this.$emit('update:note', note);
} else if (note.id == this.note.renoteId) {
this.note.renote = note;
}
},
reply() {
(this as any).apis.post({
reply: this.p
});
},
renote() {
(this as any).apis.post({
renote: this.p
});
},
react() {
(this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton,
note: this.p,
compact: true
});
},
menu() {
(this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton,
note: this.p,
compact: true
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
font-size 12px
border-bottom solid 1px isDark ? #1c2023 : #eaeaea
&:last-of-type
border-bottom none
&.smart
> article
> .main
> header
align-items center
margin-bottom 4px
> .renote
display flex
align-items center
padding 8px 16px
line-height 28px
white-space pre
color #9dbb00
background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%)
.avatar
flex-shrink 0
display inline-block
width 20px
height 20px
margin 0 8px 0 0
border-radius 6px
[data-fa]
margin-right 4px
> span
flex-shrink 0
&:last-of-type
margin-right 8px
.name
overflow hidden
flex-shrink 1
text-overflow ellipsis
white-space nowrap
font-weight bold
> .mk-time
display block
margin-left auto
flex-shrink 0
font-size 0.9em
& + article
padding-top 8px
> article
display flex
padding 16px 16px 9px
> .avatar
flex-shrink 0
display block
margin 0 10px 8px 0
width 42px
height 42px
border-radius 6px
//position -webkit-sticky
//position sticky
//top 62px
> .main
flex 1
min-width 0
> header
display flex
align-items baseline
white-space nowrap
> .avatar
flex-shrink 0
margin-right 8px
width 20px
height 20px
border-radius 100%
> .name
display block
margin 0 0.5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #627079
font-weight bold
text-decoration none
text-overflow ellipsis
> .is-admin
> .is-bot
> .is-cat
align-self center
margin 0 0.5em 0 0
padding 1px 6px
font-size 0.8em
color isDark ? #758188 : #aaa
border solid 1px isDark ? #57616f : #ddd
border-radius 3px
&.is-admin
border-color isDark ? #d42c41 : #f56a7b
color isDark ? #d42c41 : #f56a7b
> .username
margin 0 0.5em 0 0
overflow hidden
text-overflow ellipsis
color isDark ? #606984 : #ccc
> .info
margin-left auto
font-size 0.9em
> *
color isDark ? #606984 : #c0c0c0
> .mobile
margin-right 6px
> .visibility
margin-left 6px
> .body
> .cw
cursor default
display block
margin 0
padding 0
overflow-wrap break-word
color isDark ? #fff : #717171
> .text
margin-right 8px
> .toggle
display inline-block
padding 4px 8px
font-size 0.7em
color isDark ? #393f4f : #fff
background isDark ? #687390 : #b1b9c1
border-radius 2px
cursor pointer
user-select none
&:hover
background isDark ? #707b97 : #bbc4ce
> .content
> .text
display block
margin 0
padding 0
overflow-wrap break-word
color isDark ? #fff : #717171
>>> .title
display block
margin-bottom 4px
padding 4px
font-size 90%
text-align center
background isDark ? #2f3944 : #eef1f3
border-radius 4px
>>> .code
margin 8px 0
>>> .quote
margin 8px
padding 6px 12px
color isDark ? #6f808e : #aaa
border-left solid 3px isDark ? #637182 : #eee
> .reply
margin-right 8px
color isDark ? #99abbf : #717171
> .rp
margin-left 4px
font-style oblique
color #a0bf46
[data-is-me]:after
content "you"
padding 0 4px
margin-left 4px
font-size 80%
color $theme-color-foreground
background $theme-color
border-radius 4px
.mk-url-preview
margin-top 8px
> .tags
margin 4px 0 0 0
> *
display inline-block
margin 0 8px 0 0
padding 2px 8px 2px 16px
font-size 90%
color #8d969e
background isDark ? #313543 : #edf0f3
border-radius 4px
&:before
content ""
display block
position absolute
top 0
bottom 0
left 4px
width 8px
height 8px
margin auto 0
background isDark ? #282c37 : #fff
border-radius 100%
> .media
> img
display block
max-width 100%
> .location
margin 4px 0
font-size 12px
color #ccc
> .map
width 100%
height 200px
&:empty
display none
> .mk-poll
font-size 80%
> .renote
margin 8px 0
> .mk-note-preview
padding 16px
border dashed 1px isDark ? #4e945e : #c0dac6
border-radius 8px
> .app
font-size 12px
color #ccc
> footer
> button
margin 0
padding 8px
background transparent
border none
box-shadow none
font-size 1em
color isDark ? #606984 : #ddd
cursor pointer
&:not(:last-child)
margin-right 28px
&:hover
color isDark ? #9198af : #666
> .count
display inline
margin 0 0 0 8px
color #999
&.reacted
color $theme-color
.zyjjkidcqjnlegkqebitfviomuqmseqk[data-darkmode]
root(true)
.zyjjkidcqjnlegkqebitfviomuqmseqk:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,236 @@
<template>
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
<div v-if="!fetching && requestInitPromise != null">
<p>%i18n:@error%</p>
<button @click="resolveInitPromise">%i18n:@retry%</button>
</div>
<transition-group name="mk-notes" class="transition">
<template v-for="(note, i) in _notes">
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<span>%fa:angle-up%{{ note._datetext }}</span>
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
</p>
</template>
</transition-group>
<footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">%i18n:@load-more%</template>
<template v-if="moreFetching">%fa:spinner .pulse .fw%</template>
</button>
</footer>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { url } from '../../../config';
import getNoteSummary from '../../../../../renderers/get-note-summary';
import XNote from './deck.note.vue';
const displayLimit = 30;
export default Vue.extend({
components: {
XNote
},
inject: ['column', 'isScrollTop', 'indicate'],
props: {
more: {
type: Function,
required: false
}
},
data() {
return {
rootEl: null,
requestInitPromise: null as () => Promise<any[]>,
notes: [],
queue: [],
unreadCount: 0,
fetching: true,
moreFetching: false
};
},
computed: {
_notes(): any[] {
return (this.notes as any).map(note => {
const date = new Date(note.createdAt).getDate();
const month = new Date(note.createdAt).getMonth() + 1;
note._date = date;
note._datetext = `${month}${date}`;
return note;
});
}
},
created() {
this.column.$on('top', this.onTop);
this.column.$on('bottom', this.onBottom);
},
beforeDestroy() {
this.column.$off('top', this.onTop);
this.column.$off('bottom', this.onBottom);
},
methods: {
focus() {
(this.$el as any).children[0].focus();
},
onNoteUpdated(i, note) {
Vue.set((this as any).notes, i, note);
},
init(promiseGenerator: () => Promise<any[]>) {
this.requestInitPromise = promiseGenerator;
this.resolveInitPromise();
},
resolveInitPromise() {
this.queue = [];
this.notes = [];
this.fetching = true;
const promise = this.requestInitPromise();
promise.then(notes => {
this.notes = notes;
this.requestInitPromise = null;
this.fetching = false;
}, e => {
this.fetching = false;
});
},
prepend(note, silent = false) {
//#region 弾く
const isMyNote = note.userId == this.$store.state.i.id;
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
if (this.$store.state.settings.showMyRenotes === false) {
if (isMyNote && isPureRenote) {
return;
}
}
if (this.$store.state.settings.showRenotedMyNotes === false) {
if (isPureRenote && (note.renote.userId == this.$store.state.i.id)) {
return;
}
}
//#endregion
if (this.isScrollTop()) {
// Prepend the note
this.notes.unshift(note);
// オーバーフローしたら古い投稿は捨てる
if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit);
}
} else {
this.queue.push(note);
this.indicate(true);
}
},
append(note) {
this.notes.push(note);
},
tail() {
return this.notes[this.notes.length - 1];
},
releaseQueue() {
this.queue.forEach(n => this.prepend(n, true));
this.queue = [];
this.indicate(false);
},
async loadMore() {
if (this.more == null) return;
if (this.moreFetching) return;
this.moreFetching = true;
await this.more();
this.moreFetching = false;
},
onTop() {
this.releaseQueue();
},
onBottom() {
this.loadMore();
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
.transition
.mk-notes-enter
.mk-notes-leave-to
opacity 0
transform translateY(-30px)
> *
transition transform .3s ease, opacity .3s ease
> .date
display block
margin 0
line-height 32px
font-size 14px
text-align center
color isDark ? #666b79 : #aaa
background isDark ? #242731 : #fdfdfd
border-bottom solid 1px isDark ? #1c2023 : #eaeaea
span
margin 0 16px
[data-fa]
margin-right 8px
> footer
> button
display block
margin 0
padding 16px
width 100%
text-align center
color #ccc
background isDark ? #282C37 : #fff
border-top solid 1px isDark ? #1c2023 : #eaeaea
border-bottom-left-radius 6px
border-bottom-right-radius 6px
&:hover
background isDark ? #2e3440 : #f5f5f5
&:active
background isDark ? #21242b : #eee
.eamppglmnmimdhrlzhplwpvyeaqmmhxu[data-darkmode]
root(true)
.eamppglmnmimdhrlzhplwpvyeaqmmhxu:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,179 @@
<template>
<div class="dsfykdcjpuwfvpefwufddclpjhzktmpw">
<div class="notification reaction" v-if="notification.type == 'reaction'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
<mk-reaction-icon :reaction="notification.reaction"/>
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
<router-link class="note-ref" :to="notification.note | notePage">
%fa:quote-left%{{ getNoteSummary(notification.note) }}
%fa:quote-right%
</router-link>
</div>
</div>
<div class="notification renote" v-if="notification.type == 'renote'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
%fa:retweet%
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
<router-link class="note-ref" :to="notification.note | notePage">
%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%
</router-link>
</div>
</div>
<div class="notification follow" v-if="notification.type == 'follow'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
%fa:user-plus%
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
</div>
</div>
<div class="notification followRequest" v-if="notification.type == 'receiveFollowRequest'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
%fa:user-clock%
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
</div>
</div>
<div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
%fa:chart-pie%
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
<router-link class="note-ref" :to="notification.note | notePage">
%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%
</router-link>
</div>
</div>
<template v-if="notification.type == 'quote'">
<x-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
<template v-if="notification.type == 'reply'">
<x-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
<template v-if="notification.type == 'mention'">
<x-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import getNoteSummary from '../../../../../../renderers/get-note-summary';
import XNote from './deck.note.vue';
export default Vue.extend({
components: {
XNote
},
props: ['notification'],
data() {
return {
getNoteSummary
};
},
methods: {
onNoteUpdated(note) {
switch (this.notification.type) {
case 'quote':
case 'reply':
case 'mention':
Vue.set(this.notification, 'note', note);
break;
}
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
> .notification
padding 16px
font-size 12px
overflow-wrap break-word
&:after
content ""
display block
clear both
> .avatar
display block
float left
width 36px
height 36px
border-radius 6px
> div
float right
width calc(100% - 36px)
padding-left 8px
> header
display flex
align-items baseline
white-space nowrap
i, .mk-reaction-icon
margin-right 4px
> .mk-time
margin-left auto
color isDark ? #606984 : #c0c0c0
font-size 0.9em
> .note-preview
color isDark ? #fff : #717171
> .note-ref
color isDark ? #fff : #717171
[data-fa]
font-size 1em
font-weight normal
font-style normal
display inline-block
margin-right 3px
&.renote
> div > header i
color #77B255
&.follow
> div > header i
color #53c7ce
&.receiveFollowRequest
> div > header i
color #888
.dsfykdcjpuwfvpefwufddclpjhzktmpw[data-darkmode]
root(true)
.dsfykdcjpuwfvpefwufddclpjhzktmpw:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,36 @@
<template>
<div>
<x-column :id="column.id" :name="name">
<span slot="header">%fa:bell R%{{ name }}</span>
<x-notifications/>
</x-column>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XNotifications from './deck.notifications.vue';
export default Vue.extend({
components: {
XColumn,
XNotifications
},
props: {
column: {
type: Object,
required: true
}
},
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.notifications%';
}
},
});
</script>

View File

@ -0,0 +1,179 @@
<template>
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
<transition-group name="mk-notifications" class="transition notifications">
<template v-for="(notification, i) in _notifications">
<x-notification class="notification" :notification="notification" :key="notification.id"/>
<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
<span>%fa:angle-up%{{ notification._datetext }}</span>
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
</p>
</template>
</transition-group>
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
</button>
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
<p class="loading" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import XNotification from './deck.notification.vue';
export default Vue.extend({
components: {
XNotification
},
data() {
return {
fetching: true,
fetchingMoreNotifications: false,
notifications: [],
moreNotifications: false,
connection: null,
connectionId: null
};
},
computed: {
_notifications(): any[] {
return (this.notifications as any).map(notification => {
const date = new Date(notification.createdAt).getDate();
const month = new Date(notification.createdAt).getMonth() + 1;
notification._date = date;
notification._datetext = `${month}${date}`;
return notification;
});
}
},
mounted() {
this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use();
this.connection.on('notification', this.onNotification);
const max = 10;
(this as any).api('i/notifications', {
limit: max + 1
}).then(notifications => {
if (notifications.length == max + 1) {
this.moreNotifications = true;
notifications.pop();
}
this.notifications = notifications;
this.fetching = false;
});
},
beforeDestroy() {
this.connection.off('notification', this.onNotification);
(this as any).os.stream.dispose(this.connectionId);
},
methods: {
fetchMoreNotifications() {
this.fetchingMoreNotifications = true;
const max = 30;
(this as any).api('i/notifications', {
limit: max + 1,
untilId: this.notifications[this.notifications.length - 1].id
}).then(notifications => {
if (notifications.length == max + 1) {
this.moreNotifications = true;
notifications.pop();
} else {
this.moreNotifications = false;
}
this.notifications = this.notifications.concat(notifications);
this.fetchingMoreNotifications = false;
});
},
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({
type: 'read_notification',
id: notification.id
});
this.notifications.unshift(notification);
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
.transition
.mk-notifications-enter
.mk-notifications-leave-to
opacity 0
transform translateY(-30px)
> *
transition transform .3s ease, opacity .3s ease
> .notifications
> .notification:not(:last-child)
border-bottom solid 1px isDark ? #1c2023 : #eaeaea
> .date
display block
margin 0
line-height 32px
text-align center
font-size 0.8em
color isDark ? #666b79 : #aaa
background isDark ? #242731 : #fdfdfd
border-bottom solid 1px isDark ? #1c2023 : #eaeaea
span
margin 0 16px
i
margin-right 8px
> .more
display block
width 100%
padding 16px
color #555
border-top solid 1px rgba(#000, 0.05)
&:hover
background rgba(#000, 0.025)
&:active
background rgba(#000, 0.05)
&.fetching
cursor wait
> [data-fa]
margin-right 4px
> .empty
margin 0
padding 16px
text-align center
color #aaa
> .loading
margin 0
padding 16px
text-align center
color #aaa
> [data-fa]
margin-right 4px
.oxynyeqmfvracxnglgulyqfgqxnxmehl[data-darkmode]
root(true)
.oxynyeqmfvracxnglgulyqfgqxnxmehl:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,73 @@
<template>
<div>
<x-column :id="column.id" :menu="menu" :name="name">
<span slot="header">
<template v-if="column.type == 'home'">%fa:home%</template>
<template v-if="column.type == 'local'">%fa:R comments%</template>
<template v-if="column.type == 'global'">%fa:globe%</template>
<template v-if="column.type == 'list'">%fa:list%</template>
<span>{{ name }}</span>
</span>
<div class="editor" v-if="edit">
<mk-switch v-model="column.isMediaOnly" @change="onChangeSettings" text="%i18n:@is-media-only%"/>
<mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
</div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly"/>
</x-column>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XTl from './deck.tl.vue';
import XListTl from './deck.list-tl.vue';
export default Vue.extend({
components: {
XColumn,
XTl,
XListTl
},
props: {
column: {
type: Object,
required: true
}
},
data() {
return {
edit: false,
menu: [{
content: '%fa:cog% %i18n:@edit%',
onClick: () => {
this.edit = !this.edit;
}
}]
}
},
computed: {
name(): string {
if (this.column.name) return this.column.name;
switch (this.column.type) {
case 'home': return '%i18n:common.deck.home%';
case 'local': return '%i18n:common.deck.local%';
case 'global': return '%i18n:common.deck.global%';
case 'list': return this.column.list.title;
}
}
},
methods: {
onChangeSettings(v) {
this.$store.dispatch('settings/saveDeck');
}
}
});
</script>

View File

@ -0,0 +1,147 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</template>
<script lang="ts">
import Vue from 'vue';
import XNotes from './deck.notes.vue';
const fetchLimit = 10;
export default Vue.extend({
components: {
XNotes
},
props: {
src: {
type: String,
required: false,
default: 'home'
},
mediaOnly: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
connection: null,
connectionId: null
};
},
watch: {
mediaOnly() {
this.fetch();
}
},
computed: {
stream(): any {
return this.src == 'home'
? (this as any).os.stream
: this.src == 'local'
? (this as any).os.streams.localTimelineStream
: (this as any).os.streams.globalTimelineStream;
},
endpoint(): string {
return this.src == 'home'
? 'notes/timeline'
: this.src == 'local'
? 'notes/local-timeline'
: 'notes/global-timeline';
}
},
mounted() {
this.connection = this.stream.getConnection();
this.connectionId = this.stream.use();
this.connection.on('note', this.onNote);
if (this.src == 'home') {
this.connection.on('follow', this.onChangeFollowing);
this.connection.on('unfollow', this.onChangeFollowing);
}
this.fetch();
},
beforeDestroy() {
this.connection.off('note', this.onNote);
if (this.src == 'home') {
this.connection.off('follow', this.onChangeFollowing);
this.connection.off('unfollow', this.onChangeFollowing);
}
this.stream.dispose(this.connectionId);
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api(this.endpoint, {
limit: fetchLimit + 1,
mediaOnly: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = (this as any).api(this.endpoint, {
limit: fetchLimit + 1,
mediaOnly: this.mediaOnly,
untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
notes.forEach(n => (this.$refs.timeline as any).append(n));
this.moreFetching = false;
});
return promise;
},
onNote(note) {
if (this.mediaOnly && note.media.length == 0) return;
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
onChangeFollowing() {
this.fetch();
},
focus() {
(this.$refs.timeline as any).focus();
}
}
});
</script>

View File

@ -0,0 +1,185 @@
<template>
<mk-ui :class="$style.root">
<div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode">
<template v-for="column in columns">
<x-widgets-column v-if="column.type == 'widgets'" :key="column.id" :column="column"/>
<x-notifications-column v-if="column.type == 'notifications'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'home'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'local'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'global'" :key="column.id" :column="column"/>
<x-tl-column v-if="column.type == 'list'" :key="column.id" :column="column"/>
</template>
<button ref="add" @click="add" title="%i18n:common.deck.add-column%">%fa:plus%</button>
</div>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
import XTlColumn from './deck.tl-column.vue';
import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue';
import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue';
import * as uuid from 'uuid';
export default Vue.extend({
components: {
XTlColumn,
XNotificationsColumn,
XWidgetsColumn
},
computed: {
columns() {
if (this.$store.state.settings.deck == null) return [];
return this.$store.state.settings.deck.columns;
}
},
created() {
if (this.$store.state.settings.deck == null) {
const deck = {
columns: [/*{
type: 'widgets',
widgets: []
}, */{
id: uuid(),
type: 'home'
}, {
id: uuid(),
type: 'notifications'
}, {
id: uuid(),
type: 'local'
}, {
id: uuid(),
type: 'global'
}]
};
this.$store.dispatch('settings/set', {
key: 'deck',
value: deck
});
}
},
mounted() {
document.documentElement.style.overflow = 'hidden';
},
beforeDestroy() {
document.documentElement.style.overflow = 'auto';
},
methods: {
add() {
this.os.new(Menu, {
source: this.$refs.add,
compact: true,
items: [{
content: '%i18n:common.deck.home%',
onClick: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'home'
});
}
}, {
content: '%i18n:common.deck.local%',
onClick: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'local'
});
}
}, {
content: '%i18n:common.deck.global%',
onClick: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'global'
});
}
}, {
content: '%i18n:common.deck.list%',
onClick: () => {
const w = (this as any).os.new(MkUserListsWindow);
w.$once('choosen', list => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'list',
list: list
});
w.close();
});
}
}, {
content: '%i18n:common.deck.notifications%',
onClick: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'notifications'
});
}
}, {
content: '%i18n:common.deck.widgets%',
onClick: () => {
this.$store.dispatch('settings/addDeckColumn', {
id: uuid(),
type: 'widgets',
widgets: []
});
}
}]
});
}
}
});
</script>
<style lang="stylus" module>
.root
height 100vh
</style>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
display flex
flex 1
padding 16px 0 16px 16px
overflow auto
> div
margin-right 8px
&:last-of-type
margin-right 0
> *
&:first-child
margin-left auto
&:last-child
margin-right auto
> button
padding 0 16px
color isDark ? #93a0a5 : #888
&:hover
color isDark ? #b8c5ca : #777
&:active
color isDark ? #fff : #555
.qlvquzbjribqcaozciifydkngcwtyzje[data-darkmode]
root(true)
.qlvquzbjribqcaozciifydkngcwtyzje:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,159 @@
<template>
<div class="wtdtxvecapixsepjtcupubtsmometobz">
<x-column :id="column.id" :menu="menu" :naked="true" :narrow="true" :name="name">
<span slot="header">%fa:calculator%{{ name }}</span>
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
<template v-if="edit">
<header>
<select v-model="widgetAdderSelected">
<option value="profile">%i18n:common.widgets.profile%</option>
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
<option value="calendar">%i18n:common.widgets.calendar%</option>
<option value="timemachine">%i18n:common.widgets.timemachine%</option>
<option value="activity">%i18n:common.widgets.activity%</option>
<option value="rss">%i18n:common.widgets.rss%</option>
<option value="trends">%i18n:common.widgets.trends%</option>
<option value="photo-stream">%i18n:common.widgets.photo-stream%</option>
<option value="slideshow">%i18n:common.widgets.slideshow%</option>
<option value="version">%i18n:common.widgets.version%</option>
<option value="broadcast">%i18n:common.widgets.broadcast%</option>
<option value="notifications">%i18n:common.widgets.notifications%</option>
<option value="users">%i18n:common.widgets.users%</option>
<option value="polls">%i18n:common.widgets.polls%</option>
<option value="post-form">%i18n:common.widgets.post-form%</option>
<option value="messaging">%i18n:common.widgets.messaging%</option>
<option value="memo">%i18n:common.widgets.memo%</option>
<option value="server">%i18n:common.widgets.server%</option>
<option value="donation">%i18n:common.widgets.donation%</option>
<option value="nav">%i18n:common.widgets.nav%</option>
<option value="tips">%i18n:common.widgets.tips%</option>
</select>
<button @click="addWidget">%i18n:@add%</button>
</header>
<x-draggable
:list="column.widgets"
:options="{ handle: '.handle', animation: 150 }"
@sort="onWidgetSort"
>
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
<header>
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
</div>
</div>
</x-draggable>
</template>
<template v-else>
<component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/>
</template>
</div>
</x-column>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid';
export default Vue.extend({
components: {
XColumn,
XDraggable
},
props: {
column: {
type: Object,
required: true
}
},
data() {
return {
edit: false,
menu: null,
widgetAdderSelected: null
}
},
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.widgets%';
}
},
created() {
this.menu = [{
content: '%fa:cog% %i18n:@edit%',
onClick: () => {
this.edit = !this.edit;
}
}];
},
methods: {
widgetFunc(id) {
const w = this.$refs[id][0];
if (w.func) w.func();
},
onWidgetSort() {
this.saveWidgets();
},
addWidget() {
this.$store.dispatch('settings/addDeckWidget', {
id: this.column.id,
widget: {
name: this.widgetAdderSelected,
id: uuid(),
data: {}
}
});
},
removeWidget(widget) {
this.$store.dispatch('settings/removeDeckWidget', {
id: this.column.id,
widget
});
},
saveWidgets() {
this.$store.dispatch('settings/saveDeck');
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
.gqpwvtwtprsbmnssnbicggtwqhmylhnq
.widget, .customize-container
margin 8px
&:first-of-type
margin-top 0
.customize-container
background #fff
> header
color isDark ? #fff : #000
.wtdtxvecapixsepjtcupubtsmometobz[data-darkmode]
root(true)
.wtdtxvecapixsepjtcupubtsmometobz:not([data-darkmode])
root(false)
</style>

View File

@ -46,7 +46,7 @@ export default Vue.extend({
},
mounted() {
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll);
window.addEventListener('scroll', this.onScroll, { passive: true });
this.fetch();
},

View File

@ -37,7 +37,7 @@ export default Vue.extend({
mounted() {
if (this.user.bannerUrl) {
window.addEventListener('load', this.onScroll);
window.addEventListener('scroll', this.onScroll);
window.addEventListener('scroll', this.onScroll, { passive: true });
window.addEventListener('resize', this.onScroll);
}
},

View File

@ -83,9 +83,7 @@ export default Vue.extend({
@import url(https://fonts.googleapis.com/earlyaccess/notosansjp.css);
root(isDark)
display flex
flex-direction column
flex 1
min-height 100vh
background-image isDark ? url('/assets/welcome-bg.dark.svg') : url('/assets/welcome-bg.light.svg')
background-size cover
background-position center

View File

@ -73,12 +73,12 @@ export default class MiOS extends EventEmitter {
public app: Vue;
public new(vm, props) {
const w = new vm({
const x = new vm({
parent: this.app,
propsData: props
}).$mount();
document.body.appendChild(w.$el);
return w;
document.body.appendChild(x.$el);
return x;
}
/**

View File

@ -32,11 +32,14 @@ import MkNotifications from './views/pages/notifications.vue';
import MkWidgets from './views/pages/widgets.vue';
import MkMessaging from './views/pages/messaging.vue';
import MkMessagingRoom from './views/pages/messaging-room.vue';
import MkReceivedFollowRequests from './views/pages/received-follow-requests.vue';
import MkNote from './views/pages/note.vue';
import MkSearch from './views/pages/search.vue';
import MkFollowers from './views/pages/followers.vue';
import MkFollowing from './views/pages/following.vue';
import MkFavorites from './views/pages/favorites.vue';
import MkUserLists from './views/pages/user-lists.vue';
import MkUserList from './views/pages/user-list.vue';
import MkSettings from './views/pages/settings.vue';
import MkOthello from './views/pages/othello.vue';
@ -74,6 +77,9 @@ init((launch) => {
{ path: '/i/settings', name: 'settings', component: MkSettings },
{ path: '/i/notifications', name: 'notifications', component: MkNotifications },
{ path: '/i/favorites', name: 'favorites', component: MkFavorites },
{ path: '/i/lists', name: 'user-lists', component: MkUserLists },
{ path: '/i/lists/:list', name: 'user-list', component: MkUserList },
{ path: '/i/received-follow-requests', name: 'received-follow-requests', component: MkReceivedFollowRequests },
{ path: '/i/widgets', name: 'widgets', component: MkWidgets },
{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
{ path: '/i/messaging/:user', component: MkMessagingRoom },

View File

@ -1,13 +1,16 @@
<template>
<button class="mk-follow-button"
:class="{ wait: wait, follow: !user.isFollowing, unfollow: user.isFollowing }"
:class="{ wait: wait, active: u.isFollowing || u.hasPendingFollowRequestFromYou }"
@click="onClick"
:disabled="wait"
>
<template v-if="!wait && user.isFollowing">%fa:minus%</template>
<template v-if="!wait && !user.isFollowing">%fa:plus%</template>
<template v-if="wait">%fa:spinner .pulse .fw%</template>
{{ user.isFollowing ? '%i18n:@unfollow%' : '%i18n:@follow%' }}
<template v-if="!wait">
<template v-if="u.hasPendingFollowRequestFromYou">%fa:hourglass-half% %i18n:@request-pending%</template>
<template v-else-if="u.isFollowing">%fa:minus% %i18n:@following%</template>
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus% %i18n:@follow-request%</template>
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus% %i18n:@follow%</template>
</template>
<template v-else>%fa:spinner .pulse .fw%</template>
</button>
</template>
@ -22,6 +25,7 @@ export default Vue.extend({
},
data() {
return {
u: this.user,
wait: false,
connection: null,
connectionId: null
@ -42,39 +46,44 @@ export default Vue.extend({
methods: {
onFollow(user) {
if (user.id == this.user.id) {
this.user.isFollowing = user.isFollowing;
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
}
},
onUnfollow(user) {
if (user.id == this.user.id) {
this.user.isFollowing = user.isFollowing;
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
}
},
onClick() {
async onClick() {
this.wait = true;
if (this.user.isFollowing) {
(this as any).api('following/delete', {
userId: this.user.id
}).then(() => {
this.user.isFollowing = false;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
});
} else {
(this as any).api('following/create', {
userId: this.user.id
}).then(() => {
this.user.isFollowing = true;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
});
try {
if (this.u.isFollowing) {
this.u = await (this as any).api('following/delete', {
userId: this.u.id
});
} else {
if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) {
this.u = await (this as any).api('following/requests/cancel', {
userId: this.u.id
});
} else if (this.u.isLocked) {
this.u = await (this as any).api('following/create', {
userId: this.u.id
});
} else {
this.u = await (this as any).api('following/create', {
userId: this.user.id
});
}
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
@ -90,34 +99,39 @@ export default Vue.extend({
cursor pointer
padding 0 16px
margin 0
height inherit
font-size 16px
min-width 150px
line-height 36px
font-size 14px
font-weight bold
color $theme-color
background transparent
outline none
border solid 1px $theme-color
border-radius 4px
border-radius 36px
*
pointer-events none
&:hover
background rgba($theme-color, 0.1)
&.follow
color $theme-color
background transparent
&:active
background rgba($theme-color, 0.2)
&:hover
background rgba($theme-color, 0.1)
&:active
background rgba($theme-color, 0.2)
&.unfollow
&.active
color $theme-color-foreground
background $theme-color
&:hover
background lighten($theme-color, 10%)
border-color lighten($theme-color, 10%)
&:active
background darken($theme-color, 10%)
border-color darken($theme-color, 10%)
&.wait
cursor wait !important
opacity 0.7
> [data-fa]
margin-right 4px
*
pointer-events none
</style>

View File

@ -502,7 +502,7 @@ root(isDark)
padding 2px 8px 2px 16px
font-size 90%
color #8d969e
background #edf0f3
background isDark ? #313543 : #edf0f3
border-radius 4px
&:before
@ -515,7 +515,7 @@ root(isDark)
width 8px
height 8px
margin auto 0
background #fff
background isDark ? #282c37 : #fff
border-radius 100%
> .media

View File

@ -81,7 +81,7 @@ export default Vue.extend({
mounted() {
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
window.addEventListener('scroll', this.onScroll);
window.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {

View File

@ -1,7 +1,7 @@
<template>
<div class="mk-notification-preview" :class="notification.type">
<template v-if="notification.type == 'reaction'">
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
<p><mk-reaction-icon :reaction="notification.reaction"/>{{ notification.user | userName }}</p>
<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p>
@ -9,7 +9,7 @@
</template>
<template v-if="notification.type == 'renote'">
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
<p>%fa:retweet%{{ notification.note.user | userName }}</p>
<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note.renote) }}%fa:quote-right%</p>
@ -17,7 +17,7 @@
</template>
<template v-if="notification.type == 'quote'">
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
<p>%fa:quote-left%{{ notification.note.user | userName }}</p>
<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
@ -25,14 +25,21 @@
</template>
<template v-if="notification.type == 'follow'">
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
<p>%fa:user-plus%{{ notification.user | userName }}</p>
</div>
</template>
<template v-if="notification.type == 'receiveFollowRequest'">
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
<p>%fa:user-clock%{{ notification.user | userName }}</p>
</div>
</template>
<template v-if="notification.type == 'reply'">
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
<p>%fa:reply%{{ notification.note.user | userName }}</p>
<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
@ -40,7 +47,7 @@
</template>
<template v-if="notification.type == 'mention'">
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.note.user"/>
<div class="text">
<p>%fa:at%{{ notification.note.user | userName }}</p>
<p class="note-preview">{{ getNoteSummary(notification.note) }}</p>
@ -48,7 +55,7 @@
</template>
<template v-if="notification.type == 'poll_vote'">
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
<mk-avatar class="avatar" :user="notification.user"/>
<div class="text">
<p>%fa:chart-pie%{{ notification.user | userName }}</p>
<p class="note-ref">%fa:quote-left%{{ getNoteSummary(notification.note) }}%fa:quote-right%</p>
@ -83,16 +90,14 @@ export default Vue.extend({
display block
clear both
img
> .avatar
display block
float left
min-width 36px
min-height 36px
max-width 36px
max-height 36px
width 36px
height 36px
border-radius 6px
.text
> .text
float right
width calc(100% - 36px)
padding-left 8px
@ -120,6 +125,10 @@ export default Vue.extend({
.text p i
color #53c7ce
&.receiveFollowRequest
.text p i
color #888
&.reply, &.mention
.text p i
color #fff

View File

@ -40,6 +40,17 @@
</div>
</div>
<div class="notification followRequest" v-if="notification.type == 'receiveFollowRequest'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
<header>
%fa:user-clock%
<router-link :to="notification.user | userPage">{{ notification.user | userName }}</router-link>
<mk-time :time="notification.createdAt"/>
</header>
</div>
</div>
<div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
<mk-avatar class="avatar" :user="notification.user"/>
<div>
@ -55,15 +66,15 @@
</div>
<template v-if="notification.type == 'quote'">
<mk-note :note="notification.note"/>
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
<template v-if="notification.type == 'reply'">
<mk-note :note="notification.note"/>
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
<template v-if="notification.type == 'mention'">
<mk-note :note="notification.note"/>
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
</template>
</div>
</template>
@ -78,6 +89,17 @@ export default Vue.extend({
return {
getNoteSummary
};
},
methods: {
onNoteUpdated(note) {
switch (this.notification.type) {
case 'quote':
case 'reply':
case 'mention':
Vue.set(this.notification, 'note', note);
break;
}
}
}
});
</script>
@ -156,6 +178,10 @@ root(isDark)
> div > header i
color #53c7ce
&.receiveFollowRequest
> div > header i
color #888
.mk-notification[data-darkmode]
root(true)

View File

@ -5,7 +5,7 @@
<div>
<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
<span class="geo" v-if="geo">%fa:map-marker-alt%</span>
<button class="submit" :disabled="posting" @click="post">{{ submitText }}</button>
<button class="submit" :disabled="!canPost" @click="post">{{ submitText }}</button>
</div>
</header>
<div class="form">

View File

@ -15,14 +15,16 @@
</router-link>
<div class="links">
<ul>
<li><router-link to="/" :data-active="$route.name == 'index'">%fa:home%%i18n:@home%%fa:angle-right%</router-link></li>
<li><router-link to="/" :data-active="$route.name == 'index'">%fa:home%%i18n:@timeline%%fa:angle-right%</router-link></li>
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'">%fa:R bell%%i18n:@notifications%<template v-if="hasUnreadNotification">%fa:circle%</template>%fa:angle-right%</router-link></li>
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'">%fa:R comments%%i18n:@messaging%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template>%fa:angle-right%</router-link></li>
<li v-if="$store.getters.isSignedIn && $store.state.i.isLocked"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
<li><router-link to="/othello" :data-active="$route.name == 'othello'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
</ul>
<ul>
<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'">%fa:quidditch%%i18n:@widgets%%fa:angle-right%</router-link></li>
<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'">%fa:R calendar-alt%%i18n:@widgets%%fa:angle-right%</router-link></li>
<li><router-link to="/i/favorites" :data-active="$route.name == 'favorites'">%fa:star%%i18n:@favorites%%fa:angle-right%</router-link></li>
<li><router-link to="/i/lists" :data-active="$route.name == 'user-lists'">%fa:list%%i18n:@user-lists%%fa:angle-right%</router-link></li>
<li><router-link to="/i/drive" :data-active="$route.name == 'drive'">%fa:cloud%%i18n:@drive%%fa:angle-right%</router-link></li>
</ul>
<ul>
@ -157,7 +159,10 @@ root(isDark)
&:first-child
margin-top 0
li
&:last-child
margin-bottom 0
> li
display block
font-size 1em
line-height 1em
@ -199,7 +204,7 @@ root(isDark)
opacity 0.5
.about
margin 0
margin 0 0 8px 0
padding 1em 0
text-align center
font-size 0.8em

View File

@ -0,0 +1,78 @@
<template>
<mk-ui>
<span slot="header">%fa:envelope R%%i18n:@title%</span>
<main>
<div v-for="req in requests">
<router-link :key="req.id" :to="req.follower | userPage">{{ req.follower | userName }}</router-link>
<span>
<a @click="accept(req.follower)">%i18n:@accept%</a>|<a @click="reject(req.follower)">%i18n:@reject%</a>
</span>
</div>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
data() {
return {
fetching: true,
requests: []
};
},
mounted() {
document.title = 'Misskey | %i18n:@title%';
Progress.start();
(this as any).api('following/requests/list').then(requests => {
this.fetching = false;
this.requests = requests;
Progress.done();
});
},
methods: {
accept(user) {
(this as any).api('following/requests/accept', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
},
reject(user) {
(this as any).api('following/requests/reject', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
main
width 100%
max-width 680px
margin 0 auto
padding 8px
@media (min-width 500px)
padding 16px
@media (min-width 600px)
padding 32px
> div
display flex
padding 16px
border solid 1px isDark ? #1c2023 : #eee
border-radius 4px
> span
margin 0 0 0 auto
</style>

View File

@ -0,0 +1,70 @@
<template>
<mk-ui>
<span slot="header" v-if="!fetching">%fa:list%{{ list.title }}</span>
<main v-if="!fetching">
<ul>
<li v-for="user in users" :key="user.id"><router-link :to="user | userPage">{{ user | userName }}</router-link></li>
</ul>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
data() {
return {
fetching: true,
list: null,
users: null
};
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.fetching = true;
(this as any).api('users/lists/show', {
listId: this.$route.params.list
}).then(list => {
this.list = list;
this.fetching = false;
Progress.done();
(this as any).api('users/show', {
userIds: this.list.userIds
}).then(users => {
this.users = users;
});
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
main
width 100%
max-width 680px
margin 0 auto
padding 8px
@media (min-width 500px)
padding 16px
@media (min-width 600px)
padding 32px
</style>

View File

@ -0,0 +1,68 @@
<template>
<mk-ui>
<span slot="header">%fa:list%%i18n:@title%</span>
<template slot="func"><button @click="fn">%fa:plus%</button></template>
<main>
<ul>
<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.title }}</router-link></li>
</ul>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
data() {
return {
fetching: true,
lists: []
};
},
mounted() {
document.title = 'Misskey | %i18n:@title%';
Progress.start();
(this as any).api('users/lists/list').then(lists => {
this.fetching = false;
this.lists = lists;
Progress.done();
});
},
methods: {
fn() {
(this as any).apis.input({
title: '%i18n:@enter-list-name%',
}).then(async title => {
const list = await (this as any).api('users/lists/create', {
title
});
this.$router.push('/i/lists/' + list.id);
});
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
main
width 100%
max-width 680px
margin 0 auto
padding 8px
@media (min-width 500px)
padding 16px
@media (min-width 600px)
padding 32px
</style>

View File

@ -184,7 +184,6 @@ root(isDark)
> .mk-follow-button
float right
height 40px
> .title
margin 8px 0

View File

@ -35,13 +35,13 @@
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" :is-mobile="true"/>
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="mobile"/>
</div>
</div>
</x-draggable>
</template>
<template v-else>
<component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" :is-mobile="true"/>
<component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="mobile"/>
</template>
</main>
</mk-ui>

View File

@ -5,8 +5,9 @@ import MiOS from './mios';
import { hostname } from './config';
const defaultSettings = {
home: [],
home: null,
mobileHome: [],
deck: null,
fetchOnScroll: true,
showMaps: true,
showPostFormOnTopOfTl: false,
@ -123,13 +124,6 @@ export default (os: MiOS) => new Vuex.Store({
state.home = data;
},
setHomeWidget(state, x) {
const w = state.home.find(w => w.id == x.id);
if (w) {
w.data = x.data;
}
},
addHomeWidget(state, widget) {
state.home.unshift(widget);
},
@ -138,11 +132,36 @@ export default (os: MiOS) => new Vuex.Store({
state.mobileHome = data;
},
setMobileHomeWidget(state, x) {
const w = state.mobileHome.find(w => w.id == x.id);
if (w) {
w.data = x.data;
setWidget(state, x) {
let w;
//#region Decktop home
if (state.home) {
w = state.home.find(w => w.id == x.id);
if (w) {
w.data = x.data;
}
}
//#endregion
//#region Mobile home
if (state.mobileHome) {
w = state.mobileHome.find(w => w.id == x.id);
if (w) {
w.data = x.data;
}
}
//#endregion
//#region Deck
if (state.deck && state.deck.columns) {
state.deck.columns.filter(c => c.type == 'widgets').forEach(c => {
c.widgets.forEach(w => {
if (w.id == x.id) w.data = x.data;
});
});
}
//#endregion
},
addMobileHomeWidget(state, widget) {
@ -151,11 +170,70 @@ export default (os: MiOS) => new Vuex.Store({
removeMobileHomeWidget(state, widget) {
state.mobileHome = state.mobileHome.filter(w => w.id != widget.id);
},
addDeckColumn(state, column) {
if (state.deck.columns == null) state.deck.columns = [];
state.deck.columns.push(column);
},
removeDeckColumn(state, id) {
if (state.deck.columns == null) return;
state.deck.columns = state.deck.columns.filter(c => c.id != id);
},
swapLeftDeckColumn(state, id) {
if (state.deck.columns == null) return;
state.deck.columns.some((c, i) => {
if (c.id == id) {
const left = state.deck.columns[i - 1];
if (left) {
state.deck.columns[i - 1] = state.deck.columns[i];
state.deck.columns[i] = left;
}
return true;
}
});
},
swapRightDeckColumn(state, id) {
if (state.deck.columns == null) return;
state.deck.columns.some((c, i) => {
if (c.id == id) {
const right = state.deck.columns[i + 1];
if (right) {
state.deck.columns[i + 1] = state.deck.columns[i];
state.deck.columns[i] = right;
}
return true;
}
});
},
addDeckWidget(state, x) {
if (state.deck.columns == null) return;
const column = state.deck.columns.find(c => c.id == x.id);
if (column == null) return;
column.widgets.unshift(x.widget);
},
removeDeckWidget(state, x) {
if (state.deck.columns == null) return;
const column = state.deck.columns.find(c => c.id == x.id);
if (column == null) return;
column.widgets = column.widgets.filter(w => w.id != x.widget.id);
},
renameDeckColumn(state, x) {
const column = state.deck.columns.find(c => c.id == x.id);
if (column == null) return;
column.name = x.name;
}
},
actions: {
merge(ctx, settings) {
if (settings == null) return;
Object.entries(settings).forEach(([key, value]) => {
ctx.commit('set', { key, value });
});
@ -172,6 +250,48 @@ export default (os: MiOS) => new Vuex.Store({
}
},
saveDeck(ctx) {
os.api('i/update_client_setting', {
name: 'deck',
value: ctx.state.deck
});
},
addDeckColumn(ctx, column) {
ctx.commit('addDeckColumn', column);
ctx.dispatch('saveDeck');
},
removeDeckColumn(ctx, id) {
ctx.commit('removeDeckColumn', id);
ctx.dispatch('saveDeck');
},
swapLeftDeckColumn(ctx, id) {
ctx.commit('swapLeftDeckColumn', id);
ctx.dispatch('saveDeck');
},
swapRightDeckColumn(ctx, id) {
ctx.commit('swapRightDeckColumn', id);
ctx.dispatch('saveDeck');
},
addDeckWidget(ctx, x) {
ctx.commit('addDeckWidget', x);
ctx.dispatch('saveDeck');
},
removeDeckWidget(ctx, x) {
ctx.commit('removeDeckWidget', x);
ctx.dispatch('saveDeck');
},
renameDeckColumn(ctx, x) {
ctx.commit('renameDeckColumn', x);
ctx.dispatch('saveDeck');
},
addHomeWidget(ctx, widget) {
ctx.commit('addHomeWidget', widget);

View File

@ -0,0 +1,87 @@
import * as mongo from 'mongodb';
import * as deepcopy from 'deepcopy';
import db from '../db/mongodb';
import { pack as packUser } from './user';
const FollowRequest = db.get<IFollowRequest>('followRequests');
FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true });
export default FollowRequest;
export type IFollowRequest = {
_id: mongo.ObjectID;
createdAt: Date;
followeeId: mongo.ObjectID;
followerId: mongo.ObjectID;
// 非正規化
_followee: {
host: string;
inbox?: string;
},
_follower: {
host: string;
inbox?: string;
}
};
/**
* FollowRequestを物理削除します
*/
export async function deleteFollowRequest(followRequest: string | mongo.ObjectID | IFollowRequest) {
let f: IFollowRequest;
// Populate
if (mongo.ObjectID.prototype.isPrototypeOf(followRequest)) {
f = await FollowRequest.findOne({
_id: followRequest
});
} else if (typeof followRequest === 'string') {
f = await FollowRequest.findOne({
_id: new mongo.ObjectID(followRequest)
});
} else {
f = followRequest as IFollowRequest;
}
if (f == null) return;
// このFollowingを削除
await FollowRequest.remove({
_id: f._id
});
}
/**
* Pack a request for API response
*/
export const pack = (
request: any,
me?: any
) => new Promise<any>(async (resolve, reject) => {
let _request: any;
// Populate the request if 'request' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(request)) {
_request = await FollowRequest.findOne({
_id: request
});
} else if (typeof request === 'string') {
_request = await FollowRequest.findOne({
_id: new mongo.ObjectID(request)
});
} else {
_request = deepcopy(request);
}
// Rename _id to id
_request.id = _request._id;
delete _request._id;
// Populate follower
_request.follower = await packUser(_request.followerId, me);
// Populate followee
_request.followee = await packUser(_request.followeeId, me);
resolve(_request);
});

View File

@ -111,6 +111,7 @@ export const pack = (notification: any) => new Promise<any>(async (resolve, reje
switch (_notification.type) {
case 'follow':
case 'receiveFollowRequest':
// nope
break;
case 'mention':

View File

@ -22,6 +22,7 @@ import FollowedLog, { deleteFollowedLog } from './followed-log';
import SwSubscription, { deleteSwSubscription } from './sw-subscription';
import Notification, { deleteNotification } from './notification';
import UserList, { deleteUserList } from './user-list';
import FollowRequest, { deleteFollowRequest } from './follow-request';
const User = db.get<IUser>('users');
@ -47,10 +48,26 @@ type IUserBase = {
usernameLower: string;
avatarId: mongo.ObjectID;
bannerId: mongo.ObjectID;
wallpaperId: mongo.ObjectID;
data: any;
description: string;
pinnedNoteId: mongo.ObjectID;
/**
* 凍結されているか否か
*/
isSuspended: boolean;
/**
* 鍵アカウントか否か
*/
isLocked: boolean;
/**
* このアカウントに届いているフォローリクエストの数
*/
pendingReceivedFollowRequestsCount: number;
host: string;
};
@ -240,6 +257,16 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
await Following.find({ followeeId: u._id })
).map(x => deleteFollowing(x)));
// このユーザーのFollowRequestをすべて削除
await Promise.all((
await FollowRequest.find({ followerId: u._id })
).map(x => deleteFollowRequest(x)));
// このユーザーへのFollowRequestをすべて削除
await Promise.all((
await FollowRequest.find({ followeeId: u._id })
).map(x => deleteFollowRequest(x)));
// このユーザーのFollowingLogをすべて削除
await Promise.all((
await FollowingLog.find({ userId: u._id })
@ -386,6 +413,10 @@ export const pack = (
? `${config.drive_url}/${_user.bannerId}`
: null;
_user.wallpaperUrl = _user.wallpaperId != null
? `${config.drive_url}/${_user.wallpaperId}`
: null;
if (!meId || !meId.equals(_user.id) || !opts.detail) {
delete _user.avatarId;
delete _user.bannerId;
@ -395,7 +426,7 @@ export const pack = (
}
if (meId && !meId.equals(_user.id)) {
const [following1, following2, mute] = await Promise.all([
const [following1, following2, followReq1, followReq2, mute] = await Promise.all([
Following.findOne({
followerId: meId,
followeeId: _user.id
@ -404,6 +435,14 @@ export const pack = (
followerId: _user.id,
followeeId: meId
}),
_user.isLocked ? FollowRequest.findOne({
followerId: meId,
followeeId: _user.id
}) : Promise.resolve(null),
FollowRequest.findOne({
followerId: _user.id,
followeeId: meId
}),
Mute.findOne({
muterId: meId,
muteeId: _user.id
@ -414,6 +453,9 @@ export const pack = (
_user.isFollowing = following1 !== null;
_user.isStalking = following1 && following1.stalk;
_user.hasPendingFollowRequestFromYou = followReq1 !== null;
_user.hasPendingFollowRequestToYou = followReq2 !== null;
// Whether the user is followed
_user.isFollowed = following2 !== null;

View File

@ -0,0 +1,27 @@
import * as mongo from 'mongodb';
import User, { IRemoteUser } from '../../../../models/user';
import config from '../../../../config';
import accept from '../../../../services/following/requests/accept';
import { IFollow } from '../../type';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (!id.startsWith(config.url + '/')) {
return null;
}
const follower = await User.findOne({
_id: new mongo.ObjectID(id.split('/').pop())
});
if (follower === null) {
throw new Error('follower not found');
}
if (follower.host != null) {
throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません');
}
await accept(actor, follower);
};

View File

@ -0,0 +1,35 @@
import * as debug from 'debug';
import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/user';
import acceptFollow from './follow';
import { IAccept } from '../../type';
const log = debug('misskey:activitypub');
export default async (actor: IRemoteUser, activity: IAccept): Promise<void> => {
const uri = activity.id || activity;
log(`Accept: ${uri}`);
const resolver = new Resolver();
let object;
try {
object = await resolver.resolve(activity.object);
} catch (e) {
log(`Resolution failed: ${e}`);
throw e;
}
switch (object.type) {
case 'Follow':
acceptFollow(actor, object);
break;
default:
console.warn(`Unknown accept type: ${object.type}`);
break;
}
};

View File

@ -23,5 +23,5 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
throw new Error('フォローしようとしているユーザーはローカルユーザーではありません');
}
await follow(actor, followee, activity);
await follow(actor, followee);
};

View File

@ -6,6 +6,8 @@ import follow from './follow';
import undo from './undo';
import like from './like';
import announce from './announce';
import accept from './accept';
import reject from './reject';
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
switch (activity.type) {
@ -22,7 +24,11 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
break;
case 'Accept':
// noop
await accept(actor, activity);
break;
case 'Reject':
await reject(actor, activity);
break;
case 'Announce':

View File

@ -0,0 +1,27 @@
import * as mongo from 'mongodb';
import User, { IRemoteUser } from '../../../../models/user';
import config from '../../../../config';
import reject from '../../../../services/following/requests/reject';
import { IFollow } from '../../type';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (!id.startsWith(config.url + '/')) {
return null;
}
const follower = await User.findOne({
_id: new mongo.ObjectID(id.split('/').pop())
});
if (follower === null) {
throw new Error('follower not found');
}
if (follower.host != null) {
throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません');
}
await reject(actor, follower);
};

View File

@ -0,0 +1,35 @@
import * as debug from 'debug';
import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/user';
import rejectFollow from './follow';
import { IReject } from '../../type';
const log = debug('misskey:activitypub');
export default async (actor: IRemoteUser, activity: IReject): Promise<void> => {
const uri = activity.id || activity;
log(`Reject: ${uri}`);
const resolver = new Resolver();
let object;
try {
object = await resolver.resolve(activity.object);
} catch (e) {
log(`Resolution failed: ${e}`);
throw e;
}
switch (object.type) {
case 'Follow':
rejectFollow(actor, object);
break;
default:
console.warn(`Unknown reject type: ${object.type}`);
break;
}
};

View File

@ -2,7 +2,9 @@ import * as mongo from 'mongodb';
import User, { IRemoteUser } from '../../../../models/user';
import config from '../../../../config';
import unfollow from '../../../../services/following/delete';
import cancelRequest from '../../../../services/following/requests/cancel';
import { IFollow } from '../../type';
import FollowRequest from '../../../../models/follow-request';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
@ -23,5 +25,14 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
throw new Error('フォロー解除しようとしているユーザーはローカルユーザーではありません');
}
await unfollow(actor, followee, activity);
const req = await FollowRequest.findOne({
followerId: actor._id,
followeeId: followee._id
});
if (req) {
await cancelRequest(actor, followee);
} else {
await unfollow(actor, followee);
}
};

View File

@ -93,6 +93,7 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
notesCount,
name: person.name,
driveCapacity: 1024 * 1024 * 8, // 8MiB
isLocked: person.manuallyApprovesFollowers,
username: person.preferredUsername,
usernameLower: person.preferredUsername.toLowerCase(),
host,

View File

@ -1,8 +1,8 @@
import config from '../../../config';
import { IRemoteUser, ILocalUser } from '../../../models/user';
import { IUser, isLocalUser } from '../../../models/user';
export default (follower: ILocalUser, followee: IRemoteUser) => ({
export default (follower: IUser, followee: IUser) => ({
type: 'Follow',
actor: `${config.url}/users/${follower._id}`,
object: followee.uri
actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri,
object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri
});

View File

@ -1,8 +1,9 @@
import renderImage from './image';
import renderKey from './key';
import config from '../../../config';
import { ILocalUser } from '../../../models/user';
export default user => {
export default (user: ILocalUser) => {
const id = `${config.url}/users/${user._id}`;
return {
@ -17,6 +18,7 @@ export default user => {
summary: user.description,
icon: user.avatarId && renderImage({ _id: user.avatarId }),
image: user.bannerId && renderImage({ _id: user.bannerId }),
manuallyApprovesFollowers: user.isLocked,
publicKey: renderKey(user)
};
};

View File

@ -0,0 +1,4 @@
export default object => ({
type: 'Reject',
object
});

View File

@ -45,6 +45,7 @@ export interface IPerson extends IObject {
type: 'Person';
name: string;
preferredUsername: string;
manuallyApprovesFollowers: boolean;
inbox: string;
publicKey: any;
followers: any;
@ -82,6 +83,10 @@ export interface IAccept extends IActivity {
type: 'Accept';
}
export interface IReject extends IActivity {
type: 'Reject';
}
export interface ILike extends IActivity {
type: 'Like';
_misskey_reaction: string;
@ -99,5 +104,6 @@ export type Object =
IUndo |
IFollow |
IAccept |
IReject |
ILike |
IAnnounce;

View File

@ -7,7 +7,7 @@ const httpSignature = require('http-signature');
import { createHttp } from '../queue';
import pack from '../remote/activitypub/renderer';
import Note from '../models/note';
import User, { isLocalUser } from '../models/user';
import User, { isLocalUser, ILocalUser } from '../models/user';
import renderNote from '../remote/activitypub/renderer/note';
import renderKey from '../remote/activitypub/renderer/key';
import renderPerson from '../remote/activitypub/renderer/person';
@ -69,7 +69,10 @@ router.get('/notes/:note', async (ctx, next) => {
router.get('/users/:user/outbox', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({ _id: userId });
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
@ -91,7 +94,10 @@ router.get('/users/:user/outbox', async ctx => {
router.get('/users/:user/publickey', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({ _id: userId });
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
@ -109,14 +115,17 @@ router.get('/users/:user/publickey', async ctx => {
router.get('/users/:user', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({ _id: userId });
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
ctx.body = pack(renderPerson(user));
ctx.body = pack(renderPerson(user as ILocalUser));
});
// follow form

View File

@ -189,6 +189,11 @@ const endpoints: Endpoint[] = [
withCredential: true,
secure: true
},
{
name: 'i/update_widget',
withCredential: true,
secure: true
},
{
name: 'i/change_password',
withCredential: true,
@ -448,6 +453,26 @@ const endpoints: Endpoint[] = [
},
kind: 'following-write'
},
{
name: 'following/requests/accept',
withCredential: true,
kind: 'following-write'
},
{
name: 'following/requests/reject',
withCredential: true,
kind: 'following-write'
},
{
name: 'following/requests/cancel',
withCredential: true,
kind: 'following-write'
},
{
name: 'following/requests/list',
withCredential: true,
kind: 'following-read'
},
{
name: 'following/stalk',
withCredential: true,
@ -555,7 +580,7 @@ const endpoints: Endpoint[] = [
withCredential: true,
limit: {
duration: ms('1hour'),
max: 100
max: 300
},
kind: 'reaction-write'
},

View File

@ -2,7 +2,7 @@
* Module dependencies
*/
import $ from 'cafy'; import ID from '../../../../cafy-id';
import User from '../../../../models/user';
import User, { pack } from '../../../../models/user';
import Following from '../../../../models/following';
import create from '../../../../services/following/create';
@ -49,5 +49,5 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
create(follower, followee);
// Send response
res();
res(await pack(followee._id, user));
});

View File

@ -2,7 +2,7 @@
* Module dependencies
*/
import $ from 'cafy'; import ID from '../../../../cafy-id';
import User from '../../../../models/user';
import User, { pack } from '../../../../models/user';
import Following from '../../../../models/following';
import deleteFollowing from '../../../../services/following/delete';
@ -49,5 +49,5 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
deleteFollowing(follower, followee);
// Send response
res();
res(await pack(followee._id, user));
});

View File

@ -0,0 +1,26 @@
import $ from 'cafy'; import ID from '../../../../../cafy-id';
import acceptFollowRequest from '../../../../../services/following/requests/accept';
import User from '../../../../../models/user';
/**
* Accept a follow request
*/
module.exports = (params, user) => new Promise(async (res, rej) => {
// Get 'userId' parameter
const [followerId, followerIdErr] = $.type(ID).get(params.userId);
if (followerIdErr) return rej('invalid userId param');
// Fetch follower
const follower = await User.findOne({
_id: followerId
});
if (follower === null) {
return rej('follower not found');
}
await acceptFollowRequest(user, follower);
// Send response
res();
});

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