mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-11 13:05:33 +02:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e8a1086d8 | ||
|
|
c78945436e | ||
|
|
6eff8fde74 | ||
|
|
726d5a177e | ||
|
|
33495b5cb3 | ||
|
|
fe159a13a9 | ||
|
|
22a1dc0566 | ||
|
|
02e6b732e9 | ||
|
|
cc6fa135ac | ||
|
|
5747732156 | ||
|
|
581d1617d8 | ||
|
|
6152fd20bf | ||
|
|
19300ca65c | ||
|
|
2f3d744e19 | ||
|
|
724e812972 | ||
|
|
9a6246fd4e | ||
|
|
34f44de59c | ||
|
|
16e446c121 | ||
|
|
8f232a9da9 | ||
|
|
ebeb7f8578 | ||
|
|
f790673068 | ||
|
|
335ab5ab54 | ||
|
|
00e0d6ce2c | ||
|
|
414fb6d303 | ||
|
|
9c35a12211 | ||
|
|
bb4fe5174f | ||
|
|
3ffd6ff5a2 | ||
|
|
b05feb5bf7 | ||
|
|
fa171f237d | ||
|
|
f2ccb684eb | ||
|
|
ffea6522ac | ||
|
|
3d40a7df00 | ||
|
|
638c41476b | ||
|
|
c6d3088374 | ||
|
|
0f93be9dd4 | ||
|
|
f59982c9c5 | ||
|
|
dff67a5e54 | ||
|
|
6adcc3b2ed | ||
|
|
877ed3663c | ||
|
|
6000a82917 | ||
|
|
6805f9b3e0 | ||
|
|
1366c785f9 | ||
|
|
70540b4500 | ||
|
|
0967f23b6e | ||
|
|
1f7d66169c | ||
|
|
af501f5eeb | ||
|
|
60be60c923 | ||
|
|
48746101e0 | ||
|
|
af9c5c6ab7 |
40
README.md
40
README.md
@@ -12,15 +12,17 @@
|
||||
> Lead Maintainer: [syuilo][syuilo-link]
|
||||
|
||||
**[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>
|
||||
|
||||

|
||||
|
||||
:sparkles: Features
|
||||
----------------------------------------------------------------
|
||||
* Reactions
|
||||
* User lists
|
||||
* Cusromizable column view (known as MisskeyDeck)
|
||||
* Customizable column view (known as MisskeyDeck)
|
||||
* Private messages
|
||||
* Mute
|
||||
* Streaming
|
||||
@@ -46,18 +48,9 @@ If you want to...
|
||||
[![Backers][backers-image]][support-url]
|
||||
[![Sponsors][sponsors-image]][support-url]
|
||||
|
||||
:mortar_board: Notable contributors
|
||||
----------------------------------------------------------------
|
||||
| ![syuilo][syuilo-icon] | ![Morisawa Aya][ayamorisawa-icon] | ![otofune][otofune-icon] | ![akihikodaki][akihikodaki-icon] | ![tamaina][tamaina-icon] | ![rinsuki][rinsuki-icon] |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| [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 |
|
||||
| ![][ooo-icon] |
|
||||
|:-:|
|
||||
| [ooo][ooo-link] |
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
----------------------------------------------------------------
|
||||
@@ -85,23 +78,8 @@ Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
|
||||
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
|
||||
[support-url]: https://opencollective.com/misskey#support
|
||||
|
||||
<!-- Contributors Info -->
|
||||
[syuilo-link]: https://syuilo.com
|
||||
[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
|
||||
[mirro-san-icon]: https://avatars1.githubusercontent.com/u/17948612?s=70&v=4
|
||||
[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
|
||||
[ooo-link]: https://www.patreon.com/user/creators?u=11601413
|
||||
[ooo-icon]: https://c10.patreonusercontent.com/3/eyJ2IjoiMSIsInciOjIwMH0%3D/patreon-media/user/11601413/20cb15f209924302b399b99d3c98b850?token-time=2145916800&token-hash=IO31nK6VZCMWBWU2VAk2c824BX2QZ4DNPKyHHZXS0iw%3D
|
||||
|
||||
12
cli/update-remote-user.js
Normal file
12
cli/update-remote-user.js
Normal 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);
|
||||
});
|
||||
108
locales/de.yml
108
locales/de.yml
@@ -70,19 +70,19 @@ common:
|
||||
nav: "Navigation"
|
||||
tips: "Tipps"
|
||||
deck:
|
||||
widgets: "ウィジェット"
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
global: "グローバル"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
swap-right: "右に移動"
|
||||
swap-up: "上に移動"
|
||||
swap-down: "下に移動"
|
||||
remove: "カラムを削除"
|
||||
add-column: "カラムを追加"
|
||||
rename: "名前を変更"
|
||||
widgets: "Widget hinzufügen:"
|
||||
home: "Startseite"
|
||||
local: "Lokal"
|
||||
global: "Global"
|
||||
notifications: "Mitteilungen"
|
||||
list: "Listen"
|
||||
swap-left: "Nach links"
|
||||
swap-right: "Nach rechts"
|
||||
swap-up: "Nach oben"
|
||||
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:
|
||||
@@ -203,7 +203,7 @@ common/views/components/uploader.vue:
|
||||
common/views/components/visibility-chooser.vue:
|
||||
public: "Öffentlich"
|
||||
home: "Home"
|
||||
home-desc: "ホームタイムラインにのみ公開"
|
||||
home-desc: "Nur auf die Startseite posten"
|
||||
followers: "Folgende"
|
||||
followers-desc: "Nur für diejenigen sichtbar, die dir folgen"
|
||||
specified: "Direkt"
|
||||
@@ -264,7 +264,7 @@ desktop/views/components/drive.file.vue:
|
||||
rename: "Umbenennen"
|
||||
copy-url: "URL kopieren"
|
||||
download: "Download"
|
||||
else-files: "その他..."
|
||||
else-files: "Anderes…"
|
||||
set-as-avatar: "Als Avatar festlegen"
|
||||
set-as-banner: "Setze als Banner"
|
||||
open-in-app: "In der App öffnen"
|
||||
@@ -306,10 +306,10 @@ desktop/views/components/drive.vue:
|
||||
upload: "Eine Datei hochladen"
|
||||
url-upload: "Von einer URL hochladen"
|
||||
desktop/views/components/follow-button.vue:
|
||||
following: "フォロー中"
|
||||
follow: "フォロー"
|
||||
request-pending: "フォロー許可待ち"
|
||||
follow-request: "フォロー申請"
|
||||
following: "Folge ich"
|
||||
follow: "Folgen"
|
||||
request-pending: "Ausstehend"
|
||||
follow-request: "Follower-Anfragen"
|
||||
desktop/views/components/followers-window.vue:
|
||||
followers: "{} のフォロワー"
|
||||
desktop/views/components/followers.vue:
|
||||
@@ -317,18 +317,18 @@ desktop/views/components/followers.vue:
|
||||
desktop/views/components/following-window.vue:
|
||||
following: "{} のフォロー"
|
||||
desktop/views/components/following.vue:
|
||||
empty: "フォロー中のユーザーはいないようです。"
|
||||
empty: "Du folgst niemanden"
|
||||
desktop/views/components/friends-maker.vue:
|
||||
title: "気になるユーザーをフォロー:"
|
||||
empty: "おすすめのユーザーは見つかりませんでした。"
|
||||
fetching: "読み込んでいます"
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
title: "Wem folgen?"
|
||||
empty: "Der ausgewählte Benutzer konnte nicht gefunden werden."
|
||||
fetching: "Lade…"
|
||||
refresh: "Mehr"
|
||||
close: "Schließen"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
done: "Verbunden"
|
||||
add-widget: "Widget hinzufügen:"
|
||||
add: "Hinzufügen"
|
||||
desktop/views/input-dialog.vue:
|
||||
cancel: "Abbrechen"
|
||||
@@ -339,9 +339,9 @@ desktop/views/components/messaging-window.vue:
|
||||
title: "Nachrichten"
|
||||
desktop/views/components/note-detail.vue:
|
||||
more: "Lade weitere Konversationen"
|
||||
private: "この投稿は非公開です"
|
||||
deleted: "この投稿は削除されました"
|
||||
reposted-by: "{}がRenote"
|
||||
private: "Dieser Post ist privat"
|
||||
deleted: "Dieser Beitrag wurde entfernt"
|
||||
reposted-by: "Repostet von {}"
|
||||
location: "Ort"
|
||||
renote: "Anmerkung"
|
||||
add-reaction: "Reaktion hinzufügen"
|
||||
@@ -351,8 +351,8 @@ desktop/views/components/notes.note.vue:
|
||||
renote: "Anmerken"
|
||||
add-reaction: "Eine Reaktion hinzufügen"
|
||||
detail: "Zeige Details"
|
||||
private: "この投稿は非公開です"
|
||||
deleted: "この投稿は削除されました"
|
||||
private: "Dieser Beitrag ist eine privat"
|
||||
deleted: "Dieser Beitrag wurde entfernt"
|
||||
desktop/views/components/notes.vue:
|
||||
error: "Laden fehlgeschlagen."
|
||||
retry: "Erneut versuchen"
|
||||
@@ -393,34 +393,34 @@ desktop/views/components/renote-form.vue:
|
||||
success: "Weitergesagt!"
|
||||
failure: "Weitersagen fehlgeschlagen"
|
||||
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:
|
||||
settings: "設定"
|
||||
settings: "Experimentelles"
|
||||
desktop/views/components/settings.vue:
|
||||
profile: "プロフィール"
|
||||
notification: "通知"
|
||||
apps: "アプリ"
|
||||
mute: "ミュート"
|
||||
drive: "ドライブ"
|
||||
notification: "Mitteilungen"
|
||||
apps: "In App öffnen"
|
||||
mute: "Stummschalten"
|
||||
drive: "Dateien vom Drive anfügen"
|
||||
security: "セキュリティ"
|
||||
signin: "サインイン履歴"
|
||||
password: "パスワード"
|
||||
2fa: "二段階認証"
|
||||
other: "その他"
|
||||
license: "ライセンス"
|
||||
behaviour: "動作"
|
||||
behaviour: "Verhalten"
|
||||
fetch-on-scroll: "スクロールで自動読み込み"
|
||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||
auto-popout: "ウィンドウの自動ポップアウト"
|
||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||
advanced: "詳細設定"
|
||||
api-via-stream: "ストリームを経由したAPIリクエスト"
|
||||
advanced: "Erweiterte Einstellungen"
|
||||
api-via-stream: "API-Anfrage via stream"
|
||||
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
|
||||
display: "デザインと表示"
|
||||
customize: "ホームをカスタマイズ"
|
||||
dark-mode: "ダークモード"
|
||||
circle-icons: "円形のアイコンを使用"
|
||||
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
|
||||
display: "Erscheinungsbild und Anzeige"
|
||||
customize: "Startseite anpassen"
|
||||
dark-mode: "Nacht Modus"
|
||||
circle-icons: "Kreisförmige Icons"
|
||||
gradient-window-header: "Übergang in Fensterköpfen"
|
||||
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
|
||||
show-reply-target: "リプライ先を表示する"
|
||||
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
|
||||
@@ -432,12 +432,12 @@ desktop/views/components/settings.vue:
|
||||
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
|
||||
volume: "ボリューム"
|
||||
test: "テスト"
|
||||
mobile: "モバイル"
|
||||
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
||||
language: "言語"
|
||||
pick-language: "言語を選択"
|
||||
recommended: "推奨"
|
||||
auto: "自動"
|
||||
mobile: "Mobil"
|
||||
disable-via-mobile: "Diesen Beitrag nicht mit 'vom Handy' absenden"
|
||||
language: "Sprache"
|
||||
pick-language: "Sprache auswählen"
|
||||
recommended: "Empfohlen"
|
||||
auto: "Automatisch"
|
||||
specify-language: "言語を指定"
|
||||
language-desc: "変更はページの再度読み込み後に反映されます。"
|
||||
cache: "キャッシュ"
|
||||
@@ -563,7 +563,7 @@ desktop/views/components/users-list.vue:
|
||||
all: "すべて"
|
||||
iknow: "知り合い"
|
||||
load-more: "もっと"
|
||||
fetching: "読み込んでいます"
|
||||
fetching: "Lade…"
|
||||
desktop/views/components/users-list-item.vue:
|
||||
followed: "フォローされています"
|
||||
desktop/views/components/window.vue:
|
||||
@@ -685,7 +685,7 @@ mobile/views/components/follow-button.vue:
|
||||
mobile/views/components/friends-maker.vue:
|
||||
title: "気になるユーザーをフォロー"
|
||||
empty: "おすすめのユーザーは見つかりませんでした。"
|
||||
fetching: "読み込んでいます"
|
||||
fetching: "Lade…"
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
mobile/views/components/note.vue:
|
||||
|
||||
@@ -5,7 +5,7 @@ meta:
|
||||
common:
|
||||
misskey: "Une planète du fédiverse"
|
||||
about-title: "Une ⭐ du 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:
|
||||
unknown: "inconnu"
|
||||
future: "future"
|
||||
@@ -24,7 +24,7 @@ common:
|
||||
wednesday: "M"
|
||||
thursday: "J"
|
||||
friday: "V"
|
||||
saturday: "土"
|
||||
saturday: "S"
|
||||
reactions:
|
||||
like: "Aime"
|
||||
love: "Adore"
|
||||
@@ -78,13 +78,13 @@ common:
|
||||
list: "Liste"
|
||||
swap-left: "Déplacer à gauche"
|
||||
swap-right: "Déplacer à droite"
|
||||
swap-up: "上に移動"
|
||||
swap-down: "下に移動"
|
||||
swap-up: "Vers le haut"
|
||||
swap-down: "Vers le bas"
|
||||
remove: "Supprimer"
|
||||
add-column: "Ajouter une colonne"
|
||||
rename: "Renommer"
|
||||
stack-left: "左に重ねる"
|
||||
pop-right: "右に出す"
|
||||
stack-left: "Vers la gauche"
|
||||
pop-right: "Vers la droite"
|
||||
common/views/components/connect-failed.vue:
|
||||
title: "Impossible de se connecter au server."
|
||||
description: "Il y a soit un problème avec votre connexion internet, soit le serveur est hors-ligne ou en maintenance. Veuillez {ressayer} plus tard."
|
||||
@@ -308,7 +308,7 @@ desktop/views/components/drive.vue:
|
||||
desktop/views/components/follow-button.vue:
|
||||
following: "Abonnements"
|
||||
follow: "Suivre"
|
||||
request-pending: "フォロー許可待ち"
|
||||
request-pending: "En attente d'approbation"
|
||||
follow-request: "Demande d'abonnement"
|
||||
desktop/views/components/followers-window.vue:
|
||||
followers: "{} abonnés"
|
||||
@@ -320,7 +320,7 @@ desktop/views/components/following.vue:
|
||||
empty: "Vous ne suivez aucun compte."
|
||||
desktop/views/components/friends-maker.vue:
|
||||
title: "Utilisateurs recommandés :"
|
||||
empty: "おすすめのユーザーは見つかりませんでした。"
|
||||
empty: "Impossible de trouver des utilisateurs à recommander."
|
||||
fetching: "Chargement"
|
||||
refresh: "Plus"
|
||||
close: "Fermer"
|
||||
@@ -411,7 +411,7 @@ desktop/views/components/settings.vue:
|
||||
behaviour: "Comportement"
|
||||
fetch-on-scroll: "Chargement lors du défilement"
|
||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||
auto-popout: "ウィンドウの自動ポップアウト"
|
||||
auto-popout: "Fenêtre contextuelle automatique"
|
||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||
advanced: "Paramètres avancés"
|
||||
api-via-stream: "Requête API via le flux"
|
||||
@@ -810,7 +810,7 @@ mobile/views/pages/selectdrive.vue:
|
||||
mobile/views/pages/settings.vue:
|
||||
signed-in-as: "Connecté en tant que {}"
|
||||
lang: "Langue"
|
||||
lang-tip: "変更はページの再読み込み後に反映されます。"
|
||||
lang-tip: "Le rechargement de la page est requis afin d'appliquer les modifications."
|
||||
recommended: "Recommandé"
|
||||
auto: "Automatique"
|
||||
specify-language: "Spécifier la langue"
|
||||
|
||||
@@ -63,6 +63,7 @@ common:
|
||||
memo: "メモ"
|
||||
trends: "トレンド"
|
||||
photo-stream: "フォトストリーム"
|
||||
posts-monitor: "投稿チャート"
|
||||
slideshow: "スライドショー"
|
||||
version: "バージョン"
|
||||
broadcast: "ブロードキャスト"
|
||||
@@ -249,6 +250,10 @@ common/views/widgets/photo-stream.vue:
|
||||
title: "フォトストリーム"
|
||||
no-photos: "写真はありません"
|
||||
|
||||
common/views/widgets/posts-monitor.vue:
|
||||
title: "投稿チャート"
|
||||
toggle: "表示を切り替え"
|
||||
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
|
||||
@@ -78,8 +78,8 @@ common:
|
||||
list: "Listy"
|
||||
swap-left: "Przesuń w lewo"
|
||||
swap-right: "Przesuń w prawo"
|
||||
swap-up: "上に移動"
|
||||
swap-down: "下に移動"
|
||||
swap-up: "Przenieś w górę"
|
||||
swap-down: "Przenieś w dół"
|
||||
remove: "Usuń"
|
||||
add-column: "Dodaj kolumnę"
|
||||
rename: "Zmień nazwę"
|
||||
@@ -539,7 +539,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
dark: "Sprowadź ciemność"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
home: "Strona główna"
|
||||
deck: "デッキ"
|
||||
deck: "Talia"
|
||||
messaging: "Wiadomości"
|
||||
game: "Gra"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "2.30.0",
|
||||
"clientVersion": "1.0.6253",
|
||||
"version": "2.34.0",
|
||||
"clientVersion": "1.0.6302",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@@ -152,7 +152,7 @@
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
"mongodb": "3.0.8",
|
||||
"mongodb": "3.0.10",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nan": "2.10.0",
|
||||
@@ -218,6 +218,6 @@
|
||||
"webpack-cli": "2.1.4",
|
||||
"websocket": "1.0.26",
|
||||
"ws": "5.2.0",
|
||||
"xev": "2.0.0"
|
||||
"xev": "2.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import StreamManager from './stream-manager';
|
||||
import MiOS from '../../../mios';
|
||||
|
||||
/**
|
||||
* Server stream connection
|
||||
* Notes stats stream connection
|
||||
*/
|
||||
export class ServerStream extends Stream {
|
||||
export class NotesStatsStream extends Stream {
|
||||
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;
|
||||
|
||||
constructor(os: MiOS) {
|
||||
@@ -22,7 +22,7 @@ export class ServerStreamManager extends StreamManager<ServerStream> {
|
||||
|
||||
public getConnection() {
|
||||
if (this.connection == null) {
|
||||
this.connection = new ServerStream(this.os);
|
||||
this.connection = new NotesStatsStream(this.os);
|
||||
}
|
||||
|
||||
return this.connection;
|
||||
30
src/client/app/common/scripts/streaming/server-stats.ts
Normal file
30
src/client/app/common/scripts/streaming/server-stats.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||
|
||||
import analogClock from './analog-clock.vue';
|
||||
import menu from './menu.vue';
|
||||
import noteHeader from './note-header.vue';
|
||||
import signin from './signin.vue';
|
||||
import signup from './signup.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-menu', menu);
|
||||
Vue.component('mk-note-header', noteHeader);
|
||||
Vue.component('mk-signin', signin);
|
||||
Vue.component('mk-signup', signup);
|
||||
Vue.component('mk-forkit', forkit);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="popover" :class="{ hukidasi }" ref="popover">
|
||||
<template v-for="item in items">
|
||||
<div v-if="item === null"></div>
|
||||
<button v-if="item" @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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
117
src/client/app/common/views/components/note-header.vue
Normal file
117
src/client/app/common/views/components/note-header.vue
Normal 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>
|
||||
@@ -13,23 +13,23 @@ export default Vue.extend({
|
||||
items() {
|
||||
const items = [];
|
||||
items.push({
|
||||
content: '%i18n:@favorite%',
|
||||
onClick: this.favorite
|
||||
text: '%i18n:@favorite%',
|
||||
action: this.favorite
|
||||
});
|
||||
if (this.note.userId == this.$store.state.i.id) {
|
||||
items.push({
|
||||
content: '%i18n:@pin%',
|
||||
onClick: this.pin
|
||||
text: '%i18n:@pin%',
|
||||
action: this.pin
|
||||
});
|
||||
items.push({
|
||||
content: '%i18n:@delete%',
|
||||
onClick: this.del
|
||||
text: '%i18n:@delete%',
|
||||
action: this.del
|
||||
});
|
||||
}
|
||||
if (this.note.uri) {
|
||||
items.push({
|
||||
content: '%i18n:@remote%',
|
||||
onClick: () => {
|
||||
text: '%i18n:@remote%',
|
||||
action: () => {
|
||||
window.open(this.note.uri, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import wAnalogClock from './analog-clock.vue';
|
||||
import wVersion from './version.vue';
|
||||
import wRss from './rss.vue';
|
||||
import wServer from './server.vue';
|
||||
import wPostsMonitor from './posts-monitor.vue';
|
||||
import wMemo from './memo.vue';
|
||||
import wBroadcast from './broadcast.vue';
|
||||
import wCalendar from './calendar.vue';
|
||||
@@ -22,6 +23,7 @@ Vue.component('mkw-tips', wTips);
|
||||
Vue.component('mkw-donation', wDonation);
|
||||
Vue.component('mkw-broadcast', wBroadcast);
|
||||
Vue.component('mkw-server', wServer);
|
||||
Vue.component('mkw-posts-monitor', wPostsMonitor);
|
||||
Vue.component('mkw-memo', wMemo);
|
||||
Vue.component('mkw-rss', wRss);
|
||||
Vue.component('mkw-version', wVersion);
|
||||
|
||||
182
src/client/app/common/views/widgets/posts-monitor.vue
Normal file
182
src/client/app/common/views/widgets/posts-monitor.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<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 }`" preserveAspectRatio="none" v-show="props.view != 2">
|
||||
<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">
|
||||
<polyline
|
||||
:points="fediPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
||||
:style="`stroke: none; fill: url(#${ fediGradientId }); mask: url(#${ fediMaskId })`"/>
|
||||
<text x="1" y="5">Fedi</text>
|
||||
</svg>
|
||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" v-show="props.view != 1">
|
||||
<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">
|
||||
<polyline
|
||||
:points="localPolylinePoints"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
stroke-width="1"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="-1" y="-1"
|
||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
||||
:style="`stroke: none; fill: url(#${ localGradientId }); mask: url(#${ localMaskId })`"/>
|
||||
<text x="1" y="5">Local</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: ''
|
||||
};
|
||||
},
|
||||
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(0, 50) : this.stats;
|
||||
const fediPeak = Math.max.apply(null, this.stats.map(x => x.all)) || 1;
|
||||
const localPeak = Math.max.apply(null, this.stats.map(x => x.local)) || 1;
|
||||
|
||||
this.fediPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.all / fediPeak)) * this.viewBoxY}`).join(' ');
|
||||
this.localPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.local / localPeak)) * this.viewBoxY}`).join(' ');
|
||||
|
||||
this.fediPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.fediPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
this.localPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.localPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||
},
|
||||
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>
|
||||
@@ -76,9 +76,15 @@ export default Vue.extend({
|
||||
},
|
||||
mounted() {
|
||||
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);
|
||||
},
|
||||
methods: {
|
||||
onStats(stats) {
|
||||
@@ -94,6 +100,9 @@ export default Vue.extend({
|
||||
|
||||
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
|
||||
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
|
||||
},
|
||||
onStatsLog(statsLog) {
|
||||
statsLog.forEach(stats => this.onStats(stats));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,11 +55,11 @@ export default define({
|
||||
this.fetching = false;
|
||||
});
|
||||
|
||||
this.connection = (this as any).os.streams.serverStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.serverStream.use();
|
||||
this.connection = (this as any).os.streams.serverStatsStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.serverStatsStream.use();
|
||||
},
|
||||
beforeDestroy() {
|
||||
(this as any).os.streams.serverStream.dispose(this.connectionId);
|
||||
(this as any).os.streams.serverStatsStream.dispose(this.connectionId);
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<ul class="menu">
|
||||
<li v-for="(item, i) in menu" :class="item.type">
|
||||
<template v-if="item.type == 'item'">
|
||||
<p @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</p>
|
||||
</template>
|
||||
<template v-if="item.type == 'link'">
|
||||
<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>
|
||||
<template v-else-if="item.type == 'nest'">
|
||||
<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"/>
|
||||
<li v-for="(item, i) in menu" :class="item ? item.type : item === null ? 'divider' : null">
|
||||
<template v-if="item">
|
||||
<template v-if="item.type == null || item.type == 'item'">
|
||||
<p @click="click(item)"><span :class="$style.icon" v-if="item.icon" v-html="item.icon"></span>{{ item.text }}</p>
|
||||
</template>
|
||||
<template v-else-if="item.type == 'link'">
|
||||
<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>
|
||||
<template v-else-if="item.type == 'nest'">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -17,6 +17,23 @@ export default Vue.extend({
|
||||
props: ['x', 'y', 'menu'],
|
||||
mounted() {
|
||||
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 => {
|
||||
el.addEventListener('mousedown', this.onMousedown);
|
||||
});
|
||||
@@ -38,7 +55,7 @@ export default Vue.extend({
|
||||
return false;
|
||||
},
|
||||
click(item) {
|
||||
if (item.onClick) item.onClick();
|
||||
if (item.action) item.action();
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
@@ -59,7 +76,6 @@ root(isDark)
|
||||
$item-height = 38px
|
||||
$padding = 10px
|
||||
|
||||
display none
|
||||
position fixed
|
||||
top 0
|
||||
left 0
|
||||
|
||||
@@ -66,37 +66,33 @@ export default Vue.extend({
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.rename%',
|
||||
icon: '%fa:i-cursor%',
|
||||
onClick: this.rename
|
||||
action: this.rename
|
||||
}, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.copy-url%',
|
||||
icon: '%fa:link%',
|
||||
onClick: this.copyUrl
|
||||
action: this.copyUrl
|
||||
}, {
|
||||
type: 'link',
|
||||
href: `${this.file.url}?download`,
|
||||
text: '%i18n:@contextmenu.download%',
|
||||
icon: '%fa:download%',
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, {
|
||||
}, null, {
|
||||
type: 'item',
|
||||
text: '%i18n:common.delete%',
|
||||
icon: '%fa:R trash-alt%',
|
||||
onClick: this.deleteFile
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, {
|
||||
action: this.deleteFile
|
||||
}, null, {
|
||||
type: 'nest',
|
||||
text: '%i18n:@contextmenu.else-files%',
|
||||
menu: [{
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.set-as-avatar%',
|
||||
onClick: this.setAsAvatar
|
||||
action: this.setAsAvatar
|
||||
}, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.set-as-banner%',
|
||||
onClick: this.setAsBanner
|
||||
action: this.setAsBanner
|
||||
}]
|
||||
}, {
|
||||
type: 'nest',
|
||||
@@ -104,7 +100,7 @@ export default Vue.extend({
|
||||
menu: [{
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.add-app%...',
|
||||
onClick: this.addApp
|
||||
action: this.addApp
|
||||
}]
|
||||
}], {
|
||||
closed: () => {
|
||||
|
||||
@@ -56,26 +56,22 @@ export default Vue.extend({
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.move-to-this-folder%',
|
||||
icon: '%fa:arrow-right%',
|
||||
onClick: this.go
|
||||
action: this.go
|
||||
}, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.show-in-new-window%',
|
||||
icon: '%fa:R window-restore%',
|
||||
onClick: this.newWindow
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, {
|
||||
action: this.newWindow
|
||||
}, null, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.rename%',
|
||||
icon: '%fa:i-cursor%',
|
||||
onClick: this.rename
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, {
|
||||
action: this.rename
|
||||
}, null, {
|
||||
type: 'item',
|
||||
text: '%i18n:common.delete%',
|
||||
icon: '%fa:R trash-alt%',
|
||||
onClick: this.deleteFolder
|
||||
action: this.deleteFolder
|
||||
}], {
|
||||
closed: () => {
|
||||
this.isContextmenuShowing = false;
|
||||
|
||||
@@ -140,17 +140,17 @@ export default Vue.extend({
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.create-folder%',
|
||||
icon: '%fa:R folder%',
|
||||
onClick: this.createFolder
|
||||
action: this.createFolder
|
||||
}, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.upload%',
|
||||
icon: '%fa:upload%',
|
||||
onClick: this.selectLocalFile
|
||||
action: this.selectLocalFile
|
||||
}, {
|
||||
type: 'item',
|
||||
text: '%i18n:@contextmenu.url-upload%',
|
||||
icon: '%fa:cloud-upload-alt%',
|
||||
onClick: this.urlUpload
|
||||
action: this.urlUpload
|
||||
}]);
|
||||
},
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<option value="post-form">%i18n:common.widgets.post-form%</option>
|
||||
<option value="messaging">%i18n:common.widgets.messaging%</option>
|
||||
<option value="memo">%i18n:common.widgets.memo%</option>
|
||||
<option value="posts-monitor">%i18n:common.widgets.posts-monitor%</option>
|
||||
<option value="server">%i18n:common.widgets.server%</option>
|
||||
<option value="donation">%i18n:common.widgets.donation%</option>
|
||||
<option value="nav">%i18n:common.widgets.nav%</option>
|
||||
|
||||
@@ -2,22 +2,7 @@
|
||||
<div class="mk-note-preview" :title="title">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
@@ -56,43 +41,6 @@ root(isDark)
|
||||
flex 1
|
||||
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
|
||||
|
||||
> .text
|
||||
|
||||
@@ -2,25 +2,7 @@
|
||||
<div class="sub" :title="title">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="note"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
@@ -62,57 +44,8 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
> .header
|
||||
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
|
||||
|
||||
|
||||
@@ -14,26 +14,7 @@
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="p"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
@@ -409,64 +390,8 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
> .header
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-ui" :style="style">
|
||||
<x-header class="header"/>
|
||||
<x-header class="header" v-show="!zenMode"/>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@@ -16,6 +16,11 @@ export default Vue.extend({
|
||||
components: {
|
||||
XHeader
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zenMode: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
style(): any {
|
||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
||||
@@ -39,6 +44,11 @@ export default Vue.extend({
|
||||
e.preventDefault();
|
||||
(this as any).apis.post();
|
||||
}
|
||||
|
||||
if (e.which == 90) { // z
|
||||
e.preventDefault();
|
||||
this.zenMode = !this.zenMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<x-widgets-column v-if="column.type == 'widgets'"/>
|
||||
<x-notifications-column v-else-if="column.type == 'notifications'"/>
|
||||
<x-tl-column v-else-if="column.type == 'home'"/>
|
||||
<x-tl-column v-else-if="column.type == 'local'"/>
|
||||
<x-tl-column v-else-if="column.type == 'global'"/>
|
||||
<x-tl-column v-else-if="column.type == 'list'"/>
|
||||
<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">
|
||||
@@ -29,20 +29,7 @@ export default Vue.extend({
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
column: this.column,
|
||||
isStacked: this.isStacked,
|
||||
isActive: this.isActive
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
<template>
|
||||
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, isActive, isStacked }">
|
||||
<header :class="{ indicate: count > 0 }" @click="toggleActive">
|
||||
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, active, isStacked, draghover, dragging, dropready }"
|
||||
@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>
|
||||
<span class="count" v-if="count > 0">({{ count }})</span>
|
||||
<button ref="menu" @click.stop="showMenu">%fa:caret-down%</button>
|
||||
</header>
|
||||
<div ref="body" v-show="isActive">
|
||||
<div ref="body" v-show="active">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -14,9 +25,18 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Menu from '../../../../common/views/components/menu.vue';
|
||||
import contextmenu from '../../../api/contextmenu';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
column: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isStacked: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: false
|
||||
@@ -39,24 +59,27 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
inject: {
|
||||
column: { from: 'column' },
|
||||
_isActive: { from: 'isActive' },
|
||||
isStacked: { from: 'isStacked' },
|
||||
getColumnVm: { from: 'getColumnVm' }
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
isActive: this._isActive
|
||||
active: true,
|
||||
dragging: false,
|
||||
draghover: false,
|
||||
dropready: false
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
isActive(v) {
|
||||
active(v) {
|
||||
if (v && this.isScrollTop()) {
|
||||
this.$emit('top');
|
||||
}
|
||||
},
|
||||
dragging(v) {
|
||||
this.$root.$emit(v ? 'deck.column.dragStart' : 'deck.column.dragEnd');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,21 +93,34 @@ export default Vue.extend({
|
||||
|
||||
mounted() {
|
||||
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() {
|
||||
this.$refs.body.removeEventListener('scroll', this.onScroll);
|
||||
this.$root.$off('deck.column.dragStart', this.onOtherDragStart);
|
||||
this.$root.$off('deck.column.dragEnd', this.onOtherDragEnd);
|
||||
},
|
||||
|
||||
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.isActive && vms.filter(vm => vm.$el.classList.contains('isActive')).length == 1) return;
|
||||
this.isActive = !this.isActive;
|
||||
if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return;
|
||||
this.active = !this.active;
|
||||
},
|
||||
|
||||
isScrollTop() {
|
||||
return this.isActive && this.$refs.body.scrollTop == 0;
|
||||
return this.active && this.$refs.body.scrollTop == 0;
|
||||
},
|
||||
|
||||
onScroll() {
|
||||
@@ -98,10 +134,11 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
showMenu() {
|
||||
getMenu() {
|
||||
const items = [{
|
||||
content: '%fa:pencil-alt% %i18n:common.deck.rename%',
|
||||
onClick: () => {
|
||||
icon: '%fa:pencil-alt%',
|
||||
text: '%i18n:common.deck.rename%',
|
||||
action: () => {
|
||||
(this as any).apis.input({
|
||||
title: '%i18n:common.deck.rename%',
|
||||
default: this.name,
|
||||
@@ -111,38 +148,45 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
}, null, {
|
||||
content: '%fa:arrow-left% %i18n:common.deck.swap-left%',
|
||||
onClick: () => {
|
||||
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%',
|
||||
onClick: () => {
|
||||
icon: '%fa:arrow-right%',
|
||||
text: '%i18n:common.deck.swap-right%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/swapRightDeckColumn', this.column.id);
|
||||
}
|
||||
}, this.isStacked ? {
|
||||
content: '%fa:arrow-up% %i18n:common.deck.swap-up%',
|
||||
onClick: () => {
|
||||
icon: '%fa:arrow-up%',
|
||||
text: '%i18n:common.deck.swap-up%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/swapUpDeckColumn', this.column.id);
|
||||
}
|
||||
} : undefined, this.isStacked ? {
|
||||
content: '%fa:arrow-down% %i18n:common.deck.swap-down%',
|
||||
onClick: () => {
|
||||
icon: '%fa:arrow-down%',
|
||||
text: '%i18n:common.deck.swap-down%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/swapDownDeckColumn', this.column.id);
|
||||
}
|
||||
} : undefined, null, {
|
||||
content: '%fa:window-restore R% %i18n:common.deck.stack-left%',
|
||||
onClick: () => {
|
||||
icon: '%fa:window-restore R%',
|
||||
text: '%i18n:common.deck.stack-left%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/stackLeftDeckColumn', this.column.id);
|
||||
}
|
||||
}, this.isStacked ? {
|
||||
content: '%fa:window-maximize R% %i18n:common.deck.pop-right%',
|
||||
onClick: () => {
|
||||
icon: '%fa:window-maximize R%',
|
||||
text: '%i18n:common.deck.pop-right%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/popRightDeckColumn', this.column.id);
|
||||
}
|
||||
} : undefined, null, {
|
||||
content: '%fa:trash-alt R% %i18n:common.deck.remove%',
|
||||
onClick: () => {
|
||||
icon: '%fa:trash-alt R%',
|
||||
text: '%i18n:common.deck.remove%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/removeDeckColumn', this.column.id);
|
||||
}
|
||||
}];
|
||||
@@ -152,11 +196,63 @@ export default Vue.extend({
|
||||
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, {
|
||||
source: this.$refs.menu,
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -176,7 +272,17 @@ root(isDark)
|
||||
box-shadow 0 2px 16px rgba(#000, 0.1)
|
||||
overflow hidden
|
||||
|
||||
&:not(.isActive)
|
||||
&.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
|
||||
|
||||
@@ -203,10 +309,14 @@ root(isDark)
|
||||
color isDark ? #e3e5e8 : #888
|
||||
background isDark ? #313543 : #fff
|
||||
box-shadow 0 1px rgba(#000, 0.15)
|
||||
cursor pointer
|
||||
|
||||
&, *
|
||||
user-select none
|
||||
|
||||
*:not(button)
|
||||
pointer-events none
|
||||
|
||||
&.indicate
|
||||
box-shadow 0 3px 0 0 $theme-color
|
||||
|
||||
@@ -224,6 +334,7 @@ root(isDark)
|
||||
right 0
|
||||
width $header-height
|
||||
line-height $header-height
|
||||
font-size 16px
|
||||
color isDark ? #9baec8 : #ccc
|
||||
|
||||
&:hover
|
||||
|
||||
@@ -2,25 +2,7 @@
|
||||
<div class="fnlfosztlhtptnongximhlbykxblytcq">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
||||
<span class="is-admin" v-if="note.user.isAdmin">%i18n:@admin%</span>
|
||||
<span class="is-bot" v-if="note.user.isBot">%i18n:@bot%</span>
|
||||
<span class="is-cat" v-if="note.user.isCat">%i18n:@cat%</span>
|
||||
<span class="username"><mk-acct :user="note.user"/></span>
|
||||
<div class="info">
|
||||
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
|
||||
<router-link class="created-at" :to="note | notePage">
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</router-link>
|
||||
<span class="visibility" v-if="note.visibility != 'public'">
|
||||
<template v-if="note.visibility == 'home'">%fa:home%</template>
|
||||
<template v-if="note.visibility == 'followers'">%fa:unlock%</template>
|
||||
<template v-if="note.visibility == 'specified'">%fa:envelope%</template>
|
||||
<template v-if="note.visibility == 'private'">%fa:lock%</template>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
@@ -72,66 +54,8 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
> .header
|
||||
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
|
||||
|
||||
|
||||
@@ -14,25 +14,7 @@
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
|
||||
<span class="is-admin" v-if="p.user.isAdmin">admin</span>
|
||||
<span class="is-bot" v-if="p.user.isBot">bot</span>
|
||||
<span class="is-cat" v-if="p.user.isCat">cat</span>
|
||||
<span class="username"><mk-acct :user="p.user"/></span>
|
||||
<div class="info">
|
||||
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
||||
<router-link class="created-at" :to="p | notePage">
|
||||
<mk-time :time="p.createdAt"/>
|
||||
</router-link>
|
||||
<span class="visibility" v-if="p.visibility != 'public'">
|
||||
<template v-if="p.visibility == 'home'">%fa:home%</template>
|
||||
<template v-if="p.visibility == 'followers'">%fa:unlock%</template>
|
||||
<template v-if="p.visibility == 'specified'">%fa:envelope%</template>
|
||||
<template v-if="p.visibility == 'private'">%fa:lock%</template>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
@@ -234,7 +216,7 @@ root(isDark)
|
||||
> .renote
|
||||
display flex
|
||||
align-items center
|
||||
padding 8px 16px
|
||||
padding 8px 16px 0 16px
|
||||
line-height 28px
|
||||
white-space pre
|
||||
color #9dbb00
|
||||
@@ -292,62 +274,6 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
white-space nowrap
|
||||
|
||||
> .avatar
|
||||
flex-shrink 0
|
||||
margin-right 8px
|
||||
width 20px
|
||||
height 20px
|
||||
border-radius 100%
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0 0.5em 0 0
|
||||
padding 0
|
||||
overflow hidden
|
||||
color isDark ? #fff : #627079
|
||||
font-weight bold
|
||||
text-decoration none
|
||||
text-overflow ellipsis
|
||||
|
||||
> .is-admin
|
||||
> .is-bot
|
||||
> .is-cat
|
||||
align-self center
|
||||
margin 0 0.5em 0 0
|
||||
padding 1px 6px
|
||||
font-size 0.8em
|
||||
color isDark ? #758188 : #aaa
|
||||
border solid 1px isDark ? #57616f : #ddd
|
||||
border-radius 3px
|
||||
|
||||
&.is-admin
|
||||
border-color isDark ? #d42c41 : #f56a7b
|
||||
color isDark ? #d42c41 : #f56a7b
|
||||
|
||||
> .username
|
||||
margin 0 0.5em 0 0
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
color isDark ? #606984 : #ccc
|
||||
|
||||
> .info
|
||||
margin-left auto
|
||||
font-size 0.9em
|
||||
|
||||
> *
|
||||
color isDark ? #606984 : #c0c0c0
|
||||
|
||||
> .mobile
|
||||
margin-right 6px
|
||||
|
||||
> .visibility
|
||||
margin-left 6px
|
||||
|
||||
> .body
|
||||
|
||||
> .cw
|
||||
|
||||
@@ -31,7 +31,7 @@ import Vue from 'vue';
|
||||
|
||||
import XNote from './deck.note.vue';
|
||||
|
||||
const displayLimit = 30;
|
||||
const displayLimit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<x-column :name="name">
|
||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
|
||||
<span slot="header">%fa:bell R%{{ name }}</span>
|
||||
|
||||
<x-notifications/>
|
||||
@@ -17,7 +17,16 @@ export default Vue.extend({
|
||||
XNotifications
|
||||
},
|
||||
|
||||
inject: ['column'],
|
||||
props: {
|
||||
column: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isStacked: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
name(): string {
|
||||
|
||||
@@ -21,20 +21,27 @@
|
||||
import Vue from 'vue';
|
||||
import XNotification from './deck.notification.vue';
|
||||
|
||||
const displayLimit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XNotification
|
||||
},
|
||||
|
||||
inject: ['column', 'isScrollTop', 'count'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
fetchingMoreNotifications: false,
|
||||
notifications: [],
|
||||
queue: [],
|
||||
moreNotifications: false,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_notifications(): any[] {
|
||||
return (this.notifications as any).map(notification => {
|
||||
@@ -46,12 +53,22 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
queue(q) {
|
||||
this.count(q.length);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
|
||||
this.connection.on('notification', this.onNotification);
|
||||
|
||||
this.column.$on('top', this.onTop);
|
||||
this.column.$on('bottom', this.onBottom);
|
||||
|
||||
const max = 10;
|
||||
|
||||
(this as any).api('i/notifications', {
|
||||
@@ -66,10 +83,15 @@ export default Vue.extend({
|
||||
this.fetching = false;
|
||||
});
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.connection.off('notification', this.onNotification);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
|
||||
this.column.$off('top', this.onTop);
|
||||
this.column.$off('bottom', this.onBottom);
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchMoreNotifications() {
|
||||
this.fetchingMoreNotifications = true;
|
||||
@@ -90,6 +112,7 @@ export default Vue.extend({
|
||||
this.fetchingMoreNotifications = false;
|
||||
});
|
||||
},
|
||||
|
||||
onNotification(notification) {
|
||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||
this.connection.send({
|
||||
@@ -97,7 +120,34 @@ export default Vue.extend({
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<x-column :menu="menu" :name="name">
|
||||
<x-column :menu="menu" :name="name" :column="column" :is-stacked="isStacked">
|
||||
<span slot="header">
|
||||
<template v-if="column.type == 'home'">%fa:home%</template>
|
||||
<template v-if="column.type == 'local'">%fa:R comments%</template>
|
||||
@@ -30,14 +30,24 @@ export default Vue.extend({
|
||||
XListTl
|
||||
},
|
||||
|
||||
inject: ['column'],
|
||||
props: {
|
||||
column: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isStacked: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
edit: false,
|
||||
menu: [{
|
||||
content: '%fa:cog% %i18n:@edit%',
|
||||
onClick: () => {
|
||||
icon: '%fa:cog%',
|
||||
text: '%i18n:@edit%',
|
||||
action: () => {
|
||||
this.edit = !this.edit;
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<template v-for="ids in layout">
|
||||
<div v-if="ids.length > 1" class="folder">
|
||||
<template v-for="id, i in ids">
|
||||
<x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true" :is-active="i == 0"/>
|
||||
<x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true"/>
|
||||
</template>
|
||||
</div>
|
||||
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])"/>
|
||||
@@ -102,32 +102,36 @@ export default Vue.extend({
|
||||
source: this.$refs.add,
|
||||
compact: true,
|
||||
items: [{
|
||||
content: '%i18n:common.deck.home%',
|
||||
onClick: () => {
|
||||
icon: '%fa:home%',
|
||||
text: '%i18n:common.deck.home%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'home'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
content: '%i18n:common.deck.local%',
|
||||
onClick: () => {
|
||||
icon: '%fa:comments R%',
|
||||
text: '%i18n:common.deck.local%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'local'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
content: '%i18n:common.deck.global%',
|
||||
onClick: () => {
|
||||
icon: '%fa:globe%',
|
||||
text: '%i18n:common.deck.global%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'global'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
content: '%i18n:common.deck.list%',
|
||||
onClick: () => {
|
||||
icon: '%fa:list%',
|
||||
text: '%i18n:common.deck.list%',
|
||||
action: () => {
|
||||
const w = (this as any).os.new(MkUserListsWindow);
|
||||
w.$once('choosen', list => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
@@ -139,16 +143,18 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
}, {
|
||||
content: '%i18n:common.deck.notifications%',
|
||||
onClick: () => {
|
||||
icon: '%fa:bell R%',
|
||||
text: '%i18n:common.deck.notifications%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'notifications'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
content: '%i18n:common.deck.widgets%',
|
||||
onClick: () => {
|
||||
icon: '%fa:calculator%',
|
||||
text: '%i18n:common.deck.widgets%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'widgets',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<x-column :menu="menu" :naked="true" :narrow="true" :name="name" class="wtdtxvecapixsepjtcupubtsmometobz">
|
||||
<x-column :menu="menu" :naked="true" :narrow="true" :name="name" :column="column" :is-stacked="isStacked" class="wtdtxvecapixsepjtcupubtsmometobz">
|
||||
<span slot="header">%fa:calculator%{{ name }}</span>
|
||||
|
||||
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
|
||||
@@ -23,6 +23,7 @@
|
||||
<option value="post-form">%i18n:common.widgets.post-form%</option>
|
||||
<option value="messaging">%i18n:common.widgets.messaging%</option>
|
||||
<option value="memo">%i18n:common.widgets.memo%</option>
|
||||
<option value="posts-monitor">%i18n:common.widgets.posts-monitor%</option>
|
||||
<option value="server">%i18n:common.widgets.server%</option>
|
||||
<option value="donation">%i18n:common.widgets.donation%</option>
|
||||
<option value="nav">%i18n:common.widgets.nav%</option>
|
||||
@@ -64,7 +65,16 @@ export default Vue.extend({
|
||||
XDraggable
|
||||
},
|
||||
|
||||
inject: ['column'],
|
||||
props: {
|
||||
column: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isStacked: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@@ -83,8 +93,9 @@ export default Vue.extend({
|
||||
|
||||
created() {
|
||||
this.menu = [{
|
||||
content: '%fa:cog% %i18n:@edit%',
|
||||
onClick: () => {
|
||||
icon: '%fa:cog%',
|
||||
text: '%i18n:@edit%',
|
||||
action: () => {
|
||||
this.edit = !this.edit;
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -8,7 +8,8 @@ import Progress from './common/scripts/loading';
|
||||
import Connection from './common/scripts/streaming/stream';
|
||||
import { HomeStreamManager } from './common/scripts/streaming/home';
|
||||
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 { OthelloStreamManager } from './common/scripts/streaming/othello';
|
||||
|
||||
@@ -104,14 +105,16 @@ export default class MiOS extends EventEmitter {
|
||||
localTimelineStream: LocalTimelineStreamManager;
|
||||
globalTimelineStream: GlobalTimelineStreamManager;
|
||||
driveStream: DriveStreamManager;
|
||||
serverStream: ServerStreamManager;
|
||||
serverStatsStream: ServerStatsStreamManager;
|
||||
notesStatsStream: NotesStatsStreamManager;
|
||||
messagingIndexStream: MessagingIndexStreamManager;
|
||||
othelloStream: OthelloStreamManager;
|
||||
} = {
|
||||
localTimelineStream: null,
|
||||
globalTimelineStream: null,
|
||||
driveStream: null,
|
||||
serverStream: null,
|
||||
serverStatsStream: null,
|
||||
notesStatsStream: null,
|
||||
messagingIndexStream: null,
|
||||
othelloStream: null
|
||||
};
|
||||
@@ -218,7 +221,8 @@ export default class MiOS extends EventEmitter {
|
||||
this.store = initStore(this);
|
||||
|
||||
//#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', () => {
|
||||
// Init home stream manager
|
||||
|
||||
@@ -2,26 +2,7 @@
|
||||
<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'"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
@@ -79,64 +60,8 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
> .header
|
||||
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
|
||||
|
||||
|
||||
@@ -2,26 +2,7 @@
|
||||
<div class="sub" :class="{ smart: $store.state.device.postStyle == 'smart' }">
|
||||
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
@@ -92,66 +73,8 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
> .header
|
||||
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
|
||||
|
||||
|
||||
@@ -14,26 +14,7 @@
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<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>
|
||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
@@ -358,65 +339,10 @@ root(isDark)
|
||||
flex 1
|
||||
min-width 0
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items baseline
|
||||
white-space nowrap
|
||||
|
||||
> .header
|
||||
@media (min-width 500px)
|
||||
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
|
||||
@media (min-width 700px)
|
||||
font-size 1.1em
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<option value="rss">%i18n:common.widgets.rss%</option>
|
||||
<option value="photo-stream">%i18n:common.widgets.photo-stream%</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="server">%i18n:common.widgets.server%</option>
|
||||
<option value="memo">%i18n:common.widgets.memo%</option>
|
||||
|
||||
@@ -182,6 +182,17 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
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) {
|
||||
state.deck.layout.some((ids, i) => {
|
||||
if (ids.indexOf(id) != -1) {
|
||||
@@ -306,6 +317,11 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
ctx.dispatch('saveDeck');
|
||||
},
|
||||
|
||||
swapDeckColumn(ctx, id) {
|
||||
ctx.commit('swapDeckColumn', id);
|
||||
ctx.dispatch('saveDeck');
|
||||
},
|
||||
|
||||
swapLeftDeckColumn(ctx, id) {
|
||||
ctx.commit('swapLeftDeckColumn', id);
|
||||
ctx.dispatch('saveDeck');
|
||||
|
||||
@@ -12,7 +12,10 @@ const uri = u && p
|
||||
*/
|
||||
import mongo from 'monk';
|
||||
|
||||
const db = mongo(uri);
|
||||
const db = mongo(uri, {
|
||||
poolSize: 16,
|
||||
keepAlive: 1
|
||||
});
|
||||
|
||||
export default db;
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import ProgressBar from './utils/cli/progressbar';
|
||||
import EnvironmentInfo from './utils/environmentInfo';
|
||||
import MachineInfo from './utils/machineInfo';
|
||||
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 { Config } from './config/types';
|
||||
@@ -49,7 +50,8 @@ function main() {
|
||||
masterMain(opt);
|
||||
|
||||
ev.mount();
|
||||
stats();
|
||||
serverStats();
|
||||
notesStats();
|
||||
} else {
|
||||
workerMain(opt);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import Following from './following';
|
||||
const Note = db.get<INote>('notes');
|
||||
Note.createIndex('uri', { sparse: true, unique: true });
|
||||
Note.createIndex('userId');
|
||||
Note.createIndex({
|
||||
createdAt: -1
|
||||
});
|
||||
export default Note;
|
||||
|
||||
export function isValidText(text: string): boolean {
|
||||
|
||||
20
src/notes-stats-child.ts
Normal file
20
src/notes-stats-child.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import Note from './models/note';
|
||||
|
||||
setInterval(async () => {
|
||||
const [all, local] = await Promise.all([Note.count({
|
||||
createdAt: {
|
||||
$gte: new Date(Date.now() - 3000)
|
||||
}
|
||||
}), Note.count({
|
||||
createdAt: {
|
||||
$gte: new Date(Date.now() - 3000)
|
||||
},
|
||||
'_user.host': null
|
||||
})]);
|
||||
|
||||
const stats = {
|
||||
all, local
|
||||
};
|
||||
|
||||
process.send(stats);
|
||||
}, 3000);
|
||||
20
src/notes-stats.ts
Normal file
20
src/notes-stats.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -6,13 +6,19 @@ import Xev from 'xev';
|
||||
const ev = new Xev();
|
||||
|
||||
/**
|
||||
* Report stats regularly
|
||||
* Report server stats regularly
|
||||
*/
|
||||
export default function() {
|
||||
const log = [];
|
||||
|
||||
ev.on('requestServerStatsLog', id => {
|
||||
ev.emit('serverStatsLog:' + id, log);
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
osUtils.cpuUsage(cpuUsage => {
|
||||
const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/');
|
||||
ev.emit('stats', {
|
||||
const stats = {
|
||||
cpu_usage: cpuUsage,
|
||||
mem: {
|
||||
total: os.totalmem(),
|
||||
@@ -21,7 +27,10 @@ export default function() {
|
||||
disk,
|
||||
os_uptime: os.uptime(),
|
||||
process_uptime: process.uptime()
|
||||
});
|
||||
};
|
||||
ev.emit('serverStats', stats);
|
||||
log.push(stats);
|
||||
if (log.length > 50) log.shift();
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
@@ -140,7 +140,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
||||
}
|
||||
|
||||
// テキストが無いかつ添付ファイルが無いかつ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');
|
||||
}
|
||||
|
||||
|
||||
35
src/server/api/stream/notes-stats.ts
Normal file
35
src/server/api/stream/notes-stats.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
35
src/server/api/stream/server-stats.ts
Normal file
35
src/server/api/stream/server-stats.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import messagingStream from './stream/messaging';
|
||||
import messagingIndexStream from './stream/messaging-index';
|
||||
import othelloGameStream from './stream/othello-game';
|
||||
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 { ParsedUrlQuery } from 'querystring';
|
||||
import authenticate from './authenticate';
|
||||
@@ -28,8 +29,13 @@ module.exports = (server: http.Server) => {
|
||||
ws.on('request', async (request) => {
|
||||
const connection = request.accept();
|
||||
|
||||
if (request.resourceURL.pathname === '/server') {
|
||||
serverStream(request, connection);
|
||||
if (request.resourceURL.pathname === '/server-stats') {
|
||||
serverStatsStream(request, connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.resourceURL.pathname === '/notes-stats') {
|
||||
notesStatsStream(request, connection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ export default async function(
|
||||
'metadata.deletedAt': { $exists: false }
|
||||
});
|
||||
|
||||
if (much !== null) {
|
||||
log('file with same hash is found');
|
||||
if (much) {
|
||||
log(`file with same hash is found: ${much._id}`);
|
||||
return much;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user