Compare commits

...

140 Commits

Author SHA1 Message Date
syuilo
af2d36a3c9 2.34.3 2018-06-09 11:41:22 +09:00
syuilo
42a4f92cfa Fix #1125 2018-06-09 11:40:42 +09:00
syuilo
ccb9ed3489 🎨 2018-06-09 11:29:50 +09:00
syuilo
773b2aa3d1 2.34.2 2018-06-09 10:16:29 +09:00
syuilo
30d5b8d65b 🎨 2018-06-09 10:15:45 +09:00
syuilo
763676a18c ✌️ 2018-06-09 10:12:03 +09:00
syuilo
e166ad6780 Better init screen 2018-06-09 10:06:27 +09:00
syuilo
034c96d070 Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-09 08:38:33 +09:00
syuilo
f34f8d304c Improve performance 2018-06-09 08:38:30 +09:00
syuilo
944000c05c Update README.md 2018-06-09 06:52:33 +09:00
syuilo
52db63bca2 2.34.1 2018-06-09 06:28:22 +09:00
syuilo
55dfd9e2a1 Merge pull request #1687 from syuilo/l10n_master
New Crowdin translations
2018-06-09 06:27:57 +09:00
syuilo
d193cbf2b7 ✌️ 2018-06-09 06:27:12 +09:00
syuilo
bdec56a543 ✌️ 2018-06-09 06:25:41 +09:00
syuilo
e0a6d9740c ✌️ 2018-06-09 06:21:13 +09:00
syuilo
0ce9c057e1 Fix chart rendering 2018-06-09 06:04:41 +09:00
syuilo
12a2fdbc20 New translations ja.yml (Portuguese) 2018-06-09 04:21:28 +09:00
syuilo
57c294bc89 New translations ja.yml (Korean) 2018-06-09 04:21:26 +09:00
syuilo
9758757805 New translations ja.yml (Polish) 2018-06-09 04:21:25 +09:00
syuilo
f9350fa35f New translations ja.yml (Chinese Simplified) 2018-06-09 04:21:22 +09:00
syuilo
e120da4ecd New translations ja.yml (Italian) 2018-06-09 04:21:20 +09:00
syuilo
328a10b70c New translations ja.yml (Russian) 2018-06-09 04:21:18 +09:00
syuilo
1ed97c8deb New translations ja.yml (English) 2018-06-09 04:21:17 +09:00
syuilo
91b970e2aa New translations ja.yml (Spanish) 2018-06-09 04:21:15 +09:00
syuilo
99af1bb479 New translations ja.yml (German) 2018-06-09 04:21:13 +09:00
syuilo
11ddcbdee3 New translations ja.yml (French) 2018-06-09 04:21:11 +09:00
syuilo
6e8a1086d8 2.34.0 2018-06-09 04:15:18 +09:00
syuilo
c78945436e #1686 2018-06-09 04:14:26 +09:00
syuilo
6eff8fde74 サーバーの統計情報をメモリに記憶するようにするなど 2018-06-09 01:45:25 +09:00
syuilo
726d5a177e Merge pull request #1685 from 2vg/patch-1
fix: when text is null, bug can pass validation.
2018-06-08 22:04:22 +09:00
momf
33495b5cb3 fix: "or" operator. 2018-06-08 22:04:07 +09:00
syuilo
fe159a13a9 2.33.2 2018-06-08 22:03:24 +09:00
syuilo
22a1dc0566 Better log 2018-06-08 22:03:14 +09:00
momf
02e6b732e9 fix: when text is null, bug can pass validation.
fixed. (maybe?)
2018-06-08 22:00:18 +09:00
syuilo
cc6fa135ac 2.33.1 2018-06-08 21:50:41 +09:00
syuilo
5747732156 Fix 2018-06-08 21:50:31 +09:00
syuilo
581d1617d8 2.33.0 2018-06-08 21:39:24 +09:00
syuilo
6152fd20bf Improve deck 2018-06-08 21:37:20 +09:00
syuilo
19300ca65c Add new cli tool 2018-06-08 21:22:13 +09:00
syuilo
2f3d744e19 🎨 2018-06-08 21:17:48 +09:00
syuilo
724e812972 Refactor 2018-06-08 20:57:02 +09:00
syuilo
9a6246fd4e New: Zen mode 2018-06-08 20:34:44 +09:00
syuilo
34f44de59c Update README.md 2018-06-08 13:14:30 +09:00
syuilo
16e446c121 2.32.0 2018-06-08 11:47:33 +09:00
syuilo
8f232a9da9 Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-08 11:46:48 +09:00
syuilo
ebeb7f8578 ✌️ 2018-06-08 11:46:45 +09:00
syuilo
f790673068 Merge pull request #1684 from syuilo/l10n_master
New Crowdin translations
2018-06-08 11:17:44 +09:00
syuilo
335ab5ab54 Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-08 11:17:30 +09:00
syuilo
00e0d6ce2c Improve usability 2018-06-08 11:17:22 +09:00
syuilo
414fb6d303 New translations ja.yml (French) 2018-06-08 10:41:25 +09:00
syuilo
9c35a12211 New translations ja.yml (French) 2018-06-08 10:12:39 +09:00
syuilo
bb4fe5174f Update README.md 2018-06-08 09:08:09 +09:00
syuilo
3ffd6ff5a2 2.31.0 2018-06-08 08:39:04 +09:00
syuilo
b05feb5bf7 MisskeyDeck: ドラッグでカラムを入れ替えられるように 2018-06-08 08:38:32 +09:00
syuilo
fa171f237d 2.30.2 2018-06-08 08:06:19 +09:00
syuilo
f2ccb684eb 🎨 2018-06-08 08:05:25 +09:00
syuilo
ffea6522ac Merge pull request #1683 from syuilo/l10n_master
New Crowdin translations
2018-06-08 07:44:17 +09:00
syuilo
3d40a7df00 ✌️ 2018-06-08 07:43:12 +09:00
syuilo
638c41476b New translations ja.yml (German) 2018-06-08 07:40:48 +09:00
syuilo
c6d3088374 Merge pull request #1682 from syuilo/l10n_master
New Crowdin translations
2018-06-08 07:26:58 +09:00
syuilo
0f93be9dd4 New translations ja.yml (German) 2018-06-08 07:01:06 +09:00
syuilo
f59982c9c5 New translations ja.yml (German) 2018-06-08 06:51:25 +09:00
syuilo
dff67a5e54 New translations ja.yml (German) 2018-06-08 06:41:05 +09:00
syuilo
6adcc3b2ed New translations ja.yml (German) 2018-06-08 06:31:09 +09:00
syuilo
877ed3663c New translations ja.yml (German) 2018-06-08 06:21:24 +09:00
syuilo
6000a82917 New translations ja.yml (German) 2018-06-08 06:11:02 +09:00
syuilo
6805f9b3e0 New translations ja.yml (German) 2018-06-08 06:01:10 +09:00
syuilo
1366c785f9 Update README.md 2018-06-08 05:54:42 +09:00
syuilo
70540b4500 New translations ja.yml (German) 2018-06-08 05:51:01 +09:00
syuilo
0967f23b6e 2.30.1 2018-06-08 05:49:19 +09:00
syuilo
1f7d66169c Merge pull request #1681 from syuilo/l10n_master
New Crowdin translations
2018-06-08 05:48:51 +09:00
syuilo
af501f5eeb Fix bug 2018-06-08 05:48:27 +09:00
syuilo
60be60c923 New translations ja.yml (German) 2018-06-08 05:41:56 +09:00
syuilo
48746101e0 New translations ja.yml (Polish) 2018-06-08 05:20:55 +09:00
syuilo
af9c5c6ab7 typo 2018-06-08 05:07:19 +09:00
syuilo
602284d38c 2.30.0 2018-06-08 05:04:56 +09:00
syuilo
26898142c2 Merge pull request #1680 from syuilo/l10n_master
New Crowdin translations
2018-06-08 05:04:32 +09:00
syuilo
b0a8d7abe9 ✌️ 2018-06-08 05:04:21 +09:00
syuilo
dc2b266b75 New translations ja.yml (English) 2018-06-08 04:52:14 +09:00
syuilo
07bbd9506a 🎨 2018-06-08 04:46:31 +09:00
syuilo
14bb218287 New translations ja.yml (English) 2018-06-08 04:41:42 +09:00
syuilo
29f238c929 New translations ja.yml (Portuguese) 2018-06-08 04:39:44 +09:00
syuilo
a39a1d4fa5 New translations ja.yml (Korean) 2018-06-08 04:39:41 +09:00
syuilo
15117c63f5 New translations ja.yml (Polish) 2018-06-08 04:39:39 +09:00
syuilo
507ffb6fc6 New translations ja.yml (Chinese Simplified) 2018-06-08 04:39:38 +09:00
syuilo
6b2e0164cf New translations ja.yml (Italian) 2018-06-08 04:39:36 +09:00
syuilo
02e06eb1de New translations ja.yml (Russian) 2018-06-08 04:39:33 +09:00
syuilo
1b50f78733 New translations ja.yml (English) 2018-06-08 04:39:31 +09:00
syuilo
ead629407c New translations ja.yml (Spanish) 2018-06-08 04:39:29 +09:00
syuilo
0abbc9e7dd New translations ja.yml (German) 2018-06-08 04:39:26 +09:00
syuilo
37681e859e New translations ja.yml (French) 2018-06-08 04:39:24 +09:00
syuilo
caabdc68f3 MisskeyDeck: スタックしたカラムを上下移動できるように 2018-06-08 04:34:15 +09:00
syuilo
9e97eaf24d New translations ja.yml (English) 2018-06-08 04:31:03 +09:00
syuilo
4cd06a789b New translations ja.yml (Portuguese) 2018-06-08 04:22:10 +09:00
syuilo
a3ffd968de New translations ja.yml (Korean) 2018-06-08 04:22:08 +09:00
syuilo
0cf40563aa New translations ja.yml (Polish) 2018-06-08 04:22:06 +09:00
syuilo
3e7e7f864b New translations ja.yml (Chinese Simplified) 2018-06-08 04:22:03 +09:00
syuilo
6ae415e36a New translations ja.yml (Italian) 2018-06-08 04:22:01 +09:00
syuilo
6cefa3ae26 New translations ja.yml (Russian) 2018-06-08 04:21:59 +09:00
syuilo
70de3af3ea New translations ja.yml (English) 2018-06-08 04:21:57 +09:00
syuilo
66ed814527 New translations ja.yml (Spanish) 2018-06-08 04:21:55 +09:00
syuilo
e12cc3b7a8 New translations ja.yml (German) 2018-06-08 04:21:53 +09:00
syuilo
93ea19d7ad New translations ja.yml (French) 2018-06-08 04:21:51 +09:00
syuilo
79d592b431 MisskeyDeck: カラムをスタックできるように 2018-06-08 04:21:06 +09:00
syuilo
c9c3a0be82 New translations ja.yml (Polish) 2018-06-08 03:55:30 +09:00
syuilo
f04be199dd New translations ja.yml (French) 2018-06-07 22:21:47 +09:00
syuilo
f36cb1cc66 New translations ja.yml (French) 2018-06-07 22:12:09 +09:00
syuilo
a5597e3df9 New translations ja.yml (French) 2018-06-07 21:26:16 +09:00
syuilo
7f4c28053e New translations ja.yml (French) 2018-06-07 21:12:53 +09:00
syuilo
ea24043b22 New translations ja.yml (English) 2018-06-07 08:50:51 +09:00
syuilo
44ef60c8a2 2.29.1 2018-06-07 08:48:22 +09:00
syuilo
bd68ff2cf3 Merge pull request #1679 from syuilo/l10n_master
New Crowdin translations
2018-06-07 08:47:37 +09:00
syuilo
0e8a592b26 ✌️ 2018-06-07 08:41:58 +09:00
syuilo
d3b51bf94a ✌️ 2018-06-07 08:29:46 +09:00
syuilo
cc137ee1cc New translations ja.yml (Portuguese) 2018-06-07 06:21:37 +09:00
syuilo
c088482cef New translations ja.yml (Korean) 2018-06-07 06:21:35 +09:00
syuilo
70e3febe0a New translations ja.yml (Polish) 2018-06-07 06:21:33 +09:00
syuilo
f500cce293 New translations ja.yml (Chinese Simplified) 2018-06-07 06:21:31 +09:00
syuilo
c6b836b7be New translations ja.yml (Italian) 2018-06-07 06:21:29 +09:00
syuilo
15485da1bb New translations ja.yml (Russian) 2018-06-07 06:21:27 +09:00
syuilo
7195f55a44 New translations ja.yml (English) 2018-06-07 06:21:25 +09:00
syuilo
176f8803eb New translations ja.yml (Spanish) 2018-06-07 06:21:24 +09:00
syuilo
5a3a925a3c New translations ja.yml (German) 2018-06-07 06:21:21 +09:00
syuilo
29bfb9d19b New translations ja.yml (French) 2018-06-07 06:21:19 +09:00
syuilo
86b0dfdd33 2.29.0 2018-06-07 06:16:11 +09:00
syuilo
ab04f2fce0 ✌️ 2018-06-07 06:15:57 +09:00
syuilo
be9f836b21 やった 2018-06-07 06:13:57 +09:00
syuilo
818bc96aab 2.28.0 2018-06-07 05:15:05 +09:00
syuilo
14d12c21f2 nanka iroiro 2018-06-07 05:14:37 +09:00
syuilo
aa5250a37c New translations ja.yml (Polish) 2018-06-07 04:51:24 +09:00
syuilo
2053a041e5 Fix 2018-06-07 04:33:39 +09:00
syuilo
0534a0a41e ✌️ 2018-06-07 04:31:49 +09:00
syuilo
d2f9a99beb New translations ja.yml (French) 2018-06-07 04:01:46 +09:00
syuilo
9625047dc3 New translations ja.yml (French) 2018-06-07 03:51:34 +09:00
syuilo
d6b18ce536 New translations ja.yml (French) 2018-06-07 03:41:20 +09:00
syuilo
df00af1dfa New translations ja.yml (French) 2018-06-07 03:14:30 +09:00
syuilo
3570ec0430 New translations ja.yml (French) 2018-06-07 03:06:29 +09:00
syuilo
a111b014f8 New translations ja.yml (French) 2018-06-07 02:53:46 +09:00
syuilo
50eebe834a New translations ja.yml (French) 2018-06-07 02:41:46 +09:00
syuilo
f965e9f218 New translations ja.yml (French) 2018-06-07 02:34:06 +09:00
79 changed files with 1913 additions and 1279 deletions

View File

@@ -12,15 +12,18 @@
> Lead Maintainer: [syuilo][syuilo-link] > Lead Maintainer: [syuilo][syuilo-link]
**[Misskey](https://misskey.xyz)** is a completely open source, **[Misskey](https://misskey.xyz)** is a completely open source,
ultimately sophisticated new type of mini-blog based SNS. ultimately sophisticated professional microblogging software.
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a> <a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
![](https://c10.patreonusercontent.com/3/e30%3D/patreon-posts/RsKWEDEKf8D_wYDQWAbex9CSb-1DnXW1nfqfLvuys5ROj2k0VF6_luuzHMTyf95n.png?token-time=1529539200&token-hash=RmcSP0947mw5o2-B6g1L6aU_OoDXANe198kLU6HMO30%3D)
:sparkles: Features :sparkles: Features
---------------------------------------------------------------- ----------------------------------------------------------------
* Reactions * Reactions
* User lists * User lists
* Cusromizable column view (known as MisskeyDeck) * Customizable column view (known as MisskeyDeck)
* and widgets!
* Private messages * Private messages
* Mute * Mute
* Streaming * Streaming
@@ -46,18 +49,9 @@ If you want to...
[![Backers][backers-image]][support-url] [![Backers][backers-image]][support-url]
[![Sponsors][sponsors-image]][support-url] [![Sponsors][sponsors-image]][support-url]
:mortar_board: Notable contributors | ![][ooo-icon] |
---------------------------------------------------------------- |:-:|
| ![syuilo][syuilo-icon] | ![Morisawa Aya][ayamorisawa-icon] | ![otofune][otofune-icon] | ![akihikodaki][akihikodaki-icon] | ![tamaina][tamaina-icon] | ![rinsuki][rinsuki-icon] | | [ooo][ooo-link] |
|:-:|:-:|:-:|:-:|:-:|:-:|
| [syuilo][syuilo-link]<br>Owner | [Aya Morisawa][ayamorisawa-link]<br>Collaborator | [otofune][otofune-link]<br>Collaborator | [akihikodaki][akihikodaki-link] | [tamaina][tamaina-link] | [rinsuki][rinsuki-link] |
[List of all contributors](https://github.com/syuilo/misskey/graphs/contributors)
### :earth_americas: Translators
| ![][mirro-san-icon] | ![][Conan-kun-icon] | ![][m4sk1n-icon] |
|:-:|:-:|:-:|
| [Mirro][mirro-san-link]<br>English, French | [Asriel][Conan-kun-link]<br>English, French | [Marcin Mikołajczak][m4sk1n-link]<br>Polish |
:four_leaf_clover: Copyright :four_leaf_clover: Copyright
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -85,23 +79,8 @@ Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg [sponsors-image]: https://opencollective.com/misskey/sponsors.svg
[support-url]: https://opencollective.com/misskey#support [support-url]: https://opencollective.com/misskey#support
<!-- Contributors Info -->
[syuilo-link]: https://syuilo.com [syuilo-link]: https://syuilo.com
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70 [syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
[ayamorisawa-link]: https://github.com/ayamorisawa
[ayamorisawa-icon]: https://avatars0.githubusercontent.com/u/10798641?v=3&s=70
[otofune-link]: https://github.com/otofune
[otofune-icon]: https://avatars0.githubusercontent.com/u/15062473?v=3&s=70
[akihikodaki-link]: https://github.com/akihikodaki
[akihikodaki-icon]: https://avatars2.githubusercontent.com/u/17036990?s=70&v=4
[rinsuki-link]: https://github.com/rinsuki
[rinsuki-icon]: https://avatars0.githubusercontent.com/u/6533808?s=70&v=4
[tamaina-link]: https://github.com/tamaina
[tamaina-icon]: https://avatars1.githubusercontent.com/u/7973572?s=70&v=4
[mirro-san-link]: https://github.com/mirro-san [ooo-link]: https://www.patreon.com/user/creators?u=11601413
[mirro-san-icon]: https://avatars1.githubusercontent.com/u/17948612?s=70&v=4 [ooo-icon]: https://c10.patreonusercontent.com/3/eyJ2IjoiMSIsInciOjIwMH0%3D/patreon-media/user/11601413/20cb15f209924302b399b99d3c98b850?token-time=2145916800&token-hash=IO31nK6VZCMWBWU2VAk2c824BX2QZ4DNPKyHHZXS0iw%3D
[Conan-kun-link]: https://github.com/Conan-kun
[Conan-kun-icon]: https://avatars3.githubusercontent.com/u/30003708?s=70&v=4
[m4sk1n-link]: https://github.com/m4sk1n
[m4sk1n-icon]: https://avatars3.githubusercontent.com/u/21127288?s=70&v=4

View File

@@ -3,16 +3,21 @@ const User = require('../built/models/user').default;
const args = process.argv.slice(2); 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: { $set: {
isSuspended: true isSuspended: true
} }
}).then(() => { }).then(() => {
console.log(`Suspended ${userId}`); console.log(`Suspended ${user}`);
}, e => { }, e => {
console.error(e); console.error(e);
}); });

12
cli/update-remote-user.js Normal file
View File

@@ -0,0 +1,12 @@
const updatePerson = require('../built/remote/activitypub/models/person').updatePerson;
const args = process.argv.slice(2);
const user = args[0];
console.log(`Updating ${user}...`);
updatePerson(user).then(() => {
console.log(`Updated ${user}`);
}, e => {
console.error(e);
});

View File

@@ -57,6 +57,7 @@ common:
memo: "Notizen" memo: "Notizen"
trends: "Trends" trends: "Trends"
photo-stream: "Bilder" photo-stream: "Bilder"
posts-monitor: "投稿チャート"
slideshow: "Diashow" slideshow: "Diashow"
version: "Version" version: "Version"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -70,16 +71,21 @@ common:
nav: "Navigation" nav: "Navigation"
tips: "Tipps" tips: "Tipps"
deck: deck:
widgets: "ウィジェット" widgets: "Widget hinzufügen:"
home: "ホーム" home: "Startseite"
local: "ローカル" local: "Lokal"
global: "グローバル" global: "Global"
notifications: "通知" notifications: "Mitteilungen"
list: "リスト" list: "Listen"
swap-left: "左に移動" swap-left: "Nach links"
swap-right: "右に移動" swap-right: "Nach rechts"
remove: "カラムを削除" swap-up: "Nach oben"
add-column: "カラムを追加" swap-down: "Nach unten"
remove: "Spalte löschen"
add-column: "Eine Spalte hinzufügen"
rename: "Umbenennen"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "Verbindung zum Server ist fehlgeschlagen" 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." 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."
@@ -198,7 +204,7 @@ common/views/components/uploader.vue:
common/views/components/visibility-chooser.vue: common/views/components/visibility-chooser.vue:
public: "Öffentlich" public: "Öffentlich"
home: "Home" home: "Home"
home-desc: "ホームタイムラインにのみ公開" home-desc: "Nur auf die Startseite posten"
followers: "Folgende" followers: "Folgende"
followers-desc: "Nur für diejenigen sichtbar, die dir folgen" followers-desc: "Nur für diejenigen sichtbar, die dir folgen"
specified: "Direkt" specified: "Direkt"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "Fotostream" title: "Fotostream"
no-photos: "Keine Fotos" no-photos: "Keine Fotos"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Serverinformationen" title: "Serverinformationen"
toggle: "Sicht umschalten" toggle: "Sicht umschalten"
@@ -259,7 +268,7 @@ desktop/views/components/drive.file.vue:
rename: "Umbenennen" rename: "Umbenennen"
copy-url: "URL kopieren" copy-url: "URL kopieren"
download: "Download" download: "Download"
else-files: "その他..." else-files: "Anderes…"
set-as-avatar: "Als Avatar festlegen" set-as-avatar: "Als Avatar festlegen"
set-as-banner: "Setze als Banner" set-as-banner: "Setze als Banner"
open-in-app: "In der App öffnen" open-in-app: "In der App öffnen"
@@ -301,10 +310,10 @@ desktop/views/components/drive.vue:
upload: "Eine Datei hochladen" upload: "Eine Datei hochladen"
url-upload: "Von einer URL hochladen" url-upload: "Von einer URL hochladen"
desktop/views/components/follow-button.vue: desktop/views/components/follow-button.vue:
following: "フォロー中" following: "Folge ich"
follow: "フォロー" follow: "Folgen"
request-pending: "フォロー許可待ち" request-pending: "Ausstehend"
follow-request: "フォロー申請" follow-request: "Follower-Anfragen"
desktop/views/components/followers-window.vue: desktop/views/components/followers-window.vue:
followers: "{} のフォロワー" followers: "{} のフォロワー"
desktop/views/components/followers.vue: desktop/views/components/followers.vue:
@@ -312,18 +321,18 @@ desktop/views/components/followers.vue:
desktop/views/components/following-window.vue: desktop/views/components/following-window.vue:
following: "{} のフォロー" following: "{} のフォロー"
desktop/views/components/following.vue: desktop/views/components/following.vue:
empty: "フォロー中のユーザーはいないようです。" empty: "Du folgst niemanden"
desktop/views/components/friends-maker.vue: desktop/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー:" title: "Wem folgen?"
empty: "おすすめのユーザーは見つかりませんでした。" empty: "Der ausgewählte Benutzer konnte nicht gefunden werden."
fetching: "読み込んでいます" fetching: "Lade…"
refresh: "もっと見る" refresh: "Mehr"
close: "閉じる" close: "Schließen"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "オセロ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "Verbunden"
add-widget: "ウィジェットを追加:" add-widget: "Widget hinzufügen:"
add: "Hinzufügen" add: "Hinzufügen"
desktop/views/input-dialog.vue: desktop/views/input-dialog.vue:
cancel: "Abbrechen" cancel: "Abbrechen"
@@ -334,9 +343,9 @@ desktop/views/components/messaging-window.vue:
title: "Nachrichten" title: "Nachrichten"
desktop/views/components/note-detail.vue: desktop/views/components/note-detail.vue:
more: "Lade weitere Konversationen" more: "Lade weitere Konversationen"
private: "この投稿は非公開です" private: "Dieser Post ist privat"
deleted: "この投稿は削除されました" deleted: "Dieser Beitrag wurde entfernt"
reposted-by: "{}がRenote" reposted-by: "Repostet von {}"
location: "Ort" location: "Ort"
renote: "Anmerkung" renote: "Anmerkung"
add-reaction: "Reaktion hinzufügen" add-reaction: "Reaktion hinzufügen"
@@ -346,8 +355,8 @@ desktop/views/components/notes.note.vue:
renote: "Anmerken" renote: "Anmerken"
add-reaction: "Eine Reaktion hinzufügen" add-reaction: "Eine Reaktion hinzufügen"
detail: "Zeige Details" detail: "Zeige Details"
private: "この投稿は非公開です" private: "Dieser Beitrag ist eine privat"
deleted: "この投稿は削除されました" deleted: "Dieser Beitrag wurde entfernt"
desktop/views/components/notes.vue: desktop/views/components/notes.vue:
error: "Laden fehlgeschlagen." error: "Laden fehlgeschlagen."
retry: "Erneut versuchen" retry: "Erneut versuchen"
@@ -388,34 +397,34 @@ desktop/views/components/renote-form.vue:
success: "Weitergesagt!" success: "Weitergesagt!"
failure: "Weitersagen fehlgeschlagen" failure: "Weitersagen fehlgeschlagen"
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか" title: "Bist du dir sicher, dass du das reposten willst?"
desktop/views/components/settings-window.vue: desktop/views/components/settings-window.vue:
settings: "設定" settings: "Experimentelles"
desktop/views/components/settings.vue: desktop/views/components/settings.vue:
profile: "プロフィール" profile: "プロフィール"
notification: "通知" notification: "Mitteilungen"
apps: "アプリ" apps: "In App öffnen"
mute: "ミュート" mute: "Stummschalten"
drive: "ドライブ" drive: "Dateien vom Drive anfügen"
security: "セキュリティ" security: "セキュリティ"
signin: "サインイン履歴" signin: "サインイン履歴"
password: "パスワード" password: "パスワード"
2fa: "二段階認証" 2fa: "二段階認証"
other: "その他" other: "その他"
license: "ライセンス" license: "ライセンス"
behaviour: "動作" behaviour: "Verhalten"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。" fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
auto-popout: "ウィンドウの自動ポップアウト" auto-popout: "ウィンドウの自動ポップアウト"
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。" auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
advanced: "詳細設定" advanced: "Erweiterte Einstellungen"
api-via-stream: "ストリームを経由したAPIリクエスト" api-via-stream: "API-Anfrage via stream"
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。" api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
display: "デザインと表示" display: "Erscheinungsbild und Anzeige"
customize: "ホームをカスタマイズ" customize: "Startseite anpassen"
dark-mode: "ダークモード" dark-mode: "Nacht Modus"
circle-icons: "円形のアイコンを使用" circle-icons: "Kreisförmige Icons"
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用" gradient-window-header: "Übergang in Fensterköpfen"
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する" post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
show-reply-target: "リプライ先を表示する" show-reply-target: "リプライ先を表示する"
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する" show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
@@ -427,12 +436,12 @@ desktop/views/components/settings.vue:
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。" enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
volume: "ボリューム" volume: "ボリューム"
test: "テスト" test: "テスト"
mobile: "モバイル" mobile: "Mobil"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "Diesen Beitrag nicht mit 'vom Handy' absenden"
language: "言語" language: "Sprache"
pick-language: "言語を選択" pick-language: "Sprache auswählen"
recommended: "推奨" recommended: "Empfohlen"
auto: "自動" auto: "Automatisch"
specify-language: "言語を指定" specify-language: "言語を指定"
language-desc: "変更はページの再度読み込み後に反映されます。" language-desc: "変更はページの再度読み込み後に反映されます。"
cache: "キャッシュ" cache: "キャッシュ"
@@ -558,7 +567,7 @@ desktop/views/components/users-list.vue:
all: "すべて" all: "すべて"
iknow: "知り合い" iknow: "知り合い"
load-more: "もっと" load-more: "もっと"
fetching: "読み込んでいます" fetching: "Lade…"
desktop/views/components/users-list-item.vue: desktop/views/components/users-list-item.vue:
followed: "フォローされています" followed: "フォローされています"
desktop/views/components/window.vue: desktop/views/components/window.vue:
@@ -680,7 +689,7 @@ mobile/views/components/follow-button.vue:
mobile/views/components/friends-maker.vue: mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー" title: "気になるユーザーをフォロー"
empty: "おすすめのユーザーは見つかりませんでした。" empty: "おすすめのユーザーは見つかりませんでした。"
fetching: "読み込んでいます" fetching: "Lade…"
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
mobile/views/components/note.vue: mobile/views/components/note.vue:

View File

@@ -57,6 +57,7 @@ common:
memo: "Memo" memo: "Memo"
trends: "Trends" trends: "Trends"
photo-stream: "Photo stream" photo-stream: "Photo stream"
posts-monitor: "投稿チャート"
slideshow: "Slideshow" slideshow: "Slideshow"
version: "Version" version: "Version"
broadcast: "Broadcast" broadcast: "Broadcast"
@@ -76,10 +77,15 @@ common:
global: "Global" global: "Global"
notifications: "Notifications" notifications: "Notifications"
list: "List" list: "List"
swap-left: "Move Left" swap-left: "Move left"
swap-right: "Move Right" swap-right: "Move right"
swap-up: "Move upward"
swap-down: "Move downward"
remove: "Remove" remove: "Remove"
add-column: "Add a column" add-column: "Add a column"
rename: "Rename"
stack-left: "Stack to left"
pop-right: "Pop to right"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "Unable to connect to the server" 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."
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "Photostream" title: "Photostream"
no-photos: "No photos" no-photos: "No photos"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Server info" title: "Server info"
toggle: "Toggle views" toggle: "Toggle views"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -3,9 +3,9 @@ meta:
lang: "Français" lang: "Français"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "Une planète du fédiverse"
about-title: "Aof fediverse." about-title: "Unedu fédiverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Merci d'avoir découvert Misskey. Misskey est une <b>plateforme de micro-blogging distribuée</b> née sur Terre. Parce qu'il fait partie du Fédiverse (un univers composé de diverses plateformes de réseaux sociaux organisées), il est mutuellement connecté avec d'autres plateformes de réseaux sociaux. Désirez-vous prendre une pause, pendant un instant, loin de l'agitation de la ville et plonger dans un nouvel Internet ?"
time: time:
unknown: "inconnu" unknown: "inconnu"
future: "future" future: "future"
@@ -24,7 +24,7 @@ common:
wednesday: "M" wednesday: "M"
thursday: "J" thursday: "J"
friday: "V" friday: "V"
saturday: "" saturday: "S"
reactions: reactions:
like: "Aime" like: "Aime"
love: "Adore" love: "Adore"
@@ -36,50 +36,56 @@ common:
confused: "Confus" confused: "Confus"
pudding: "Pudding" pudding: "Pudding"
note-placeholders: note-placeholders:
a: "今どうしてる?" a: "Que faîtes vous à cet instant ?"
b: "何かありましたか?" b: "Quoi de neuf ?"
c: "何をお考えですか?" c: "Qu'avez-vous en tête ?"
d: "言いたいことは?" d: "Voulez-vous exprimer quelque chose ?"
e: "ここに書いてください" e: "Écrivez ici"
f: "あなたが書くのを待っています..." f: "En attente de vos écrits"
delete: "Supprimer" delete: "Supprimer"
loading: "Chargement" loading: "Chargement"
ok: "OK" ok: "OK"
update-available: "Une nouvelle version de Misskey est disponible({newer}, version actuelle: {current}). Recharger la page pour appliquer la mise à jour." update-available: "Une nouvelle version de Misskey est disponible({newer}, version actuelle: {current}). Recharger la page pour appliquer la mise à jour."
my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté." my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté."
widgets: widgets:
analog-clock: "アナログ時計" analog-clock: "Horloge analogique"
profile: "プロフィール" profile: "Profil"
calendar: "カレンダー" calendar: "Calendrier"
timemachine: "カレンダー(タイムマシン)" timemachine: "カレンダー(タイムマシン)"
activity: "アクティビティ" activity: "Activité"
rss: "RSSリーダー" rss: "Lecteur de flux RSS"
memo: "メモ" memo: "Note"
trends: "トレンド" trends: "Tendances"
photo-stream: "フォトストリーム" photo-stream: "Flux de photos"
slideshow: "スライドショー" posts-monitor: "投稿チャート"
version: "バージョン" slideshow: "Diaporama"
broadcast: "ブロードキャスト" version: "Version"
notifications: "通知" broadcast: "Diffusion"
users: "おすすめユーザー" notifications: "Notifications"
polls: "投票" users: "Utilisateurs"
polls: "Sondages"
post-form: "投稿フォーム" post-form: "投稿フォーム"
messaging: "メッセージ" messaging: "Messagerie"
server: "サーバー情報" server: "Info sur le serveur"
donation: "寄付のお願い" donation: "Dons"
nav: "ナビゲーション" nav: "Navigation"
tips: "ヒント" tips: "Conseils"
deck: deck:
widgets: "ウィジェット" widgets: "Widgets"
home: "ホーム" home: "Accueil"
local: "ローカル" local: "Local"
global: "グローバル" global: "Global"
notifications: "通知" notifications: "Notifications"
list: "リスト" list: "Liste"
swap-left: "左に移動" swap-left: "Déplacer à gauche"
swap-right: "右に移動" swap-right: "Déplacer à droite"
remove: "カラムを削除" swap-up: "Vers le haut"
add-column: "カラムを追加" swap-down: "Vers le bas"
remove: "Supprimer"
add-column: "Ajouter une colonne"
rename: "Renommer"
stack-left: "Vers la gauche"
pop-right: "Vers la droite"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "Impossible de se connecter au server." 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." 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."
@@ -102,8 +108,8 @@ common/views/components/connect-failed.troubleshooter.vue:
no-server-desc: "Votre connexion est OK, mais il a été impossible de vous connecter au serveur de Misskey. Il y a des chances que le serveur soit hors-ligne ou en maintenance, veuillez ressayer plus tard." no-server-desc: "Votre connexion est OK, mais il a été impossible de vous connecter au serveur de Misskey. Il y a des chances que le serveur soit hors-ligne ou en maintenance, veuillez ressayer plus tard."
success: "Connexion au serveur de Misskey reussie!" success: "Connexion au serveur de Misskey reussie!"
success-desc: "La connexion au serveur a été reussie. Veuillez recharger la page." success-desc: "La connexion au serveur a été reussie. Veuillez recharger la page."
flush: "キャッシュの削除" flush: "Vider le cache"
set-version: "バージョン指定" set-version: "Choisissez une version"
common/views/components/messaging.vue: common/views/components/messaging.vue:
search-user: "Trouver un utilisateur" search-user: "Trouver un utilisateur"
you: "Vous" you: "Vous"
@@ -130,12 +136,12 @@ common/views/components/nav.vue:
donors: "Donateurs" donors: "Donateurs"
repository: "Repo" repository: "Repo"
develop: "Développeurs" develop: "Développeurs"
feedback: "フィードバック" feedback: "Remarques"
common/views/components/note-menu.vue: common/views/components/note-menu.vue:
favorite: "Favorite this note" favorite: "Favorite this note"
pin: "Épingler sur votre profile" pin: "Épingler sur votre profile"
delete: "削除" delete: "Supprimer"
delete-confirm: "この投稿を削除しますか?" delete-confirm: "Supprimer cette publication ?"
remote: "投稿元で見る" remote: "投稿元で見る"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "Voter pour '{}'" vote-to: "Voter pour '{}'"
@@ -196,14 +202,14 @@ common/views/components/twitter-setting.vue:
common/views/components/uploader.vue: common/views/components/uploader.vue:
waiting: "En attente" waiting: "En attente"
common/views/components/visibility-chooser.vue: common/views/components/visibility-chooser.vue:
public: "公開" public: "Public"
home: "ホーム" home: "Accueil"
home-desc: "ホームタイムラインにのみ公開" home-desc: "Publier sur le fil d'Accueil uniquement"
followers: "フォロワー" followers: "Abonnés"
followers-desc: "自分のフォロワーにのみ公開" followers-desc: "Publier à vos abonnés uniquement"
specified: "ダイレクト" specified: "Direct"
specified-desc: "指定したユーザーにのみ公開" specified-desc: "Publier aux utilisateurs mentionnés"
private: "非公開" private: "Privé"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Récuperation" fetching: "Récuperation"
no-broadcasts: "No broadcasts" no-broadcasts: "No broadcasts"
@@ -215,18 +221,21 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "Flux de photo" title: "Flux de photo"
no-photos: "Pas de photos" no-photos: "Pas de photos"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Info sur le serveur" title: "Info sur le serveur"
toggle: "Afficher les vues" toggle: "Afficher les vues"
common/views/widgets/memo.vue: common/views/widgets/memo.vue:
title: "メモ" title: "Note"
memo: "ここに書いて!" memo: "Écrivez ici !"
save: "保存" save: "Enregistrer"
desktop/views/components/activity.chart.vue: desktop/views/components/activity.chart.vue:
total: "Black ... Total" total: "Black ... Total"
notes: "Blue ... Notes" notes: "Bleu ... Notes"
replies: "Red ... Replies" replies: "Rouge ... Réponses"
renotes: "Green ... Renotes" renotes: "Vert ... Partages"
desktop/views/components/activity.vue: desktop/views/components/activity.vue:
title: "Activitié" title: "Activitié"
toggle: "Afficher les vues" toggle: "Afficher les vues"
@@ -236,19 +245,19 @@ desktop/views/components/calendar.vue:
next: "Mois prochain" next: "Mois prochain"
go: "Cliquer pour naviguer" go: "Cliquer pour naviguer"
desktop/views/components/choose-file-from-drive-window.vue: desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "ファイル選択中" choose-file: "Sélection de fichiers"
upload: "PCからドライブにファイルをアップロード" upload: "Téléverser des fichiers à partir de votre PC"
cancel: "キャンセル" cancel: "Annuler"
ok: "決定" ok: "OK"
choose-prompt: "ファイルを選択" choose-prompt: "Choisir un fichier"
desktop/views/components/choose-folder-from-drive-window.vue: desktop/views/components/choose-folder-from-drive-window.vue:
cancel: "キャンセル" cancel: "Annuler"
ok: "決定" ok: "OK"
choose-prompt: "フォルダを選択" choose-prompt: "Choisir un dossier"
desktop/views/components/crop-window.vue: desktop/views/components/crop-window.vue:
skip: "クロップをスキップ" skip: "Ignorer la découpe"
cancel: "キャンセル" cancel: "Annuler"
ok: "決定" ok: "OK"
desktop/views/components/drive-window.vue: desktop/views/components/drive-window.vue:
used: "utilisé" used: "utilisé"
drive: "Drive" drive: "Drive"
@@ -301,65 +310,65 @@ desktop/views/components/drive.vue:
upload: "Uploader un fichier" upload: "Uploader un fichier"
url-upload: "Uploader d'un URL" url-upload: "Uploader d'un URL"
desktop/views/components/follow-button.vue: desktop/views/components/follow-button.vue:
following: "フォロー中" following: "Abonnements"
follow: "フォロー" follow: "Suivre"
request-pending: "フォロー許可待ち" request-pending: "En attente d'approbation"
follow-request: "フォロー申請" follow-request: "Demande d'abonnement"
desktop/views/components/followers-window.vue: desktop/views/components/followers-window.vue:
followers: "{} のフォロワー" followers: "{} abonnés"
desktop/views/components/followers.vue: desktop/views/components/followers.vue:
empty: "フォロワーはいないようです。" empty: "Il semble que vous n'avez pas encore d'abonnés."
desktop/views/components/following-window.vue: desktop/views/components/following-window.vue:
following: "{} のフォロー" following: "Suit {}"
desktop/views/components/following.vue: desktop/views/components/following.vue:
empty: "フォロー中のユーザーはいないようです。" empty: "Vous ne suivez aucun compte."
desktop/views/components/friends-maker.vue: desktop/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー:" title: "Utilisateurs recommandés :"
empty: "おすすめのユーザーは見つかりませんでした。" empty: "Impossible de trouver des utilisateurs à recommander."
fetching: "読み込んでいます" fetching: "Chargement"
refresh: "もっと見る" refresh: "Plus"
close: "閉じる" close: "Fermer"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "Othello"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "Envoyer"
add-widget: "ウィジェットを追加:" add-widget: "Ajouter un widget"
add: "追加" add: "Ajouter"
desktop/views/input-dialog.vue: desktop/views/input-dialog.vue:
cancel: "キャンセル" cancel: "Annuler"
ok: "決定" ok: "OK"
desktop/views/components/messaging-room-window.vue: desktop/views/components/messaging-room-window.vue:
title: "メッセージ:" title: "Messages :"
desktop/views/components/messaging-window.vue: desktop/views/components/messaging-window.vue:
title: "Messagerie" title: "Messagerie"
desktop/views/components/note-detail.vue: desktop/views/components/note-detail.vue:
more: "会話をもっと読み込む" more: "Charger davantage de conversations"
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
reposted-by: "{}がRenote" reposted-by: "Republié par {}"
location: "位置情報" location: "Géolocalisation"
renote: "Renote" renote: "Republier"
add-reaction: "リアクション" add-reaction: "Ajouter votre reaction"
desktop/views/components/notes.note.vue: desktop/views/components/notes.note.vue:
reposted-by: "Reposté par {}" reposted-by: "Reposté par {}"
reply: "Répondre" reply: "Répondre"
renote: "Renote" renote: "Republier"
add-reaction: "Ajouter votre reaction" add-reaction: "Ajouter votre reaction"
detail: "Afficher les détails" detail: "Afficher les détails"
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
desktop/views/components/notes.vue: desktop/views/components/notes.vue:
error: "読み込みに失敗しました。" error: "Échec du chargement."
retry: "リトライ" retry: "Réessayer"
desktop/views/components/notifications.vue: desktop/views/components/notifications.vue:
more: "Plus" more: "Plus"
empty: "Pas de notifications" empty: "Pas de notifications"
desktop/views/components/post-form.vue: desktop/views/components/post-form.vue:
reply-placeholder: "Répondre à cette note" reply-placeholder: "Répondre à cette note"
quote-placeholder: "Citer cette note" quote-placeholder: "Citer cette note"
submit: "投稿" submit: "Poster"
reply: "Répondre" reply: "Répondre"
renote: "Renote" renote: "Republier"
posted: "Posté!" posted: "Posté!"
replied: "Répondu!" replied: "Répondu!"
reposted: "Reposté!" reposted: "Reposté!"
@@ -379,18 +388,18 @@ desktop/views/components/post-form-window.vue:
attaches: "{} media joint(s)" attaches: "{} media joint(s)"
uploading-media: "Upload du media {}" uploading-media: "Upload du media {}"
desktop/views/components/progress-dialog.vue: desktop/views/components/progress-dialog.vue:
waiting: "待機中" waiting: "En attente"
desktop/views/components/renote-form.vue: desktop/views/components/renote-form.vue:
quote: "Citer..." quote: "Citer..."
cancel: "Annuler" cancel: "Annuler"
renote: "Renote" renote: "Republier"
reposting: "Repost en cours..." reposting: "Repost en cours..."
success: "Reposté!" success: "Reposté!"
failure: "La renote a échoué" failure: "La renote a échoué"
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "Êtes vous sûr de vouloir renote cette note?" title: "Êtes vous sûr de vouloir renote cette note?"
desktop/views/components/settings-window.vue: desktop/views/components/settings-window.vue:
settings: "設定" settings: "Paramètres"
desktop/views/components/settings.vue: desktop/views/components/settings.vue:
profile: "Profil" profile: "Profil"
notification: "Notification" notification: "Notification"
@@ -403,67 +412,67 @@ desktop/views/components/settings.vue:
2fa: "Vérification en deux étapes" 2fa: "Vérification en deux étapes"
other: "Autres" other: "Autres"
license: "License" license: "License"
behaviour: "動作" behaviour: "Comportement"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "Chargement lors du défilement"
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。" fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
auto-popout: "ウィンドウの自動ポップアウト" auto-popout: "Fenêtre contextuelle automatique"
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。" auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
advanced: "詳細設定" advanced: "Paramètres avancés"
api-via-stream: "ストリームを経由したAPIリクエスト" api-via-stream: "Requête API via le flux"
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。" api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
display: "デザインと表示" display: "Affichage et design"
customize: "ホームをカスタマイズ" customize: "Personnaliser l'Accueil"
dark-mode: "ダークモード" dark-mode: "Mode nuit"
circle-icons: "円形のアイコンを使用" circle-icons: "Utiliser des icônes circulaires"
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用" gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する" post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
show-reply-target: "リプライ先を表示する" show-reply-target: "リプライ先を表示する"
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する" show-my-renotes: "Afficher mes republications dans le fil"
show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する" show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する"
show-maps: "マップの自動展開" show-maps: "Afficher la carte"
show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。" show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
sound: "サウンド" sound: "Son"
enable-sounds: "サウンドを有効にする" enable-sounds: "Activer le son"
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。" enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
volume: "ボリューム" volume: "Volume"
test: "テスト" test: "Test"
mobile: "モバイル" mobile: "Mobile"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
language: "言語" language: "Langue"
pick-language: "言語を選択" pick-language: "Sélectionner une langue"
recommended: "推奨" recommended: "Recommandé"
auto: "自動" auto: "Automatique"
specify-language: "言語を指定" specify-language: "Spécifier la langue"
language-desc: "変更はページの再度読み込み後に反映されます。" language-desc: "変更はページの再度読み込み後に反映されます。"
cache: "キャッシュ" cache: "Cache"
clean-cache: "クリーンアップ" clean-cache: "Nettoyage"
cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。" cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。"
cache-cleared: "キャッシュを削除しました" cache-cleared: "Cache nettoyé"
cache-cleared-desc: "ページを再度読み込みしてください。" cache-cleared-desc: "Veuillez recharger la page."
auto-watch: "投稿の自動ウォッチ" auto-watch: "投稿の自動ウォッチ"
auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。" auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。"
about: "Misskeyについて" about: "À propose de Misskey"
operator: "このサーバーの運営者" operator: "L'admin de cette instance"
update: "Misskey Update" update: "Mise à jour de Misskey"
version: "バージョン:" version: "Version :"
latest-version: "最新のバージョン:" latest-version: "Dernière version :"
update-checking: "アップデートを確認中" update-checking: "Recherche de mises à jour"
do-update: "アップデートを確認" do-update: "Rechercher des mises à jour"
update-settings: "詳細設定" update-settings: "Paramètres avancés"
prevent-update: "アップデートを延期する(非推奨)" prevent-update: "アップデートを延期する(非推奨)"
prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。" prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
no-updates: "利用可能な更新はありません" no-updates: "Aucune mise à jour disponible"
no-updates-desc: "お使いのMisskeyは最新です。" no-updates-desc: "Votre Misskey est à jour."
update-available: "新しいバージョンが利用可能です" update-available: "Nouvelle version disponible !"
update-available-desc: "ページを再度読み込みすると更新が適用されます。" update-available-desc: "ページを再度読み込みすると更新が適用されます。"
advanced-settings: "高度な設定" advanced-settings: "Réglages avancés"
debug-mode: "デバッグモードを有効にする" debug-mode: "Activer le mode debug"
debug-mode-desc: "この設定はブラウザに記憶されます。" debug-mode-desc: "Ce paramètre est stocké dans le navigateur."
experimental: "実験的機能を有効にする" experimental: "Activer les fonctionnalités expérimentales"
experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。" experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。"
tools: "ツール" tools: "Outils"
task-manager: "タスクマネージャ" task-manager: "Gestionnaire de tâches"
third-parties: "サードパーティ" third-parties: "Services tiers"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "Si vous configurez la vérication en deux étapes vous aurez non seulement besoin de votre mot de passe mais aussi un appareil déjà pré-enregistré(tel que votre smartphone) ce qui ameliora grandement la sécurité de votre compte." intro: "Si vous configurez la vérication en deux étapes vous aurez non seulement besoin de votre mot de passe mais aussi un appareil déjà pré-enregistré(tel que votre smartphone) ce qui ameliora grandement la sécurité de votre compte."
detail: "Voir les détails..." detail: "Voir les détails..."
@@ -487,10 +496,10 @@ desktop/views/components/settings.api.vue:
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "Regenerer le token" regenerate-token: "Regenerer le token"
token: "Token:" token: "Jeton :"
enter-password: "Veuillez entrer le mot de passe" enter-password: "Veuillez entrer le mot de passe"
desktop/views/components/settings.apps.vue: desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません" no-apps: "Aucune application autorisée"
desktop/views/components/settings.mute.vue: desktop/views/components/settings.mute.vue:
no-users: "Aucun utilisateurs mis en sourdine" no-users: "Aucun utilisateurs mis en sourdine"
desktop/views/components/settings.password.vue: desktop/views/components/settings.password.vue:
@@ -508,26 +517,26 @@ desktop/views/components/settings.profile.vue:
description: "Description" description: "Description"
birthday: "Date de naissance" birthday: "Date de naissance"
save: "Mettre à jour le profil" save: "Mettre à jour le profil"
is-bot: "このアカウントはBotです" is-bot: "Ce compte est un Bot"
is-cat: "このアカウントはCatです" is-cat: "Ce compte est un Chat"
desktop/views/components/sub-note-content.vue: desktop/views/components/sub-note-content.vue:
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
media-count: "{}つのメディア" media-count: "{} médias attachés"
poll: "投票" poll: "Sondages"
desktop/views/components/taskmanager.vue: desktop/views/components/taskmanager.vue:
title: "タスクマネージャ" title: "Gestionnaire de tâches"
desktop/views/components/timeline.vue: desktop/views/components/timeline.vue:
home: "ホーム" home: "Accueil"
local: "ローカル" local: "Local"
global: "グローバル" global: "Global"
list: "リスト" list: "Listes"
desktop/views/components/ui.header.account.vue: desktop/views/components/ui.header.account.vue:
profile: "Votre profil" profile: "Votre profil"
drive: "Drive" drive: "Drive"
favorites: "Favorites" favorites: "Favorites"
lists: "リスト" lists: "Listes"
follow-requests: "フォロー申請" follow-requests: "Demandes de suivi"
customize: "Modifications" customize: "Modifications"
settings: "Réglages" settings: "Réglages"
signout: "Déconnexion" signout: "Déconnexion"
@@ -544,40 +553,40 @@ desktop/views/components/ui.header.post.vue:
desktop/views/components/ui.header.search.vue: desktop/views/components/ui.header.search.vue:
placeholder: "Chercher" placeholder: "Chercher"
desktop/views/components/received-follow-requests-window.vue: desktop/views/components/received-follow-requests-window.vue:
title: "フォロー申請" title: "Demandes de suivi"
accept: "承認" accept: "Approuver"
reject: "拒否" reject: "Refuser"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "リスト" title: "Listes de l'utilisateur"
create-list: "リストを作成" create-list: "Créer une liste"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "投稿" notes: "Publications"
following: "フォロー" following: "Abonné à"
followers: "フォロワー" followers: "Abonnés"
desktop/views/components/users-list.vue: desktop/views/components/users-list.vue:
all: "すべて" all: "Tout"
iknow: "知り合い" iknow: "Vous connaissez"
load-more: "もっと" load-more: "Afficher plus"
fetching: "読み込んでいます" fetching: "Chargement ..."
desktop/views/components/users-list-item.vue: desktop/views/components/users-list-item.vue:
followed: "フォローされています" followed: "vous suit"
desktop/views/components/window.vue: desktop/views/components/window.vue:
popout: "ポップアウト" popout: "ポップアウト"
close: "閉じる" close: "Fermer"
desktop/views/pages/welcome.vue: desktop/views/pages/welcome.vue:
about: "詳しく..." about: "à propos"
gotit: "わかった" gotit: "J'ai compris !"
signin: "ログイン" signin: "Connexion"
signup: "新規登録" signup: "S'enregistrer"
signin-button: "やってる" signin-button: "Se connecter"
signup-button: "やる" signup-button: "S'inscrire"
timeline: "タイムライン" timeline: "Fil d'actualité"
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Lecteur de Misskey"
desktop/views/pages/favorites.vue: desktop/views/pages/favorites.vue:
more: "さらに読み込む" more: "Plus de résultats"
desktop/views/pages/home-customize.vue: desktop/views/pages/home-customize.vue:
title: "ホームのカスタマイズ" title: "Personnaliser l'Accueil"
desktop/views/pages/note.vue: desktop/views/pages/note.vue:
prev: "Note précédente" prev: "Note précédente"
next: "Note suivante" next: "Note suivante"
@@ -587,9 +596,9 @@ desktop/views/pages/selectdrive.vue:
cancel: "Annuler" cancel: "Annuler"
upload: "Uploader un ou plusieurs fichier(s) depuis votre PC" upload: "Uploader un ou plusieurs fichier(s) depuis votre PC"
desktop/views/pages/user-list.users.vue: desktop/views/pages/user-list.users.vue:
users: "ユーザー" users: "Utilisateurs"
add-user: "ユーザーを追加" add-user: "Ajouter un utilisateur"
username: "ユーザー名" username: "Nom d'utilisateur"
desktop/views/pages/user/user.followers-you-know.vue: desktop/views/pages/user/user.followers-you-know.vue:
title: "Abonnés que vous connaissez" title: "Abonnés que vous connaissez"
loading: "Chargement en cours" loading: "Chargement en cours"
@@ -617,10 +626,10 @@ desktop/views/pages/user/user.profile.vue:
muted: "Muting" muted: "Muting"
unmute: "Enlever la sourdine" unmute: "Enlever la sourdine"
desktop/views/pages/user/user.timeline.vue: desktop/views/pages/user/user.timeline.vue:
default: "投稿" default: "Publications"
with-replies: "投稿と返信" with-replies: "Publications et réponses"
with-media: "メディア" with-media: "Média"
empty: "このユーザーはまだ何も投稿していないようです。" empty: "Cet utilisateur n'a rien posté encore."
desktop/views/widgets/messaging.vue: desktop/views/widgets/messaging.vue:
title: "Messagerie" title: "Messagerie"
desktop/views/widgets/notifications.vue: desktop/views/widgets/notifications.vue:
@@ -634,8 +643,8 @@ desktop/views/widgets/post-form.vue:
title: "Post" title: "Post"
note: "Post" note: "Post"
desktop/views/widgets/profile.vue: desktop/views/widgets/profile.vue:
update-banner: "クリックでバナー編集" update-banner: "Cliquer pour éditer votre bannière"
update-avatar: "クリックでアバター編集" update-avatar: "Cliquer pour éditer votre avatar"
desktop/views/widgets/trends.vue: desktop/views/widgets/trends.vue:
title: "Tendances" title: "Tendances"
refresh: "Afficher d'autres" refresh: "Afficher d'autres"
@@ -655,13 +664,13 @@ mobile/views/components/drive.vue:
folder-is-empty: "Ce dossier est vide" folder-is-empty: "Ce dossier est vide"
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>" prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。" deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
folder-name: "フォルダー名" folder-name: "Nom du dossier"
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。" root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。" root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
url-prompt: "アップロードしたいファイルのURL" url-prompt: "アップロードしたいファイルのURL"
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。" uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
mobile/views/components/drive-file-detail.vue: mobile/views/components/drive-file-detail.vue:
rename: "名前を変更" rename: "Renommer"
mobile/views/components/drive-file-chooser.vue: mobile/views/components/drive-file-chooser.vue:
select-file: "Choisissez un fichier" select-file: "Choisissez un fichier"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
@@ -676,72 +685,72 @@ mobile/views/components/follow-button.vue:
following: "フォロー中" following: "フォロー中"
follow: "Suivre" follow: "Suivre"
request-pending: "フォロー許可待ち" request-pending: "フォロー許可待ち"
follow-request: "フォロー申請" follow-request: "Demande d'abonnement"
mobile/views/components/friends-maker.vue: mobile/views/components/friends-maker.vue:
title: "気になるユーザーをフォロー" title: "Abonnez-vous aux utilisateurs"
empty: "おすすめのユーザーは見つかりませんでした。" empty: "おすすめのユーザーは見つかりませんでした。"
fetching: "読み込んでいます" fetching: "Chargement"
refresh: "もっと見る" refresh: "Voir plus"
close: "閉じる" close: "Fermer"
mobile/views/components/note.vue: mobile/views/components/note.vue:
reposted-by: "Renoté par {}" reposted-by: "Renoté par {}"
more: "もっと見る" more: "Voir plus"
less: "隠す" less: "Masquer"
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
location: "位置情報" location: "Géolocalisation"
mobile/views/components/note-detail.vue: mobile/views/components/note-detail.vue:
reply: "Répondre" reply: "Répondre"
reaction: "Réaction" reaction: "Réaction"
reposted-by: "{}がRenote" reposted-by: "Republié par {}"
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
location: "位置情報" location: "Lieu"
mobile/views/components/note-preview.vue: mobile/views/components/note-preview.vue:
admin: "admin" admin: "admin"
bot: "bot" bot: "bot"
cat: "cat" cat: "chat"
mobile/views/components/note-sub.vue: mobile/views/components/note-sub.vue:
admin: "admin" admin: "admin"
bot: "bot" bot: "bot"
cat: "cat" cat: "chat"
mobile/views/components/notes.vue: mobile/views/components/notes.vue:
failed: "読み込みに失敗しました。" failed: "Échec du chargement."
retry: "リトライ" retry: "Réessayer"
mobile/views/components/notifications.vue: mobile/views/components/notifications.vue:
more: "Plus" more: "Plus"
empty: "Pas de notifications" empty: "Pas de notifications"
mobile/views/components/post-form.vue: mobile/views/components/post-form.vue:
add-visible-user: "ユーザーを追加" add-visible-user: "Ajouter un utilisateur"
submit: "Poster" submit: "Poster"
reply: "返信" reply: "Répondre"
renote: "Renote" renote: "Republier"
quote-placeholder: "この投稿を引用... (オプション)" quote-placeholder: "Citer ce billet ... (Facultatif)"
reply-placeholder: "Répondre à cette note" reply-placeholder: "Répondre à cette note"
cw-placeholder: "内容への注釈 (オプション)" cw-placeholder: "内容への注釈 (オプション)"
location-alert: "お使いの端末は位置情報に対応していません" location-alert: "Votre appareil ne prend pas en charge les services de localisation"
error: "エラー" error: "Erreur"
username-prompt: "ユーザー名を入力してください" username-prompt: "Saisir un nom d'utilisateur"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
private: "この投稿は非公開です" private: "cette publication est privée"
deleted: "この投稿は削除されました" deleted: "cette publication a été supprimée"
media-count: "{}つのメディア" media-count: "{} médias attachés"
poll: "Sondage" poll: "Sondage"
mobile/views/components/timeline.vue: mobile/views/components/timeline.vue:
empty: "Pas de notes" empty: "Pas de notes"
load-more: "Afficher plus" load-more: "Afficher plus"
mobile/views/components/ui.nav.vue: mobile/views/components/ui.nav.vue:
timeline: "タイムライン" timeline: "Fil d'actualité"
notifications: "Notifications" notifications: "Notifications"
messaging: "Messages" messaging: "Messages"
follow-requests: "フォロー申請" follow-requests: "Demandes d'abonnement"
search: "Rechercher" search: "Rechercher"
drive: "Drive" drive: "Drive"
favorites: "お気に入り" favorites: "Favoris"
user-lists: "リスト" user-lists: "Listes"
widgets: "ウィジェット" widgets: "Modules"
game: "ゲーム" game: "Jeux"
darkmode: "ダークモード" darkmode: "Mode nuit"
settings: "Réglages" settings: "Réglages"
about: "À propose de Misskey" about: "À propose de Misskey"
mobile/views/components/user-timeline.vue: mobile/views/components/user-timeline.vue:
@@ -753,29 +762,29 @@ mobile/views/components/users-list.vue:
known: "Vous connaissez" known: "Vous connaissez"
load-more: "Afficher plus" load-more: "Afficher plus"
mobile/views/pages/favorites.vue: mobile/views/pages/favorites.vue:
title: "お気に入り" title: "Favoris"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "リスト" title: "Listes"
enter-list-name: "リスト名を入力してください" enter-list-name: "Nom de la liste"
mobile/views/pages/drive.vue: mobile/views/pages/drive.vue:
drive: "Drive" drive: "Drive"
more: "もっと見る" more: "Afficher plus ..."
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
followers-of: "Abonnés de {}" followers-of: "Abonnés de {}"
mobile/views/pages/following.vue: mobile/views/pages/following.vue:
following-of: "Abonnements de {}" following-of: "Abonnements de {}"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:
home: "ホーム" home: "Accueil"
local: "ローカル" local: "Local"
global: "グローバル" global: "Global"
mobile/views/pages/messaging.vue: mobile/views/pages/messaging.vue:
messaging: "Messagerie" messaging: "Messagerie"
mobile/views/pages/messaging-room.vue: mobile/views/pages/messaging-room.vue:
messaging: "Messagerie" messaging: "Messagerie"
mobile/views/pages/received-follow-requests.vue: mobile/views/pages/received-follow-requests.vue:
title: "フォロー申請" title: "Demandes d'abonnement"
accept: "承認" accept: "Approuver"
reject: "拒否" reject: "Refuser"
mobile/views/pages/note.vue: mobile/views/pages/note.vue:
title: "Post" title: "Post"
prev: "Note précedante" prev: "Note précedante"
@@ -784,19 +793,19 @@ mobile/views/pages/notifications.vue:
notifications: "Notifications" notifications: "Notifications"
read-all: "Êtes vous sûr de vouloir marqués toutes les notifications non-lus en tant que lus?" read-all: "Êtes vous sûr de vouloir marqués toutes les notifications non-lus en tant que lus?"
mobile/views/pages/settings/settings.profile.vue: mobile/views/pages/settings/settings.profile.vue:
title: "プロフィール" title: "Profil"
name: "名前" name: "Nom"
account: "アカウント" account: "Compte"
location: "場所" location: "Lieu"
description: "自己紹介" description: "Description"
birthday: "誕生日" birthday: "Date de naissance"
avatar: "アイコン" avatar: "Avatar"
banner: "バナー" banner: "Bannière"
is-cat: "このアカウントはCatです" is-cat: "Ce compte est un Bot"
save: "保存" save: "Mettre à jour le profil"
saved: "プロフィールを保存しました" saved: "Profil mis à jour avec succès"
uploading: "アップロード中" uploading: "En cours d'envoi"
upload-failed: "アップロードに失敗しました" upload-failed: "Échec de l'envoi"
mobile/views/pages/search.vue: mobile/views/pages/search.vue:
search: "Chercher" search: "Chercher"
empty: "Aucun message trouvé pour '{}' " empty: "Aucun message trouvé pour '{}' "
@@ -804,39 +813,39 @@ mobile/views/pages/selectdrive.vue:
select-file: "Choisissez un fichier" select-file: "Choisissez un fichier"
mobile/views/pages/settings.vue: mobile/views/pages/settings.vue:
signed-in-as: "Connecté en tant que {}" signed-in-as: "Connecté en tant que {}"
lang: "言語" lang: "Langue"
lang-tip: "変更はページの再読み込み後に反映されます。" lang-tip: "Le rechargement de la page est requis afin d'appliquer les modifications."
recommended: "推奨" recommended: "Recommandé"
auto: "自動" auto: "Automatique"
specify-language: "言語を指定" specify-language: "Spécifier la langue"
design: "デザインと表示" design: "Affichage et design"
dark-mode: "ダークモード" dark-mode: "Mode nuit"
i-am-under-limited-internet: "私は通信を制限されている" i-am-under-limited-internet: "J'ai un accès Internet limité"
circle-icons: "円形のアイコンを使用" circle-icons: "Utiliser des icônes circulaires"
timeline: "タイムライン" timeline: "Fil d'actualité"
show-reply-target: "リプライ先を表示する" show-reply-target: "リプライ先を表示する"
show-my-renotes: "自分の行ったRenoteを表示する" show-my-renotes: "Afficher mes republications"
show-renoted-my-notes: "Renoteされた自分の投稿を表示する" show-renoted-my-notes: "Renoteされた自分の投稿を表示する"
post-style: "投稿の表示スタイル" post-style: "Style de la publication"
post-style-standard: "標準" post-style-standard: "Standard"
post-style-smart: "スマート" post-style-smart: "Intelligent"
behavior: "動作" behavior: "Comportement"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "Chargement lors du défilement"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
load-raw-images: "添付された画像を高画質で表示する" load-raw-images: "Afficher les photos jointes en haute qualité"
load-remote-media: "リモートサーバーのメディアを表示する" load-remote-media: "Afficher les médias sur le serveur distant"
twitter: "Twitter連携" twitter: "Intégration à Twitter"
twitter-connect: "Twitterアカウントに接続する" twitter-connect: "Se connecter à votre compte Twitter"
twitter-reconnect: "再接続する" twitter-reconnect: "Reconnecter"
twitter-disconnect: "切断する" twitter-disconnect: "Déconnexion"
update: "Misskey Update" update: "Mise à jour de Misskey"
version: "バージョン:" version: "Version :"
latest-version: "最新のバージョン:" latest-version: "Dernière version :"
update-checking: "アップデートを確認中" update-checking: "Recherche de mises à jour"
check-for-updates: "アップデートを確認" check-for-updates: "Fréquence de vérification"
no-updates: "利用可能な更新はありません" no-updates: "Aucune mise à jour disponible"
no-updates-desc: "お使いのMisskeyは最新です。" no-updates-desc: "Votre Misskey est à jour."
update-available: "新しいバージョンが利用可能です" update-available: "Nouvelle version disponible !"
update-available-desc: "ページを再度読み込みすると更新が適用されます。" update-available-desc: "ページを再度読み込みすると更新が適用されます。"
settings: "Réglages" settings: "Réglages"
signout: "Déconnexion" signout: "Déconnexion"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -63,6 +63,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -85,8 +86,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
@@ -244,6 +250,10 @@ common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -5,7 +5,7 @@ meta:
common: common:
misskey: "Planeta Fediwersum" misskey: "Planeta Fediwersum"
about-title: "⭐ Fediwersum" about-title: "⭐ Fediwersum"
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Dziękujemy za znalezienie Misskey. Misskey jest <b>zdecentralizowaną platformą mikroblogową</b> powstałą na Ziemi. Ponieważ działa ona w Fediwersum (uniwersum, w którego skład wchodzi wiele sieci społecznościowych), jest ona połączona z innymi platformami społecznościowymi. Spróbujesz odpocząć od zatłoczoneo miasta i zanurzyć się w nowym Internecie?"
time: time:
unknown: "nieznany" unknown: "nieznany"
future: "w przyszłości" future: "w przyszłości"
@@ -57,6 +57,7 @@ common:
memo: "Notatki" memo: "Notatki"
trends: "Na czasie" trends: "Na czasie"
photo-stream: "Photostream" photo-stream: "Photostream"
posts-monitor: "投稿チャート"
slideshow: "Pokaz slajdów" slideshow: "Pokaz slajdów"
version: "Wersja" version: "Wersja"
broadcast: "Transmisja" broadcast: "Transmisja"
@@ -70,16 +71,21 @@ common:
nav: "Nawigacja" nav: "Nawigacja"
tips: "Wskazówki" tips: "Wskazówki"
deck: deck:
widgets: "ウィジェット" widgets: "Widżety"
home: "ホーム" home: "Strona główna"
local: "ローカル" local: "Lokalne"
global: "グローバル" global: "Globalne"
notifications: "通知" notifications: "Powiadomienia"
list: "リスト" list: "Listy"
swap-left: "左に移動" swap-left: "Przesuń w lewo"
swap-right: "右に移動" swap-right: "Przesuń w prawo"
remove: "カラムを削除" swap-up: "Przenieś w górę"
add-column: "カラムを追加" swap-down: "Przenieś w dół"
remove: "Usuń"
add-column: "Dodaj kolumnę"
rename: "Zmień nazwę"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "Nie udało się połączyć z serwerem" 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." description: "Wystąpił problem z Twoim połączeniem z Internetem, lub z serwerem. {Spróbuj ponownie} wkrótce."
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "Photostream" title: "Photostream"
no-photos: "Brak zdjęć" no-photos: "Brak zdjęć"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Informacje o serwerze" title: "Informacje o serwerze"
toggle: "Przełącz widok" toggle: "Przełącz widok"
@@ -301,7 +310,7 @@ desktop/views/components/drive.vue:
upload: "Wyślij plik" upload: "Wyślij plik"
url-upload: "Wyślij z adresu URL" url-upload: "Wyślij z adresu URL"
desktop/views/components/follow-button.vue: desktop/views/components/follow-button.vue:
following: "フォロー中" following: "Śledzisz"
follow: "Śledź" follow: "Śledź"
request-pending: "Oczekiwanie na pozwolenie" request-pending: "Oczekiwanie na pozwolenie"
follow-request: "Poproś o śledzenie" follow-request: "Poproś o śledzenie"
@@ -534,7 +543,7 @@ desktop/views/components/ui.header.account.vue:
dark: "Sprowadź ciemność" dark: "Sprowadź ciemność"
desktop/views/components/ui.header.nav.vue: desktop/views/components/ui.header.nav.vue:
home: "Strona główna" home: "Strona główna"
deck: "デッキ" deck: "Talia"
messaging: "Wiadomości" messaging: "Wiadomości"
game: "Gra" game: "Gra"
desktop/views/components/ui.header.notifications.vue: desktop/views/components/ui.header.notifications.vue:
@@ -673,7 +682,7 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
mobile/views/components/follow-button.vue: mobile/views/components/follow-button.vue:
following: "フォロー中" following: "Śledzisz"
follow: "Śledź" follow: "Śledź"
request-pending: "Oczekiwanie na pozwolenie" request-pending: "Oczekiwanie na pozwolenie"
follow-request: "Poproś o śledzenie" follow-request: "Poproś o śledzenie"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -57,6 +57,7 @@ common:
memo: "メモ" memo: "メモ"
trends: "トレンド" trends: "トレンド"
photo-stream: "フォトストリーム" photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
slideshow: "スライドショー" slideshow: "スライドショー"
version: "バージョン" version: "バージョン"
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
@@ -78,8 +79,13 @@ common:
list: "リスト" list: "リスト"
swap-left: "左に移動" swap-left: "左に移動"
swap-right: "右に移動" swap-right: "右に移動"
swap-up: "上に移動"
swap-down: "下に移動"
remove: "カラムを削除" remove: "カラムを削除"
add-column: "カラムを追加" add-column: "カラムを追加"
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
@@ -215,6 +221,9 @@ common/views/widgets/donation.vue:
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
no-photos: "写真はありません" no-photos: "写真はありません"
common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"

View File

@@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "2.27.3", "version": "2.34.3",
"clientVersion": "1.0.6188", "clientVersion": "1.0.6328",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@@ -152,7 +152,7 @@
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.2.0", "mocha": "5.2.0",
"moji": "0.5.1", "moji": "0.5.1",
"mongodb": "3.0.8", "mongodb": "3.0.10",
"monk": "6.0.6", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.1",
"nan": "2.10.0", "nan": "2.10.0",
@@ -218,6 +218,6 @@
"webpack-cli": "2.1.4", "webpack-cli": "2.1.4",
"websocket": "1.0.26", "websocket": "1.0.26",
"ws": "5.2.0", "ws": "5.2.0",
"xev": "2.0.0" "xev": "2.0.1"
} }
} }

View File

@@ -42,7 +42,7 @@ html
| JavaScriptを有効にしてください | JavaScriptを有効にしてください
br br
| Please turn on your JavaScript | Please turn on your JavaScript
div#ini: p div#ini.
span . <svg viewBox="0 0 50 50">
span . <path fill=#{themeColor} d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" />
span . </svg>

View File

@@ -3,15 +3,15 @@ import StreamManager from './stream-manager';
import MiOS from '../../../mios'; import MiOS from '../../../mios';
/** /**
* Server stream connection * Notes stats stream connection
*/ */
export class ServerStream extends Stream { export class NotesStatsStream extends Stream {
constructor(os: MiOS) { constructor(os: MiOS) {
super(os, 'server'); super(os, 'notes-stats');
} }
} }
export class ServerStreamManager extends StreamManager<ServerStream> { export class NotesStatsStreamManager extends StreamManager<NotesStatsStream> {
private os: MiOS; private os: MiOS;
constructor(os: MiOS) { constructor(os: MiOS) {
@@ -22,7 +22,7 @@ export class ServerStreamManager extends StreamManager<ServerStream> {
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new ServerStream(this.os); this.connection = new NotesStatsStream(this.os);
} }
return this.connection; return this.connection;

View File

@@ -0,0 +1,30 @@
import Stream from './stream';
import StreamManager from './stream-manager';
import MiOS from '../../../mios';
/**
* Server stats stream connection
*/
export class ServerStatsStream extends Stream {
constructor(os: MiOS) {
super(os, 'server-stats');
}
}
export class ServerStatsStreamManager extends StreamManager<ServerStatsStream> {
private os: MiOS;
constructor(os: MiOS) {
super();
this.os = os;
}
public getConnection() {
if (this.connection == null) {
this.connection = new ServerStatsStream(this.os);
}
return this.connection;
}
}

View File

@@ -2,6 +2,7 @@ import Vue from 'vue';
import analogClock from './analog-clock.vue'; import analogClock from './analog-clock.vue';
import menu from './menu.vue'; import menu from './menu.vue';
import noteHeader from './note-header.vue';
import signin from './signin.vue'; import signin from './signin.vue';
import signup from './signup.vue'; import signup from './signup.vue';
import forkit from './forkit.vue'; import forkit from './forkit.vue';
@@ -31,6 +32,7 @@ import welcomeTimeline from './welcome-timeline.vue';
Vue.component('mk-analog-clock', analogClock); Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-menu', menu); Vue.component('mk-menu', menu);
Vue.component('mk-note-header', noteHeader);
Vue.component('mk-signin', signin); Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup); Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit); Vue.component('mk-forkit', forkit);

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="mk-menu"> <div class="mk-menu">
<div class="backdrop" ref="backdrop" @click="close"></div> <div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact }" ref="popover"> <div class="popover" :class="{ hukidasi }" ref="popover">
<template v-for="item in items"> <template v-for="item in items">
<div v-if="item == null"></div> <div v-if="item === null"></div>
<button v-else @click="clicked(item.onClick)" v-html="item.content"></button> <button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text"></button>
</template> </template>
</div> </div>
</div> </div>
@@ -16,6 +16,11 @@ import * as anime from 'animejs';
export default Vue.extend({ export default Vue.extend({
props: ['source', 'compact', 'items'], props: ['source', 'compact', 'items'],
data() {
return {
hukidasi: !this.compact
};
},
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
const popover = this.$refs.popover as any; const popover = this.$refs.popover as any;
@@ -24,18 +29,34 @@ export default Vue.extend({
const width = popover.offsetWidth; const width = popover.offsetWidth;
const height = popover.offsetHeight; const height = popover.offsetHeight;
let left;
let top;
if (this.compact) { if (this.compact) {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2); const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
popover.style.left = (x - (width / 2)) + 'px'; left = (x - (width / 2));
popover.style.top = (y - (height / 2)) + 'px'; top = (y - (height / 2));
} else { } else {
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + this.source.offsetHeight; const y = rect.top + window.pageYOffset + this.source.offsetHeight;
popover.style.left = (x - (width / 2)) + 'px'; left = (x - (width / 2));
popover.style.top = y + 'px'; top = y;
} }
if (left + width > window.innerWidth) {
left = window.innerWidth - width;
this.hukidasi = false;
}
if (top + height > window.innerHeight) {
top = window.innerHeight - height;
this.hukidasi = false;
}
popover.style.left = left + 'px';
popover.style.top = top + 'px';
anime({ anime({
targets: this.$refs.backdrop, targets: this.$refs.backdrop,
opacity: 1, opacity: 1,
@@ -113,7 +134,7 @@ $border-color = rgba(27, 31, 35, 0.15)
$balloon-size = 16px $balloon-size = 16px
&:not(.compact) &.hukidasi
margin-top $balloon-size margin-top $balloon-size
transform-origin center -($balloon-size) transform-origin center -($balloon-size)

View File

@@ -0,0 +1,117 @@
<template>
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="is-cat" v-if="note.user.isCat">cat</span>
<span class="username"><mk-acct :user="note.user"/></span>
<div class="info">
<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span>
<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>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
note: {
type: Object,
required: true
},
mini: {
type: Boolean,
required: false,
default: false
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
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 .5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #627079
font-size 1em
font-weight bold
text-decoration none
text-overflow ellipsis
&:hover
text-decoration underline
> .is-admin
> .is-bot
> .is-cat
align-self center
margin 0 .5em 0 0
padding 1px 6px
font-size 80%
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 .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 8px
> .app
margin-right 8px
padding-right 8px
border-right solid 1px isDark ? #1c2023 : #eaeaea
> .visibility
margin-left 8px
.bvonvjxbwzaiskogyhbwgyxvcgserpmu[data-darkmode]
root(true)
.bvonvjxbwzaiskogyhbwgyxvcgserpmu:not([data-darkmode])
root(false)
</style>

View File

@@ -13,23 +13,23 @@ export default Vue.extend({
items() { items() {
const items = []; const items = [];
items.push({ items.push({
content: '%i18n:@favorite%', text: '%i18n:@favorite%',
onClick: this.favorite action: this.favorite
}); });
if (this.note.userId == this.$store.state.i.id) { if (this.note.userId == this.$store.state.i.id) {
items.push({ items.push({
content: '%i18n:@pin%', text: '%i18n:@pin%',
onClick: this.pin action: this.pin
}); });
items.push({ items.push({
content: '%i18n:@delete%', text: '%i18n:@delete%',
onClick: this.del action: this.del
}); });
} }
if (this.note.uri) { if (this.note.uri) {
items.push({ items.push({
content: '%i18n:@remote%', text: '%i18n:@remote%',
onClick: () => { action: () => {
window.open(this.note.uri, '_blank'); window.open(this.note.uri, '_blank');
} }
}); });

View File

@@ -59,7 +59,7 @@ export default Vue.extend({
created() { created() {
if (this.mode == 'relative' || this.mode == 'detail') { if (this.mode == 'relative' || this.mode == 'detail') {
this.tick(); this.tick();
this.tickId = setInterval(this.tick, 1000); this.tickId = setInterval(this.tick, 10000);
} }
}, },
destroyed() { destroyed() {

View File

@@ -4,6 +4,7 @@ import wAnalogClock from './analog-clock.vue';
import wVersion from './version.vue'; import wVersion from './version.vue';
import wRss from './rss.vue'; import wRss from './rss.vue';
import wServer from './server.vue'; import wServer from './server.vue';
import wPostsMonitor from './posts-monitor.vue';
import wMemo from './memo.vue'; import wMemo from './memo.vue';
import wBroadcast from './broadcast.vue'; import wBroadcast from './broadcast.vue';
import wCalendar from './calendar.vue'; import wCalendar from './calendar.vue';
@@ -22,6 +23,7 @@ Vue.component('mkw-tips', wTips);
Vue.component('mkw-donation', wDonation); Vue.component('mkw-donation', wDonation);
Vue.component('mkw-broadcast', wBroadcast); Vue.component('mkw-broadcast', wBroadcast);
Vue.component('mkw-server', wServer); Vue.component('mkw-server', wServer);
Vue.component('mkw-posts-monitor', wPostsMonitor);
Vue.component('mkw-memo', wMemo); Vue.component('mkw-memo', wMemo);
Vue.component('mkw-rss', wRss); Vue.component('mkw-rss', wRss);
Vue.component('mkw-version', wVersion); Vue.component('mkw-version', wVersion);

View File

@@ -0,0 +1,211 @@
<template>
<div class="mkw-posts-monitor">
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2">
<template slot="header">%fa:chart-line%%i18n:@title%</template>
<button slot="func" @click="toggle" title="%i18n:@toggle%">%fa:sort%</button>
<div class="qpdmibaztplkylerhdbllwcokyrfxeyj" :class="{ dual: props.view == 0 }" :data-darkmode="$store.state.device.darkmode">
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" v-show="props.view != 2">
<defs>
<linearGradient :id="localGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
</linearGradient>
<mask :id="localMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="localPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="localPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="1"/>
<circle
:cx="localHeadX"
:cy="localHeadY"
r="1.5"
fill="#fff"/>
</mask>
</defs>
<rect
x="-2" y="-2"
:width="viewBoxX + 4" :height="viewBoxY + 4"
:style="`stroke: none; fill: url(#${ localGradientId }); mask: url(#${ localMaskId })`"/>
<text x="1" y="5">Local</text>
</svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" v-show="props.view != 1">
<defs>
<linearGradient :id="fediGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
</linearGradient>
<mask :id="fediMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="fediPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="fediPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="1"/>
<circle
:cx="fediHeadX"
:cy="fediHeadY"
r="1.5"
fill="#fff"/>
</mask>
</defs>
<rect
x="-2" y="-2"
:width="viewBoxX + 4" :height="viewBoxY + 4"
:style="`stroke: none; fill: url(#${ fediGradientId }); mask: url(#${ fediMaskId })`"/>
<text x="1" y="5">Fedi</text>
</svg>
</div>
</mk-widget-container>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
import * as uuid from 'uuid';
export default define({
name: 'server',
props: () => ({
design: 0,
view: 0
})
}).extend({
data() {
return {
connection: null,
connectionId: null,
viewBoxY: 30,
stats: [],
fediGradientId: uuid(),
fediMaskId: uuid(),
localGradientId: uuid(),
localMaskId: uuid(),
fediPolylinePoints: '',
localPolylinePoints: '',
fediPolygonPoints: '',
localPolygonPoints: '',
fediHeadX: null,
fediHeadY: null,
localHeadX: null,
localHeadY: null
};
},
computed: {
viewBoxX(): number {
return this.props.view == 0 ? 50 : 100;
}
},
watch: {
viewBoxX() {
this.draw();
}
},
mounted() {
this.connection = (this as any).os.streams.notesStatsStream.getConnection();
this.connectionId = (this as any).os.streams.notesStatsStream.use();
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
id: Math.random().toString()
});
},
beforeDestroy() {
this.connection.off('stats', this.onStats);
this.connection.off('statsLog', this.onStatsLog);
(this as any).os.streams.notesStatsStream.dispose(this.connectionId);
},
methods: {
toggle() {
if (this.props.view == 2) {
this.props.view = 0;
} else {
this.props.view++;
}
this.save();
},
func() {
if (this.props.design == 2) {
this.props.design = 0;
} else {
this.props.design++;
}
this.save();
},
draw() {
const stats = this.props.view == 0 ? this.stats.slice(-50) : this.stats;
const fediPeak = Math.max.apply(null, stats.map(x => x.all)) || 1;
const localPeak = Math.max.apply(null, stats.map(x => x.local)) || 1;
const fediPolylinePoints = stats.map((s, i) => [this.viewBoxX - ((stats.length - 1) - i), (1 - (s.all / fediPeak)) * this.viewBoxY]);
const localPolylinePoints = stats.map((s, i) => [this.viewBoxX - ((stats.length - 1) - i), (1 - (s.local / localPeak)) * this.viewBoxY]);
this.fediPolylinePoints = fediPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.localPolylinePoints = localPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.fediPolygonPoints = `${this.viewBoxX - (stats.length - 1)},${ this.viewBoxY } ${ this.fediPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
this.localPolygonPoints = `${this.viewBoxX - (stats.length - 1)},${ this.viewBoxY } ${ this.localPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
this.fediHeadX = fediPolylinePoints[fediPolylinePoints.length - 1][0];
this.fediHeadY = fediPolylinePoints[fediPolylinePoints.length - 1][1];
this.localHeadX = localPolylinePoints[localPolylinePoints.length - 1][0];
this.localHeadY = localPolylinePoints[localPolylinePoints.length - 1][1];
},
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 100) this.stats.shift();
this.draw();
},
onStatsLog(statsLog) {
statsLog.forEach(stats => this.onStats(stats));
}
}
});
</script>
<style lang="stylus" scoped>
root(isDark)
&.dual
> svg
width 50%
float left
&:first-child
padding-right 5px
&:last-child
padding-left 5px
> svg
display block
padding 10px
width 100%
> text
font-size 5px
fill isDark ? rgba(#fff, 0.55) : rgba(#000, 0.55)
> tspan
opacity 0.5
&:after
content ""
display block
clear both
.qpdmibaztplkylerhdbllwcokyrfxeyj[data-darkmode]
root(true)
.qpdmibaztplkylerhdbllwcokyrfxeyj:not([data-darkmode])
root(false)
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="cpu-memory"> <div class="cpu-memory">
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none"> <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs> <defs>
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0"> <linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop> <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
@@ -16,15 +16,20 @@
fill="none" fill="none"
stroke="#fff" stroke="#fff"
stroke-width="1"/> stroke-width="1"/>
<circle
:cx="cpuHeadX"
:cy="cpuHeadY"
r="1.5"
fill="#fff"/>
</mask> </mask>
</defs> </defs>
<rect <rect
x="-1" y="-1" x="-2" y="-2"
:width="viewBoxX + 2" :height="viewBoxY + 2" :width="viewBoxX + 4" :height="viewBoxY + 4"
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/> :style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text> <text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
</svg> </svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none"> <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs> <defs>
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0"> <linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop> <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
@@ -40,11 +45,16 @@
fill="none" fill="none"
stroke="#fff" stroke="#fff"
stroke-width="1"/> stroke-width="1"/>
<circle
:cx="memHeadX"
:cy="memHeadY"
r="1.5"
fill="#fff"/>
</mask> </mask>
</defs> </defs>
<rect <rect
x="-1" y="-1" x="-2" y="-2"
:width="viewBoxX + 2" :height="viewBoxY + 2" :width="viewBoxX + 4" :height="viewBoxY + 4"
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/> :style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text> <text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
</svg> </svg>
@@ -70,15 +80,25 @@ export default Vue.extend({
memPolylinePoints: '', memPolylinePoints: '',
cpuPolygonPoints: '', cpuPolygonPoints: '',
memPolygonPoints: '', memPolygonPoints: '',
cpuHeadX: null,
cpuHeadY: null,
memHeadX: null,
memHeadY: null,
cpuP: '', cpuP: '',
memP: '' memP: ''
}; };
}, },
mounted() { mounted() {
this.connection.on('stats', this.onStats); this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
id: Math.random().toString()
});
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('stats', this.onStats); this.connection.off('stats', this.onStats);
this.connection.off('statsLog', this.onStatsLog);
}, },
methods: { methods: {
onStats(stats) { onStats(stats) {
@@ -86,14 +106,24 @@ export default Vue.extend({
this.stats.push(stats); this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift(); if (this.stats.length > 50) this.stats.shift();
this.cpuPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - s.cpu_usage) * this.viewBoxY}`).join(' '); const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
this.memPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.mem.used / s.mem.total)) * this.viewBoxY}`).join(' '); const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`; this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`; this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1];
this.cpuP = (stats.cpu_usage * 100).toFixed(0); this.cpuP = (stats.cpu_usage * 100).toFixed(0);
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0); this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
},
onStatsLog(statsLog) {
statsLog.forEach(stats => this.onStats(stats));
} }
} }
}); });

View File

@@ -55,11 +55,11 @@ export default define({
this.fetching = false; this.fetching = false;
}); });
this.connection = (this as any).os.streams.serverStream.getConnection(); this.connection = (this as any).os.streams.serverStatsStream.getConnection();
this.connectionId = (this as any).os.streams.serverStream.use(); this.connectionId = (this as any).os.streams.serverStatsStream.use();
}, },
beforeDestroy() { beforeDestroy() {
(this as any).os.streams.serverStream.dispose(this.connectionId); (this as any).os.streams.serverStatsStream.dispose(this.connectionId);
}, },
methods: { methods: {
toggle() { toggle() {

View File

@@ -1,5 +1,5 @@
<template> <template>
<svg viewBox="0 0 21 7" preserveAspectRatio="none"> <svg viewBox="0 0 21 7">
<rect v-for="record in data" class="day" <rect v-for="record in data" class="day"
width="1" height="1" width="1" height="1"
:x="record.x" :y="record.date.weekday" :x="record.x" :y="record.date.weekday"
@@ -15,7 +15,7 @@
style="pointer-events: none;"/> style="pointer-events: none;"/>
<rect class="today" <rect class="today"
width="1" height="1" width="1" height="1"
:x="data[data.length - 1].x" :y="data[data.length - 1].date.weekday" :x="data[0].x" :y="data[0].date.weekday"
rx="1" ry="1" rx="1" ry="1"
fill="none" fill="none"
stroke-width="0.1" stroke-width="0.1"
@@ -33,7 +33,7 @@ export default Vue.extend({
const peak = Math.max.apply(null, this.data.map(d => d.total)); const peak = Math.max.apply(null, this.data.map(d => d.total));
let x = 0; let x = 0;
this.data.reverse().forEach(d => { this.data.slice().reverse().forEach(d => {
d.x = x; d.x = x;
d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay(); d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();

View File

@@ -1,5 +1,5 @@
<template> <template>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown"> <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" @mousedown.prevent="onMousedown">
<title>%i18n:@total%<br/>%i18n:@notes%<br/>%i18n:@replies%<br/>%i18n:@renotes%</title> <title>%i18n:@total%<br/>%i18n:@notes%<br/>%i18n:@replies%<br/>%i18n:@renotes%</title>
<polyline <polyline
:points="pointsNote" :points="pointsNote"
@@ -55,7 +55,6 @@ export default Vue.extend({
}; };
}, },
created() { created() {
this.data.reverse();
this.data.forEach(d => d.total = d.notes + d.replies + d.renotes); this.data.forEach(d => d.total = d.notes + d.replies + d.renotes);
this.render(); this.render();
}, },
@@ -63,10 +62,11 @@ export default Vue.extend({
render() { render() {
const peak = Math.max.apply(null, this.data.map(d => d.total)); const peak = Math.max.apply(null, this.data.map(d => d.total));
if (peak != 0) { if (peak != 0) {
this.pointsNote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); const data = this.data.slice().reverse();
this.pointsReply = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); this.pointsNote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' ');
this.pointsRenote = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); this.pointsReply = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' ');
this.pointsTotal = this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); this.pointsRenote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' ');
this.pointsTotal = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
} }
}, },
onMousedown(e) { onMousedown(e) {

View File

@@ -1,15 +1,17 @@
<template> <template>
<ul class="menu"> <ul class="menu">
<li v-for="(item, i) in menu" :class="item.type"> <li v-for="(item, i) in menu" :class="item ? item.type : item === null ? 'divider' : null">
<template v-if="item.type == 'item'"> <template v-if="item">
<p @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</p> <template v-if="item.type == null || item.type == 'item'">
</template> <p @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</p>
<template v-if="item.type == 'link'"> </template>
<a :href="item.href" :target="item.target" @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</a> <template v-else-if="item.type == 'link'">
</template> <a :href="item.href" :target="item.target" @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</a>
<template v-else-if="item.type == 'nest'"> </template>
<p><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}...<span class="caret">%fa:caret-right%</span></p> <template v-else-if="item.type == 'nest'">
<me-nu :menu="item.menu" @x="click"/> <p><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}...<span class="caret">%fa:caret-right%</span></p>
<me-nu :menu="item.menu" @x="click"/>
</template>
</template> </template>
</li> </li>
</ul> </ul>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="context-menu" :style="{ left: `${x}px`, top: `${y}px` }" @contextmenu.prevent="() => {}"> <div class="context-menu" @contextmenu.prevent="() => {}">
<x-menu :menu="menu" @x="click"/> <x-menu :menu="menu" @x="click"/>
</div> </div>
</template> </template>
@@ -17,6 +17,23 @@ export default Vue.extend({
props: ['x', 'y', 'menu'], props: ['x', 'y', 'menu'],
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
const width = this.$el.offsetWidth;
const height = this.$el.offsetHeight;
let x = this.x;
let y = this.y;
if (x + width > window.innerWidth) {
x = window.innerWidth - width;
}
if (y + height > window.innerHeight) {
y = window.innerHeight - height;
}
this.$el.style.left = x + 'px';
this.$el.style.top = y + 'px';
Array.from(document.querySelectorAll('body *')).forEach(el => { Array.from(document.querySelectorAll('body *')).forEach(el => {
el.addEventListener('mousedown', this.onMousedown); el.addEventListener('mousedown', this.onMousedown);
}); });
@@ -38,7 +55,7 @@ export default Vue.extend({
return false; return false;
}, },
click(item) { click(item) {
if (item.onClick) item.onClick(); if (item.action) item.action();
this.close(); this.close();
}, },
close() { close() {
@@ -59,7 +76,6 @@ root(isDark)
$item-height = 38px $item-height = 38px
$padding = 10px $padding = 10px
display none
position fixed position fixed
top 0 top 0
left 0 left 0

View File

@@ -66,37 +66,33 @@ export default Vue.extend({
type: 'item', type: 'item',
text: '%i18n:@contextmenu.rename%', text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%', icon: '%fa:i-cursor%',
onClick: this.rename action: this.rename
}, { }, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.copy-url%', text: '%i18n:@contextmenu.copy-url%',
icon: '%fa:link%', icon: '%fa:link%',
onClick: this.copyUrl action: this.copyUrl
}, { }, {
type: 'link', type: 'link',
href: `${this.file.url}?download`, href: `${this.file.url}?download`,
text: '%i18n:@contextmenu.download%', text: '%i18n:@contextmenu.download%',
icon: '%fa:download%', icon: '%fa:download%',
}, { }, null, {
type: 'divider',
}, {
type: 'item', type: 'item',
text: '%i18n:common.delete%', text: '%i18n:common.delete%',
icon: '%fa:R trash-alt%', icon: '%fa:R trash-alt%',
onClick: this.deleteFile action: this.deleteFile
}, { }, null, {
type: 'divider',
}, {
type: 'nest', type: 'nest',
text: '%i18n:@contextmenu.else-files%', text: '%i18n:@contextmenu.else-files%',
menu: [{ menu: [{
type: 'item', type: 'item',
text: '%i18n:@contextmenu.set-as-avatar%', text: '%i18n:@contextmenu.set-as-avatar%',
onClick: this.setAsAvatar action: this.setAsAvatar
}, { }, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.set-as-banner%', text: '%i18n:@contextmenu.set-as-banner%',
onClick: this.setAsBanner action: this.setAsBanner
}] }]
}, { }, {
type: 'nest', type: 'nest',
@@ -104,7 +100,7 @@ export default Vue.extend({
menu: [{ menu: [{
type: 'item', type: 'item',
text: '%i18n:@contextmenu.add-app%...', text: '%i18n:@contextmenu.add-app%...',
onClick: this.addApp action: this.addApp
}] }]
}], { }], {
closed: () => { closed: () => {

View File

@@ -56,26 +56,22 @@ export default Vue.extend({
type: 'item', type: 'item',
text: '%i18n:@contextmenu.move-to-this-folder%', text: '%i18n:@contextmenu.move-to-this-folder%',
icon: '%fa:arrow-right%', icon: '%fa:arrow-right%',
onClick: this.go action: this.go
}, { }, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.show-in-new-window%', text: '%i18n:@contextmenu.show-in-new-window%',
icon: '%fa:R window-restore%', icon: '%fa:R window-restore%',
onClick: this.newWindow action: this.newWindow
}, { }, null, {
type: 'divider',
}, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.rename%', text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%', icon: '%fa:i-cursor%',
onClick: this.rename action: this.rename
}, { }, null, {
type: 'divider',
}, {
type: 'item', type: 'item',
text: '%i18n:common.delete%', text: '%i18n:common.delete%',
icon: '%fa:R trash-alt%', icon: '%fa:R trash-alt%',
onClick: this.deleteFolder action: this.deleteFolder
}], { }], {
closed: () => { closed: () => {
this.isContextmenuShowing = false; this.isContextmenuShowing = false;

View File

@@ -140,17 +140,17 @@ export default Vue.extend({
type: 'item', type: 'item',
text: '%i18n:@contextmenu.create-folder%', text: '%i18n:@contextmenu.create-folder%',
icon: '%fa:R folder%', icon: '%fa:R folder%',
onClick: this.createFolder action: this.createFolder
}, { }, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.upload%', text: '%i18n:@contextmenu.upload%',
icon: '%fa:upload%', icon: '%fa:upload%',
onClick: this.selectLocalFile action: this.selectLocalFile
}, { }, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.url-upload%', text: '%i18n:@contextmenu.url-upload%',
icon: '%fa:cloud-upload-alt%', icon: '%fa:cloud-upload-alt%',
onClick: this.urlUpload action: this.urlUpload
}]); }]);
}, },

View File

@@ -23,6 +23,7 @@
<option value="post-form">%i18n:common.widgets.post-form%</option> <option value="post-form">%i18n:common.widgets.post-form%</option>
<option value="messaging">%i18n:common.widgets.messaging%</option> <option value="messaging">%i18n:common.widgets.messaging%</option>
<option value="memo">%i18n:common.widgets.memo%</option> <option value="memo">%i18n:common.widgets.memo%</option>
<option value="posts-monitor">%i18n:common.widgets.posts-monitor%</option>
<option value="server">%i18n:common.widgets.server%</option> <option value="server">%i18n:common.widgets.server%</option>
<option value="donation">%i18n:common.widgets.donation%</option> <option value="donation">%i18n:common.widgets.donation%</option>
<option value="nav">%i18n:common.widgets.nav%</option> <option value="nav">%i18n:common.widgets.nav%</option>

View File

@@ -2,22 +2,7 @@
<div class="mk-note-preview" :title="title"> <div class="mk-note-preview" :title="title">
<mk-avatar class="avatar" :user="note.user"/> <mk-avatar class="avatar" :user="note.user"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="note" :mini="true"/>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<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"> <div class="body">
<mk-sub-note-content class="text" :note="note"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
@@ -56,43 +41,6 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header
display flex
align-items baseline
white-space nowrap
> .name
margin 0 .5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #607073
font-size 1em
font-weight bold
text-decoration none
text-overflow ellipsis
&:hover
text-decoration underline
> .username
margin 0 .5em 0 0
overflow hidden
text-overflow ellipsis
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 > .body
> .text > .text

View File

@@ -2,25 +2,7 @@
<div class="sub" :title="title"> <div class="sub" :title="title">
<mk-avatar class="avatar" :user="note.user"/> <mk-avatar class="avatar" :user="note.user"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="note"/>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="is-cat" v-if="note.user.isCat">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"> <div class="body">
<mk-sub-note-content class="text" :note="note"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
@@ -62,57 +44,8 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
margin-bottom 2px margin-bottom 2px
white-space nowrap
> .name
display block
margin 0 .5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #607073
font-size 1em
font-weight bold
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 10px
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 .5em 0 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 > .body

View File

@@ -14,26 +14,7 @@
<article> <article>
<mk-avatar class="avatar" :user="p.user"/> <mk-avatar class="avatar" :user="p.user"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="p"/>
<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ 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="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
<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"> <div class="body">
<p v-if="p.cw != null" class="cw"> <p v-if="p.cw != null" class="cw">
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span> <span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
@@ -409,64 +390,8 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
margin-bottom 4px margin-bottom 4px
white-space nowrap
> .name
display block
margin 0 .5em 0 0
padding 0
overflow hidden
color isDark ? #fff : #627079
font-size 1em
font-weight bold
text-decoration none
text-overflow ellipsis
&:hover
text-decoration underline
> .is-admin
> .is-bot
> .is-cat
align-self center
margin 0 .5em 0 0
padding 1px 6px
font-size 12px
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 .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 8px
> .app
margin-right 8px
padding-right 8px
border-right solid 1px isDark ? #1c2023 : #eaeaea
> .visibility
margin-left 8px
> .body > .body

View File

@@ -206,7 +206,7 @@ root(isDark)
margin 0 margin 0
padding 16px padding 16px
overflow-wrap break-word overflow-wrap break-word
font-size 12px font-size 13px
border-bottom solid 1px isDark ? #1c2023 : rgba(#000, 0.05) border-bottom solid 1px isDark ? #1c2023 : rgba(#000, 0.05)
&:last-child &:last-child

View File

@@ -40,6 +40,8 @@
<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">%i18n:@customize%</button> <button class="ui button" @click="customizeHome" style="margin-bottom: 16px">%i18n:@customize%</button>
</div> </div>
<div class="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="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.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/> <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.$router.push('/i/customize-home');
this.$emit('done'); 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) { onChangeFetchOnScroll(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'fetchOnScroll', key: 'fetchOnScroll',

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-ui"> <div class="mk-ui" :style="style">
<x-header class="header"/> <x-header class="header" v-show="!zenMode"/>
<div class="content"> <div class="content">
<slot></slot> <slot></slot>
</div> </div>
@@ -16,6 +16,20 @@ export default Vue.extend({
components: { components: {
XHeader XHeader
}, },
data() {
return {
zenMode: false
};
},
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() { mounted() {
document.addEventListener('keydown', this.onKeydown); document.addEventListener('keydown', this.onKeydown);
}, },
@@ -30,6 +44,11 @@ export default Vue.extend({
e.preventDefault(); e.preventDefault();
(this as any).apis.post(); (this as any).apis.post();
} }
if (e.which == 90) { // z
e.preventDefault();
this.zenMode = !this.zenMode;
}
} }
} }
}); });
@@ -40,6 +59,9 @@ export default Vue.extend({
display flex display flex
flex-direction column flex-direction column
flex 1 flex 1
background-size cover
background-position center
background-attachment fixed
> .header > .header
@media (max-width 1000px) @media (max-width 1000px)

View File

@@ -0,0 +1,35 @@
<template>
<x-widgets-column v-if="column.type == 'widgets'" :column="column" :is-stacked="isStacked"/>
<x-notifications-column v-else-if="column.type == 'notifications'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'home'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'local'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
</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';
export default Vue.extend({
components: {
XTlColumn,
XNotificationsColumn,
XWidgetsColumn
},
props: {
column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: false,
default: false
}
}
});
</script>

View File

@@ -1,10 +1,22 @@
<template> <template>
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow }"> <div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, active, isStacked, draghover, dragging, dropready }"
<header :class="{ indicate }"> @dragover.prevent.stop="onDragover"
@dragenter.prevent="onDragenter"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop"
>
<header :class="{ indicate: count > 0 }"
draggable="true"
@click="toggleActive"
@dragstart="onDragstart"
@dragend="onDragend"
@contextmenu.prevent.stop="onContextmenu"
>
<slot name="header"></slot> <slot name="header"></slot>
<button ref="menu" @click="showMenu">%fa:caret-down%</button> <span class="count" v-if="count > 0">({{ count }})</span>
<button ref="menu" @click.stop="showMenu">%fa:caret-down%</button>
</header> </header>
<div ref="body"> <div ref="body" v-show="active">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
@@ -13,16 +25,26 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import Menu from '../../../../common/views/components/menu.vue'; import Menu from '../../../../common/views/components/menu.vue';
import contextmenu from '../../../api/contextmenu';
export default Vue.extend({ export default Vue.extend({
props: { props: {
id: { column: {
type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true
},
name: {
type: String, type: String,
required: false required: false
}, },
menu: { menu: {
type: Array, type: Array,
required: false required: false,
default: null
}, },
naked: { naked: {
type: Boolean, type: Boolean,
@@ -36,30 +58,69 @@ export default Vue.extend({
} }
}, },
inject: {
getColumnVm: { from: 'getColumnVm' }
},
data() { data() {
return { return {
indicate: false count: 0,
active: true,
dragging: false,
draghover: false,
dropready: false
}; };
}, },
watch: {
active(v) {
if (v && this.isScrollTop()) {
this.$emit('top');
}
},
dragging(v) {
this.$root.$emit(v ? 'deck.column.dragStart' : 'deck.column.dragEnd');
}
},
provide() { provide() {
return { return {
column: this, column: this,
isScrollTop: this.isScrollTop, isScrollTop: this.isScrollTop,
indicate: v => this.indicate = v count: v => this.count = v
}; };
}, },
mounted() { mounted() {
this.$refs.body.addEventListener('scroll', this.onScroll, { passive: true }); this.$refs.body.addEventListener('scroll', this.onScroll, { passive: true });
this.$root.$on('deck.column.dragStart', this.onOtherDragStart);
this.$root.$on('deck.column.dragEnd', this.onOtherDragEnd);
}, },
beforeDestroy() { beforeDestroy() {
this.$refs.body.removeEventListener('scroll', this.onScroll); this.$refs.body.removeEventListener('scroll', this.onScroll);
this.$root.$off('deck.column.dragStart', this.onOtherDragStart);
this.$root.$off('deck.column.dragEnd', this.onOtherDragEnd);
}, },
methods: { methods: {
onOtherDragStart() {
this.dropready = true;
},
onOtherDragEnd() {
this.dropready = false;
},
toggleActive() {
if (!this.isStacked) return;
const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return;
this.active = !this.active;
},
isScrollTop() { isScrollTop() {
return this.$refs.body.scrollTop == 0; return this.active && this.$refs.body.scrollTop == 0;
}, },
onScroll() { onScroll() {
@@ -73,21 +134,60 @@ export default Vue.extend({
} }
}, },
showMenu() { getMenu() {
const items = [{ const items = [{
content: '%fa:arrow-left% %i18n:common.deck.swap-left%', icon: '%fa:pencil-alt%',
onClick: () => { text: '%i18n:common.deck.rename%',
this.$store.dispatch('settings/swapLeftDeckColumn', this.id); action: () => {
(this as any).apis.input({
title: '%i18n:common.deck.rename%',
default: this.name,
allowEmpty: false
}).then(name => {
this.$store.dispatch('settings/renameDeckColumn', { id: this.column.id, name });
});
}
}, null, {
icon: '%fa:arrow-left%',
text: '%i18n:common.deck.swap-left%',
action: () => {
this.$store.dispatch('settings/swapLeftDeckColumn', this.column.id);
} }
}, { }, {
content: '%fa:arrow-right% %i18n:common.deck.swap-right%', icon: '%fa:arrow-right%',
onClick: () => { text: '%i18n:common.deck.swap-right%',
this.$store.dispatch('settings/swapRightDeckColumn', this.id); action: () => {
this.$store.dispatch('settings/swapRightDeckColumn', this.column.id);
} }
}, { }, this.isStacked ? {
content: '%fa:trash-alt R% %i18n:common.deck.remove%', icon: '%fa:arrow-up%',
onClick: () => { text: '%i18n:common.deck.swap-up%',
this.$store.dispatch('settings/removeDeckColumn', this.id); action: () => {
this.$store.dispatch('settings/swapUpDeckColumn', this.column.id);
}
} : undefined, this.isStacked ? {
icon: '%fa:arrow-down%',
text: '%i18n:common.deck.swap-down%',
action: () => {
this.$store.dispatch('settings/swapDownDeckColumn', this.column.id);
}
} : undefined, null, {
icon: '%fa:window-restore R%',
text: '%i18n:common.deck.stack-left%',
action: () => {
this.$store.dispatch('settings/stackLeftDeckColumn', this.column.id);
}
}, this.isStacked ? {
icon: '%fa:window-maximize R%',
text: '%i18n:common.deck.pop-right%',
action: () => {
this.$store.dispatch('settings/popRightDeckColumn', this.column.id);
}
} : undefined, null, {
icon: '%fa:trash-alt R%',
text: '%i18n:common.deck.remove%',
action: () => {
this.$store.dispatch('settings/removeDeckColumn', this.column.id);
} }
}]; }];
@@ -96,11 +196,63 @@ export default Vue.extend({
this.menu.reverse().forEach(i => items.unshift(i)); this.menu.reverse().forEach(i => items.unshift(i));
} }
return items;
},
onContextmenu(e) {
contextmenu((this as any).os)(e, this.getMenu());
},
showMenu() {
this.os.new(Menu, { this.os.new(Menu, {
source: this.$refs.menu, source: this.$refs.menu,
compact: false, compact: false,
items items: this.getMenu()
}); });
},
onDragstart(e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('mk-deck-column', this.column.id);
this.dragging = true;
},
onDragend(e) {
this.dragging = false;
},
onDragover(e) {
// 自分自身がドラッグされている場合
if (this.dragging) {
// 自分自身にはドロップさせない
e.dataTransfer.dropEffect = 'none';
return;
}
const isDeckColumn = e.dataTransfer.types[0] == 'mk-deck-column';
e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
},
onDragenter() {
if (!this.dragging) this.draghover = true;
},
onDragleave() {
this.draghover = false;
},
onDrop(e) {
this.draghover = false;
this.$root.$emit('deck.column.dragEnd');
const id = e.dataTransfer.getData('mk-deck-column');
if (id != null && id != '') {
this.$store.dispatch('settings/swapDeckColumn', {
a: this.column.id,
b: id
});
}
} }
} }
}); });
@@ -112,18 +264,31 @@ export default Vue.extend({
root(isDark) root(isDark)
$header-height = 42px $header-height = 42px
flex 1 width 330px
min-width 330px min-width 330px
max-width 330px
height 100% height 100%
background isDark ? #282C37 : #fff background isDark ? #282C37 : #fff
border-radius 6px border-radius 6px
box-shadow 0 2px 16px rgba(#000, 0.1) box-shadow 0 2px 16px rgba(#000, 0.1)
overflow hidden overflow hidden
&.narrow &.draghover
box-shadow 0 0 0 2px rgba($theme-color, 0.8)
&.dragging
box-shadow 0 0 0 2px rgba($theme-color, 0.4)
&.dropready
*
pointer-events none
&:not(.active)
flex-basis $header-height
min-height $header-height
&:not(.isStacked).narrow
width 285px
min-width 285px min-width 285px
max-width 285px
&.naked &.naked
background rgba(#000, isDark ? 0.25 : 0.1) background rgba(#000, isDark ? 0.25 : 0.1)
@@ -140,9 +305,17 @@ root(isDark)
z-index 1 z-index 1
line-height $header-height line-height $header-height
padding 0 16px padding 0 16px
font-size 14px
color isDark ? #e3e5e8 : #888 color isDark ? #e3e5e8 : #888
background isDark ? #313543 : #fff background isDark ? #313543 : #fff
box-shadow 0 1px rgba(#000, 0.15) box-shadow 0 1px rgba(#000, 0.15)
cursor pointer
&, *
user-select none
*:not(button)
pointer-events none
&.indicate &.indicate
box-shadow 0 3px 0 0 $theme-color box-shadow 0 3px 0 0 $theme-color
@@ -151,12 +324,17 @@ root(isDark)
[data-fa] [data-fa]
margin-right 8px margin-right 8px
> .count
margin-left 4px
opacity 0.5
> button > button
position absolute position absolute
top 0 top 0
right 0 right 0
width $header-height width $header-height
line-height $header-height line-height $header-height
font-size 16px
color isDark ? #9baec8 : #ccc color isDark ? #9baec8 : #ccc
&:hover &:hover

View File

@@ -18,6 +18,11 @@ export default Vue.extend({
list: { list: {
type: Object, type: Object,
required: true required: true
},
mediaOnly: {
type: Boolean,
required: false,
default: false
} }
}, },
@@ -30,6 +35,12 @@ export default Vue.extend({
}; };
}, },
watch: {
mediaOnly() {
this.fetch();
}
},
mounted() { mounted() {
if (this.connection) this.connection.close(); if (this.connection) this.connection.close();
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id); this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
@@ -52,6 +63,7 @@ export default Vue.extend({
(this as any).api('notes/user-list-timeline', { (this as any).api('notes/user-list-timeline', {
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
mediaOnly: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
@@ -72,6 +84,7 @@ export default Vue.extend({
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
mediaOnly: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
}); });
@@ -89,6 +102,8 @@ export default Vue.extend({
return promise; return promise;
}, },
onNote(note) { onNote(note) {
if (this.mediaOnly && note.media.length == 0) return;
// Prepend a note // Prepend a note
(this.$refs.timeline as any).prepend(note); (this.$refs.timeline as any).prepend(note);
}, },

View File

@@ -2,25 +2,7 @@
<div class="fnlfosztlhtptnongximhlbykxblytcq"> <div class="fnlfosztlhtptnongximhlbykxblytcq">
<mk-avatar class="avatar" :user="note.user"/> <mk-avatar class="avatar" :user="note.user"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="note" :mini="true"/>
<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"> <div class="body">
<mk-sub-note-content class="text" :note="note"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
@@ -72,66 +54,8 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
margin-bottom 2px 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 > .body

View File

@@ -14,25 +14,7 @@
<article> <article>
<mk-avatar class="avatar" :user="p.user"/> <mk-avatar class="avatar" :user="p.user"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="p" :mini="true"/>
<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"> <div class="body">
<p v-if="p.cw != null" class="cw"> <p v-if="p.cw != null" class="cw">
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span> <span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
@@ -218,7 +200,7 @@ export default Vue.extend({
@import '~const.styl' @import '~const.styl'
root(isDark) root(isDark)
font-size 12px font-size 13px
border-bottom solid 1px isDark ? #1c2023 : #eaeaea border-bottom solid 1px isDark ? #1c2023 : #eaeaea
&:last-of-type &:last-of-type
@@ -234,7 +216,7 @@ root(isDark)
> .renote > .renote
display flex display flex
align-items center align-items center
padding 8px 16px padding 8px 16px 0 16px
line-height 28px line-height 28px
white-space pre white-space pre
color #9dbb00 color #9dbb00
@@ -292,62 +274,6 @@ root(isDark)
flex 1 flex 1
min-width 0 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 > .body
> .cw > .cw

View File

@@ -28,19 +28,17 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { url } from '../../../config';
import getNoteSummary from '../../../../../renderers/get-note-summary';
import XNote from './deck.note.vue'; import XNote from './deck.note.vue';
const displayLimit = 30; const displayLimit = 20;
export default Vue.extend({ export default Vue.extend({
components: { components: {
XNote XNote
}, },
inject: ['column', 'isScrollTop', 'indicate'], inject: ['column', 'isScrollTop', 'count'],
props: { props: {
more: { more: {
@@ -55,7 +53,6 @@ export default Vue.extend({
requestInitPromise: null as () => Promise<any[]>, requestInitPromise: null as () => Promise<any[]>,
notes: [], notes: [],
queue: [], queue: [],
unreadCount: 0,
fetching: true, fetching: true,
moreFetching: false moreFetching: false
}; };
@@ -73,6 +70,12 @@ export default Vue.extend({
} }
}, },
watch: {
queue(q) {
this.count(q.length);
}
},
created() { created() {
this.column.$on('top', this.onTop); this.column.$on('top', this.onTop);
this.column.$on('bottom', this.onBottom); this.column.$on('bottom', this.onBottom);
@@ -141,7 +144,6 @@ export default Vue.extend({
} }
} else { } else {
this.queue.push(note); this.queue.push(note);
this.indicate(true);
} }
}, },
@@ -156,7 +158,6 @@ export default Vue.extend({
releaseQueue() { releaseQueue() {
this.queue.forEach(n => this.prepend(n, true)); this.queue.forEach(n => this.prepend(n, true));
this.queue = []; this.queue = [];
this.indicate(false);
}, },
async loadMore() { async loadMore() {

View File

@@ -112,7 +112,7 @@ export default Vue.extend({
root(isDark) root(isDark)
> .notification > .notification
padding 16px padding 16px
font-size 12px font-size 13px
overflow-wrap break-word overflow-wrap break-word
&:after &:after

View File

@@ -1,11 +1,9 @@
<template> <template>
<div> <x-column :name="name" :column="column" :is-stacked="isStacked">
<x-column :id="id"> <span slot="header">%fa:bell R%{{ name }}</span>
<span slot="header">%fa:bell R%%i18n:common.deck.notifications%</span>
<x-notifications/> <x-notifications/>
</x-column> </x-column>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -20,10 +18,21 @@ export default Vue.extend({
}, },
props: { props: {
id: { column: {
type: String, type: Object,
required: true
},
isStacked: {
type: Boolean,
required: true required: true
} }
} },
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.notifications%';
}
},
}); });
</script> </script>

View File

@@ -21,20 +21,27 @@
import Vue from 'vue'; import Vue from 'vue';
import XNotification from './deck.notification.vue'; import XNotification from './deck.notification.vue';
const displayLimit = 20;
export default Vue.extend({ export default Vue.extend({
components: { components: {
XNotification XNotification
}, },
inject: ['column', 'isScrollTop', 'count'],
data() { data() {
return { return {
fetching: true, fetching: true,
fetchingMoreNotifications: false, fetchingMoreNotifications: false,
notifications: [], notifications: [],
queue: [],
moreNotifications: false, moreNotifications: false,
connection: null, connection: null,
connectionId: null connectionId: null
}; };
}, },
computed: { computed: {
_notifications(): any[] { _notifications(): any[] {
return (this.notifications as any).map(notification => { return (this.notifications as any).map(notification => {
@@ -46,12 +53,22 @@ export default Vue.extend({
}); });
} }
}, },
watch: {
queue(q) {
this.count(q.length);
}
},
mounted() { mounted() {
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
this.connection.on('notification', this.onNotification); this.connection.on('notification', this.onNotification);
this.column.$on('top', this.onTop);
this.column.$on('bottom', this.onBottom);
const max = 10; const max = 10;
(this as any).api('i/notifications', { (this as any).api('i/notifications', {
@@ -66,15 +83,20 @@ export default Vue.extend({
this.fetching = false; this.fetching = false;
}); });
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('notification', this.onNotification); this.connection.off('notification', this.onNotification);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
this.column.$off('top', this.onTop);
this.column.$off('bottom', this.onBottom);
}, },
methods: { methods: {
fetchMoreNotifications() { fetchMoreNotifications() {
this.fetchingMoreNotifications = true; this.fetchingMoreNotifications = true;
const max = 30; const max = 20;
(this as any).api('i/notifications', { (this as any).api('i/notifications', {
limit: max + 1, limit: max + 1,
@@ -90,6 +112,7 @@ export default Vue.extend({
this.fetchingMoreNotifications = false; this.fetchingMoreNotifications = false;
}); });
}, },
onNotification(notification) { onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({ this.connection.send({
@@ -97,7 +120,34 @@ export default Vue.extend({
id: notification.id id: notification.id
}); });
this.notifications.unshift(notification); this.prepend(notification);
},
prepend(notification) {
if (this.isScrollTop()) {
// Prepend the notification
this.notifications.unshift(notification);
// オーバーフローしたら古い通知は捨てる
if (this.notifications.length >= displayLimit) {
this.notifications = this.notifications.slice(0, displayLimit);
}
} else {
this.queue.push(notification);
}
},
releaseQueue() {
this.queue.forEach(n => this.prepend(n));
this.queue = [];
},
onTop() {
this.releaseQueue();
},
onBottom() {
this.fetchMoreNotifications();
} }
} }
}); });

View File

@@ -1,17 +1,20 @@
<template> <template>
<div> <x-column :menu="menu" :name="name" :column="column" :is-stacked="isStacked">
<x-column :id="column.id"> <span slot="header">
<span slot="header"> <template v-if="column.type == 'home'">%fa:home%</template>
<template v-if="column.type == 'home'">%fa:home%%i18n:common.deck.home%</template> <template v-if="column.type == 'local'">%fa:R comments%</template>
<template v-if="column.type == 'local'">%fa:R comments%%i18n:common.deck.local%</template> <template v-if="column.type == 'global'">%fa:globe%</template>
<template v-if="column.type == 'global'">%fa:globe%%i18n:common.deck.global%</template> <template v-if="column.type == 'list'">%fa:list%</template>
<template v-if="column.type == 'list'">%fa:list%{{ column.list.title }}</template> <span>{{ name }}</span>
</span> </span>
<x-list-tl v-if="column.type == 'list'" :list="column.list"/> <div class="editor" v-if="edit">
<x-tl v-else :src="column.type"/> <mk-switch v-model="column.isMediaOnly" @change="onChangeSettings" text="%i18n:@is-media-only%"/>
</x-column> <mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
</div> </div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly"/>
</x-column>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -31,6 +34,42 @@ export default Vue.extend({
column: { column: {
type: Object, type: Object,
required: true required: true
},
isStacked: {
type: Boolean,
required: true
}
},
data() {
return {
edit: false,
menu: [{
icon: '%fa:cog%',
text: '%i18n:@edit%',
action: () => {
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');
} }
} }
}); });

View File

@@ -18,6 +18,11 @@ export default Vue.extend({
type: String, type: String,
required: false, required: false,
default: 'home' default: 'home'
},
mediaOnly: {
type: Boolean,
required: false,
default: false
} }
}, },
@@ -31,6 +36,12 @@ export default Vue.extend({
}; };
}, },
watch: {
mediaOnly() {
this.fetch();
}
},
computed: { computed: {
stream(): any { stream(): any {
return this.src == 'home' return this.src == 'home'
@@ -78,6 +89,7 @@ export default Vue.extend({
(this.$refs.timeline as any).init(() => new Promise((res, rej) => { (this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
mediaOnly: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
@@ -97,6 +109,7 @@ export default Vue.extend({
const promise = (this as any).api(this.endpoint, { const promise = (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
mediaOnly: this.mediaOnly,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes
@@ -116,6 +129,8 @@ export default Vue.extend({
}, },
onNote(note) { onNote(note) {
if (this.mediaOnly && note.media.length == 0) return;
// Prepend a note // Prepend a note
(this.$refs.timeline as any).prepend(note); (this.$refs.timeline as any).prepend(note);
}, },

View File

@@ -1,13 +1,13 @@
<template> <template>
<mk-ui :class="$style.root"> <mk-ui :class="$style.root">
<div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode"> <div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode">
<template v-for="column in columns"> <template v-for="ids in layout">
<x-widgets-column v-if="column.type == 'widgets'" :key="column.id" :column="column"/> <div v-if="ids.length > 1" class="folder">
<x-notifications-column v-if="column.type == 'notifications'" :key="column.id" :id="column.id"/> <template v-for="id, i in ids">
<x-tl-column v-if="column.type == 'home'" :key="column.id" :column="column"/> <x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true"/>
<x-tl-column v-if="column.type == 'local'" :key="column.id" :column="column"/> </template>
<x-tl-column v-if="column.type == 'global'" :key="column.id" :column="column"/> </div>
<x-tl-column v-if="column.type == 'list'" :key="column.id" :column="column"/> <x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])"/>
</template> </template>
<button ref="add" @click="add" title="%i18n:common.deck.add-column%">%fa:plus%</button> <button ref="add" @click="add" title="%i18n:common.deck.add-column%">%fa:plus%</button>
</div> </div>
@@ -16,27 +16,34 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XTlColumn from './deck.tl-column.vue'; import XColumnCore from './deck.column-core.vue';
import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue';
import Menu from '../../../../common/views/components/menu.vue'; import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue'; import MkUserListsWindow from '../../components/user-lists-window.vue';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XTlColumn, XColumnCore
XNotificationsColumn,
XWidgetsColumn
}, },
computed: { computed: {
columns() { columns(): any[] {
if (this.$store.state.settings.deck == null) return []; if (this.$store.state.settings.deck == null) return [];
return this.$store.state.settings.deck.columns; return this.$store.state.settings.deck.columns;
},
layout(): any[] {
if (this.$store.state.settings.deck == null) return [];
if (this.$store.state.settings.deck.layout == null) return this.$store.state.settings.deck.columns.map(c => [c.id]);
return this.$store.state.settings.deck.layout;
} }
}, },
provide() {
return {
getColumnVm: this.getColumnVm
};
},
created() { created() {
if (this.$store.state.settings.deck == null) { if (this.$store.state.settings.deck == null) {
const deck = { const deck = {
@@ -58,11 +65,23 @@ export default Vue.extend({
}] }]
}; };
deck.layout = deck.columns.map(c => [c.id]);
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'deck', key: 'deck',
value: deck value: deck
}); });
} }
// 互換性のため
if (this.$store.state.settings.deck != null && this.$store.state.settings.deck.layout == null) {
this.$store.dispatch('settings/set', {
key: 'deck',
value: Object.assign({}, this.$store.state.settings.deck, {
layout: this.$store.state.settings.deck.columns.map(c => [c.id])
})
});
}
}, },
mounted() { mounted() {
@@ -74,37 +93,45 @@ export default Vue.extend({
}, },
methods: { methods: {
getColumnVm(id) {
return this.$refs[id][0];
},
add() { add() {
this.os.new(Menu, { this.os.new(Menu, {
source: this.$refs.add, source: this.$refs.add,
compact: true, compact: true,
items: [{ items: [{
content: '%i18n:common.deck.home%', icon: '%fa:home%',
onClick: () => { text: '%i18n:common.deck.home%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
id: uuid(), id: uuid(),
type: 'home' type: 'home'
}); });
} }
}, { }, {
content: '%i18n:common.deck.local%', icon: '%fa:comments R%',
onClick: () => { text: '%i18n:common.deck.local%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
id: uuid(), id: uuid(),
type: 'local' type: 'local'
}); });
} }
}, { }, {
content: '%i18n:common.deck.global%', icon: '%fa:globe%',
onClick: () => { text: '%i18n:common.deck.global%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
id: uuid(), id: uuid(),
type: 'global' type: 'global'
}); });
} }
}, { }, {
content: '%i18n:common.deck.list%', icon: '%fa:list%',
onClick: () => { text: '%i18n:common.deck.list%',
action: () => {
const w = (this as any).os.new(MkUserListsWindow); const w = (this as any).os.new(MkUserListsWindow);
w.$once('choosen', list => { w.$once('choosen', list => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
@@ -116,16 +143,18 @@ export default Vue.extend({
}); });
} }
}, { }, {
content: '%i18n:common.deck.notifications%', icon: '%fa:bell R%',
onClick: () => { text: '%i18n:common.deck.notifications%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
id: uuid(), id: uuid(),
type: 'notifications' type: 'notifications'
}); });
} }
}, { }, {
content: '%i18n:common.deck.widgets%', icon: '%fa:calculator%',
onClick: () => { text: '%i18n:common.deck.widgets%',
action: () => {
this.$store.dispatch('settings/addDeckColumn', { this.$store.dispatch('settings/addDeckColumn', {
id: uuid(), id: uuid(),
type: 'widgets', type: 'widgets',
@@ -150,7 +179,6 @@ export default Vue.extend({
root(isDark) root(isDark)
display flex display flex
flex 1 flex 1
justify-content center
padding 16px 0 16px 16px padding 16px 0 16px 16px
overflow auto overflow auto
@@ -160,6 +188,20 @@ root(isDark)
&:last-of-type &:last-of-type
margin-right 0 margin-right 0
&.folder
display flex
flex-direction column
> *:not(:last-child)
margin-bottom 8px
> *
&:first-child
margin-left auto
&:last-child
margin-right auto
> button > button
padding 0 16px padding 0 16px
color isDark ? #93a0a5 : #888 color isDark ? #93a0a5 : #888

View File

@@ -1,57 +1,56 @@
<template> <template>
<div class="wtdtxvecapixsepjtcupubtsmometobz"> <x-column :menu="menu" :naked="true" :narrow="true" :name="name" :column="column" :is-stacked="isStacked" class="wtdtxvecapixsepjtcupubtsmometobz">
<x-column :id="column.id" :menu="menu" :naked="true" :narrow="true"> <span slot="header">%fa:calculator%{{ name }}</span>
<span slot="header">%fa:calculator%%i18n:common.deck.widgets%</span>
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq"> <div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
<template v-if="edit"> <template v-if="edit">
<header> <header>
<select v-model="widgetAdderSelected"> <select v-model="widgetAdderSelected">
<option value="profile">%i18n:common.widgets.profile%</option> <option value="profile">%i18n:common.widgets.profile%</option>
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option> <option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
<option value="calendar">%i18n:common.widgets.calendar%</option> <option value="calendar">%i18n:common.widgets.calendar%</option>
<option value="timemachine">%i18n:common.widgets.timemachine%</option> <option value="timemachine">%i18n:common.widgets.timemachine%</option>
<option value="activity">%i18n:common.widgets.activity%</option> <option value="activity">%i18n:common.widgets.activity%</option>
<option value="rss">%i18n:common.widgets.rss%</option> <option value="rss">%i18n:common.widgets.rss%</option>
<option value="trends">%i18n:common.widgets.trends%</option> <option value="trends">%i18n:common.widgets.trends%</option>
<option value="photo-stream">%i18n:common.widgets.photo-stream%</option> <option value="photo-stream">%i18n:common.widgets.photo-stream%</option>
<option value="slideshow">%i18n:common.widgets.slideshow%</option> <option value="slideshow">%i18n:common.widgets.slideshow%</option>
<option value="version">%i18n:common.widgets.version%</option> <option value="version">%i18n:common.widgets.version%</option>
<option value="broadcast">%i18n:common.widgets.broadcast%</option> <option value="broadcast">%i18n:common.widgets.broadcast%</option>
<option value="notifications">%i18n:common.widgets.notifications%</option> <option value="notifications">%i18n:common.widgets.notifications%</option>
<option value="users">%i18n:common.widgets.users%</option> <option value="users">%i18n:common.widgets.users%</option>
<option value="polls">%i18n:common.widgets.polls%</option> <option value="polls">%i18n:common.widgets.polls%</option>
<option value="post-form">%i18n:common.widgets.post-form%</option> <option value="post-form">%i18n:common.widgets.post-form%</option>
<option value="messaging">%i18n:common.widgets.messaging%</option> <option value="messaging">%i18n:common.widgets.messaging%</option>
<option value="memo">%i18n:common.widgets.memo%</option> <option value="memo">%i18n:common.widgets.memo%</option>
<option value="server">%i18n:common.widgets.server%</option> <option value="posts-monitor">%i18n:common.widgets.posts-monitor%</option>
<option value="donation">%i18n:common.widgets.donation%</option> <option value="server">%i18n:common.widgets.server%</option>
<option value="nav">%i18n:common.widgets.nav%</option> <option value="donation">%i18n:common.widgets.donation%</option>
<option value="tips">%i18n:common.widgets.tips%</option> <option value="nav">%i18n:common.widgets.nav%</option>
</select> <option value="tips">%i18n:common.widgets.tips%</option>
<button @click="addWidget">%i18n:@add%</button> </select>
</header> <button @click="addWidget">%i18n:@add%</button>
<x-draggable </header>
:list="column.widgets" <x-draggable
:options="{ handle: '.handle', animation: 150 }" :list="column.widgets"
@sort="onWidgetSort" :options="{ handle: '.handle', animation: 150 }"
> @sort="onWidgetSort"
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id"> >
<header> <div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button> <header>
</header> <span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
<div @click="widgetFunc(widget.id)"> </header>
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/> <div @click="widgetFunc(widget.id)">
</div> <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
</div> </div>
</x-draggable> </div>
</template> </x-draggable>
<template v-else> </template>
<component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/> <template v-else>
</template> <component class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="deck"/>
</div> </template>
</x-column> </div>
</div> </x-column>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -70,6 +69,10 @@ export default Vue.extend({
column: { column: {
type: Object, type: Object,
required: true required: true
},
isStacked: {
type: Boolean,
required: true
} }
}, },
@@ -81,10 +84,18 @@ export default Vue.extend({
} }
}, },
computed: {
name(): string {
if (this.column.name) return this.column.name;
return '%i18n:common.deck.widgets%';
}
},
created() { created() {
this.menu = [{ this.menu = [{
content: '%fa:cog% %i18n:@edit%', icon: '%fa:cog%',
onClick: () => { text: '%i18n:@edit%',
action: () => {
this.edit = !this.edit; this.edit = !this.edit;
} }
}]; }];

View File

@@ -32,42 +32,30 @@ body > noscript {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
text-align: center;
background: #fff; background: #fff;
cursor: wait; cursor: wait;
} }
#ini > p { #ini > svg {
display: block; position: absolute;
user-select: none; top: 0;
margin: 32px; right: 0;
font-size: 4em; bottom: 0;
color: #555; left: 0;
margin: auto;
width: 64px;
height: 64px;
animation: ini 0.6s infinite linear;
} }
#ini > p > span {
animation: ini 1.4s infinite ease-in-out both;
}
#ini > p > span:nth-child(1) {
animation-delay: 0s;
}
#ini > p > span:nth-child(2) {
animation-delay: 0.16s;
}
#ini > p > span:nth-child(3) {
animation-delay: 0.32s;
}
html[data-darkmode] #ini { html[data-darkmode] #ini {
background: #191b22; background: #191b22;
} }
html[data-darkmode] #ini > p {
color: #fff;
}
@keyframes ini { @keyframes ini {
0%, 80%, 100% { from {
opacity: 1; transform: rotate(0deg);
} }
40% { to {
opacity: 0; transform: rotate(360deg);
} }
} }

View File

@@ -8,7 +8,8 @@ import Progress from './common/scripts/loading';
import Connection from './common/scripts/streaming/stream'; import Connection from './common/scripts/streaming/stream';
import { HomeStreamManager } from './common/scripts/streaming/home'; import { HomeStreamManager } from './common/scripts/streaming/home';
import { DriveStreamManager } from './common/scripts/streaming/drive'; import { DriveStreamManager } from './common/scripts/streaming/drive';
import { ServerStreamManager } from './common/scripts/streaming/server'; import { ServerStatsStreamManager } from './common/scripts/streaming/server-stats';
import { NotesStatsStreamManager } from './common/scripts/streaming/notes-stats';
import { MessagingIndexStreamManager } from './common/scripts/streaming/messaging-index'; import { MessagingIndexStreamManager } from './common/scripts/streaming/messaging-index';
import { OthelloStreamManager } from './common/scripts/streaming/othello'; import { OthelloStreamManager } from './common/scripts/streaming/othello';
@@ -104,14 +105,16 @@ export default class MiOS extends EventEmitter {
localTimelineStream: LocalTimelineStreamManager; localTimelineStream: LocalTimelineStreamManager;
globalTimelineStream: GlobalTimelineStreamManager; globalTimelineStream: GlobalTimelineStreamManager;
driveStream: DriveStreamManager; driveStream: DriveStreamManager;
serverStream: ServerStreamManager; serverStatsStream: ServerStatsStreamManager;
notesStatsStream: NotesStatsStreamManager;
messagingIndexStream: MessagingIndexStreamManager; messagingIndexStream: MessagingIndexStreamManager;
othelloStream: OthelloStreamManager; othelloStream: OthelloStreamManager;
} = { } = {
localTimelineStream: null, localTimelineStream: null,
globalTimelineStream: null, globalTimelineStream: null,
driveStream: null, driveStream: null,
serverStream: null, serverStatsStream: null,
notesStatsStream: null,
messagingIndexStream: null, messagingIndexStream: null,
othelloStream: null othelloStream: null
}; };
@@ -218,7 +221,8 @@ export default class MiOS extends EventEmitter {
this.store = initStore(this); this.store = initStore(this);
//#region Init stream managers //#region Init stream managers
this.streams.serverStream = new ServerStreamManager(this); this.streams.serverStatsStream = new ServerStatsStreamManager(this);
this.streams.notesStatsStream = new NotesStatsStreamManager(this);
this.once('signedin', () => { this.once('signedin', () => {
// Init home stream manager // Init home stream manager

View File

@@ -2,26 +2,7 @@
<div class="mk-note-preview" :class="{ smart: $store.state.device.postStyle == 'smart' }"> <div class="mk-note-preview" :class="{ smart: $store.state.device.postStyle == 'smart' }">
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/> <mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="note" :mini="true"/>
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
<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"> <div class="body">
<mk-sub-note-content class="text" :note="note"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
@@ -79,64 +60,8 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
margin-bottom 2px 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 .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
> .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 .5em 0 0
overflow hidden
text-overflow ellipsis
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 > .body

View File

@@ -2,26 +2,7 @@
<div class="sub" :class="{ smart: $store.state.device.postStyle == 'smart' }"> <div class="sub" :class="{ smart: $store.state.device.postStyle == 'smart' }">
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/> <mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="note" :mini="true"/>
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
<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"> <div class="body">
<mk-sub-note-content class="text" :note="note"/> <mk-sub-note-content class="text" :note="note"/>
</div> </div>
@@ -92,66 +73,8 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
margin-bottom 2px 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 > .body

View File

@@ -14,26 +14,7 @@
<article> <article>
<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle != 'smart'"/> <mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle != 'smart'"/>
<div class="main"> <div class="main">
<header> <mk-note-header class="header" :note="p" :mini="true"/>
<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle == 'smart'"/>
<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"> <div class="body">
<p v-if="p.cw != null" class="cw"> <p v-if="p.cw != null" class="cw">
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span> <span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
@@ -358,65 +339,10 @@ root(isDark)
flex 1 flex 1
min-width 0 min-width 0
> header > .header
display flex
align-items baseline
white-space nowrap
@media (min-width 500px) @media (min-width 500px)
margin-bottom 2px margin-bottom 2px
> .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 > .body
@media (min-width 700px) @media (min-width 700px)
font-size 1.1em font-size 1.1em

View File

@@ -15,6 +15,7 @@
<option value="rss">%i18n:common.widgets.rss%</option> <option value="rss">%i18n:common.widgets.rss%</option>
<option value="photo-stream">%i18n:common.widgets.photo-stream%</option> <option value="photo-stream">%i18n:common.widgets.photo-stream%</option>
<option value="slideshow">%i18n:common.widgets.slideshow%</option> <option value="slideshow">%i18n:common.widgets.slideshow%</option>
<option value="posts-monitor">%i18n:common.widgets.posts-monitor%</option>
<option value="version">%i18n:common.widgets.version%</option> <option value="version">%i18n:common.widgets.version%</option>
<option value="server">%i18n:common.widgets.server%</option> <option value="server">%i18n:common.widgets.server%</option>
<option value="memo">%i18n:common.widgets.memo%</option> <option value="memo">%i18n:common.widgets.memo%</option>

View File

@@ -173,23 +173,33 @@ export default (os: MiOS) => new Vuex.Store({
}, },
addDeckColumn(state, column) { addDeckColumn(state, column) {
if (state.deck.columns == null) state.deck.columns = [];
state.deck.columns.push(column); state.deck.columns.push(column);
state.deck.layout.push([column.id]);
}, },
removeDeckColumn(state, id) { removeDeckColumn(state, id) {
if (state.deck.columns == null) return;
state.deck.columns = state.deck.columns.filter(c => c.id != id); state.deck.columns = state.deck.columns.filter(c => c.id != id);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
},
swapDeckColumn(state, x) {
const a = x.a;
const b = x.b;
const aX = state.deck.layout.findIndex(ids => ids.indexOf(a) != -1);
const aY = state.deck.layout[aX].findIndex(id => id == a);
const bX = state.deck.layout.findIndex(ids => ids.indexOf(b) != -1);
const bY = state.deck.layout[bX].findIndex(id => id == b);
state.deck.layout[aX][aY] = b;
state.deck.layout[bX][bY] = a;
}, },
swapLeftDeckColumn(state, id) { swapLeftDeckColumn(state, id) {
if (state.deck.columns == null) return; state.deck.layout.some((ids, i) => {
state.deck.columns.some((c, i) => { if (ids.indexOf(id) != -1) {
if (c.id == id) { const left = state.deck.layout[i - 1];
const left = state.deck.columns[i - 1];
if (left) { if (left) {
state.deck.columns[i - 1] = state.deck.columns[i]; state.deck.layout[i - 1] = state.deck.layout[i];
state.deck.columns[i] = left; state.deck.layout[i] = left;
} }
return true; return true;
} }
@@ -197,31 +207,77 @@ export default (os: MiOS) => new Vuex.Store({
}, },
swapRightDeckColumn(state, id) { swapRightDeckColumn(state, id) {
if (state.deck.columns == null) return; state.deck.layout.some((ids, i) => {
state.deck.columns.some((c, i) => { if (ids.indexOf(id) != -1) {
if (c.id == id) { const right = state.deck.layout[i + 1];
const right = state.deck.columns[i + 1];
if (right) { if (right) {
state.deck.columns[i + 1] = state.deck.columns[i]; state.deck.layout[i + 1] = state.deck.layout[i];
state.deck.columns[i] = right; state.deck.layout[i] = right;
} }
return true; return true;
} }
}); });
}, },
swapUpDeckColumn(state, id) {
const ids = state.deck.layout.find(ids => ids.indexOf(id) != -1);
ids.some((x, i) => {
if (x == id) {
const up = ids[i - 1];
if (up) {
ids[i - 1] = id;
ids[i] = up;
}
return true;
}
});
},
swapDownDeckColumn(state, id) {
const ids = state.deck.layout.find(ids => ids.indexOf(id) != -1);
ids.some((x, i) => {
if (x == id) {
const down = ids[i + 1];
if (down) {
ids[i + 1] = id;
ids[i] = down;
}
return true;
}
});
},
stackLeftDeckColumn(state, id) {
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
const left = state.deck.layout[i - 1];
if (left) state.deck.layout[i - 1].push(id);
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
},
popRightDeckColumn(state, id) {
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
state.deck.layout.splice(i + 1, 0, [id]);
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
},
addDeckWidget(state, x) { addDeckWidget(state, x) {
if (state.deck.columns == null) return;
const column = state.deck.columns.find(c => c.id == x.id); const column = state.deck.columns.find(c => c.id == x.id);
if (column == null) return; if (column == null) return;
column.widgets.unshift(x.widget); column.widgets.unshift(x.widget);
}, },
removeDeckWidget(state, x) { removeDeckWidget(state, x) {
if (state.deck.columns == null) return;
const column = state.deck.columns.find(c => c.id == x.id); const column = state.deck.columns.find(c => c.id == x.id);
if (column == null) return; if (column == null) return;
column.widgets = column.widgets.filter(w => w.id != x.widget.id); 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;
} }
}, },
@@ -261,6 +317,11 @@ export default (os: MiOS) => new Vuex.Store({
ctx.dispatch('saveDeck'); ctx.dispatch('saveDeck');
}, },
swapDeckColumn(ctx, id) {
ctx.commit('swapDeckColumn', id);
ctx.dispatch('saveDeck');
},
swapLeftDeckColumn(ctx, id) { swapLeftDeckColumn(ctx, id) {
ctx.commit('swapLeftDeckColumn', id); ctx.commit('swapLeftDeckColumn', id);
ctx.dispatch('saveDeck'); ctx.dispatch('saveDeck');
@@ -271,6 +332,26 @@ export default (os: MiOS) => new Vuex.Store({
ctx.dispatch('saveDeck'); ctx.dispatch('saveDeck');
}, },
swapUpDeckColumn(ctx, id) {
ctx.commit('swapUpDeckColumn', id);
ctx.dispatch('saveDeck');
},
swapDownDeckColumn(ctx, id) {
ctx.commit('swapDownDeckColumn', id);
ctx.dispatch('saveDeck');
},
stackLeftDeckColumn(ctx, id) {
ctx.commit('stackLeftDeckColumn', id);
ctx.dispatch('saveDeck');
},
popRightDeckColumn(ctx, id) {
ctx.commit('popRightDeckColumn', id);
ctx.dispatch('saveDeck');
},
addDeckWidget(ctx, x) { addDeckWidget(ctx, x) {
ctx.commit('addDeckWidget', x); ctx.commit('addDeckWidget', x);
ctx.dispatch('saveDeck'); ctx.dispatch('saveDeck');
@@ -281,6 +362,11 @@ export default (os: MiOS) => new Vuex.Store({
ctx.dispatch('saveDeck'); ctx.dispatch('saveDeck');
}, },
renameDeckColumn(ctx, x) {
ctx.commit('renameDeckColumn', x);
ctx.dispatch('saveDeck');
},
addHomeWidget(ctx, widget) { addHomeWidget(ctx, widget) {
ctx.commit('addHomeWidget', widget); ctx.commit('addHomeWidget', widget);

View File

@@ -12,7 +12,10 @@ const uri = u && p
*/ */
import mongo from 'monk'; import mongo from 'monk';
const db = mongo(uri); const db = mongo(uri, {
poolSize: 16,
keepAlive: 1
});
export default db; export default db;

View File

@@ -17,7 +17,8 @@ import ProgressBar from './utils/cli/progressbar';
import EnvironmentInfo from './utils/environmentInfo'; import EnvironmentInfo from './utils/environmentInfo';
import MachineInfo from './utils/machineInfo'; import MachineInfo from './utils/machineInfo';
import DependencyInfo from './utils/dependencyInfo'; import DependencyInfo from './utils/dependencyInfo';
import stats from './utils/stats'; import serverStats from './server-stats';
import notesStats from './notes-stats';
import loadConfig from './config/load'; import loadConfig from './config/load';
import { Config } from './config/types'; import { Config } from './config/types';
@@ -49,7 +50,8 @@ function main() {
masterMain(opt); masterMain(opt);
ev.mount(); ev.mount();
stats(); serverStats();
notesStats();
} else { } else {
workerMain(opt); workerMain(opt);
} }

View File

@@ -16,6 +16,9 @@ import Following from './following';
const Note = db.get<INote>('notes'); const Note = db.get<INote>('notes');
Note.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('uri', { sparse: true, unique: true });
Note.createIndex('userId'); Note.createIndex('userId');
Note.createIndex({
createdAt: -1
});
export default Note; export default Note;
export function isValidText(text: string): boolean { export function isValidText(text: string): boolean {

View File

@@ -48,6 +48,7 @@ type IUserBase = {
usernameLower: string; usernameLower: string;
avatarId: mongo.ObjectID; avatarId: mongo.ObjectID;
bannerId: mongo.ObjectID; bannerId: mongo.ObjectID;
wallpaperId: mongo.ObjectID;
data: any; data: any;
description: string; description: string;
pinnedNoteId: mongo.ObjectID; pinnedNoteId: mongo.ObjectID;
@@ -412,6 +413,10 @@ export const pack = (
? `${config.drive_url}/${_user.bannerId}` ? `${config.drive_url}/${_user.bannerId}`
: null; : null;
_user.wallpaperUrl = _user.wallpaperId != null
? `${config.drive_url}/${_user.wallpaperId}`
: null;
if (!meId || !meId.equals(_user.id) || !opts.detail) { if (!meId || !meId.equals(_user.id) || !opts.detail) {
delete _user.avatarId; delete _user.avatarId;
delete _user.bannerId; delete _user.bannerId;

22
src/notes-stats-child.ts Normal file
View File

@@ -0,0 +1,22 @@
import Note from './models/note';
const interval = 5000;
setInterval(async () => {
const [all, local] = await Promise.all([Note.count({
createdAt: {
$gte: new Date(Date.now() - interval)
}
}), Note.count({
createdAt: {
$gte: new Date(Date.now() - interval)
},
'_user.host': null
})]);
const stats = {
all, local
};
process.send(stats);
}, interval);

20
src/notes-stats.ts Normal file
View File

@@ -0,0 +1,20 @@
import * as childProcess from 'child_process';
import Xev from 'xev';
const ev = new Xev();
export default function() {
const log = [];
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
p.on('message', stats => {
ev.emit('notesStats', stats);
log.push(stats);
if (log.length > 100) log.shift();
});
ev.on('requestNotesStatsLog', id => {
ev.emit('notesStatsLog:' + id, log);
});
}

View File

@@ -6,13 +6,19 @@ import Xev from 'xev';
const ev = new Xev(); const ev = new Xev();
/** /**
* Report stats regularly * Report server stats regularly
*/ */
export default function() { export default function() {
const log = [];
ev.on('requestServerStatsLog', id => {
ev.emit('serverStatsLog:' + id, log);
});
setInterval(() => { setInterval(() => {
osUtils.cpuUsage(cpuUsage => { osUtils.cpuUsage(cpuUsage => {
const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/'); const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/');
ev.emit('stats', { const stats = {
cpu_usage: cpuUsage, cpu_usage: cpuUsage,
mem: { mem: {
total: os.totalmem(), total: os.totalmem(),
@@ -21,7 +27,10 @@ export default function() {
disk, disk,
os_uptime: os.uptime(), os_uptime: os.uptime(),
process_uptime: process.uptime() process_uptime: process.uptime()
}); };
ev.emit('serverStats', stats);
log.push(stats);
if (log.length > 50) log.shift();
}); });
}, 1000); }, 1000);
} }

View File

@@ -45,6 +45,11 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => {
if (bannerIdErr) return rej('invalid bannerId param'); if (bannerIdErr) return rej('invalid bannerId param');
if (bannerId !== undefined) updates.bannerId = bannerId; if (bannerId !== undefined) updates.bannerId = bannerId;
// Get 'wallpaperId' parameter
const [wallpaperId, wallpaperIdErr] = $.type(ID).optional().nullable().get(params.wallpaperId);
if (wallpaperIdErr) return rej('invalid wallpaperId param');
if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId;
// Get 'isLocked' parameter // Get 'isLocked' parameter
const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked); const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked);
if (isLockedErr) return rej('invalid isLocked param'); if (isLockedErr) return rej('invalid isLocked param');
@@ -85,6 +90,16 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => {
} }
} }
if (wallpaperId) {
const wallpaper = await DriveFile.findOne({
_id: wallpaperId
});
if (wallpaper != null && wallpaper.metadata.properties.avgColor) {
updates.wallpaperColor = wallpaper.metadata.properties.avgColor;
}
}
await User.update(user._id, { await User.update(user._id, {
$set: updates $set: updates
}); });

View File

@@ -140,7 +140,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
} }
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
if (text === undefined && files === null && renote === null && poll === undefined) { if ((text === undefined || text === null) && files === null && renote === null && poll === undefined) {
return rej('text, mediaIds, renoteId or poll is required'); return rej('text, mediaIds, renoteId or poll is required');
} }

View File

@@ -35,6 +35,10 @@ module.exports = async (params, user) => {
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
} }
// Get 'mediaOnly' parameter
const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
if (mediaOnlyErr) throw 'invalid mediaOnly param';
// ミュートしているユーザーを取得 // ミュートしているユーザーを取得
const mutedUserIds = user ? (await Mute.find({ const mutedUserIds = user ? (await Mute.find({
muterId: user._id muterId: user._id
@@ -64,6 +68,10 @@ module.exports = async (params, user) => {
}; };
} }
if (mediaOnly) {
query.mediaIds = { $exists: true, $ne: [] };
}
if (sinceId) { if (sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {

View File

@@ -35,6 +35,10 @@ module.exports = async (params, user) => {
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
} }
// Get 'mediaOnly' parameter
const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
if (mediaOnlyErr) throw 'invalid mediaOnly param';
// ミュートしているユーザーを取得 // ミュートしているユーザーを取得
const mutedUserIds = user ? (await Mute.find({ const mutedUserIds = user ? (await Mute.find({
muterId: user._id muterId: user._id
@@ -67,6 +71,10 @@ module.exports = async (params, user) => {
}; };
} }
if (mediaOnly) {
query.mediaIds = { $exists: true, $ne: [] };
}
if (sinceId) { if (sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {

View File

@@ -44,6 +44,10 @@ module.exports = async (params, user, app) => {
const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
// Get 'mediaOnly' parameter
const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
if (mediaOnlyErr) throw 'invalid mediaOnly param';
const [followings, mutedUserIds] = await Promise.all([ const [followings, mutedUserIds] = await Promise.all([
// フォローを取得 // フォローを取得
// Fetch following // Fetch following
@@ -137,6 +141,12 @@ module.exports = async (params, user, app) => {
}); });
} }
if (mediaOnly) {
query.$and.push({
mediaIds: { $exists: true, $ne: [] }
});
}
if (sinceId) { if (sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {

View File

@@ -44,6 +44,10 @@ module.exports = async (params, user, app) => {
const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes); const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
// Get 'mediaOnly' parameter
const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
if (mediaOnlyErr) throw 'invalid mediaOnly param';
// Get 'listId' parameter // Get 'listId' parameter
const [listId, listIdErr] = $.type(ID).get(params.listId); const [listId, listIdErr] = $.type(ID).get(params.listId);
if (listIdErr) throw 'invalid listId param'; if (listIdErr) throw 'invalid listId param';
@@ -146,6 +150,12 @@ module.exports = async (params, user, app) => {
}); });
} }
if (mediaOnly) {
query.$and.push({
mediaIds: { $exists: true, $ne: [] }
});
}
if (sinceId) { if (sinceId) {
sort._id = 1; sort._id = 1;
query._id = { query._id = {

View File

@@ -0,0 +1,35 @@
import * as websocket from 'websocket';
import Xev from 'xev';
const ev = new Xev();
export default function(request: websocket.request, connection: websocket.connection): void {
const onStats = stats => {
connection.send(JSON.stringify({
type: 'stats',
body: stats
}));
};
connection.on('message', async data => {
const msg = JSON.parse(data.utf8Data);
switch (msg.type) {
case 'requestLog':
ev.once('notesStatsLog:' + msg.id, statsLog => {
connection.send(JSON.stringify({
type: 'statsLog',
body: statsLog
}));
});
ev.emit('requestNotesStatsLog', msg.id);
break;
}
});
ev.addListener('notesStats', onStats);
connection.on('close', () => {
ev.removeListener('notesStats', onStats);
});
}

View File

@@ -0,0 +1,35 @@
import * as websocket from 'websocket';
import Xev from 'xev';
const ev = new Xev();
export default function(request: websocket.request, connection: websocket.connection): void {
const onStats = stats => {
connection.send(JSON.stringify({
type: 'stats',
body: stats
}));
};
connection.on('message', async data => {
const msg = JSON.parse(data.utf8Data);
switch (msg.type) {
case 'requestLog':
ev.once('serverStatsLog:' + msg.id, statsLog => {
connection.send(JSON.stringify({
type: 'statsLog',
body: statsLog
}));
});
ev.emit('requestServerStatsLog', msg.id);
break;
}
});
ev.addListener('serverStats', onStats);
connection.on('close', () => {
ev.removeListener('serverStats', onStats);
});
}

View File

@@ -1,19 +0,0 @@
import * as websocket from 'websocket';
import Xev from 'xev';
const ev = new Xev();
export default function(request: websocket.request, connection: websocket.connection): void {
const onStats = stats => {
connection.send(JSON.stringify({
type: 'stats',
body: stats
}));
};
ev.addListener('stats', onStats);
connection.on('close', () => {
ev.removeListener('stats', onStats);
});
}

View File

@@ -12,7 +12,8 @@ import messagingStream from './stream/messaging';
import messagingIndexStream from './stream/messaging-index'; import messagingIndexStream from './stream/messaging-index';
import othelloGameStream from './stream/othello-game'; import othelloGameStream from './stream/othello-game';
import othelloStream from './stream/othello'; import othelloStream from './stream/othello';
import serverStream from './stream/server'; import serverStatsStream from './stream/server-stats';
import notesStatsStream from './stream/notes-stats';
import requestsStream from './stream/requests'; import requestsStream from './stream/requests';
import { ParsedUrlQuery } from 'querystring'; import { ParsedUrlQuery } from 'querystring';
import authenticate from './authenticate'; import authenticate from './authenticate';
@@ -28,8 +29,13 @@ module.exports = (server: http.Server) => {
ws.on('request', async (request) => { ws.on('request', async (request) => {
const connection = request.accept(); const connection = request.accept();
if (request.resourceURL.pathname === '/server') { if (request.resourceURL.pathname === '/server-stats') {
serverStream(request, connection); serverStatsStream(request, connection);
return;
}
if (request.resourceURL.pathname === '/notes-stats') {
notesStatsStream(request, connection);
return; return;
} }

View File

@@ -164,8 +164,8 @@ export default async function(
'metadata.deletedAt': { $exists: false } 'metadata.deletedAt': { $exists: false }
}); });
if (much !== null) { if (much) {
log('file with same hash is found'); log(`file with same hash is found: ${much._id}`);
return much; return much;
} }
} }