Merge branch 'master' into l10n_master
This commit is contained in:
commit
e52246a5f0
75 changed files with 523 additions and 1813 deletions
|
@ -1,12 +1,8 @@
|
||||||
Misskey's Translation - English version
|
Misskey's Translation
|
||||||
============
|
=====================
|
||||||
|
|
||||||
How to add a new language?
|
|
||||||
----------------------
|
|
||||||
Copy a language file to `/locales` and rename it after the language you wish to add.
|
|
||||||
|
|
||||||
If you find an untranslated part on Misskey:
|
If you find an untranslated part on Misskey:
|
||||||
-------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
1. Look for untranslated parts in the miskey's source code.
|
1. Look for untranslated parts in the miskey's source code.
|
||||||
- For instance, if you find an untranslated part in: `src/client/app/mobile/views/pages/home.vue`.
|
- For instance, if you find an untranslated part in: `src/client/app/mobile/views/pages/home.vue`.
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
Traduction de Misskey - Version Française
|
Traduction de Misskey
|
||||||
============
|
=====================
|
||||||
|
|
||||||
Comment ajouter une nouvelle langue ?
|
|
||||||
----------------------
|
|
||||||
Veuillez copier un fichier de langue dans /locales puis renommez-le du nom de la langue que vous voulez ajouter et modifier.
|
|
||||||
|
|
||||||
Si vous trouvez un segment non-traduit sur Misskey :
|
Si vous trouvez un segment non-traduit sur Misskey :
|
||||||
-------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
1. Veuillez chercher des parties non-traduites dans le code source de Misskey.
|
1. Veuillez chercher des parties non-traduites dans le code source de Misskey.
|
||||||
- Par exemple, supposons que vous trouviez un segment non-traduit dans : `src/client/app/mobile/views/pages/home.vue`.
|
- Par exemple, supposons que vous trouviez un segment non-traduit dans : `src/client/app/mobile/views/pages/home.vue`.
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
Misskeyの翻訳
|
Misskeyの翻訳
|
||||||
============
|
============
|
||||||
|
|
||||||
新たな言語を追加するには
|
|
||||||
----------------------
|
|
||||||
/locales 内に既にある何らかの言語ファイルをコピーして、追加したい言語名にリネームして編集してください。
|
|
||||||
|
|
||||||
Misskey内の未翻訳箇所を見つけたら
|
Misskey内の未翻訳箇所を見つけたら
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
|
|
@ -633,20 +633,20 @@ mobile/views/pages/notifications.vue:
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
read-all: "Are you sure you want to mark all unread notifications as read?"
|
read-all: "Are you sure you want to mark all unread notifications as read?"
|
||||||
mobile/views/pages/settings/settings.profile.vue:
|
mobile/views/pages/settings/settings.profile.vue:
|
||||||
title: "プロフィール設定"
|
title: "Profile settings"
|
||||||
will-be-published: "これらのプロフィールは公開されます。"
|
will-be-published: "These profile settings will be updated."
|
||||||
name: "名前"
|
name: "Name"
|
||||||
location: "場所"
|
location: "Location"
|
||||||
description: "自己紹介"
|
description: "Description"
|
||||||
birthday: "誕生日"
|
birthday: "Birthday"
|
||||||
avatar: "アイコン"
|
avatar: "Avatar"
|
||||||
banner: "バナー"
|
banner: "Banner"
|
||||||
avatar-saved: "アイコンを保存しました"
|
avatar-saved: "Avatar updated successfully"
|
||||||
banner-saved: "バナーを保存しました"
|
banner-saved: "Banner updated successfully"
|
||||||
set-avatar: "アイコンを選択する"
|
set-avatar: "Choose an avatar"
|
||||||
set-banner: "バナーを選択する"
|
set-banner: "Choose a banner"
|
||||||
save: "保存"
|
save: "Save"
|
||||||
saved: "プロフィールを保存しました"
|
saved: "Profile updated successfully"
|
||||||
mobile/views/pages/search.vue:
|
mobile/views/pages/search.vue:
|
||||||
search: "Search"
|
search: "Search"
|
||||||
empty: "No posts were found for '{}'"
|
empty: "No posts were found for '{}'"
|
||||||
|
|
|
@ -633,20 +633,20 @@ mobile/views/pages/notifications.vue:
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
read-all: "Êtes vous sûr de vouloir marqués toutes les notifications non-lus en tant que lus?"
|
read-all: "Êtes vous sûr de vouloir marqués toutes les notifications non-lus en tant que lus?"
|
||||||
mobile/views/pages/settings/settings.profile.vue:
|
mobile/views/pages/settings/settings.profile.vue:
|
||||||
title: "プロフィール設定"
|
title: "Réglages du profiles"
|
||||||
will-be-published: "これらのプロフィールは公開されます。"
|
will-be-published: "Ces profiles vont être publier"
|
||||||
name: "名前"
|
name: "Nom"
|
||||||
location: "場所"
|
location: "Localisation"
|
||||||
description: "自己紹介"
|
description: "Description"
|
||||||
birthday: "誕生日"
|
birthday: "Anniversaire"
|
||||||
avatar: "アイコン"
|
avatar: "Avatar"
|
||||||
banner: "バナー"
|
banner: "Bannière"
|
||||||
avatar-saved: "アイコンを保存しました"
|
avatar-saved: "Avatar mis à jour avec succès!"
|
||||||
banner-saved: "バナーを保存しました"
|
banner-saved: "Bannière mise à jour avec succès!"
|
||||||
set-avatar: "アイコンを選択する"
|
set-avatar: "Choisir un avatar"
|
||||||
set-banner: "バナーを選択する"
|
set-banner: "Choisir une bannière"
|
||||||
save: "保存"
|
save: "Sauvegarder"
|
||||||
saved: "プロフィールを保存しました"
|
saved: "Profil mis à jour avec succès"
|
||||||
mobile/views/pages/search.vue:
|
mobile/views/pages/search.vue:
|
||||||
search: "Chercher"
|
search: "Chercher"
|
||||||
empty: "Aucun message trouvé pour '{}' "
|
empty: "Aucun message trouvé pour '{}' "
|
||||||
|
|
152
locales/ja.yml
152
locales/ja.yml
|
@ -191,6 +191,12 @@ common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
|
||||||
|
desktop/views/components/activity.chart.vue:
|
||||||
|
total: "Black ... Total"
|
||||||
|
notes: "Blue ... Notes"
|
||||||
|
replies: "Red ... Replies"
|
||||||
|
renotes: "Green ... Renotes"
|
||||||
|
|
||||||
desktop/views/components/activity.vue:
|
desktop/views/components/activity.vue:
|
||||||
title: "アクティビティ"
|
title: "アクティビティ"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -201,6 +207,23 @@ desktop/views/components/calendar.vue:
|
||||||
next: "次の月"
|
next: "次の月"
|
||||||
go: "クリックして時間遡行"
|
go: "クリックして時間遡行"
|
||||||
|
|
||||||
|
desktop/views/components/choose-file-from-drive-window.vue:
|
||||||
|
choose-file: "ファイル選択中"
|
||||||
|
upload: "PCからドライブにファイルをアップロード"
|
||||||
|
cancel: "キャンセル"
|
||||||
|
ok: "決定"
|
||||||
|
choose-prompt: "ファイルを選択"
|
||||||
|
|
||||||
|
desktop/views/components/choose-folder-from-drive-window.vue:
|
||||||
|
cancel: "キャンセル"
|
||||||
|
ok: "決定"
|
||||||
|
choose-prompt: "フォルダを選択"
|
||||||
|
|
||||||
|
desktop/views/components/crop-window.vue:
|
||||||
|
skip: "クロップをスキップ"
|
||||||
|
cancel: "キャンセル"
|
||||||
|
ok: "決定"
|
||||||
|
|
||||||
desktop/views/components/drive-window.vue:
|
desktop/views/components/drive-window.vue:
|
||||||
used: "使用中"
|
used: "使用中"
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
|
@ -257,6 +280,32 @@ desktop/views/components/drive.vue:
|
||||||
upload: "ファイルをアップロード"
|
upload: "ファイルをアップロード"
|
||||||
url-upload: "URLからアップロード"
|
url-upload: "URLからアップロード"
|
||||||
|
|
||||||
|
desktop/views/components/follow-button.vue:
|
||||||
|
unfollow: "フォロー解除"
|
||||||
|
follow: "フォローする"
|
||||||
|
|
||||||
|
desktop/views/components/followers-window.vue:
|
||||||
|
followers: "{} のフォロワー"
|
||||||
|
|
||||||
|
desktop/views/components/followers.vue:
|
||||||
|
empty: "フォロワーはいないようです。"
|
||||||
|
|
||||||
|
desktop/views/components/following-window.vue:
|
||||||
|
following: "{} のフォロー"
|
||||||
|
|
||||||
|
desktop/views/components/following.vue:
|
||||||
|
empty: "フォロー中のユーザーはいないようです。"
|
||||||
|
|
||||||
|
desktop/views/components/friends-maker.vue:
|
||||||
|
title: "気になるユーザーをフォロー:"
|
||||||
|
empty: "おすすめのユーザーは見つかりませんでした。"
|
||||||
|
fetching: "読み込んでいます"
|
||||||
|
refresh: "もっと見る"
|
||||||
|
close: "閉じる"
|
||||||
|
|
||||||
|
desktop/views/components/game-window.vue:
|
||||||
|
game: "オセロ"
|
||||||
|
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "完了"
|
done: "完了"
|
||||||
add-widget: "ウィジェットを追加:"
|
add-widget: "ウィジェットを追加:"
|
||||||
|
@ -275,17 +324,25 @@ desktop/views/components/home.vue:
|
||||||
polls: "投票"
|
polls: "投票"
|
||||||
post-form: "投稿フォーム"
|
post-form: "投稿フォーム"
|
||||||
messaging: "メッセージ"
|
messaging: "メッセージ"
|
||||||
channel: "チャンネル"
|
|
||||||
access-log: "アクセスログ"
|
|
||||||
server: "サーバー情報"
|
server: "サーバー情報"
|
||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
add: "追加"
|
add: "追加"
|
||||||
|
|
||||||
|
desktop/views/input-dialog.vue:
|
||||||
|
cancel: "キャンセル"
|
||||||
|
ok: "決定"
|
||||||
|
|
||||||
|
desktop/views/components/messaging-room-window.vue:
|
||||||
|
title: "メッセージ:"
|
||||||
|
|
||||||
desktop/views/components/messaging-window.vue:
|
desktop/views/components/messaging-window.vue:
|
||||||
title: "メッセージ"
|
title: "メッセージ"
|
||||||
|
|
||||||
|
desktop/views/components/note-detail.sub.vue:
|
||||||
|
private: "(この投稿は非公開です)"
|
||||||
|
|
||||||
desktop/views/components/notes.note.vue:
|
desktop/views/components/notes.note.vue:
|
||||||
reposted-by: "{}がRenote"
|
reposted-by: "{}がRenote"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
|
@ -293,6 +350,10 @@ desktop/views/components/notes.note.vue:
|
||||||
add-reaction: "リアクション"
|
add-reaction: "リアクション"
|
||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
|
|
||||||
|
desktop/views/components/notes.vue:
|
||||||
|
error: "読み込みに失敗しました。"
|
||||||
|
retry: "リトライ"
|
||||||
|
|
||||||
desktop/views/components/notifications.vue:
|
desktop/views/components/notifications.vue:
|
||||||
more: "もっと見る"
|
more: "もっと見る"
|
||||||
empty: "ありません!"
|
empty: "ありません!"
|
||||||
|
@ -324,6 +385,9 @@ desktop/views/components/post-form-window.vue:
|
||||||
attaches: "添付: {}メディア"
|
attaches: "添付: {}メディア"
|
||||||
uploading-media: "{}個のメディアをアップロード中"
|
uploading-media: "{}個のメディアをアップロード中"
|
||||||
|
|
||||||
|
desktop/views/components/progress-dialog.vue:
|
||||||
|
waiting: "待機中"
|
||||||
|
|
||||||
desktop/views/components/renote-form.vue:
|
desktop/views/components/renote-form.vue:
|
||||||
quote: "引用する..."
|
quote: "引用する..."
|
||||||
cancel: "キャンセル"
|
cancel: "キャンセル"
|
||||||
|
@ -348,6 +412,80 @@ desktop/views/components/settings.vue:
|
||||||
other: "その他"
|
other: "その他"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
|
|
||||||
|
behaviour: "動作"
|
||||||
|
fetch-on-scroll: "スクロールで自動読み込み"
|
||||||
|
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||||
|
auto-popout: "ウィンドウの自動ポップアウト"
|
||||||
|
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||||
|
advanced: "詳細設定"
|
||||||
|
api-via-stream: "ストリームを経由したAPIリクエスト"
|
||||||
|
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
|
||||||
|
|
||||||
|
display: "デザインと表示"
|
||||||
|
customize: "ホームをカスタマイズ"
|
||||||
|
dark-mode: "ダークモード"
|
||||||
|
circle-icons: "円形のアイコンを使用"
|
||||||
|
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
|
||||||
|
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
|
||||||
|
show-reply-target: "リプライ先を表示する"
|
||||||
|
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
|
||||||
|
show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する"
|
||||||
|
show-maps: "マップの自動展開"
|
||||||
|
show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
|
||||||
|
|
||||||
|
sound: "サウンド"
|
||||||
|
enable-sounds: "サウンドを有効にする"
|
||||||
|
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
|
||||||
|
volume: "ボリューム"
|
||||||
|
test: "テスト"
|
||||||
|
|
||||||
|
mobile: "モバイル"
|
||||||
|
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
||||||
|
|
||||||
|
language: "言語"
|
||||||
|
pick-language: "言語を選択"
|
||||||
|
recommended: "推奨"
|
||||||
|
auto: "自動"
|
||||||
|
specify-language: "言語を指定"
|
||||||
|
language-desc: "変更はページの再度読み込み後に反映されます。"
|
||||||
|
|
||||||
|
cache: "キャッシュ"
|
||||||
|
clean-cache: "クリーンアップ"
|
||||||
|
cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。"
|
||||||
|
cache-cleared: "キャッシュを削除しました"
|
||||||
|
cache-cleared-desc: "ページを再度読み込みしてください。"
|
||||||
|
|
||||||
|
auto-watch: "投稿の自動ウォッチ"
|
||||||
|
auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。"
|
||||||
|
|
||||||
|
about: "Misskeyについて"
|
||||||
|
operator: "このサーバーの運営者"
|
||||||
|
|
||||||
|
update: "Misskey Update"
|
||||||
|
version: "バージョン:"
|
||||||
|
latest-version: "最新のバージョン:"
|
||||||
|
update-checking: "アップデートを確認中"
|
||||||
|
do-update: "アップデートを確認"
|
||||||
|
update-settings: "詳細設定"
|
||||||
|
prevent-update: "アップデートを延期する(非推奨)"
|
||||||
|
prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
|
||||||
|
no-updates: "利用可能な更新はありません"
|
||||||
|
no-updates-desc: "お使いのMisskeyは最新です。"
|
||||||
|
update-available: "新しいバージョンが利用可能です"
|
||||||
|
update-available-desc: "ページを再度読み込みすると更新が適用されます。"
|
||||||
|
|
||||||
|
advanced-settings: "高度な設定"
|
||||||
|
debug-mode: "デバッグモードを有効にする"
|
||||||
|
debug-mode-desc: "この設定はブラウザに記憶されます。"
|
||||||
|
use-raw-script: "生のスクリプトを読み込む"
|
||||||
|
use-raw-script-desc: "圧縮されていない「生の」スクリプトを使用します。サイズが大きいため、読み込みに時間がかかる場合があります。この設定はブラウザに記憶されます。"
|
||||||
|
source-info: "Misskeyはソースマップも提供しています。"
|
||||||
|
experimental: "実験的機能を有効にする"
|
||||||
|
experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。"
|
||||||
|
tools: "ツール"
|
||||||
|
task-manager: "タスクマネージャ"
|
||||||
|
third-parties: "サードパーティ"
|
||||||
|
|
||||||
desktop/views/components/settings.2fa.vue:
|
desktop/views/components/settings.2fa.vue:
|
||||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||||
detail: "詳細..."
|
detail: "詳細..."
|
||||||
|
@ -398,6 +536,9 @@ desktop/views/components/settings.profile.vue:
|
||||||
birthday: "誕生日"
|
birthday: "誕生日"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
|
|
||||||
|
desktop/views/components/taskmanager.vue:
|
||||||
|
title: "タスクマネージャ"
|
||||||
|
|
||||||
desktop/views/components/timeline.vue:
|
desktop/views/components/timeline.vue:
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
|
@ -511,11 +652,6 @@ desktop/views/widgets/users.vue:
|
||||||
refresh: "他を見る"
|
refresh: "他を見る"
|
||||||
no-one: "いません!"
|
no-one: "いません!"
|
||||||
|
|
||||||
desktop/views/widgets/channel.vue:
|
|
||||||
title: "チャンネル"
|
|
||||||
settings: "ウィジェットの設定"
|
|
||||||
get-started: "右上の歯車をクリックして受信するチャンネルを指定してください"
|
|
||||||
|
|
||||||
mobile/views/components/drive.vue:
|
mobile/views/components/drive.vue:
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
used: "使用中"
|
used: "使用中"
|
||||||
|
@ -616,7 +752,7 @@ mobile/views/pages/notifications.vue:
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
read-all: "すべての通知を既読にしますか?"
|
read-all: "すべての通知を既読にしますか?"
|
||||||
|
|
||||||
mobile/views/pages/profile-setting.vue:
|
mobile/views/pages/settings/settings.profile.vue:
|
||||||
title: "プロフィール設定"
|
title: "プロフィール設定"
|
||||||
will-be-published: "これらのプロフィールは公開されます。"
|
will-be-published: "これらのプロフィールは公開されます。"
|
||||||
name: "名前"
|
name: "名前"
|
||||||
|
|
|
@ -633,20 +633,20 @@ mobile/views/pages/notifications.vue:
|
||||||
notifications: "Powiadomienia"
|
notifications: "Powiadomienia"
|
||||||
read-all: "Czy na pewno chcesz oznaczyć wszystkie powiadomienia jako przeczytane?"
|
read-all: "Czy na pewno chcesz oznaczyć wszystkie powiadomienia jako przeczytane?"
|
||||||
mobile/views/pages/settings/settings.profile.vue:
|
mobile/views/pages/settings/settings.profile.vue:
|
||||||
title: "プロフィール設定"
|
title: "Ustawienia profilu"
|
||||||
will-be-published: "これらのプロフィールは公開されます。"
|
will-be-published: "Te ustawienia profilu zostaną zaktualizowane."
|
||||||
name: "名前"
|
name: "Nazwa"
|
||||||
location: "場所"
|
location: "Lokalizacja"
|
||||||
description: "自己紹介"
|
description: "Opis"
|
||||||
birthday: "誕生日"
|
birthday: "Data urodzenia"
|
||||||
avatar: "アイコン"
|
avatar: "Awatar"
|
||||||
banner: "バナー"
|
banner: "Baner"
|
||||||
avatar-saved: "アイコンを保存しました"
|
avatar-saved: "Pomyślnie zaktualizowano awatar"
|
||||||
banner-saved: "バナーを保存しました"
|
banner-saved: "Pomyślnie zaktualizowano baner"
|
||||||
set-avatar: "アイコンを選択する"
|
set-avatar: "Wybierz awatar"
|
||||||
set-banner: "バナーを選択する"
|
set-banner: "Wybierz baner"
|
||||||
save: "保存"
|
save: "Zapisz"
|
||||||
saved: "プロフィールを保存しました"
|
saved: "Pomyślnie zaktualizowano profil"
|
||||||
mobile/views/pages/search.vue:
|
mobile/views/pages/search.vue:
|
||||||
search: "Szukaj"
|
search: "Szukaj"
|
||||||
empty: "Nie znaleziono wpisów zawierających '{}'"
|
empty: "Nie znaleziono wpisów zawierających '{}'"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "2.10.0",
|
"version": "2.10.1",
|
||||||
"clientVersion": "1.0.5406",
|
"clientVersion": "1.0.5407",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
"style-loader": "0.21.0",
|
"style-loader": "0.21.0",
|
||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.2",
|
"stylus-loader": "3.0.2",
|
||||||
"summaly": "2.0.4",
|
"summaly": "2.0.6",
|
||||||
"swagger-jsdoc": "1.9.7",
|
"swagger-jsdoc": "1.9.7",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"tcp-port-used": "0.1.2",
|
"tcp-port-used": "0.1.2",
|
||||||
|
@ -206,6 +206,7 @@
|
||||||
"vue-js-modal": "1.3.13",
|
"vue-js-modal": "1.3.13",
|
||||||
"vue-json-tree-view": "2.1.4",
|
"vue-json-tree-view": "2.1.4",
|
||||||
"vue-loader": "15.0.11",
|
"vue-loader": "15.0.11",
|
||||||
|
"vue-material": "^1.0.0-beta-10.2",
|
||||||
"vue-router": "3.0.1",
|
"vue-router": "3.0.1",
|
||||||
"vue-template-compiler": "2.5.16",
|
"vue-template-compiler": "2.5.16",
|
||||||
"vuedraggable": "2.16.0",
|
"vuedraggable": "2.16.0",
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* Channels
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Style
|
|
||||||
import './style.styl';
|
|
||||||
|
|
||||||
require('./tags');
|
|
||||||
import init from '../init';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
init(() => {
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
@import "../app"
|
|
||||||
|
|
||||||
html
|
|
||||||
padding 8px
|
|
||||||
background #efefef
|
|
||||||
|
|
||||||
#wait
|
|
||||||
top auto
|
|
||||||
bottom 15px
|
|
||||||
left 15px
|
|
|
@ -1,409 +0,0 @@
|
||||||
<mk-channel>
|
|
||||||
<mk-header/>
|
|
||||||
<hr>
|
|
||||||
<main v-if="!fetching">
|
|
||||||
<h1>{ channel.title }</h1>
|
|
||||||
|
|
||||||
<div v-if="$root.$data.os.isSignedIn">
|
|
||||||
<p v-if="channel.isWatching">このチャンネルをウォッチしています <a @click="unwatch">ウォッチ解除</a></p>
|
|
||||||
<p v-if="!channel.isWatching"><a @click="watch">このチャンネルをウォッチする</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="share">
|
|
||||||
<mk-twitter-button/>
|
|
||||||
<mk-line-button/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body">
|
|
||||||
<p v-if="notesFetching">読み込み中<mk-ellipsis/></p>
|
|
||||||
<div v-if="!notesFetching">
|
|
||||||
<p v-if="notes == null || notes.length == 0">まだ投稿がありません</p>
|
|
||||||
<template v-if="notes != null">
|
|
||||||
<mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<mk-channel-form v-if="$root.$data.os.isSignedIn" channel={ channel } ref="form"/>
|
|
||||||
<div v-if="!$root.$data.os.isSignedIn">
|
|
||||||
<p>参加するには<a href={ _URL_ }>ログインまたは新規登録</a>してください</p>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<footer>
|
|
||||||
<small><a href={ _URL_ }>Misskey</a> ver { _VERSION_ } (葵 aoi)</small>
|
|
||||||
</footer>
|
|
||||||
</main>
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
:scope
|
|
||||||
display block
|
|
||||||
|
|
||||||
> main
|
|
||||||
> h1
|
|
||||||
font-size 1.5em
|
|
||||||
color #f00
|
|
||||||
|
|
||||||
> .share
|
|
||||||
> *
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
> .body
|
|
||||||
margin 8px 0 0 0
|
|
||||||
|
|
||||||
> mk-channel-form
|
|
||||||
max-width 500px
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<script lang="typescript">
|
|
||||||
import Progress from '../../common/scripts/loading';
|
|
||||||
import ChannelStream from '../../common/scripts/streaming/channel-stream';
|
|
||||||
|
|
||||||
this.mixin('i');
|
|
||||||
this.mixin('api');
|
|
||||||
|
|
||||||
this.id = this.opts.id;
|
|
||||||
this.fetching = true;
|
|
||||||
this.notesFetching = true;
|
|
||||||
this.channel = null;
|
|
||||||
this.notes = null;
|
|
||||||
this.connection = new ChannelStream(this.id);
|
|
||||||
this.unreadCount = 0;
|
|
||||||
|
|
||||||
this.on('mount', () => {
|
|
||||||
document.documentElement.style.background = '#efefef';
|
|
||||||
|
|
||||||
Progress.start();
|
|
||||||
|
|
||||||
let fetched = false;
|
|
||||||
|
|
||||||
// チャンネル概要読み込み
|
|
||||||
this.$root.$data.os.api('channels/show', {
|
|
||||||
channelId: this.id
|
|
||||||
}).then(channel => {
|
|
||||||
if (fetched) {
|
|
||||||
Progress.done();
|
|
||||||
} else {
|
|
||||||
Progress.set(0.5);
|
|
||||||
fetched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update({
|
|
||||||
fetching: false,
|
|
||||||
channel: channel
|
|
||||||
});
|
|
||||||
|
|
||||||
document.title = channel.title + ' | Misskey'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 投稿読み込み
|
|
||||||
this.$root.$data.os.api('channels/notes', {
|
|
||||||
channelId: this.id
|
|
||||||
}).then(notes => {
|
|
||||||
if (fetched) {
|
|
||||||
Progress.done();
|
|
||||||
} else {
|
|
||||||
Progress.set(0.5);
|
|
||||||
fetched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update({
|
|
||||||
notesFetching: false,
|
|
||||||
notes: notes
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.on('note', this.onNote);
|
|
||||||
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('unmount', () => {
|
|
||||||
this.connection.off('note', this.onNote);
|
|
||||||
this.connection.close();
|
|
||||||
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onNote = note => {
|
|
||||||
this.notes.unshift(note);
|
|
||||||
this.update();
|
|
||||||
|
|
||||||
if (document.hidden && this.$root.$data.os.isSignedIn && note.userId !== this.$root.$data.os.i.id) {
|
|
||||||
this.unreadCount++;
|
|
||||||
document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onVisibilitychange = () => {
|
|
||||||
if (!document.hidden) {
|
|
||||||
this.unreadCount = 0;
|
|
||||||
document.title = this.channel.title + ' | Misskey'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.watch = () => {
|
|
||||||
this.$root.$data.os.api('channels/watch', {
|
|
||||||
channelId: this.id
|
|
||||||
}).then(() => {
|
|
||||||
this.channel.isWatching = true;
|
|
||||||
this.update();
|
|
||||||
}, e => {
|
|
||||||
alert('error');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.unwatch = () => {
|
|
||||||
this.$root.$data.os.api('channels/unwatch', {
|
|
||||||
channelId: this.id
|
|
||||||
}).then(() => {
|
|
||||||
this.channel.isWatching = false;
|
|
||||||
this.update();
|
|
||||||
}, e => {
|
|
||||||
alert('error');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</mk-channel>
|
|
||||||
|
|
||||||
<mk-channel-note>
|
|
||||||
<header>
|
|
||||||
<a class="index" @click="reply">{ note.index }:</a>
|
|
||||||
<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a>
|
|
||||||
<mk-time time={ note.createdAt }/>
|
|
||||||
<mk-time time={ note.createdAt } mode="detail"/>
|
|
||||||
<span>ID:<i>{ acct }</i></span>
|
|
||||||
</header>
|
|
||||||
<div>
|
|
||||||
<a v-if="note.reply">>>{ note.reply.index }</a>
|
|
||||||
{ note.text }
|
|
||||||
<div class="media" v-if="note.media">
|
|
||||||
<template each={ file in note.media }>
|
|
||||||
<a href={ file.url } target="_blank">
|
|
||||||
<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
:scope
|
|
||||||
display block
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
|
|
||||||
> header
|
|
||||||
position -webkit-sticky
|
|
||||||
position sticky
|
|
||||||
z-index 1
|
|
||||||
top 0
|
|
||||||
background rgba(239, 239, 239, 0.9)
|
|
||||||
|
|
||||||
> .index
|
|
||||||
margin-right 0.25em
|
|
||||||
color #000
|
|
||||||
|
|
||||||
> .name
|
|
||||||
margin-right 0.5em
|
|
||||||
color #008000
|
|
||||||
|
|
||||||
> mk-time
|
|
||||||
margin-right 0.5em
|
|
||||||
|
|
||||||
&:first-of-type
|
|
||||||
display none
|
|
||||||
|
|
||||||
@media (max-width 600px)
|
|
||||||
> mk-time
|
|
||||||
&:first-of-type
|
|
||||||
display initial
|
|
||||||
|
|
||||||
&:last-of-type
|
|
||||||
display none
|
|
||||||
|
|
||||||
> div
|
|
||||||
padding 0 0 1em 2em
|
|
||||||
|
|
||||||
> .media
|
|
||||||
> a
|
|
||||||
display inline-block
|
|
||||||
|
|
||||||
> img
|
|
||||||
max-width 100%
|
|
||||||
vertical-align bottom
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<script lang="typescript">
|
|
||||||
import getAcct from '../../../../acct/render';
|
|
||||||
import getUserName from '../../../../renderers/get-user-name';
|
|
||||||
|
|
||||||
this.note = this.opts.note;
|
|
||||||
this.form = this.opts.form;
|
|
||||||
this.acct = getAcct(this.note.user);
|
|
||||||
this.name = getUserName(this.note.user);
|
|
||||||
|
|
||||||
this.reply = () => {
|
|
||||||
this.form.update({
|
|
||||||
reply: this.note
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</mk-channel-note>
|
|
||||||
|
|
||||||
<mk-channel-form>
|
|
||||||
<p v-if="reply"><b>>>{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p>
|
|
||||||
<textarea ref="text" disabled={ wait } oninput={ update } onkeydown={ onkeydown } onpaste={ onpaste } placeholder="%i18n:ch.tags.mk-channel-form.textarea%"></textarea>
|
|
||||||
<div class="actions">
|
|
||||||
<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button>
|
|
||||||
<button @click="drive">%fa:cloud%%i18n:ch.tags.mk-channel-form.drive%</button>
|
|
||||||
<button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="note">
|
|
||||||
<template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:!ch.tags.mk-channel-form.posting%' : '%i18n:!ch.tags.mk-channel-form.note%' }<mk-ellipsis v-if="wait"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<mk-uploader ref="uploader"/>
|
|
||||||
<ol v-if="files">
|
|
||||||
<li each={ files }>{ name }</li>
|
|
||||||
</ol>
|
|
||||||
<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
:scope
|
|
||||||
display block
|
|
||||||
|
|
||||||
> textarea
|
|
||||||
width 100%
|
|
||||||
max-width 100%
|
|
||||||
min-width 100%
|
|
||||||
min-height 5em
|
|
||||||
|
|
||||||
> .actions
|
|
||||||
display flex
|
|
||||||
|
|
||||||
> button
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 0.25em
|
|
||||||
|
|
||||||
&:last-child
|
|
||||||
margin-left auto
|
|
||||||
|
|
||||||
&.wait
|
|
||||||
cursor wait
|
|
||||||
|
|
||||||
> input[type='file']
|
|
||||||
display none
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<script lang="typescript">
|
|
||||||
import getUserName from '../../../../renderers/get-user-name';
|
|
||||||
|
|
||||||
this.mixin('api');
|
|
||||||
|
|
||||||
this.channel = this.opts.channel;
|
|
||||||
this.files = null;
|
|
||||||
|
|
||||||
this.on('mount', () => {
|
|
||||||
this.$refs.uploader.on('uploaded', file => {
|
|
||||||
this.update({
|
|
||||||
files: [file]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.upload = file => {
|
|
||||||
this.$refs.uploader.upload(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.clearReply = () => {
|
|
||||||
this.update({
|
|
||||||
reply: null
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.clear = () => {
|
|
||||||
this.clearReply();
|
|
||||||
this.update({
|
|
||||||
files: null
|
|
||||||
});
|
|
||||||
this.$refs.text.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
this.note = () => {
|
|
||||||
this.update({
|
|
||||||
wait: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const files = this.files && this.files.length > 0
|
|
||||||
? this.files.map(f => f.id)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
this.$root.$data.os.api('notes/create', {
|
|
||||||
text: this.$refs.text.value == '' ? undefined : this.$refs.text.value,
|
|
||||||
mediaIds: files,
|
|
||||||
replyId: this.reply ? this.reply.id : undefined,
|
|
||||||
channelId: this.channel.id
|
|
||||||
}).then(data => {
|
|
||||||
this.clear();
|
|
||||||
}).catch(err => {
|
|
||||||
alert('失敗した');
|
|
||||||
}).then(() => {
|
|
||||||
this.update({
|
|
||||||
wait: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.changeFile = () => {
|
|
||||||
Array.from(this.$refs.file.files).forEach(this.upload);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.selectFile = () => {
|
|
||||||
this.$refs.file.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.drive = () => {
|
|
||||||
window['cb'] = files => {
|
|
||||||
this.update({
|
|
||||||
files: files
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.open(_URL_ + '/selectdrive?multiple=true',
|
|
||||||
'drive_window',
|
|
||||||
'height=500,width=800');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onkeydown = e => {
|
|
||||||
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onpaste = e => {
|
|
||||||
Array.from(e.clipboardData.items).forEach(item => {
|
|
||||||
if (item.kind == 'file') {
|
|
||||||
this.upload(item.getAsFile());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getUserName = getUserName;
|
|
||||||
</script>
|
|
||||||
</mk-channel-form>
|
|
||||||
|
|
||||||
<mk-twitter-button>
|
|
||||||
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-show-count="false">Tweet</a>
|
|
||||||
<script lang="typescript">
|
|
||||||
this.on('mount', () => {
|
|
||||||
const head = document.getElementsByTagName('head')[0];
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.setAttribute('src', 'https://platform.twitter.com/widgets.js');
|
|
||||||
script.setAttribute('async', 'async');
|
|
||||||
head.appendChild(script);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</mk-twitter-button>
|
|
||||||
|
|
||||||
<mk-line-button>
|
|
||||||
<div class="line-it-button" data-lang="ja" data-type="share-a" data-url={ _CH_URL_ } style="display: none;"></div>
|
|
||||||
<script lang="typescript">
|
|
||||||
this.on('mount', () => {
|
|
||||||
const head = document.getElementsByTagName('head')[0];
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.setAttribute('src', 'https://d.line-scdn.net/r/web/social-plugin/js/thirdparty/loader.min.js');
|
|
||||||
script.setAttribute('async', 'async');
|
|
||||||
head.appendChild(script);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</mk-line-button>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<mk-header>
|
|
||||||
<div>
|
|
||||||
<a href={ _CH_URL_ }>Index</a> | <a href={ _URL_ }>Misskey</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a v-if="!$root.$data.os.isSignedIn" href={ _URL_ }>ログイン(新規登録)</a>
|
|
||||||
<a v-if="$root.$data.os.isSignedIn" href={ _URL_ + '/@' + I.username }>{ I.username }</a>
|
|
||||||
</div>
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
:scope
|
|
||||||
display flex
|
|
||||||
|
|
||||||
> div:last-child
|
|
||||||
margin-left auto
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<script lang="typescript">
|
|
||||||
this.mixin('i');
|
|
||||||
</script>
|
|
||||||
</mk-header>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<mk-index>
|
|
||||||
<mk-header/>
|
|
||||||
<hr>
|
|
||||||
<button @click="n">%i18n:ch.tags.mk-index.new%</button>
|
|
||||||
<hr>
|
|
||||||
<ul v-if="channels">
|
|
||||||
<li each={ channels }><a href={ '/' + this.id }>{ this.title }</a></li>
|
|
||||||
</ul>
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
:scope
|
|
||||||
display block
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<script lang="typescript">
|
|
||||||
this.mixin('api');
|
|
||||||
|
|
||||||
this.on('mount', () => {
|
|
||||||
this.$root.$data.os.api('channels', {
|
|
||||||
limit: 100
|
|
||||||
}).then(channels => {
|
|
||||||
this.update({
|
|
||||||
channels: channels
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.n = () => {
|
|
||||||
const title = window.prompt('%i18n:!ch.tags.mk-index.channel-title%');
|
|
||||||
|
|
||||||
this.$root.$data.os.api('channels/create', {
|
|
||||||
title: title
|
|
||||||
}).then(channel => {
|
|
||||||
location.href = '/' + channel.id;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</mk-index>
|
|
|
@ -1,3 +0,0 @@
|
||||||
require('./index.tag');
|
|
||||||
require('./channel.tag');
|
|
||||||
require('./header.tag');
|
|
|
@ -1,13 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel stream connection
|
|
||||||
*/
|
|
||||||
export default class Connection extends Stream {
|
|
||||||
constructor(os: MiOS, channelId) {
|
|
||||||
super(os, 'channel', {
|
|
||||||
channel: channelId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mkw-access-log">
|
|
||||||
<mk-widget-container :show-header="props.design == 0">
|
|
||||||
<template slot="header">%fa:server%%i18n:@title%</template>
|
|
||||||
|
|
||||||
<div :class="$style.logs" ref="log">
|
|
||||||
<p v-for="req in requests">
|
|
||||||
<span :class="$style.ip" :style="`color:${ req.fg }; background:${ req.bg }`">{{ req.ip }}</span>
|
|
||||||
<b>{{ req.method }}</b>
|
|
||||||
<span>{{ req.path }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</mk-widget-container>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import define from '../../../common/define-widget';
|
|
||||||
import * as seedrandom from 'seedrandom';
|
|
||||||
|
|
||||||
export default define({
|
|
||||||
name: 'broadcast',
|
|
||||||
props: () => ({
|
|
||||||
design: 0
|
|
||||||
})
|
|
||||||
}).extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
requests: [],
|
|
||||||
connection: null,
|
|
||||||
connectionId: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.connection = (this as any).os.streams.requestsStream.getConnection();
|
|
||||||
this.connectionId = (this as any).os.streams.requestsStream.use();
|
|
||||||
this.connection.on('request', this.onRequest);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.off('request', this.onRequest);
|
|
||||||
(this as any).os.streams.requestsStream.dispose(this.connectionId);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onRequest(request) {
|
|
||||||
const random = seedrandom(request.ip);
|
|
||||||
const r = Math.floor(random() * 255);
|
|
||||||
const g = Math.floor(random() * 255);
|
|
||||||
const b = Math.floor(random() * 255);
|
|
||||||
const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b); // SMPTE C, Rec. 709 weightings
|
|
||||||
request.bg = `rgb(${r}, ${g}, ${b})`;
|
|
||||||
request.fg = luma >= 165 ? '#000' : '#fff';
|
|
||||||
|
|
||||||
this.requests.push(request);
|
|
||||||
if (this.requests.length > 30) this.requests.shift();
|
|
||||||
|
|
||||||
(this.$refs.log as any).scrollTop = (this.$refs.log as any).scrollHeight;
|
|
||||||
},
|
|
||||||
func() {
|
|
||||||
if (this.props.design == 1) {
|
|
||||||
this.props.design = 0;
|
|
||||||
} else {
|
|
||||||
this.props.design++;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" module>
|
|
||||||
.logs
|
|
||||||
max-height 250px
|
|
||||||
overflow auto
|
|
||||||
|
|
||||||
> p
|
|
||||||
margin 0
|
|
||||||
padding 8px
|
|
||||||
font-size 0.8em
|
|
||||||
color #555
|
|
||||||
|
|
||||||
&:nth-child(odd)
|
|
||||||
background rgba(#000, 0.025)
|
|
||||||
|
|
||||||
> b
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
.ip
|
|
||||||
margin-right 4px
|
|
||||||
padding 0 4px
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import wAccessLog from './access-log.vue';
|
|
||||||
import wVersion from './version.vue';
|
import wVersion from './version.vue';
|
||||||
import wRss from './rss.vue';
|
import wRss from './rss.vue';
|
||||||
import wServer from './server.vue';
|
import wServer from './server.vue';
|
||||||
|
@ -22,4 +21,3 @@ Vue.component('mkw-broadcast', wBroadcast);
|
||||||
Vue.component('mkw-server', wServer);
|
Vue.component('mkw-server', wServer);
|
||||||
Vue.component('mkw-rss', wRss);
|
Vue.component('mkw-rss', wRss);
|
||||||
Vue.component('mkw-version', wVersion);
|
Vue.component('mkw-version', wVersion);
|
||||||
Vue.component('mkw-access-log', wAccessLog);
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown">
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown">
|
||||||
<title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title>
|
<title>%i18n:@total%<br/>%i18n:@notes%<br/>%i18n:@replies%<br/>%i18n:@renotes%</title>
|
||||||
<polyline
|
<polyline
|
||||||
:points="pointsNote"
|
:points="pointsNote"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
|
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
<span v-html="title" :class="$style.title"></span>
|
<span v-html="title" :class="$style.title"></span>
|
||||||
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}ファイル選択中)</span>
|
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<mk-drive
|
<mk-drive
|
||||||
|
@ -13,9 +13,9 @@
|
||||||
@change-selection="onChangeSelection"
|
@change-selection="onChangeSelection"
|
||||||
/>
|
/>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<button :class="$style.upload" title="PCからドライブにファイルをアップロード" @click="upload">%fa:upload%</button>
|
<button :class="$style.upload" title="%i18n:@upload%" @click="upload">%fa:upload%</button>
|
||||||
<button :class="$style.cancel" @click="cancel">キャンセル</button>
|
<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
|
||||||
<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">決定</button>
|
<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">%i18n:@ok%</button>
|
||||||
</div>
|
</div>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
@ -28,7 +28,7 @@ export default Vue.extend({
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
default: '%fa:R file%ファイルを選択'
|
default: '%fa:R file%%i18n:@choose-prompt%s'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -177,4 +177,3 @@ export default Vue.extend({
|
||||||
border-color #dcdcdc
|
border-color #dcdcdc
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
/>
|
/>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<button :class="$style.cancel" @click="cancel">キャンセル</button>
|
<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
|
||||||
<button :class="$style.ok" @click="ok">決定</button>
|
<button :class="$style.ok" @click="ok">%i18n:@ok%</button>
|
||||||
</div>
|
</div>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,7 +21,7 @@ import Vue from 'vue';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
default: '%fa:R folder%フォルダを選択'
|
default: '%fa:R folder%%i18n:@choose-prompt%'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.actions">
|
<div :class="$style.actions">
|
||||||
<button :class="$style.skip" @click="skip">クロップをスキップ</button>
|
<button :class="$style.skip" @click="skip">%i18n:@skip%</button>
|
||||||
<button :class="$style.cancel" @click="cancel">キャンセル</button>
|
<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
|
||||||
<button :class="$style.ok" @click="ok">決定</button>
|
<button :class="$style.ok" @click="ok">%i18n:@ok%</button>
|
||||||
</div>
|
</div>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,15 +3,15 @@
|
||||||
:class="{ wait, follow: !user.isFollowing, unfollow: user.isFollowing, big: size == 'big' }"
|
:class="{ wait, follow: !user.isFollowing, unfollow: user.isFollowing, big: size == 'big' }"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
:title="user.isFollowing ? 'フォロー解除' : 'フォローする'"
|
:title="user.isFollowing ? '%i18n:@unfollow%' : '%i18n:@follow%'"
|
||||||
>
|
>
|
||||||
<template v-if="!wait && user.isFollowing">
|
<template v-if="!wait && user.isFollowing">
|
||||||
<template v-if="size == 'compact'">%fa:minus%</template>
|
<template v-if="size == 'compact'">%fa:minus%</template>
|
||||||
<template v-if="size == 'big'">%fa:minus%フォロー解除</template>
|
<template v-if="size == 'big'">%fa:minus%%i18n:@unfollow%</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!wait && !user.isFollowing">
|
<template v-if="!wait && !user.isFollowing">
|
||||||
<template v-if="size == 'compact'">%fa:plus%</template>
|
<template v-if="size == 'compact'">%fa:plus%</template>
|
||||||
<template v-if="size == 'big'">%fa:plus%フォロー</template>
|
<template v-if="size == 'big'">%fa:plus%%i18n:@follow%</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="wait">%fa:spinner .pulse .fw%</template>
|
<template v-if="wait">%fa:spinner .pulse .fw%</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロワー
|
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>%i18n:!@followers%.replace('{}', {{ user | userName }})
|
||||||
</span>
|
</span>
|
||||||
<mk-followers :user="user"/>
|
<mk-followers :user="user"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
:count="user.followersCount"
|
:count="user.followersCount"
|
||||||
:you-know-count="user.followersYouKnowCount"
|
:you-know-count="user.followersYouKnowCount"
|
||||||
>
|
>
|
||||||
フォロワーはいないようです。
|
%i18n:@empty%
|
||||||
</mk-users-list>
|
</mk-users-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロー
|
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>%i18n:!@following%.replace('{}', {{ user | userName }})
|
||||||
</span>
|
</span>
|
||||||
<mk-following :user="user"/>
|
<mk-following :user="user"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
:count="user.followingCount"
|
:count="user.followingCount"
|
||||||
:you-know-count="user.followingYouKnowCount"
|
:you-know-count="user.followingYouKnowCount"
|
||||||
>
|
>
|
||||||
フォロー中のユーザーはいないようです。
|
%i18n:@empty%
|
||||||
</mk-users-list>
|
</mk-users-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-friends-maker">
|
<div class="mk-friends-maker">
|
||||||
<p class="title">気になるユーザーをフォロー:</p>
|
<p class="title">%i18n:@title%</p>
|
||||||
<div class="users" v-if="!fetching && users.length > 0">
|
<div class="users" v-if="!fetching && users.length > 0">
|
||||||
<div class="user" v-for="user in users" :key="user.id">
|
<div class="user" v-for="user in users" :key="user.id">
|
||||||
<mk-avatar class="avatar" :user="user" target="_blank"/>
|
<mk-avatar class="avatar" :user="user" target="_blank"/>
|
||||||
|
@ -11,10 +11,10 @@
|
||||||
<mk-follow-button :user="user"/>
|
<mk-follow-button :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty" v-if="!fetching && users.length == 0">おすすめのユーザーは見つかりませんでした。</p>
|
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
|
||||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p>
|
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p>
|
||||||
<a class="refresh" @click="refresh">もっと見る</a>
|
<a class="refresh" @click="refresh">%i18n:@refresh%</a>
|
||||||
<button class="close" @click="$destroy()" title="閉じる">%fa:times%</button>
|
<button class="close" @click="$destroy()" title="%i18n:@close%">%fa:times%</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">%fa:gamepad%オセロ</span>
|
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
|
||||||
<mk-othello :class="$style.content" @gamed="g => game = g"/>
|
<mk-othello :class="$style.content" @gamed="g => game = g"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -21,8 +21,6 @@
|
||||||
<option value="polls">%i18n:@polls%</option>
|
<option value="polls">%i18n:@polls%</option>
|
||||||
<option value="post-form">%i18n:@post-form%</option>
|
<option value="post-form">%i18n:@post-form%</option>
|
||||||
<option value="messaging">%i18n:@messaging%</option>
|
<option value="messaging">%i18n:@messaging%</option>
|
||||||
<option value="channel">%i18n:@channel%</option>
|
|
||||||
<option value="access-log">%i18n:@access-log%</option>
|
|
||||||
<option value="server">%i18n:@server%</option>
|
<option value="server">%i18n:@server%</option>
|
||||||
<option value="donation">%i18n:@donation%</option>
|
<option value="donation">%i18n:@donation%</option>
|
||||||
<option value="nav">%i18n:@nav%</option>
|
<option value="nav">%i18n:@nav%</option>
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
<input ref="text" v-model="text" :type="type" @keydown="onKeydown" :placeholder="placeholder"/>
|
<input ref="text" v-model="text" :type="type" @keydown="onKeydown" :placeholder="placeholder"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.actions">
|
<div :class="$style.actions">
|
||||||
<button :class="$style.cancel" @click="cancel">キャンセル</button>
|
<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
|
||||||
<button :class="$style.ok" :disabled="!allowEmpty && text.length == 0" @click="ok">決定</button>
|
<button :class="$style.ok" :disabled="!allowEmpty && text.length == 0" @click="ok">%i18n:@ok%</button>
|
||||||
</div>
|
</div>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-mentions">
|
|
||||||
<header>
|
|
||||||
<span :data-active="mode == 'all'" @click="mode = 'all'">すべて</span>
|
|
||||||
<span :data-active="mode == 'following'" @click="mode = 'following'">フォロー中</span>
|
|
||||||
</header>
|
|
||||||
<div class="fetching" v-if="fetching">
|
|
||||||
<mk-ellipsis-icon/>
|
|
||||||
</div>
|
|
||||||
<p class="empty" v-if="notes.length == 0 && !fetching">
|
|
||||||
%fa:R comments%
|
|
||||||
<span v-if="mode == 'all'">あなた宛ての投稿はありません。</span>
|
|
||||||
<span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません。</span>
|
|
||||||
</p>
|
|
||||||
<mk-notes :notes="notes" ref="timeline"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
moreFetching: false,
|
|
||||||
mode: 'all',
|
|
||||||
notes: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode() {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
|
||||||
window.addEventListener('scroll', this.onScroll);
|
|
||||||
|
|
||||||
this.fetch(() => this.$emit('loaded'));
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
|
||||||
window.removeEventListener('scroll', this.onScroll);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onDocumentKeydown(e) {
|
|
||||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
|
||||||
if (e.which == 84) { // t
|
|
||||||
(this.$refs.timeline as any).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onScroll() {
|
|
||||||
const current = window.scrollY + window.innerHeight;
|
|
||||||
if (current > document.body.offsetHeight - 8) this.more();
|
|
||||||
},
|
|
||||||
fetch(cb?) {
|
|
||||||
this.fetching = true;
|
|
||||||
this.notes = [];
|
|
||||||
(this as any).api('notes/mentions', {
|
|
||||||
following: this.mode == 'following'
|
|
||||||
}).then(notes => {
|
|
||||||
this.notes = notes;
|
|
||||||
this.fetching = false;
|
|
||||||
if (cb) cb();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
more() {
|
|
||||||
if (this.moreFetching || this.fetching || this.notes.length == 0) return;
|
|
||||||
this.moreFetching = true;
|
|
||||||
(this as any).api('notes/mentions', {
|
|
||||||
following: this.mode == 'following',
|
|
||||||
untilId: this.notes[this.notes.length - 1].id
|
|
||||||
}).then(notes => {
|
|
||||||
this.notes = this.notes.concat(notes);
|
|
||||||
this.moreFetching = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.mk-mentions
|
|
||||||
background #fff
|
|
||||||
border solid 1px rgba(#000, 0.075)
|
|
||||||
border-radius 6px
|
|
||||||
|
|
||||||
> header
|
|
||||||
padding 8px 16px
|
|
||||||
border-bottom solid 1px #eee
|
|
||||||
|
|
||||||
> span
|
|
||||||
margin-right 16px
|
|
||||||
line-height 27px
|
|
||||||
font-size 18px
|
|
||||||
color #555
|
|
||||||
|
|
||||||
&:not([data-active])
|
|
||||||
color $theme-color
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
text-decoration underline
|
|
||||||
|
|
||||||
> .fetching
|
|
||||||
padding 64px 0
|
|
||||||
|
|
||||||
> .empty
|
|
||||||
display block
|
|
||||||
margin 0 auto
|
|
||||||
padding 32px
|
|
||||||
max-width 400px
|
|
||||||
text-align center
|
|
||||||
color #999
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
display block
|
|
||||||
margin-bottom 16px
|
|
||||||
font-size 3em
|
|
||||||
color #ccc
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ user | userName }}</span>
|
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
|
||||||
<mk-messaging-room :user="user" :class="$style.content"/>
|
<mk-messaging-room :user="user" :class="$style.content"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="note.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span>
|
<span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||||
<mk-note-html v-if="note.text" :text="note.text" :i="os.i"/>
|
<mk-note-html v-if="note.text" :text="note.text" :i="os.i"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="note.mediaIds.length > 0">
|
<div class="media" v-if="note.mediaIds.length > 0">
|
||||||
|
|
|
@ -33,9 +33,6 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p class="channel" v-if="p.channel">
|
|
||||||
<a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>:
|
|
||||||
</p>
|
|
||||||
<p v-if="p.cw != null" class="cw">
|
<p v-if="p.cw != null" class="cw">
|
||||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
||||||
|
@ -574,9 +571,6 @@ root(isDark)
|
||||||
.mk-url-preview
|
.mk-url-preview
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .channel
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
> .mk-poll
|
> .mk-poll
|
||||||
font-size 80%
|
font-size 80%
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
||||||
|
|
||||||
<div v-if="!fetching && requestInitPromise != null">
|
<div v-if="!fetching && requestInitPromise != null">
|
||||||
<p>読み込みに失敗しました。</p>
|
<p>%i18n:@error%</p>
|
||||||
<button @click="resolveInitPromise">リトライ</button>
|
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition-group name="mk-notes" class="transition">
|
<transition-group name="mk-notes" class="transition">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
|
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
|
||||||
<span slot="header">{{ title }}<mk-ellipsis/></span>
|
<span slot="header">{{ title }}<mk-ellipsis/></span>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<p :class="$style.init" v-if="isNaN(value)">待機中<mk-ellipsis/></p>
|
<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
|
||||||
<p :class="$style.percentage" v-if="!isNaN(value)">{{ Math.floor((value / max) * 100) }}</p>
|
<p :class="$style.percentage" v-if="!isNaN(value)">{{ Math.floor((value / max) * 100) }}</p>
|
||||||
<progress :class="$style.progress"
|
<progress :class="$style.progress"
|
||||||
v-if="!isNaN(value) && value < max"
|
v-if="!isNaN(value) && value < max"
|
||||||
|
|
|
@ -19,91 +19,91 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>動作</h1>
|
<h1>%i18n:@behaviour%</h1>
|
||||||
<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
|
<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%">
|
||||||
<span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。</span>
|
<span>%i18n:@fetch-on-scroll-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト">
|
<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
|
||||||
<span>ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。</span>
|
<span>%i18n:@auto-popout-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<details>
|
<details>
|
||||||
<summary>詳細設定</summary>
|
<summary>%i18n:@advanced%</summary>
|
||||||
<mk-switch v-model="apiViaStream" text="ストリームを経由したAPIリクエスト">
|
<mk-switch v-model="apiViaStream" text="%i18n:@api-via-stream%">
|
||||||
<span>この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。</span>
|
<span>%i18n:@api-via-stream-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>デザインと表示</h1>
|
<h1>%i18n:@display%</h1>
|
||||||
<div class="div">
|
<div class="div">
|
||||||
<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">ホームをカスタマイズ</button>
|
<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">%i18n:@customize%</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="div">
|
<div class="div">
|
||||||
<mk-switch v-model="darkmode" text="ダークモード"/>
|
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
|
||||||
<mk-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons" text="円形のアイコンを使用"/>
|
<mk-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
|
||||||
<mk-switch v-model="clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/>
|
<mk-switch v-model="clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
|
<mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
|
||||||
<mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/>
|
<mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/>
|
||||||
<mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/>
|
<mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="%i18n:@show-my-renotes%"/>
|
||||||
<mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/>
|
<mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
|
||||||
<mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
|
<mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
|
||||||
<span>位置情報が添付された投稿のマップを自動的に展開します。</span>
|
<span>%i18n:@show-maps-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>サウンド</h1>
|
<h1>%i18n:@sound%</h1>
|
||||||
<mk-switch v-model="enableSounds" text="サウンドを有効にする">
|
<mk-switch v-model="enableSounds" text="%i18n:@enable-sounds%">
|
||||||
<span>投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。</span>
|
<span>%i18n:@enable-sounds-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<label>ボリューム</label>
|
<label>%i18n:@volume%</label>
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="soundVolume"
|
v-model="soundVolume"
|
||||||
:show-input="true"
|
:show-input="true"
|
||||||
:format-tooltip="v => `${v}%`"
|
:format-tooltip="v => `${v}%`"
|
||||||
:disabled="!enableSounds"
|
:disabled="!enableSounds"
|
||||||
/>
|
/>
|
||||||
<button class="ui button" @click="soundTest">%fa:volume-up% テスト</button>
|
<button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>モバイル</h1>
|
<h1>%i18n:@mobile%</h1>
|
||||||
<mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
|
<mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="%i18n:@disable-via-mobile%"/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>言語</h1>
|
<h1>%i18n:@language%</h1>
|
||||||
<el-select v-model="lang" placeholder="言語を選択">
|
<el-select v-model="lang" placeholder="%i18n:@pick-language%">
|
||||||
<el-option-group label="推奨">
|
<el-option-group label="%i18n:@recommended%">
|
||||||
<el-option label="自動" value=""/>
|
<el-option label="%i18n:@auto%" value=""/>
|
||||||
</el-option-group>
|
</el-option-group>
|
||||||
<el-option-group label="言語を指定">
|
<el-option-group label="%i18n:@specify-language%">
|
||||||
<el-option label="ja" value="ja"/>
|
<el-option label="日本語" value="ja"/>
|
||||||
<el-option label="en" value="en"/>
|
<el-option label="English" value="en"/>
|
||||||
<el-option label="fr" value="fr"/>
|
<el-option label="Français" value="fr"/>
|
||||||
<el-option label="pl" value="pl"/>
|
<el-option label="Polski" value="pl"/>
|
||||||
<el-option label="de" value="de"/>
|
<el-option label="Deutsch" value="de"/>
|
||||||
</el-option-group>
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
<div class="none ui info">
|
<div class="none ui info">
|
||||||
<p>%fa:info-circle%変更はページの再度読み込み後に反映されます。</p>
|
<p>%fa:info-circle%%i18n:@language-desc%</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
<h1>キャッシュ</h1>
|
<h1>%i18n:@cache%</h1>
|
||||||
<button class="ui button" @click="clean">クリーンアップ</button>
|
<button class="ui button" @click="clean">%i18n:@clean-cache%</button>
|
||||||
<div class="none ui info warn">
|
<div class="none ui info warn">
|
||||||
<p>%fa:exclamation-triangle%クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。</p>
|
<p>%fa:exclamation-triangle%%i18n:@cache-warn%</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="notification" v-show="page == 'notification'">
|
<section class="notification" v-show="page == 'notification'">
|
||||||
<h1>%i18n:@notification%</h1>
|
<h1>%i18n:@notification%</h1>
|
||||||
<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
|
<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="%i18n:@auto-watch%">
|
||||||
<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。</span>
|
<span>%i18n:@auto-watch-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -148,57 +148,57 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="other" v-show="page == 'other'">
|
<section class="other" v-show="page == 'other'">
|
||||||
<h1>Misskeyについて</h1>
|
<h1>%i18n:@about%</h1>
|
||||||
<p v-if="meta">このサーバーの運営者: <i><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></i></p>
|
<p v-if="meta">%i18n:@operator%: <i><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></i></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="other" v-show="page == 'other'">
|
<section class="other" v-show="page == 'other'">
|
||||||
<h1>Misskey Update</h1>
|
<h1>%i18n:@update%</h1>
|
||||||
<p>
|
<p>
|
||||||
<span>バージョン: <i>{{ version }}</i></span>
|
<span>%i18n:@version% <i>{{ version }}</i></span>
|
||||||
<template v-if="latestVersion !== undefined">
|
<template v-if="latestVersion !== undefined">
|
||||||
<br>
|
<br>
|
||||||
<span>最新のバージョン: <i>{{ latestVersion ? latestVersion : version }}</i></span>
|
<span>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></span>
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<button class="ui button block" @click="checkForUpdate" :disabled="checkingForUpdate">
|
<button class="ui button block" @click="checkForUpdate" :disabled="checkingForUpdate">
|
||||||
<template v-if="checkingForUpdate">アップデートを確認中<mk-ellipsis/></template>
|
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
|
||||||
<template v-else>アップデートを確認</template>
|
<template v-else>%i18n:@do-update%</template>
|
||||||
</button>
|
</button>
|
||||||
<details>
|
<details>
|
||||||
<summary>詳細設定</summary>
|
<summary>%i18n:@update-settings%</summary>
|
||||||
<mk-switch v-model="preventUpdate" text="アップデートを延期する(非推奨)">
|
<mk-switch v-model="preventUpdate" text="%i18n:@prevent-update%">
|
||||||
<span>この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。</span>
|
<span>%i18n:@prevent-update-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="other" v-show="page == 'other'">
|
<section class="other" v-show="page == 'other'">
|
||||||
<h1>高度な設定</h1>
|
<h1>%i18n:@advanced-settings%</h1>
|
||||||
<mk-switch v-model="debug" text="デバッグモードを有効にする">
|
<mk-switch v-model="debug" text="%i18n:@debug-mode%">
|
||||||
<span>この設定はブラウザに記憶されます。</span>
|
<span>%i18n:@debug-mode-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<template v-if="debug">
|
<template v-if="debug">
|
||||||
<mk-switch v-model="useRawScript" text="生のスクリプトを読み込む">
|
<mk-switch v-model="useRawScript" text="%i18n:@use-raw-script%">
|
||||||
<span>圧縮されていない「生の」スクリプトを使用します。サイズが大きいため、読み込みに時間がかかる場合があります。この設定はブラウザに記憶されます。</span>
|
<span>%i18n:@use-raw-script-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<div class="none ui info">
|
<div class="none ui info">
|
||||||
<p>%fa:info-circle%Misskeyはソースマップも提供しています。</p>
|
<p>%fa:info-circle%%i18n:@source-info%</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<mk-switch v-model="enableExperimental" text="実験的機能を有効にする">
|
<mk-switch v-model="enableExperimental" text="%i18n:@experimental%">
|
||||||
<span>実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。</span>
|
<span>%i18n:@experimental-desc%</span>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<details v-if="debug">
|
<details v-if="debug">
|
||||||
<summary>ツール</summary>
|
<summary>%i18n:@tools%</summary>
|
||||||
<button class="ui button block" @click="taskmngr">タスクマネージャ</button>
|
<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="other" v-show="page == 'other'">
|
<section class="other" v-show="page == 'other'">
|
||||||
<h1>%i18n:@license%</h1>
|
<h1>%i18n:@license%</h1>
|
||||||
<div v-html="license"></div>
|
<div v-html="license"></div>
|
||||||
<a :href="licenseUrl" target="_blank">サードパーティ</a>
|
<a :href="licenseUrl" target="_blank">%i18n:@third-parties%</a>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -371,13 +371,13 @@ export default Vue.extend({
|
||||||
this.latestVersion = newer;
|
this.latestVersion = newer;
|
||||||
if (newer == null) {
|
if (newer == null) {
|
||||||
(this as any).apis.dialog({
|
(this as any).apis.dialog({
|
||||||
title: '利用可能な更新はありません',
|
title: '%i18n:!@no-updates%',
|
||||||
text: 'お使いのMisskeyは最新です。'
|
text: '%i18n:!@no-updates-desc%'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
(this as any).apis.dialog({
|
(this as any).apis.dialog({
|
||||||
title: '新しいバージョンが利用可能です',
|
title: '%i18n:!@update-available%',
|
||||||
text: 'ページを再度読み込みすると更新が適用されます。'
|
text: '%i18n:!@update-available-desc%'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -385,8 +385,8 @@ export default Vue.extend({
|
||||||
clean() {
|
clean() {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
(this as any).apis.dialog({
|
(this as any).apis.dialog({
|
||||||
title: 'キャッシュを削除しました',
|
title: '%i18n:!@cache-cleared%',
|
||||||
text: 'ページを再度読み込みしてください。'
|
text: '%i18n:!@caache-cleared-desc%'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
soundTest() {
|
soundTest() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
|
<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
|
||||||
<span slot="header" :class="$style.header">%fa:stethoscope%タスクマネージャ</span>
|
<span slot="header" :class="$style.header">%fa:stethoscope%%i18n:@title%</span>
|
||||||
<el-tabs :class="$style.content">
|
<el-tabs :class="$style.content">
|
||||||
<el-tab-pane label="Requests">
|
<el-tab-pane label="Requests">
|
||||||
<el-table
|
<el-table
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
|
<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
|
<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
|
||||||
<p class="username">@{{ u | acct }}</p>
|
<p class="username"><mk-acct :user="u"/></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">{{ u.description }}</div>
|
<div class="description">{{ u.description }}</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<p class="name">{{ user | userName }}</p>
|
<p class="name">{{ user | userName }}</p>
|
||||||
<p class="username">@{{ user | acct }}</p>
|
<p class="username"><mk-acct :user="user"/></p>
|
||||||
<p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p>
|
<p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="form">
|
|
||||||
<input v-model="text" :disabled="wait" @keydown="onKeydown" placeholder="書いて">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
text: '',
|
|
||||||
wait: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onKeydown(e) {
|
|
||||||
if (e.which == 10 || e.which == 13) this.post();
|
|
||||||
},
|
|
||||||
post() {
|
|
||||||
this.wait = true;
|
|
||||||
|
|
||||||
let reply = null;
|
|
||||||
|
|
||||||
if (/^>>([0-9]+) /.test(this.text)) {
|
|
||||||
const index = this.text.match(/^>>([0-9]+) /)[1];
|
|
||||||
reply = (this.$parent as any).notes.find(p => p.index.toString() == index);
|
|
||||||
this.text = this.text.replace(/^>>([0-9]+) /, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
(this as any).api('notes/create', {
|
|
||||||
text: this.text,
|
|
||||||
replyId: reply ? reply.id : undefined,
|
|
||||||
channelId: (this.$parent as any).channel.id
|
|
||||||
}).then(data => {
|
|
||||||
this.text = '';
|
|
||||||
}).catch(err => {
|
|
||||||
alert('失敗した');
|
|
||||||
}).then(() => {
|
|
||||||
this.wait = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.form
|
|
||||||
width 100%
|
|
||||||
height 38px
|
|
||||||
padding 4px
|
|
||||||
border-top solid 1px #ddd
|
|
||||||
|
|
||||||
> input
|
|
||||||
padding 0 8px
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
font-size 14px
|
|
||||||
color #55595c
|
|
||||||
border solid 1px #dadada
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
&:focus
|
|
||||||
border-color #aeaeae
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="note">
|
|
||||||
<header>
|
|
||||||
<a class="index" @click="reply">{{ note.index }}:</a>
|
|
||||||
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id"><b>{{ note.user | userName }}</b></router-link>
|
|
||||||
<span>ID:<i>{{ note.user | acct }}</i></span>
|
|
||||||
</header>
|
|
||||||
<div>
|
|
||||||
<a v-if="note.reply">>>{{ note.reply.index }}</a>
|
|
||||||
{{ note.text }}
|
|
||||||
<div class="media" v-if="note.media">
|
|
||||||
<a v-for="file in note.media" :href="file.url" target="_blank">
|
|
||||||
<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
props: ['note'],
|
|
||||||
methods: {
|
|
||||||
reply() {
|
|
||||||
this.$emit('reply', this.note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.note
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
color #444
|
|
||||||
|
|
||||||
> header
|
|
||||||
position -webkit-sticky
|
|
||||||
position sticky
|
|
||||||
z-index 1
|
|
||||||
top 0
|
|
||||||
padding 8px 4px 4px 16px
|
|
||||||
background rgba(255, 255, 255, 0.9)
|
|
||||||
|
|
||||||
> .index
|
|
||||||
margin-right 0.25em
|
|
||||||
|
|
||||||
> .name
|
|
||||||
margin-right 0.5em
|
|
||||||
color #008000
|
|
||||||
|
|
||||||
> div
|
|
||||||
padding 0 16px 16px 16px
|
|
||||||
|
|
||||||
> .media
|
|
||||||
> a
|
|
||||||
display inline-block
|
|
||||||
|
|
||||||
> img
|
|
||||||
max-width 100%
|
|
||||||
vertical-align bottom
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,106 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="channel">
|
|
||||||
<p v-if="fetching">読み込み中<mk-ellipsis/></p>
|
|
||||||
<div v-if="!fetching" ref="notes" class="notes">
|
|
||||||
<p v-if="notes.length == 0">まだ投稿がありません</p>
|
|
||||||
<x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.id" @reply="reply"/>
|
|
||||||
</div>
|
|
||||||
<x-form class="form" ref="form"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import ChannelStream from '../../../common/scripts/streaming/channel';
|
|
||||||
import XForm from './channel.channel.form.vue';
|
|
||||||
import XNote from './channel.channel.note.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
XForm,
|
|
||||||
XNote
|
|
||||||
},
|
|
||||||
props: ['channel'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
notes: [],
|
|
||||||
connection: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
channel() {
|
|
||||||
this.zap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.zap();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.disconnect();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
zap() {
|
|
||||||
this.fetching = true;
|
|
||||||
|
|
||||||
(this as any).api('channels/notes', {
|
|
||||||
channelId: this.channel.id
|
|
||||||
}).then(notes => {
|
|
||||||
this.notes = notes;
|
|
||||||
this.fetching = false;
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.scrollToBottom();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.disconnect();
|
|
||||||
this.connection = new ChannelStream((this as any).os, this.channel.id);
|
|
||||||
this.connection.on('note', this.onNote);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
disconnect() {
|
|
||||||
if (this.connection) {
|
|
||||||
this.connection.off('note', this.onNote);
|
|
||||||
this.connection.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onNote(note) {
|
|
||||||
this.notes.unshift(note);
|
|
||||||
this.scrollToBottom();
|
|
||||||
},
|
|
||||||
scrollToBottom() {
|
|
||||||
(this.$refs.notes as any).scrollTop = (this.$refs.notes as any).scrollHeight;
|
|
||||||
},
|
|
||||||
reply(note) {
|
|
||||||
(this.$refs.form as any).text = `>>${ note.index } `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.channel
|
|
||||||
|
|
||||||
> p
|
|
||||||
margin 0
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
> .notes
|
|
||||||
height calc(100% - 38px)
|
|
||||||
overflow auto
|
|
||||||
font-size 0.9em
|
|
||||||
|
|
||||||
> .note
|
|
||||||
border-bottom solid 1px #eee
|
|
||||||
|
|
||||||
&:last-child
|
|
||||||
border-bottom none
|
|
||||||
|
|
||||||
> .form
|
|
||||||
position absolute
|
|
||||||
left 0
|
|
||||||
bottom 0
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,108 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mkw-channel">
|
|
||||||
<template v-if="!props.compact">
|
|
||||||
<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:!@title%' }}</p>
|
|
||||||
<button @click="settings" title="%i18n:@settings%">%fa:cog%</button>
|
|
||||||
</template>
|
|
||||||
<p class="get-started" v-if="props.channel == null">%i18n:@get-started%</p>
|
|
||||||
<x-channel class="channel" :channel="channel" v-if="channel != null"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import define from '../../../common/define-widget';
|
|
||||||
import XChannel from './channel.channel.vue';
|
|
||||||
|
|
||||||
export default define({
|
|
||||||
name: 'server',
|
|
||||||
props: () => ({
|
|
||||||
channel: null,
|
|
||||||
compact: false
|
|
||||||
})
|
|
||||||
}).extend({
|
|
||||||
components: {
|
|
||||||
XChannel
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
channel: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (this.props.channel) {
|
|
||||||
this.zap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
func() {
|
|
||||||
this.props.compact = !this.props.compact;
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
settings() {
|
|
||||||
const id = window.prompt('チャンネルID');
|
|
||||||
if (!id) return;
|
|
||||||
this.props.channel = id;
|
|
||||||
this.zap();
|
|
||||||
},
|
|
||||||
zap() {
|
|
||||||
this.fetching = true;
|
|
||||||
|
|
||||||
(this as any).api('channels/show', {
|
|
||||||
channelId: this.props.channel
|
|
||||||
}).then(channel => {
|
|
||||||
this.channel = channel;
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mkw-channel
|
|
||||||
background #fff
|
|
||||||
border solid 1px rgba(#000, 0.075)
|
|
||||||
border-radius 6px
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
> .title
|
|
||||||
z-index 2
|
|
||||||
margin 0
|
|
||||||
padding 0 16px
|
|
||||||
line-height 42px
|
|
||||||
font-size 0.9em
|
|
||||||
font-weight bold
|
|
||||||
color #888
|
|
||||||
box-shadow 0 1px rgba(#000, 0.07)
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
> button
|
|
||||||
position absolute
|
|
||||||
z-index 2
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
padding 0
|
|
||||||
width 42px
|
|
||||||
font-size 0.9em
|
|
||||||
line-height 42px
|
|
||||||
color #ccc
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
&:active
|
|
||||||
color #999
|
|
||||||
|
|
||||||
> .get-started
|
|
||||||
margin 0
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
> .channel
|
|
||||||
height 200px
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -8,7 +8,6 @@ import wUsers from './users.vue';
|
||||||
import wPolls from './polls.vue';
|
import wPolls from './polls.vue';
|
||||||
import wPostForm from './post-form.vue';
|
import wPostForm from './post-form.vue';
|
||||||
import wMessaging from './messaging.vue';
|
import wMessaging from './messaging.vue';
|
||||||
import wChannel from './channel.vue';
|
|
||||||
import wProfile from './profile.vue';
|
import wProfile from './profile.vue';
|
||||||
|
|
||||||
Vue.component('mkw-notifications', wNotifications);
|
Vue.component('mkw-notifications', wNotifications);
|
||||||
|
@ -19,5 +18,4 @@ Vue.component('mkw-users', wUsers);
|
||||||
Vue.component('mkw-polls', wPolls);
|
Vue.component('mkw-polls', wPolls);
|
||||||
Vue.component('mkw-post-form', wPostForm);
|
Vue.component('mkw-post-form', wPostForm);
|
||||||
Vue.component('mkw-messaging', wMessaging);
|
Vue.component('mkw-messaging', wMessaging);
|
||||||
Vue.component('mkw-channel', wChannel);
|
|
||||||
Vue.component('mkw-profile', wProfile);
|
Vue.component('mkw-profile', wProfile);
|
||||||
|
|
|
@ -2,11 +2,17 @@
|
||||||
* Mobile Client
|
* Mobile Client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
|
|
||||||
|
import { MdCard, MdButton, MdField, MdMenu, MdList, MdSwitch } from 'vue-material/dist/components';
|
||||||
|
import 'vue-material/dist/vue-material.min.css';
|
||||||
|
import 'vue-material/dist/theme/default.css';
|
||||||
|
|
||||||
// Style
|
// Style
|
||||||
import './style.styl';
|
import './style.styl';
|
||||||
import '../../element.scss';
|
import '../../element.scss';
|
||||||
|
import '../../md.scss';
|
||||||
|
|
||||||
import init from '../init';
|
import init from '../init';
|
||||||
|
|
||||||
|
@ -34,6 +40,13 @@ import MkSettings from './views/pages/settings.vue';
|
||||||
import MkProfileSetting from './views/pages/profile-setting.vue';
|
import MkProfileSetting from './views/pages/profile-setting.vue';
|
||||||
import MkOthello from './views/pages/othello.vue';
|
import MkOthello from './views/pages/othello.vue';
|
||||||
|
|
||||||
|
Vue.use(MdCard);
|
||||||
|
Vue.use(MdButton);
|
||||||
|
Vue.use(MdField);
|
||||||
|
Vue.use(MdMenu);
|
||||||
|
Vue.use(MdList);
|
||||||
|
Vue.use(MdSwitch);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init
|
* init
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,10 +8,13 @@
|
||||||
|
|
||||||
html
|
html
|
||||||
height 100%
|
height 100%
|
||||||
background #ececed
|
background #ececed !important
|
||||||
|
|
||||||
|
// for md
|
||||||
|
transition none !important
|
||||||
|
|
||||||
&[data-darkmode]
|
&[data-darkmode]
|
||||||
background #191B22
|
background #191B22 !important
|
||||||
|
|
||||||
body
|
body
|
||||||
display flex
|
display flex
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
||||||
<span class="username">@{{ note.user | acct }}</span>
|
<span class="username"><mk-acct :user="note.user"/></span>
|
||||||
<router-link class="time" :to="note | notePage">
|
<router-link class="time" :to="note | notePage">
|
||||||
<mk-time :time="note.createdAt"/>
|
<mk-time :time="note.createdAt"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<mk-avatar class="avatar" :user="p.user"/>
|
<mk-avatar class="avatar" :user="p.user"/>
|
||||||
<div>
|
<div>
|
||||||
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
|
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
|
||||||
<span class="username">@{{ p.user | acct }}</span>
|
<span class="username"><mk-acct :user="p.user"/></span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
||||||
<span class="username">@{{ note.user | acct }}</span>
|
<span class="username"><mk-acct :user="note.user"/></span>
|
||||||
<router-link class="time" :to="note | notePage">
|
<router-link class="time" :to="note | notePage">
|
||||||
<mk-time :time="note.createdAt"/>
|
<mk-time :time="note.createdAt"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
|
||||||
<span class="username">@{{ note.user | acct }}</span>
|
<span class="username"><mk-acct :user="note.user"/></span>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
|
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
|
||||||
<router-link class="created-at" :to="note | notePage">
|
<router-link class="created-at" :to="note | notePage">
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
|
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
|
||||||
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
|
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
|
||||||
<span class="username">@{{ p.user | acct }}</span>
|
<span class="username"><mk-acct :user="p.user"/></span>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
||||||
<router-link class="created-at" :to="p | notePage">
|
<router-link class="created-at" :to="p | notePage">
|
||||||
|
@ -32,7 +32,6 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
|
|
||||||
<p v-if="p.cw != null" class="cw">
|
<p v-if="p.cw != null" class="cw">
|
||||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '隠す' : 'もっと見る' }}</span>
|
||||||
|
@ -470,9 +469,6 @@ root(isDark)
|
||||||
.mk-url-preview
|
.mk-url-preview
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .channel
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
> .tags
|
> .tags
|
||||||
margin 4px 0 0 0
|
margin 4px 0 0 0
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
|
<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
|
||||||
<p class="username">@{{ user | acct }}</p>
|
<p class="username"><mk-acct :user="user"/></p>
|
||||||
<mk-follow-button :user="user"/>
|
<mk-follow-button :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="user | userPage">{{ user | userName }}</router-link>
|
<router-link class="name" :to="user | userPage">{{ user | userName }}</router-link>
|
||||||
<span class="username">@{{ user | acct }}</span>
|
<span class="username"><mk-acct :user="user"/></span>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="description">{{ user.description }}</div>
|
<div class="description">{{ user.description }}</div>
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<span slot="header">%fa:cog%%i18n:@settings%</span>
|
<span slot="header">%fa:cog%%i18n:@settings%</span>
|
||||||
<div :class="$style.content">
|
<main>
|
||||||
<p v-html="'%i18n:!@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p>
|
<p v-html="'%i18n:!@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p>
|
||||||
<ul>
|
<div>
|
||||||
<li><router-link to="./settings/profile">%fa:user%%i18n:@profile%%fa:angle-right%</router-link></li>
|
<x-profile/>
|
||||||
<li><router-link to="./settings/twitter">%fa:B twitter%%i18n:@twitter%%fa:angle-right%</router-link></li>
|
|
||||||
<li><router-link to="./settings/signin-history">%fa:sign-in-alt%%i18n:@signin-history%%fa:angle-right%</router-link></li>
|
<md-card class="md-layout-item md-size-50 md-small-size-100">
|
||||||
</ul>
|
<md-card-header>
|
||||||
<ul>
|
<div class="md-title">%i18n:@design%</div>
|
||||||
<li><a @click="signout">%fa:power-off%%i18n:@signout%</a></li>
|
</md-card-header>
|
||||||
</ul>
|
|
||||||
<p><small>ver {{ version }} ({{ codename }})</small></p>
|
<md-card-content>
|
||||||
|
<div>
|
||||||
|
<md-switch v-model="darkmode">%i18n:@dark-mode%</md-switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<md-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</md-switch>
|
||||||
|
</div>
|
||||||
|
</md-card-content>
|
||||||
|
</md-card>
|
||||||
|
</div>
|
||||||
|
<p><small>ver {{ version }} ({{ codename }})</small></p>
|
||||||
|
</main>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,31 +31,59 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { version, codename } from '../../../config';
|
import { version, codename } from '../../../config';
|
||||||
|
|
||||||
|
import XProfile from './settings/settings.profile.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XProfile
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
codename
|
codename,
|
||||||
|
darkmode: localStorage.getItem('darkmode') == 'true'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
name(): string {
|
name(): string {
|
||||||
return Vue.filter('userName')((this as any).os.i);
|
return Vue.filter('userName')((this as any).os.i);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
darkmode() {
|
||||||
|
(this as any)._updateDarkmode_(this.darkmode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = 'Misskey | %i18n:@settings%';
|
document.title = 'Misskey | %i18n:@settings%';
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
signout() {
|
signout() {
|
||||||
(this as any).os.signout();
|
(this as any).os.signout();
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeCircleIcons(v) {
|
||||||
|
this.$store.dispatch('settings/set', {
|
||||||
|
key: 'circleIcons',
|
||||||
|
value: v
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" module>
|
<style lang="stylus" scoped>
|
||||||
.content
|
main
|
||||||
|
padding 0 16px
|
||||||
|
|
||||||
|
> div
|
||||||
|
> *
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
> p
|
> p
|
||||||
display block
|
display block
|
||||||
|
|
126
src/client/app/mobile/views/pages/settings/settings.profile.vue
Normal file
126
src/client/app/mobile/views/pages/settings/settings.profile.vue
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<template>
|
||||||
|
<md-card class="md-layout-item md-size-50 md-small-size-100">
|
||||||
|
<md-card-header>
|
||||||
|
<div class="md-title">%i18n:@title%</div>
|
||||||
|
</md-card-header>
|
||||||
|
|
||||||
|
<md-card-content>
|
||||||
|
<md-field>
|
||||||
|
<label>%i18n:@name%</label>
|
||||||
|
<md-input v-model="name" :disabled="saving"/>
|
||||||
|
</md-field>
|
||||||
|
|
||||||
|
<md-field>
|
||||||
|
<label>%i18n:@location%</label>
|
||||||
|
<md-input v-model="location" :disabled="saving"/>
|
||||||
|
</md-field>
|
||||||
|
|
||||||
|
<md-field>
|
||||||
|
<label>%i18n:@description%</label>
|
||||||
|
<md-textarea v-model="description" :disabled="saving"/>
|
||||||
|
</md-field>
|
||||||
|
|
||||||
|
<md-field>
|
||||||
|
<label>%i18n:@birthday%</label>
|
||||||
|
<md-input type="date" v-model="birthday" :disabled="saving"/>
|
||||||
|
</md-field>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="md-body-2">%i18n:@avatar%</div>
|
||||||
|
<md-menu md-direction="bottom-end" :md-close-on-select="true">
|
||||||
|
<md-button md-menu-trigger>%i18n:@set-avatar%</md-button>
|
||||||
|
<md-menu-content>
|
||||||
|
<md-menu-item @click="uploadAvatar">%i18n:@upload-avatar%</md-menu-item>
|
||||||
|
<md-menu-item @click="chooseAvatar">%i18n:@choose-avatar%</md-menu-item>
|
||||||
|
</md-menu-content>
|
||||||
|
</md-menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="md-body-2">%i18n:@banner%</div>
|
||||||
|
<md-menu md-direction="bottom-end" :md-close-on-select="true">
|
||||||
|
<md-button md-menu-trigger>%i18n:@set-banner%</md-button>
|
||||||
|
<md-menu-content>
|
||||||
|
<md-menu-item @click="uploadAvatar">%i18n:@upload-banner%</md-menu-item>
|
||||||
|
<md-menu-item @click="chooseAvatar">%i18n:@choose-banner%</md-menu-item>
|
||||||
|
</md-menu-content>
|
||||||
|
</md-menu>
|
||||||
|
</div>
|
||||||
|
</md-card-content>
|
||||||
|
|
||||||
|
<md-card-actions>
|
||||||
|
<md-button class="md-primary" :disabled="saving" @click="save">%i18n:@save%</md-button>
|
||||||
|
</md-card-actions>
|
||||||
|
</md-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: null,
|
||||||
|
location: null,
|
||||||
|
description: null,
|
||||||
|
birthday: null,
|
||||||
|
saving: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.name = (this as any).os.i.name || '';
|
||||||
|
this.location = (this as any).os.i.profile.location;
|
||||||
|
this.description = (this as any).os.i.description;
|
||||||
|
this.birthday = (this as any).os.i.profile.birthday;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
chooseAvatar() {
|
||||||
|
(this as any).apis.chooseDriveFile({
|
||||||
|
multiple: false
|
||||||
|
}).then(file => {
|
||||||
|
this.avatarSaving = true;
|
||||||
|
|
||||||
|
(this as any).api('i/update', {
|
||||||
|
avatarId: file.id
|
||||||
|
}).then(() => {
|
||||||
|
this.avatarSaving = false;
|
||||||
|
alert('%i18n:!@avatar-saved%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
chooseBanner() {
|
||||||
|
(this as any).apis.chooseDriveFile({
|
||||||
|
multiple: false
|
||||||
|
}).then(file => {
|
||||||
|
this.bannerSaving = true;
|
||||||
|
|
||||||
|
(this as any).api('i/update', {
|
||||||
|
bannerId: file.id
|
||||||
|
}).then(() => {
|
||||||
|
this.bannerSaving = false;
|
||||||
|
alert('%i18n:!@banner-saved%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
uploadAvatar() {
|
||||||
|
// a
|
||||||
|
},
|
||||||
|
uploadBanner() {
|
||||||
|
// a
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.saving = true;
|
||||||
|
|
||||||
|
(this as any).api('i/update', {
|
||||||
|
name: this.name || null,
|
||||||
|
location: this.location || null,
|
||||||
|
description: this.description || null,
|
||||||
|
birthday: this.birthday || null
|
||||||
|
}).then(() => {
|
||||||
|
this.saving = false;
|
||||||
|
alert('%i18n:!@saved%');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -15,7 +15,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>{{ user | userName }}</h1>
|
<h1>{{ user | userName }}</h1>
|
||||||
<span class="username">@{{ user | acct }}</span>
|
<span class="username"><mk-acct :user="user"/></span>
|
||||||
<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
|
<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">{{ user.description }}</div>
|
<div class="description">{{ user.description }}</div>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
<option value="photo-stream">フォトストリーム</option>
|
<option value="photo-stream">フォトストリーム</option>
|
||||||
<option value="slideshow">スライドショー</option>
|
<option value="slideshow">スライドショー</option>
|
||||||
<option value="version">バージョン</option>
|
<option value="version">バージョン</option>
|
||||||
<option value="access-log">アクセスログ</option>
|
|
||||||
<option value="server">サーバー情報</option>
|
<option value="server">サーバー情報</option>
|
||||||
<option value="donation">寄付のお願い</option>
|
<option value="donation">寄付のお願い</option>
|
||||||
<option value="nav">ナビゲーション</option>
|
<option value="nav">ナビゲーション</option>
|
||||||
|
|
13
src/client/md.scss
Normal file
13
src/client/md.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/* SEE: https://vuematerial.io/themes/configuration */
|
||||||
|
|
||||||
|
@import '../const.json';
|
||||||
|
|
||||||
|
@import "~vue-material/dist/theme/engine";
|
||||||
|
|
||||||
|
@include md-register-theme("default", (
|
||||||
|
primary: $themeColor,
|
||||||
|
accent: md-get-palette-color(red, A200)
|
||||||
|
));
|
||||||
|
|
||||||
|
@import "~vue-material/dist/components/MdButton/theme";
|
||||||
|
@import "~vue-material/dist/components/MdField/theme";
|
|
@ -1,13 +0,0 @@
|
||||||
import * as mongo from 'mongodb';
|
|
||||||
import db from '../db/mongodb';
|
|
||||||
|
|
||||||
const ChannelWatching = db.get<IChannelWatching>('channelWatching');
|
|
||||||
export default ChannelWatching;
|
|
||||||
|
|
||||||
export interface IChannelWatching {
|
|
||||||
_id: mongo.ObjectID;
|
|
||||||
createdAt: Date;
|
|
||||||
deletedAt: Date;
|
|
||||||
channelId: mongo.ObjectID;
|
|
||||||
userId: mongo.ObjectID;
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
import * as mongo from 'mongodb';
|
|
||||||
import deepcopy = require('deepcopy');
|
|
||||||
import { IUser } from './user';
|
|
||||||
import Watching from './channel-watching';
|
|
||||||
import db from '../db/mongodb';
|
|
||||||
|
|
||||||
const Channel = db.get<IChannel>('channels');
|
|
||||||
export default Channel;
|
|
||||||
|
|
||||||
export type IChannel = {
|
|
||||||
_id: mongo.ObjectID;
|
|
||||||
createdAt: Date;
|
|
||||||
title: string;
|
|
||||||
userId: mongo.ObjectID;
|
|
||||||
index: number;
|
|
||||||
watchingCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pack a channel for API response
|
|
||||||
*
|
|
||||||
* @param channel target
|
|
||||||
* @param me? serializee
|
|
||||||
* @return response
|
|
||||||
*/
|
|
||||||
export const pack = (
|
|
||||||
channel: string | mongo.ObjectID | IChannel,
|
|
||||||
me?: string | mongo.ObjectID | IUser
|
|
||||||
) => new Promise<any>(async (resolve, reject) => {
|
|
||||||
|
|
||||||
let _channel: any;
|
|
||||||
|
|
||||||
// Populate the channel if 'channel' is ID
|
|
||||||
if (mongo.ObjectID.prototype.isPrototypeOf(channel)) {
|
|
||||||
_channel = await Channel.findOne({
|
|
||||||
_id: channel
|
|
||||||
});
|
|
||||||
} else if (typeof channel === 'string') {
|
|
||||||
_channel = await Channel.findOne({
|
|
||||||
_id: new mongo.ObjectID(channel)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_channel = deepcopy(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename _id to id
|
|
||||||
_channel.id = _channel._id;
|
|
||||||
delete _channel._id;
|
|
||||||
|
|
||||||
// Remove needless properties
|
|
||||||
delete _channel.userId;
|
|
||||||
|
|
||||||
// Me
|
|
||||||
const meId: mongo.ObjectID = me
|
|
||||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
|
||||||
? me as mongo.ObjectID
|
|
||||||
: typeof me === 'string'
|
|
||||||
? new mongo.ObjectID(me)
|
|
||||||
: (me as IUser)._id
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (me) {
|
|
||||||
//#region Watchしているかどうか
|
|
||||||
const watch = await Watching.findOne({
|
|
||||||
userId: meId,
|
|
||||||
channelId: _channel.id,
|
|
||||||
deletedAt: { $exists: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
_channel.isWatching = watch !== null;
|
|
||||||
//#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(_channel);
|
|
||||||
});
|
|
|
@ -4,7 +4,6 @@ import rap from '@prezzemolo/rap';
|
||||||
import db from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import { IUser, pack as packUser } from './user';
|
import { IUser, pack as packUser } from './user';
|
||||||
import { pack as packApp } from './app';
|
import { pack as packApp } from './app';
|
||||||
import { pack as packChannel } from './channel';
|
|
||||||
import PollVote, { deletePollVote } from './poll-vote';
|
import PollVote, { deletePollVote } from './poll-vote';
|
||||||
import Reaction, { deleteNoteReaction } from './note-reaction';
|
import Reaction, { deleteNoteReaction } from './note-reaction';
|
||||||
import { pack as packFile } from './drive-file';
|
import { pack as packFile } from './drive-file';
|
||||||
|
@ -29,7 +28,6 @@ export function isValidCw(text: string): boolean {
|
||||||
|
|
||||||
export type INote = {
|
export type INote = {
|
||||||
_id: mongo.ObjectID;
|
_id: mongo.ObjectID;
|
||||||
channelId: mongo.ObjectID;
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
deletedAt: Date;
|
deletedAt: Date;
|
||||||
mediaIds: mongo.ObjectID[];
|
mediaIds: mongo.ObjectID[];
|
||||||
|
@ -258,11 +256,6 @@ export const pack = async (
|
||||||
_note.app = packApp(_note.appId);
|
_note.app = packApp(_note.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate channel
|
|
||||||
if (_note.channelId) {
|
|
||||||
_note.channel = packChannel(_note.channelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate media
|
// Populate media
|
||||||
_note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
|
_note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
|
||||||
packFile(fileId)
|
packFile(fileId)
|
||||||
|
|
|
@ -45,10 +45,6 @@ class MisskeyEvent {
|
||||||
this.publish(`othello-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
|
this.publish(`othello-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public publishChannelStream(channelId: ID, type: string, value?: any): void {
|
|
||||||
this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public publishLocalTimelineStream(note: any): void {
|
public publishLocalTimelineStream(note: any): void {
|
||||||
this.redisClient.publish('misskey:local-timeline', JSON.stringify(note));
|
this.redisClient.publish('misskey:local-timeline', JSON.stringify(note));
|
||||||
}
|
}
|
||||||
|
@ -79,4 +75,3 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
|
||||||
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
|
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
|
||||||
export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
|
export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
|
||||||
export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
|
export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
|
||||||
export const publishChannelStream = ev.publishChannelStream.bind(ev);
|
|
||||||
|
|
|
@ -9,9 +9,6 @@ const summarize = (note: any): string => {
|
||||||
|
|
||||||
let summary = '';
|
let summary = '';
|
||||||
|
|
||||||
// チャンネル
|
|
||||||
summary += note.channel ? `${note.channel.title}:` : '';
|
|
||||||
|
|
||||||
// 本文
|
// 本文
|
||||||
summary += note.text ? note.text : '';
|
summary += note.text ? note.text : '';
|
||||||
|
|
||||||
|
|
|
@ -621,33 +621,7 @@ const endpoints: Endpoint[] = [
|
||||||
name: 'messaging/messages/create',
|
name: 'messaging/messages/create',
|
||||||
withCredential: true,
|
withCredential: true,
|
||||||
kind: 'messaging-write'
|
kind: 'messaging-write'
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels/create',
|
|
||||||
withCredential: true,
|
|
||||||
limit: {
|
|
||||||
duration: ms('1hour'),
|
|
||||||
max: 3,
|
|
||||||
minInterval: ms('10seconds')
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels/show'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels/notes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels/watch',
|
|
||||||
withCredential: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels/unwatch',
|
|
||||||
withCredential: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'channels'
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default endpoints;
|
export default endpoints;
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../cafy-id';
|
|
||||||
import Channel, { pack } from '../../../models/channel';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all channels
|
|
||||||
*
|
|
||||||
* @param {any} params
|
|
||||||
* @param {any} me
|
|
||||||
* @return {Promise<any>}
|
|
||||||
*/
|
|
||||||
module.exports = (params, me) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'limit' parameter
|
|
||||||
const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
|
|
||||||
if (limitErr) return rej('invalid limit param');
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
|
|
||||||
if (sinceIdErr) return rej('invalid sinceId param');
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
|
|
||||||
if (untilIdErr) return rej('invalid untilId param');
|
|
||||||
|
|
||||||
// Check if both of sinceId and untilId is specified
|
|
||||||
if (sinceId && untilId) {
|
|
||||||
return rej('cannot set sinceId and untilId');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct query
|
|
||||||
const sort = {
|
|
||||||
_id: -1
|
|
||||||
};
|
|
||||||
const query = {} as any;
|
|
||||||
if (sinceId) {
|
|
||||||
sort._id = 1;
|
|
||||||
query._id = {
|
|
||||||
$gt: sinceId
|
|
||||||
};
|
|
||||||
} else if (untilId) {
|
|
||||||
query._id = {
|
|
||||||
$lt: untilId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue query
|
|
||||||
const channels = await Channel
|
|
||||||
.find(query, {
|
|
||||||
limit: limit,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
res(await Promise.all(channels.map(async channel =>
|
|
||||||
await pack(channel, me))));
|
|
||||||
});
|
|
|
@ -1,35 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy';
|
|
||||||
import Channel from '../../../../models/channel';
|
|
||||||
import Watching from '../../../../models/channel-watching';
|
|
||||||
import { pack } from '../../../../models/channel';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a channel
|
|
||||||
*/
|
|
||||||
module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'title' parameter
|
|
||||||
const [title, titleErr] = $.str.range(1, 100).get(params.title);
|
|
||||||
if (titleErr) return rej('invalid title param');
|
|
||||||
|
|
||||||
// Create a channel
|
|
||||||
const channel = await Channel.insert({
|
|
||||||
createdAt: new Date(),
|
|
||||||
userId: user._id,
|
|
||||||
title: title,
|
|
||||||
index: 0,
|
|
||||||
watchingCount: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
// Response
|
|
||||||
res(await pack(channel));
|
|
||||||
|
|
||||||
// Create Watching
|
|
||||||
await Watching.insert({
|
|
||||||
createdAt: new Date(),
|
|
||||||
userId: user._id,
|
|
||||||
channelId: channel._id
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,74 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
|
||||||
import { default as Channel, IChannel } from '../../../../models/channel';
|
|
||||||
import Note, { pack } from '../../../../models/note';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a notes of a channel
|
|
||||||
*/
|
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'limit' parameter
|
|
||||||
const [limit = 1000, limitErr] = $.num.optional().range(1, 1000).get(params.limit);
|
|
||||||
if (limitErr) return rej('invalid limit param');
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
|
|
||||||
if (sinceIdErr) return rej('invalid sinceId param');
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
|
|
||||||
if (untilIdErr) return rej('invalid untilId param');
|
|
||||||
|
|
||||||
// Check if both of sinceId and untilId is specified
|
|
||||||
if (sinceId && untilId) {
|
|
||||||
return rej('cannot set sinceId and untilId');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get 'channelId' parameter
|
|
||||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
|
||||||
if (channelIdErr) return rej('invalid channelId param');
|
|
||||||
|
|
||||||
// Fetch channel
|
|
||||||
const channel: IChannel = await Channel.findOne({
|
|
||||||
_id: channelId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (channel === null) {
|
|
||||||
return rej('channel not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region Construct query
|
|
||||||
const sort = {
|
|
||||||
_id: -1
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
channelId: channel._id
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
if (sinceId) {
|
|
||||||
sort._id = 1;
|
|
||||||
query._id = {
|
|
||||||
$gt: sinceId
|
|
||||||
};
|
|
||||||
} else if (untilId) {
|
|
||||||
query._id = {
|
|
||||||
$lt: untilId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//#endregion Construct query
|
|
||||||
|
|
||||||
// Issue query
|
|
||||||
const notes = await Note
|
|
||||||
.find(query, {
|
|
||||||
limit: limit,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
res(await Promise.all(notes.map(async (note) =>
|
|
||||||
await pack(note, user)
|
|
||||||
)));
|
|
||||||
});
|
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
|
||||||
import Channel, { IChannel, pack } from '../../../../models/channel';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a channel
|
|
||||||
*/
|
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'channelId' parameter
|
|
||||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
|
||||||
if (channelIdErr) return rej('invalid channelId param');
|
|
||||||
|
|
||||||
// Fetch channel
|
|
||||||
const channel: IChannel = await Channel.findOne({
|
|
||||||
_id: channelId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (channel === null) {
|
|
||||||
return rej('channel not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
res(await pack(channel, user));
|
|
||||||
});
|
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
|
||||||
import Channel from '../../../../models/channel';
|
|
||||||
import Watching from '../../../../models/channel-watching';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unwatch a channel
|
|
||||||
*/
|
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'channelId' parameter
|
|
||||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
|
||||||
if (channelIdErr) return rej('invalid channelId param');
|
|
||||||
|
|
||||||
//#region Fetch channel
|
|
||||||
const channel = await Channel.findOne({
|
|
||||||
_id: channelId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (channel === null) {
|
|
||||||
return rej('channel not found');
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Check whether not watching
|
|
||||||
const exist = await Watching.findOne({
|
|
||||||
userId: user._id,
|
|
||||||
channelId: channel._id,
|
|
||||||
deletedAt: { $exists: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exist === null) {
|
|
||||||
return rej('already not watching');
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
// Delete watching
|
|
||||||
await Watching.update({
|
|
||||||
_id: exist._id
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
deletedAt: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send response
|
|
||||||
res();
|
|
||||||
|
|
||||||
// Decrement watching count
|
|
||||||
Channel.update(channel._id, {
|
|
||||||
$inc: {
|
|
||||||
watchingCount: -1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,54 +0,0 @@
|
||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
|
||||||
import Channel from '../../../../models/channel';
|
|
||||||
import Watching from '../../../../models/channel-watching';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch a channel
|
|
||||||
*/
|
|
||||||
module.exports = (params, user) => new Promise(async (res, rej) => {
|
|
||||||
// Get 'channelId' parameter
|
|
||||||
const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
|
|
||||||
if (channelIdErr) return rej('invalid channelId param');
|
|
||||||
|
|
||||||
//#region Fetch channel
|
|
||||||
const channel = await Channel.findOne({
|
|
||||||
_id: channelId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (channel === null) {
|
|
||||||
return rej('channel not found');
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Check whether already watching
|
|
||||||
const exist = await Watching.findOne({
|
|
||||||
userId: user._id,
|
|
||||||
channelId: channel._id,
|
|
||||||
deletedAt: { $exists: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exist !== null) {
|
|
||||||
return rej('already watching');
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
// Create Watching
|
|
||||||
await Watching.insert({
|
|
||||||
createdAt: new Date(),
|
|
||||||
userId: user._id,
|
|
||||||
channelId: channel._id
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send response
|
|
||||||
res();
|
|
||||||
|
|
||||||
// Increment watching count
|
|
||||||
Channel.update(channel._id, {
|
|
||||||
$inc: {
|
|
||||||
watchingCount: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -4,7 +4,6 @@
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||||
import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
|
import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
|
||||||
import User, { ILocalUser } from '../../../../models/user';
|
import User, { ILocalUser } from '../../../../models/user';
|
||||||
import Channel, { IChannel } from '../../../../models/channel';
|
|
||||||
import DriveFile from '../../../../models/drive-file';
|
import DriveFile from '../../../../models/drive-file';
|
||||||
import create from '../../../../services/note/create';
|
import create from '../../../../services/note/create';
|
||||||
import { IApp } from '../../../../models/app';
|
import { IApp } from '../../../../models/app';
|
||||||
|
@ -89,7 +88,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
||||||
if (renoteIdErr) return rej('invalid renoteId');
|
if (renoteIdErr) return rej('invalid renoteId');
|
||||||
|
|
||||||
let renote: INote = null;
|
let renote: INote = null;
|
||||||
let isQuote = false;
|
|
||||||
if (renoteId !== undefined) {
|
if (renoteId !== undefined) {
|
||||||
// Fetch renote to note
|
// Fetch renote to note
|
||||||
renote = await Note.findOne({
|
renote = await Note.findOne({
|
||||||
|
@ -101,8 +99,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
||||||
} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
|
} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
|
||||||
return rej('cannot renote to renote');
|
return rej('cannot renote to renote');
|
||||||
}
|
}
|
||||||
|
|
||||||
isQuote = text != null || files != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'replyId' parameter
|
// Get 'replyId' parameter
|
||||||
|
@ -126,47 +122,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'channelId' parameter
|
|
||||||
const [channelId, channelIdErr] = $.type(ID).optional().get(params.channelId);
|
|
||||||
if (channelIdErr) return rej('invalid channelId');
|
|
||||||
|
|
||||||
let channel: IChannel = null;
|
|
||||||
if (channelId !== undefined) {
|
|
||||||
// Fetch channel
|
|
||||||
channel = await Channel.findOne({
|
|
||||||
_id: channelId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (channel === null) {
|
|
||||||
return rej('channel not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返信対象の投稿がこのチャンネルじゃなかったらダメ
|
|
||||||
if (reply && !channelId.equals(reply.channelId)) {
|
|
||||||
return rej('チャンネル内部からチャンネル外部の投稿に返信することはできません');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renote対象の投稿がこのチャンネルじゃなかったらダメ
|
|
||||||
if (renote && !channelId.equals(renote.channelId)) {
|
|
||||||
return rej('チャンネル内部からチャンネル外部の投稿をRenoteすることはできません');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 引用ではないRenoteはダメ
|
|
||||||
if (renote && !isQuote) {
|
|
||||||
return rej('チャンネル内部では引用ではないRenoteをすることはできません');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 返信対象の投稿がチャンネルへの投稿だったらダメ
|
|
||||||
if (reply && reply.channelId != null) {
|
|
||||||
return rej('チャンネル外部からチャンネル内部の投稿に返信することはできません');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renote対象の投稿がチャンネルへの投稿だったらダメ
|
|
||||||
if (renote && renote.channelId != null) {
|
|
||||||
return rej('チャンネル外部からチャンネル内部の投稿をRenoteすることはできません');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get 'poll' parameter
|
// Get 'poll' parameter
|
||||||
const [poll, pollErr] = $.obj.optional().strict()
|
const [poll, pollErr] = $.obj.optional().strict()
|
||||||
.have('choices', $.arr($.str)
|
.have('choices', $.arr($.str)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
import $ from 'cafy'; import ID from '../../../../cafy-id';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import ChannelWatching from '../../../../models/channel-watching';
|
|
||||||
import { getFriends } from '../../common/get-friends';
|
import { getFriends } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
|
|
||||||
|
@ -45,18 +44,11 @@ module.exports = async (params, user, app) => {
|
||||||
const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
|
const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
|
||||||
if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
|
if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
|
||||||
|
|
||||||
const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([
|
const [followings, mutedUserIds] = await Promise.all([
|
||||||
// フォローを取得
|
// フォローを取得
|
||||||
// Fetch following
|
// Fetch following
|
||||||
getFriends(user._id),
|
getFriends(user._id),
|
||||||
|
|
||||||
// Watchしているチャンネルを取得
|
|
||||||
ChannelWatching.find({
|
|
||||||
userId: user._id,
|
|
||||||
// 削除されたドキュメントは除く
|
|
||||||
deletedAt: { $exists: false }
|
|
||||||
}).then(watches => watches.map(w => w.channelId)),
|
|
||||||
|
|
||||||
// ミュートしているユーザーを取得
|
// ミュートしているユーザーを取得
|
||||||
Mute.find({
|
Mute.find({
|
||||||
muterId: user._id
|
muterId: user._id
|
||||||
|
@ -93,26 +85,9 @@ module.exports = async (params, user, app) => {
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
$and: [{
|
$and: [{
|
||||||
$or: [{
|
// フォローしている人の投稿
|
||||||
$and: [{
|
$or: followQuery,
|
||||||
// フォローしている人のタイムラインへの投稿
|
|
||||||
$or: followQuery
|
|
||||||
}, {
|
|
||||||
// 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
|
|
||||||
$or: [{
|
|
||||||
channelId: {
|
|
||||||
$exists: false
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
channelId: null
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
// Watchしているチャンネルへの投稿
|
|
||||||
channelId: {
|
|
||||||
$in: watchingChannelIds
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
// mute
|
// mute
|
||||||
userId: {
|
userId: {
|
||||||
$nin: mutedUserIds
|
$nin: mutedUserIds
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as websocket from 'websocket';
|
|
||||||
import * as redis from 'redis';
|
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
|
||||||
|
|
||||||
export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void {
|
|
||||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
|
||||||
const channel = q.channel;
|
|
||||||
|
|
||||||
// Subscribe channel stream
|
|
||||||
subscriber.subscribe(`misskey:channel-stream:${channel}`);
|
|
||||||
subscriber.on('message', (_, data) => {
|
|
||||||
connection.send(data);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -14,7 +14,6 @@ import othelloGameStream from './stream/othello-game';
|
||||||
import othelloStream from './stream/othello';
|
import othelloStream from './stream/othello';
|
||||||
import serverStream from './stream/server';
|
import serverStream from './stream/server';
|
||||||
import requestsStream from './stream/requests';
|
import requestsStream from './stream/requests';
|
||||||
import channelStream from './stream/channel';
|
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
import authenticate from './authenticate';
|
import authenticate from './authenticate';
|
||||||
|
|
||||||
|
@ -48,11 +47,6 @@ module.exports = (server: http.Server) => {
|
||||||
subscriber.quit();
|
subscriber.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (request.resourceURL.pathname === '/channel') {
|
|
||||||
channelStream(request, connection, subscriber);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
const q = request.resourceURL.query as ParsedUrlQuery;
|
||||||
const [user, app] = await authenticate(q.i as string);
|
const [user, app] = await authenticate(q.i as string);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue