Compare commits
164 Commits
Author | SHA1 | Date | |
---|---|---|---|
1bec4e2d12 | |||
03cd1d27bf | |||
9427a756c9 | |||
d32b2a8ce5 | |||
15473b4368 | |||
54de0dc4a7 | |||
0162eaf826 | |||
572cfafbe1 | |||
4d6335ce9a | |||
1c9c4af9f1 | |||
a6844ebc9d | |||
072492c29b | |||
99da4f9839 | |||
88664486af | |||
80daf7c749 | |||
beb2f7e558 | |||
6243184c95 | |||
1b3baef966 | |||
98f38ee29b | |||
09b82bfea4 | |||
937f686264 | |||
9bc9cbac21 | |||
6024550158 | |||
4ae5f82171 | |||
6d2c9dcee9 | |||
0f1b0e1870 | |||
81c682cdc8 | |||
ab9fa67d9f | |||
9537fce335 | |||
9d97e7e348 | |||
ebe7939412 | |||
807e3e8ca7 | |||
a59faf9117 | |||
d786036155 | |||
61d6ed5489 | |||
b38200d48a | |||
a0c396a842 | |||
88fbc53e37 | |||
a2206b2d52 | |||
a95ff447d7 | |||
49dbd7f9d2 | |||
2ad2779096 | |||
23045369aa | |||
116faf26e6 | |||
2582b8d132 | |||
63f7941073 | |||
676f026085 | |||
a13319fd86 | |||
be8765278c | |||
c8bb3dc209 | |||
ea16befb73 | |||
20b1bb7681 | |||
bd10eb50eb | |||
d47c0eb31a | |||
177e8bb19f | |||
d156111637 | |||
8c13d3e50b | |||
6ff01016f0 | |||
5d659da012 | |||
28e7552a1a | |||
53d264814b | |||
2d6b20d34b | |||
99073b56df | |||
5dce81c0db | |||
be82d845a4 | |||
f49ccd0cd3 | |||
69d83f535d | |||
c7988fb6f5 | |||
3961fd08c9 | |||
e3faf64061 | |||
ed83993e15 | |||
0f8847bb74 | |||
a72cfa7535 | |||
514b74a19d | |||
a2c124306f | |||
273f67e268 | |||
2870a7e463 | |||
935b074a7a | |||
9d9c609bfb | |||
f6a664f181 | |||
fce68d1f75 | |||
88739c2444 | |||
7e2f10fce3 | |||
a494c3a5cc | |||
d6bb702883 | |||
d15a972c68 | |||
2ae7d31725 | |||
2e329b1888 | |||
522d40328b | |||
2ecbff45bf | |||
b6f7282c13 | |||
65e5cfa68e | |||
10e59957d1 | |||
4f74373df3 | |||
2d414bbf86 | |||
a199969b81 | |||
3aef5e6748 | |||
2b536a7443 | |||
20fe68de05 | |||
c7684b59de | |||
a7237d157a | |||
35f91fa280 | |||
299ac32225 | |||
a038738d72 | |||
2b0a919fb5 | |||
946c706913 | |||
89b5d976ee | |||
6f679bb6b4 | |||
db4e7b0e16 | |||
9ca942490d | |||
ebcf249c8b | |||
939c487503 | |||
981a8b267e | |||
9531da80a0 | |||
e1109b168c | |||
b7c70039aa | |||
17b6f6cf2a | |||
dd88483ba4 | |||
0ff27f65b3 | |||
b1655740df | |||
6d562aece1 | |||
2182c3372b | |||
d3331bfe82 | |||
cfc4a2e8b4 | |||
36c41c8eb3 | |||
d255157e6e | |||
c12e07277d | |||
06b4fb5095 | |||
8fafdcb428 | |||
537a606bb6 | |||
3dc7a4463c | |||
fd6ff05b60 | |||
1a159e41b8 | |||
23533cdd16 | |||
2f598b8fa1 | |||
bca349fec1 | |||
719fac6480 | |||
1012b2b2c7 | |||
5149be4b1b | |||
d12deeb0d8 | |||
9df81d1939 | |||
3be0079868 | |||
9b253ccb3a | |||
dded76099c | |||
41a7ec7d3d | |||
168c773ba0 | |||
9abed92196 | |||
4a75e3602a | |||
1a689f6641 | |||
08d7ae11d6 | |||
9535759787 | |||
f8fc31f14a | |||
b74bf97761 | |||
a090b908bd | |||
3046821026 | |||
e94c73efe2 | |||
e85f9f4aa5 | |||
ad67886f96 | |||
5df0e102fd | |||
b9290a021b | |||
129ce93868 | |||
5f41e5d6d0 | |||
c706d030ea | |||
34716a34f8 |
@ -1,12 +1,8 @@
|
||||
maintainer: '@syuilo'
|
||||
url: 'https://misskey.xyz'
|
||||
secondary_url: 'https://himasaku.net'
|
||||
maintainer:
|
||||
name: syuilo
|
||||
url: 'https://syuilo.com'
|
||||
url: 'http://misskey.local'
|
||||
port: 80
|
||||
https:
|
||||
enable: false
|
||||
key: null
|
||||
cert: null
|
||||
ca: null
|
||||
mongodb:
|
||||
host: localhost
|
||||
port: 27017
|
||||
@ -21,6 +17,3 @@ elasticsearch:
|
||||
host: localhost
|
||||
port: 9200
|
||||
pass: ''
|
||||
recaptcha:
|
||||
site_key: hima
|
||||
secret_key: saku
|
||||
|
@ -1,12 +1,8 @@
|
||||
maintainer: '@syuilo'
|
||||
url: 'https://misskey.xyz'
|
||||
secondary_url: 'https://himasaku.net'
|
||||
maintainer:
|
||||
name: syuilo
|
||||
url: 'https://syuilo.com'
|
||||
url: 'http://misskey.local'
|
||||
port: 80
|
||||
https:
|
||||
enable: false
|
||||
key: null
|
||||
cert: null
|
||||
ca: null
|
||||
mongodb:
|
||||
host: localhost
|
||||
port: 27017
|
||||
@ -21,6 +17,3 @@ elasticsearch:
|
||||
host: localhost
|
||||
port: 9200
|
||||
pass: ''
|
||||
recaptcha:
|
||||
site_key: hima
|
||||
secret_key: saku
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -3,16 +3,16 @@ meta:
|
||||
lang: "Deutsch"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
misskey: "Ein ⭐ des Fediversums"
|
||||
about-title: "Ein ⭐ des Fediversums."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
intro:
|
||||
title: "Misskeyって?"
|
||||
title: "Was ist Misskey?"
|
||||
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
||||
features: "特徴"
|
||||
rich-contents: "投稿"
|
||||
features: "Funktionen"
|
||||
rich-contents: "Notizen"
|
||||
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
|
||||
reaction: "リアクション"
|
||||
reaction: "Reaktionen"
|
||||
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
|
||||
ui: "インターフェース"
|
||||
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
|
||||
@ -23,7 +23,7 @@ common:
|
||||
detected: "広告ブロッカーを無効にしてください"
|
||||
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "閉じる"
|
||||
close: "Schließen"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
||||
got-it: "わかった"
|
||||
customization-tips:
|
||||
@ -34,13 +34,13 @@ common:
|
||||
paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
|
||||
gotit: "Got it!"
|
||||
notification:
|
||||
file-uploaded: "ファイルがアップロードされました"
|
||||
file-uploaded: "Datei hochgeladen!"
|
||||
message-from: "{}さんからメッセージ:"
|
||||
reversi-invited: "対局への招待があります"
|
||||
reversi-invited-by: "{}さんから"
|
||||
notified-by: "{}さんから"
|
||||
reply-from: "{}さんから返信:"
|
||||
quoted-by: "{}さんが引用:"
|
||||
notified-by: "Benachrichtigt von {}:"
|
||||
reply-from: "Antwort von {}:"
|
||||
quoted-by: "Zitiert von {}:"
|
||||
time:
|
||||
unknown: "Unbekannt"
|
||||
future: "Zukunft"
|
||||
@ -53,7 +53,7 @@ common:
|
||||
months_ago: "vor {0} Monat{0:en}"
|
||||
years_ago: "vor {} Jahr{0:en}"
|
||||
month-and-day: "{month}月 {day}日"
|
||||
trash: "ゴミ箱"
|
||||
trash: "Papierkorb"
|
||||
weekday-short:
|
||||
sunday: "So"
|
||||
monday: "Mo"
|
||||
@ -63,13 +63,13 @@ common:
|
||||
friday: "Fr"
|
||||
saturday: "Sa"
|
||||
weekday:
|
||||
sunday: "日曜日"
|
||||
monday: "月曜日"
|
||||
tuesday: "火曜日"
|
||||
wednesday: "水曜日"
|
||||
thursday: "木曜日"
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
sunday: "Sonntag"
|
||||
monday: "Montag"
|
||||
tuesday: "Dienstag"
|
||||
wednesday: "Mittwoch"
|
||||
thursday: "Donnerstag"
|
||||
friday: "Freitag"
|
||||
saturday: "Samstag"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
love: "Lieben"
|
||||
@ -82,10 +82,10 @@ common:
|
||||
rip: "RIP"
|
||||
pudding: "Pudding"
|
||||
note-visibility:
|
||||
public: "公開"
|
||||
public: "Öffentlich"
|
||||
home: "ホーム"
|
||||
home-desc: "ホームタイムラインにのみ公開"
|
||||
followers: "フォロワー"
|
||||
followers: "Abonnenten"
|
||||
followers-desc: "自分のフォロワーにのみ公開"
|
||||
specified: "ダイレクト"
|
||||
specified-desc: "指定したユーザーにのみ公開"
|
||||
@ -122,9 +122,9 @@ common:
|
||||
turn-of: "{}のターンです"
|
||||
past-turn-of: "{}のターン"
|
||||
won: "{}の勝ち"
|
||||
black: "黒"
|
||||
white: "白"
|
||||
total: "合計"
|
||||
black: "Schwarz"
|
||||
white: "Weiß"
|
||||
total: "Gesamt"
|
||||
this-turn: "{}ターン目"
|
||||
widgets:
|
||||
analog-clock: "Analoge Uhr"
|
||||
@ -142,23 +142,23 @@ common:
|
||||
broadcast: "ブロードキャスト"
|
||||
notifications: "Benachrichtigungen"
|
||||
users: "Empfohlene Benutzer"
|
||||
polls: "アンケート"
|
||||
polls: "Umfrage"
|
||||
post-form: "Beitragsform"
|
||||
messaging: "Nachrichten"
|
||||
server: "Server-Info"
|
||||
donation: "Spenden"
|
||||
nav: "Navigation"
|
||||
tips: "Tipps"
|
||||
hashtags: "ハッシュタグ"
|
||||
hashtags: "Hashtags"
|
||||
deck:
|
||||
widgets: "Widget hinzufügen:"
|
||||
home: "Startseite"
|
||||
local: "Lokal"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
hashtag: "Hashtag"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
mentions: "Erwähnungen"
|
||||
direct: "Direktnachrichten"
|
||||
notifications: "Mitteilungen"
|
||||
list: "Listen"
|
||||
swap-left: "Nach links"
|
||||
@ -182,10 +182,10 @@ auth/views/form.vue:
|
||||
drive-write: "ドライブを操作する。"
|
||||
notification-read: "通知を見る。"
|
||||
notification-write: "通知を操作する。"
|
||||
cancel: "キャンセル"
|
||||
accept: "アクセスを許可"
|
||||
cancel: "Abbrechen"
|
||||
accept: "Zugriff erlauben."
|
||||
auth/views/index.vue:
|
||||
loading: "読み込み中"
|
||||
loading: "Lädt"
|
||||
denied: "アプリケーションの連携をキャンセルしました。"
|
||||
denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
|
||||
already-authorized: "このアプリは既に連携済みです"
|
||||
@ -196,10 +196,10 @@ auth/views/index.vue:
|
||||
sign-in: "サインインしてください"
|
||||
common/views/components/games/reversi/reversi.vue:
|
||||
matching:
|
||||
waiting-for: "{}を待っています"
|
||||
cancel: "キャンセル"
|
||||
waiting-for: "Warten auf {}"
|
||||
cancel: "Abbrechen"
|
||||
common/views/components/games/reversi/reversi.game.vue:
|
||||
surrender: "投了"
|
||||
surrender: "Aufgeben"
|
||||
surrendered: "投了により"
|
||||
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
||||
looped-map: "ループマップ"
|
||||
@ -208,9 +208,9 @@ common/views/components/games/reversi/reversi.index.vue:
|
||||
title: "Misskey Reversi"
|
||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||
invite: "招待"
|
||||
rule: "遊び方"
|
||||
rule: "Spielanleitung"
|
||||
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
|
||||
mode-invite: "招待"
|
||||
mode-invite: "Einladen"
|
||||
mode-invite-desc: "指定したユーザーと対戦するモードです。"
|
||||
invitations: "対局の招待があります!"
|
||||
my-games: "自分の対局"
|
||||
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "Diese Anmerkung favorisieren"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "An die Profilseite pinnen"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "Löschen"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Werkzeuge"
|
||||
task-manager: "Taskmanager"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Profil aktualisieren"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -265,40 +265,40 @@ common/views/components/media-banner.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "Click to show"
|
||||
common/views/components/theme.vue:
|
||||
light-theme: "非ダークモード時に使用するテーマ"
|
||||
dark-theme: "ダークモード時に使用するテーマ"
|
||||
light-themes: "明るいテーマ"
|
||||
dark-themes: "暗いテーマ"
|
||||
install-a-theme: "テーマのインストール"
|
||||
theme-code: "テーマコード"
|
||||
install: "インストール"
|
||||
installed: "「{}」をインストールしました"
|
||||
create-a-theme: "テーマの作成"
|
||||
save-created-theme: "テーマを保存"
|
||||
primary-color: "プライマリ カラー"
|
||||
secondary-color: "セカンダリ カラー"
|
||||
text-color: "文字色"
|
||||
base-theme: "ベーステーマ"
|
||||
light-theme: "Theme during non-dark mode"
|
||||
dark-theme: "Theme during dark mode"
|
||||
light-themes: "Light theme"
|
||||
dark-themes: "Dark theme"
|
||||
install-a-theme: "Install a theme"
|
||||
theme-code: "Theme code"
|
||||
install: "Install"
|
||||
installed: "\"{}\" has been installed"
|
||||
create-a-theme: "Create a theme"
|
||||
save-created-theme: "Save a theme"
|
||||
primary-color: "Primary color"
|
||||
secondary-color: "Secondary color"
|
||||
text-color: "Text color"
|
||||
base-theme: "Base theme"
|
||||
base-theme-light: "Light"
|
||||
base-theme-dark: "Dark"
|
||||
theme-name: "テーマ名"
|
||||
preview-created-theme: "プレビュー"
|
||||
invalid-theme: "テーマが正しくありません。"
|
||||
already-installed: "既にそのテーマはインストールされています。"
|
||||
saved: "保存しました"
|
||||
manage-themes: "テーマの管理"
|
||||
builtin-themes: "標準テーマ"
|
||||
my-themes: "マイテーマ"
|
||||
installed-themes: "インストールされたテーマ"
|
||||
select-theme: "テーマを選択してください"
|
||||
uninstall: "アンインストール"
|
||||
uninstalled: "「{}」をアンインストールしました"
|
||||
author: "作者"
|
||||
desc: "説明"
|
||||
export: "エクスポート"
|
||||
import: "インポート"
|
||||
import-by-code: "またはコードをペースト"
|
||||
theme-name-required: "テーマ名は必須です。"
|
||||
theme-name: "Theme name"
|
||||
preview-created-theme: "Preview"
|
||||
invalid-theme: "Not valid theme"
|
||||
already-installed: "This theme is already installed."
|
||||
saved: "Saved"
|
||||
manage-themes: "Themes manager"
|
||||
builtin-themes: "Standard themes"
|
||||
my-themes: "My themes"
|
||||
installed-themes: "Installed themes"
|
||||
select-theme: "Select your theme"
|
||||
uninstall: "Uninstall"
|
||||
uninstalled: "\"{}\" has been uninstalled"
|
||||
author: "Author"
|
||||
desc: "Description"
|
||||
export: "Export"
|
||||
import: "Import"
|
||||
import-by-code: "or paste code"
|
||||
theme-name-required: "Theme name is required"
|
||||
common/views/components/cw-button.vue:
|
||||
hide: "Hide"
|
||||
show: "See more"
|
||||
@ -335,8 +335,9 @@ common/views/components/note-menu.vue:
|
||||
detail: "Details"
|
||||
copy-link: "Copy link"
|
||||
favorite: "Favorite this note"
|
||||
unfavorite: "Unfavorite"
|
||||
pin: "Pin to your profile"
|
||||
unpin: "ピン留め解除"
|
||||
unpin: "Unpin"
|
||||
delete: "Delete"
|
||||
delete-confirm: "Delete this post?"
|
||||
remote: "Show original note"
|
||||
@ -514,13 +515,13 @@ desktop/views/components/charts.vue:
|
||||
notes: "The number of posts: increase/decrease (Combined)"
|
||||
local-notes: "The number of posts: increase/decrease (Local)"
|
||||
remote-notes: "The number of posts: increase/decrease (Remote)"
|
||||
notes-total: "投稿の積算"
|
||||
notes-total: "Total posts"
|
||||
users: "The number of users: increase/decrease"
|
||||
users-total: "ユーザーの積算"
|
||||
users-total: "Total users"
|
||||
drive: "Capacity used as the storage: increase/decrease"
|
||||
drive-total: "ドライブ使用量の積算"
|
||||
drive-total: "Total usage of Drive"
|
||||
drive-files: "The number of files on the storage: increase/decrease"
|
||||
drive-files-total: "ドライブのファイル数の積算"
|
||||
drive-files-total: "Total number of files on Drive"
|
||||
network-requests: "Requests"
|
||||
network-time: "Response time"
|
||||
network-usage: "Traffic"
|
||||
@ -730,8 +731,8 @@ desktop/views/components/settings.vue:
|
||||
choose-wallpaper: "Choose a background"
|
||||
delete-wallpaper: "Remove background"
|
||||
dark-mode: "Dark Mode"
|
||||
use-shadow: "UIに影を使用"
|
||||
rounded-corners: "UIの角を丸める"
|
||||
use-shadow: "Use shadows in the UI"
|
||||
rounded-corners: "Round corners of UI"
|
||||
circle-icons: "Use circle icons"
|
||||
contrasted-acct: "Add contrast to username"
|
||||
post-form-on-timeline: "Display post form at the top of the timeline"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Tools"
|
||||
task-manager: "Task Manager"
|
||||
third-parties: "Third-parties"
|
||||
navbar-position: "Navigation bar position"
|
||||
navbar-position-top: "Top"
|
||||
navbar-position-left: "Left"
|
||||
navbar-position-right: "Right"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "If you set up 2-step verification, you will not only need a password at sign-in, but also a pre-registered physical device (such as your smartphone), which will improve security."
|
||||
detail: "Details…"
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Update profile"
|
||||
locked-account: "Protect your account"
|
||||
is-locked: "Follow request needs approval"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "Other"
|
||||
is-bot: "This account is a Bot"
|
||||
is-cat: "This account is a Cat"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "NSFW"
|
||||
mark-as-sensitive: "Mark as 'sensitive'"
|
||||
unmark-as-sensitive: "Unmark as 'sensitive'"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "Click to show"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Banner"
|
||||
is-cat: "This account is a Cat"
|
||||
is-locked: "Follow request needs approval"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "Advanced"
|
||||
privacy: "Privacy"
|
||||
save: "Update profile"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "Detalles"
|
||||
copy-link: "Copiar enlace"
|
||||
favorite: "Me gusta esta nota"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "Fijar en el perfil"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "Borrar"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Herramientas"
|
||||
task-manager: "Navegador de tareas"
|
||||
third-parties: "Servicios externos"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "Ver detalles..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Perfil actualizado"
|
||||
locked-account: "Protege tu cuenta"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -34,7 +34,7 @@ common:
|
||||
paragraph4: "Pour terminer la personnalisation, cliquez sur \"Terminer\" dans le coin supérieur droit."
|
||||
gotit: "Compris !"
|
||||
notification:
|
||||
file-uploaded: "Le fichier a été téléversé !"
|
||||
file-uploaded: "Le fichier a été transféré !"
|
||||
message-from: "Message de {} :"
|
||||
reversi-invited: "Invité à jouer"
|
||||
reversi-invited-by: "Invité par {} :"
|
||||
@ -136,7 +136,7 @@ common:
|
||||
memo: "Pense-bête"
|
||||
trends: "Tendances"
|
||||
photo-stream: "Flux de photos"
|
||||
posts-monitor: "Graphe des publications"
|
||||
posts-monitor: "Graph des publications"
|
||||
slideshow: "Diaporama"
|
||||
version: "Version"
|
||||
broadcast: "Diffusion"
|
||||
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "Détails"
|
||||
copy-link: "Copier le lien"
|
||||
favorite: "Mettre cette note en favoris"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "Épingler sur votre profil"
|
||||
unpin: "Désépingler"
|
||||
delete: "Supprimer"
|
||||
@ -434,7 +435,7 @@ common/views/widgets/photo-stream.vue:
|
||||
title: "Flux de photos"
|
||||
no-photos: "Pas de photo"
|
||||
common/views/widgets/posts-monitor.vue:
|
||||
title: "Graphe des publications"
|
||||
title: "Graph des publications"
|
||||
toggle: "Basculer entre les vues"
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "Hashtags"
|
||||
@ -582,7 +583,7 @@ desktop/views/components/drive.vue:
|
||||
unable-to-process: "L'opération n'a pas pu être complétée"
|
||||
circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
|
||||
unhandled-error: "Erreur inconnue"
|
||||
url-upload: "Uploader d'un URL"
|
||||
url-upload: "Téléverser via une URL"
|
||||
url-of-file: "URL de l'image que vous souhaitez uploader."
|
||||
url-upload-requested: "Upload requested"
|
||||
may-take-time: "L'upload de votre fichier peut prendre un certain temps."
|
||||
@ -590,8 +591,8 @@ desktop/views/components/drive.vue:
|
||||
folder-name: "Nom du dossier"
|
||||
contextmenu:
|
||||
create-folder: "Créer un dossier"
|
||||
upload: "Uploader un fichier"
|
||||
url-upload: "Uploader d'un URL"
|
||||
upload: "Transférer un fichier"
|
||||
url-upload: "Transférer à partir d’une URL"
|
||||
desktop/views/components/media-image.vue:
|
||||
sensitive: "Le contenu est NSFW"
|
||||
click-to-show: "Cliquer pour afficher"
|
||||
@ -658,21 +659,21 @@ desktop/views/components/post-form.vue:
|
||||
add-visible-user: "+Ajouter un utilisateur"
|
||||
attach-location-information: "Attacher des informations de localisation"
|
||||
hide-contents: "Masquer les contenus"
|
||||
reply-placeholder: "Répondre à cette note"
|
||||
quote-placeholder: "Citer cette note"
|
||||
submit: "Poster"
|
||||
reply-placeholder: "Répondre à cette note …"
|
||||
quote-placeholder: "Citer cette note …"
|
||||
submit: "Publier"
|
||||
reply: "Répondre"
|
||||
renote: "Republier"
|
||||
posted: "Posté!"
|
||||
replied: "Répondu!"
|
||||
reposted: "Reposté!"
|
||||
posted: "Publié !"
|
||||
replied: "Répondu !"
|
||||
reposted: "Reposté !"
|
||||
note-failed: "La note à échoué"
|
||||
reply-failed: "La réponse à échoué"
|
||||
renote-failed: "La renote à échoué"
|
||||
posting: "Publication..."
|
||||
attach-media-from-local: "Joindre un media depuis votre PC"
|
||||
attach-media-from-drive: "Joindre un media depuis votre Drive"
|
||||
attach-cancel: "Annuler la jointure de fichier"
|
||||
renote-failed: "Échec lors de la republication"
|
||||
posting: "Publication …"
|
||||
attach-media-from-local: "Joindre un média depuis votre appareil"
|
||||
attach-media-from-drive: "Joindre un média depuis votre Drive"
|
||||
attach-cancel: "Annuler le fichier attaché"
|
||||
insert-a-kao: "v('ω')v"
|
||||
create-poll: "Créer un sondage"
|
||||
text-remain: "{} charactères restants"
|
||||
@ -687,15 +688,15 @@ desktop/views/components/post-form-window.vue:
|
||||
note: "Nouvelle note"
|
||||
reply: "Répondre"
|
||||
attaches: "{} media joint(s)"
|
||||
uploading-media: "Upload du media {}"
|
||||
uploading-media: "Transfert du média {}"
|
||||
desktop/views/components/progress-dialog.vue:
|
||||
waiting: "En attente"
|
||||
desktop/views/components/renote-form.vue:
|
||||
quote: "Citer..."
|
||||
cancel: "Annuler"
|
||||
renote: "Republier"
|
||||
reposting: "Repost en cours..."
|
||||
success: "Reposté!"
|
||||
reposting: "Republication en cours …"
|
||||
success: "Republié !"
|
||||
failure: "La renote a échoué"
|
||||
desktop/views/components/renote-form-window.vue:
|
||||
title: "Êtes vous sûr de vouloir renote cette note?"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Outils"
|
||||
task-manager: "Gestionnaire de tâches"
|
||||
third-parties: "Services tiers"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "Si vous configurez la vérication en deux étapes vous aurez non seulement besoin de votre mot de passe mais aussi un appareil déjà pré-enregistré(tel que votre smartphone) ce qui ameliora grandement la sécurité de votre compte."
|
||||
detail: "Voir les détails..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Mettre à jour le profil"
|
||||
locked-account: "Protéger votre compte"
|
||||
is-locked: "Demande d’abonnement en attente d’approbation"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "Autre"
|
||||
is-bot: "Ce compte est un Bot"
|
||||
is-cat: "Ce compte est un Chat"
|
||||
@ -878,7 +884,7 @@ desktop/views/components/ui.header.nav.vue:
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
title: "Notifications"
|
||||
desktop/views/components/ui.header.post.vue:
|
||||
post: "Composer un nouveau post"
|
||||
post: "Rédiger une nouvelle publication"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
placeholder: "Chercher"
|
||||
desktop/views/components/received-follow-requests-window.vue:
|
||||
@ -969,7 +975,7 @@ desktop/views/pages/selectdrive.vue:
|
||||
title: "Choisir fichier(s)"
|
||||
ok: "OK"
|
||||
cancel: "Annuler"
|
||||
upload: "Uploader un ou plusieurs fichier(s) depuis votre PC"
|
||||
upload: "Téléverser des fichiers à partir de votre ordinateur"
|
||||
desktop/views/pages/search.vue:
|
||||
not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance."
|
||||
not-found: "Aucun message trouvé pour '{}'"
|
||||
@ -1029,8 +1035,8 @@ desktop/views/widgets/polls.vue:
|
||||
refresh: "Afficher d'autres"
|
||||
nothing: "Rien"
|
||||
desktop/views/widgets/post-form.vue:
|
||||
title: "Post"
|
||||
note: "Post"
|
||||
title: "Publication"
|
||||
note: "Publication"
|
||||
desktop/views/widgets/profile.vue:
|
||||
update-banner: "Cliquer pour éditer votre bannière"
|
||||
update-avatar: "Cliquer pour éditer votre avatar"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "CW"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "Le contenu est NSFW"
|
||||
click-to-show: "Cliquer pour afficher"
|
||||
@ -1092,7 +1100,7 @@ mobile/views/components/friends-maker.vue:
|
||||
refresh: "Voir plus"
|
||||
close: "Fermer"
|
||||
mobile/views/components/note.vue:
|
||||
reposted-by: "Renoté par {}"
|
||||
reposted-by: "Republié par {}"
|
||||
private: "cette publication est privée"
|
||||
deleted: "cette publication a été supprimée"
|
||||
location: "Géolocalisation"
|
||||
@ -1119,7 +1127,7 @@ mobile/views/components/notifications.vue:
|
||||
empty: "Pas de notifications"
|
||||
mobile/views/components/post-form.vue:
|
||||
add-visible-user: "Ajouter un utilisateur"
|
||||
submit: "Poster"
|
||||
submit: "Publier"
|
||||
reply: "Répondre"
|
||||
renote: "Republier"
|
||||
quote-placeholder: "Citer ce billet ... (Facultatif)"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Bannière"
|
||||
is-cat: "Ce compte est un Bot"
|
||||
is-locked: "Demande d’abonnement en attente d’approbation"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "Avancé"
|
||||
privacy: "Vie privée"
|
||||
save: "Mettre à jour le profil"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -363,6 +363,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -882,6 +883,11 @@ desktop/views/components/settings.vue:
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -937,6 +943,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1232,6 +1239,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
@ -1419,6 +1428,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -265,40 +265,40 @@ common/views/components/media-banner.vue:
|
||||
sensitive: "見せたらあかん"
|
||||
click-to-show: "押してみ、見せたるわ"
|
||||
common/views/components/theme.vue:
|
||||
light-theme: "非ダークモード時に使用するテーマ"
|
||||
dark-theme: "ダークモード時に使用するテーマ"
|
||||
light-themes: "明るいテーマ"
|
||||
dark-themes: "暗いテーマ"
|
||||
install-a-theme: "テーマのインストール"
|
||||
light-theme: "ナイトゲームちゃう時のテーマどないする?"
|
||||
dark-theme: "ナイトゲームの時のテーマどないする?"
|
||||
light-themes: "デイゲーム"
|
||||
dark-themes: "ナイトゲーム"
|
||||
install-a-theme: "テーマ入れるで"
|
||||
theme-code: "テーマコード"
|
||||
install: "インストール"
|
||||
installed: "「{}」をインストールしました"
|
||||
create-a-theme: "テーマの作成"
|
||||
save-created-theme: "テーマを保存"
|
||||
primary-color: "プライマリ カラー"
|
||||
secondary-color: "セカンダリ カラー"
|
||||
text-color: "文字色"
|
||||
base-theme: "ベーステーマ"
|
||||
installed: "「{}」を入れたで!"
|
||||
create-a-theme: "テーマ作る"
|
||||
save-created-theme: "テーマ保存"
|
||||
primary-color: "この色一番重要や"
|
||||
secondary-color: "次はこの色出したって"
|
||||
text-color: "文字はこの色や!"
|
||||
base-theme: "この色が背景や!"
|
||||
base-theme-light: "Light"
|
||||
base-theme-dark: "Dark"
|
||||
theme-name: "テーマ名"
|
||||
preview-created-theme: "プレビュー"
|
||||
invalid-theme: "テーマが正しくありません。"
|
||||
already-installed: "既にそのテーマはインストールされています。"
|
||||
saved: "保存しました"
|
||||
preview-created-theme: "試してみる"
|
||||
invalid-theme: "このテーマあかんわ、なんか間違うとる"
|
||||
already-installed: "このテーマもうあるで"
|
||||
saved: "保存したで!"
|
||||
manage-themes: "テーマの管理"
|
||||
builtin-themes: "標準テーマ"
|
||||
my-themes: "マイテーマ"
|
||||
installed-themes: "インストールされたテーマ"
|
||||
select-theme: "テーマを選択してください"
|
||||
uninstall: "アンインストール"
|
||||
uninstalled: "「{}」をアンインストールしました"
|
||||
author: "作者"
|
||||
builtin-themes: "いつものテーマ"
|
||||
my-themes: "ワイのテーマ"
|
||||
installed-themes: "入れたテーマ"
|
||||
select-theme: "テーマ選んでや!"
|
||||
uninstall: "ほかす"
|
||||
uninstalled: "「{}」をほかしてもうたわ"
|
||||
author: "作った人"
|
||||
desc: "説明"
|
||||
export: "エクスポート"
|
||||
import: "インポート"
|
||||
import-by-code: "またはコードをペースト"
|
||||
theme-name-required: "テーマ名は必須です。"
|
||||
import-by-code: "それかコードを貼っつける"
|
||||
theme-name-required: "テーマ名は絶対要るで"
|
||||
common/views/components/cw-button.vue:
|
||||
hide: "もうええわ"
|
||||
show: "見たいやろ?"
|
||||
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "もっと"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入りやめる"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留めやめる"
|
||||
delete: "ほかす"
|
||||
@ -475,7 +476,7 @@ common/views/pages/follow.vue:
|
||||
following: "フォローしとる"
|
||||
follow: "フォロー"
|
||||
request-pending: "フォローの許し待っとる"
|
||||
follow-processing: "フォロー処理中"
|
||||
follow-processing: "今フォロー処理やっとる‥"
|
||||
follow-request: "フォロー許してくれや!言うてみる"
|
||||
desktop:
|
||||
banner-crop-title: "どこバナーとして出す?"
|
||||
@ -602,7 +603,7 @@ desktop/views/components/follow-button.vue:
|
||||
following: "フォローしとる"
|
||||
follow: "フォロー"
|
||||
request-pending: "フォローの許し待っとる"
|
||||
follow-processing: "フォロー処理中"
|
||||
follow-processing: "今フォロー処理やっとる‥"
|
||||
follow-request: "フォロー許してくれや!言うてみる"
|
||||
desktop/views/components/followers-window.vue:
|
||||
followers: "{} のフォロワー"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけとちゃうくて、予め登録しておいた物理的なデバイス(例えばあんさんのスマートフォンなど)も必要になり、よりセキュリティが向上すんで。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウント守る"
|
||||
is-locked: "他人のフォローは許可してからや!"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotやで"
|
||||
is-cat: "このアカウントはCatやで"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ(md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "ちょっと見せられへんわ"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "見たらあかんで"
|
||||
click-to-show: "押してみ、見せたるわ"
|
||||
@ -1083,7 +1091,7 @@ mobile/views/components/follow-button.vue:
|
||||
following: "フォローしとる"
|
||||
follow: "フォロー"
|
||||
request-pending: "フォローの許し待っとる"
|
||||
follow-processing: "フォロー処理中"
|
||||
follow-processing: "今フォロー処理やっとる‥"
|
||||
follow-request: "フォロー許してくれや!言うてみる"
|
||||
mobile/views/components/friends-maker.vue:
|
||||
title: "おもろそうやな"
|
||||
@ -1223,9 +1231,10 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatや"
|
||||
is-locked: "他人のフォローは許してからや!"
|
||||
is-locked: "他人のフォローは許可してからや!"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー⇔オカンの年齢"
|
||||
privacy: "プライバシーってなんや?オカンの年齢か?"
|
||||
save: "保存"
|
||||
saved: "プロフィールを保存したで"
|
||||
uploading: "アップロードしとるで…"
|
||||
@ -1245,7 +1254,7 @@ mobile/views/pages/settings.vue:
|
||||
specify-language: "言語選びや"
|
||||
design: "見た感じ"
|
||||
dark-mode: "ナイトゲームや!"
|
||||
i-am-under-limited-internet: "電波がバァーっといけへんねん"
|
||||
i-am-under-limited-internet: "電波と阪神がザコいんや"
|
||||
circle-icons: "アイコンもタコ焼きも丸いやんな?"
|
||||
contrasted-acct: "ユーザー名ようわからんし見やすしといて"
|
||||
timeline: "タイムライン"
|
||||
@ -1257,8 +1266,8 @@ mobile/views/pages/settings.vue:
|
||||
post-style-standard: "標準"
|
||||
post-style-smart: "べっぴんさん"
|
||||
notification-position: "通知どこ見せる?"
|
||||
notification-position-bottom: "ミナミ"
|
||||
notification-position-top: "キタ"
|
||||
notification-position-bottom: "ミナミの方"
|
||||
notification-position-top: "キタの方"
|
||||
theme: "テーマ"
|
||||
behavior: "動き"
|
||||
fetch-on-scroll: "スクロールしたらもっと見せてや"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "링크 복사"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "Deze notitie toevoegen aan favorieten"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "Vastmaken aan profielpagina"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Hulpmiddelen"
|
||||
task-manager: "Taakbeheer"
|
||||
third-parties: "Derde partij"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "Als je verificatie in twee stappen instelt, dan heb je niet alleen een wachtwoord nodig bij het inloggen, maar ook een geregistreerd fysiek apparaat (zoals je smartphone). Dit verhoogt de veiligheid. "
|
||||
detail: "Details bekijken..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Profiel bijwerken"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "Dit account is een Bot"
|
||||
is-cat: "Dit account is een Kat"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Omslagfoto"
|
||||
is-cat: "Dit account is een Kat"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "Profiel bijwerken"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "Detaljer"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "Merket som favoritt"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "Fest til profilen din"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "Slett"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Verktøy"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "Detaljer..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Lagre profilen"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "Annet"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "NSFW"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "NSFW"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Banner"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "Avansert"
|
||||
privacy: "プライバシー"
|
||||
save: "Lagre profilen"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "Dodaj do ulubionych"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "Przypnij do profilu"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "Usuń"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "Narzędzia"
|
||||
task-manager: "Menedżer zadań"
|
||||
third-parties: "Autorzy trzeci"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "Jeżeli skonfigurujesz uwierzytelnianie dwuetapowe, aby zablokować się będziesz potrzebować (oprócz hasła) kodu ze skonfigurowanego urządzenia (np. smartfonu), co zwiększy bezpieczeństwo."
|
||||
detail: "Zobacz szczegóły…"
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "Aktualizuj profil"
|
||||
locked-account: "Zabezpiecz swoje konto"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "Inne"
|
||||
is-bot: "To konto jest prowadzone przez bota"
|
||||
is-cat: "To konto jest prowadzone przez kota"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "To jest zawartość NSFW"
|
||||
click-to-show: "Naciśnij aby wyświetlić"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Baner"
|
||||
is-cat: "To konto jest prowadzone przez kota"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "Aktualizuj profil"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "Capa"
|
||||
is-cat: "Esta conta é gato"
|
||||
is-locked: "Pedido para seguir precisa ser aprovado"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "Avançado"
|
||||
privacy: "Provacidade"
|
||||
save: "Atualizar perfil"
|
||||
|
@ -3,9 +3,9 @@ meta:
|
||||
lang: "Русский язык"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
misskey: "Мы — ⭐ fediverse"
|
||||
about-title: "Мы — ⭐ fediverse"
|
||||
about: "Спасибо, что нашли Misskey. Misskey — это <b>децентрализованная платформа для микроблоггинга</b> родом с планеты Земля. Поскольку она существует внутри Fediverse (вселенной различных социальных платформ), она связана с другими платформами. Отдохните от шума большого города — и познакомьтесь с новым интернетом."
|
||||
intro:
|
||||
title: "Misskeyって?"
|
||||
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
||||
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
@ -335,6 +335,7 @@ common/views/components/note-menu.vue:
|
||||
detail: "詳細"
|
||||
copy-link: "リンクをコピー"
|
||||
favorite: "お気に入り"
|
||||
unfavorite: "お気に入り解除"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留め解除"
|
||||
delete: "削除"
|
||||
@ -784,6 +785,10 @@ desktop/views/components/settings.vue:
|
||||
tools: "ツール"
|
||||
task-manager: "タスクマネージャ"
|
||||
third-parties: "サードパーティ"
|
||||
navbar-position: "ナビゲーションバーの位置"
|
||||
navbar-position-top: "上"
|
||||
navbar-position-left: "左"
|
||||
navbar-position-right: "右"
|
||||
desktop/views/components/settings.2fa.vue:
|
||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||
detail: "詳細..."
|
||||
@ -833,6 +838,7 @@ desktop/views/components/settings.profile.vue:
|
||||
save: "保存"
|
||||
locked-account: "アカウントの保護"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
other: "その他"
|
||||
is-bot: "このアカウントはBotです"
|
||||
is-cat: "このアカウントはCatです"
|
||||
@ -1073,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "ハッシュ (md5)"
|
||||
exif: "EXIF"
|
||||
nsfw: "閲覧注意"
|
||||
mark-as-sensitive: "閲覧注意に設定"
|
||||
unmark-as-sensitive: "閲覧注意を解除"
|
||||
mobile/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
@ -1224,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
careful-bot: "Botからのフォローだけ承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
|
26
package.json
26
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.9.0",
|
||||
"clientVersion": "1.0.10443",
|
||||
"version": "10.20.0",
|
||||
"clientVersion": "1.0.10607",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -28,18 +28,19 @@
|
||||
"@prezzemolo/rap": "0.1.2",
|
||||
"@prezzemolo/zip": "0.0.3",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/chai-http": "3.0.5",
|
||||
"@types/dateformat": "1.0.1",
|
||||
"@types/debug": "0.0.31",
|
||||
"@types/deep-equal": "1.0.1",
|
||||
"@types/double-ended-queue": "2.1.0",
|
||||
"@types/elasticsearch": "5.0.26",
|
||||
"@types/elasticsearch": "5.0.27",
|
||||
"@types/file-type": "5.2.1",
|
||||
"@types/gulp": "3.8.36",
|
||||
"@types/gulp-htmlmin": "1.3.32",
|
||||
"@types/gulp-mocha": "0.0.32",
|
||||
"@types/gulp-rename": "0.0.33",
|
||||
"@types/gulp-replace": "0.0.31",
|
||||
"@types/gulp-uglify": "3.0.5",
|
||||
"@types/gulp-uglify": "3.0.6",
|
||||
"@types/gulp-util": "3.0.34",
|
||||
"@types/is-root": "1.0.0",
|
||||
"@types/is-url": "1.2.28",
|
||||
@ -60,7 +61,7 @@
|
||||
"@types/mocha": "5.2.3",
|
||||
"@types/mongodb": "3.1.12",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.11.7",
|
||||
"@types/node": "10.12.0",
|
||||
"@types/portscanner": "2.1.0",
|
||||
"@types/pug": "2.0.4",
|
||||
"@types/qrcode": "1.3.0",
|
||||
@ -70,7 +71,7 @@
|
||||
"@types/request-promise-native": "1.0.15",
|
||||
"@types/rimraf": "2.0.2",
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/sharp": "0.17.10",
|
||||
"@types/sharp": "0.21.0",
|
||||
"@types/showdown": "1.7.5",
|
||||
"@types/single-line-log": "1.1.0",
|
||||
"@types/speakeasy": "2.0.2",
|
||||
@ -78,7 +79,7 @@
|
||||
"@types/tinycolor2": "1.4.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
"@types/uuid": "3.4.4",
|
||||
"@types/webpack": "4.4.16",
|
||||
"@types/webpack": "4.4.17",
|
||||
"@types/webpack-stream": "3.2.10",
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
@ -90,8 +91,10 @@
|
||||
"bee-queue": "1.2.2",
|
||||
"bootstrap-vue": "2.0.0-rc.11",
|
||||
"cafy": "11.3.0",
|
||||
"chai": "4.2.0",
|
||||
"chai-http": "4.2.0",
|
||||
"chalk": "2.4.1",
|
||||
"chart.js": "2.7.2",
|
||||
"chart.js": "2.7.3",
|
||||
"commander": "2.19.0",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "1.0.0",
|
||||
@ -157,7 +160,7 @@
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
"mongodb": "3.1.1",
|
||||
"mongodb": "3.1.8",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nan": "2.11.1",
|
||||
@ -176,7 +179,7 @@
|
||||
"qrcode": "1.3.0",
|
||||
"ratelimiter": "3.2.0",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.1.5",
|
||||
"reconnecting-websocket": "4.1.8",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.88.0",
|
||||
"request-promise-native": "1.0.5",
|
||||
@ -213,13 +216,14 @@
|
||||
"vue": "2.5.17",
|
||||
"vue-chartjs": "3.4.0",
|
||||
"vue-color": "2.7.0",
|
||||
"vue-content-loading": "1.5.3",
|
||||
"vue-cropperjs": "2.2.2",
|
||||
"vue-js-modal": "1.3.26",
|
||||
"vue-json-tree-view": "2.1.4",
|
||||
"vue-loader": "15.4.2",
|
||||
"vue-router": "3.0.1",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.0",
|
||||
"vue-svg-inline-loader": "1.2.1",
|
||||
"vue-sweetalert2": "1.5.5",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
"vuedraggable": "2.16.0",
|
||||
|
@ -142,7 +142,7 @@
|
||||
localStorage.setItem('shouldFlush', 'false');
|
||||
|
||||
// Random
|
||||
localStorage.setItem('salt', Math.random().toString());
|
||||
localStorage.setItem('salt', Math.random().toString().substr(2, 8));
|
||||
|
||||
// Clear cache (service worker)
|
||||
try {
|
||||
|
178
src/client/app/common/scripts/note-mixin.ts
Normal file
178
src/client/app/common/scripts/note-mixin.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import parse from '../../../../mfm/parse';
|
||||
import { sum } from '../../../../prelude/array';
|
||||
import MkNoteMenu from '../views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../views/components/reaction-picker.vue';
|
||||
import Ok from '../views/components/ok.vue';
|
||||
|
||||
function focus(el, fn) {
|
||||
const target = fn(el);
|
||||
if (target) {
|
||||
if (target.hasAttribute('tabindex')) {
|
||||
target.focus();
|
||||
} else {
|
||||
focus(target, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Opts = {
|
||||
mobile?: boolean;
|
||||
};
|
||||
|
||||
export default (opts: Opts = {}) => ({
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'r|left': () => this.reply(true),
|
||||
'e|a|plus': () => this.react(true),
|
||||
'q|right': () => this.renote(true),
|
||||
'f|b': this.favorite,
|
||||
'delete|ctrl+d': this.del,
|
||||
'ctrl+q|ctrl+right': this.renoteDirectly,
|
||||
'up|k|shift+tab': this.focusBefore,
|
||||
'down|j|tab': this.focusAfter,
|
||||
'esc': this.blur,
|
||||
'm|o': () => this.menu(true),
|
||||
's': this.toggleShowContent,
|
||||
'1': () => this.reactDirectly('like'),
|
||||
'2': () => this.reactDirectly('love'),
|
||||
'3': () => this.reactDirectly('laugh'),
|
||||
'4': () => this.reactDirectly('hmm'),
|
||||
'5': () => this.reactDirectly('surprise'),
|
||||
'6': () => this.reactDirectly('congrats'),
|
||||
'7': () => this.reactDirectly('angry'),
|
||||
'8': () => this.reactDirectly('confused'),
|
||||
'9': () => this.reactDirectly('rip'),
|
||||
'0': () => this.reactDirectly('pudding'),
|
||||
};
|
||||
},
|
||||
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
appearNote(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.appearNote.reactionCounts
|
||||
? sum(Object.values(this.appearNote.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
title(): string {
|
||||
return new Date(this.appearNote.createdAt).toLocaleString();
|
||||
},
|
||||
|
||||
urls(): string[] {
|
||||
if (this.appearNote.text) {
|
||||
const ast = parse(this.appearNote.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reply(viaKeyboard = false) {
|
||||
(this as any).apis.post({
|
||||
reply: this.appearNote,
|
||||
animation: !viaKeyboard,
|
||||
cb: () => {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renote(viaKeyboard = false) {
|
||||
(this as any).apis.post({
|
||||
renote: this.appearNote,
|
||||
animation: !viaKeyboard,
|
||||
cb: () => {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renoteDirectly() {
|
||||
(this as any).api('notes/create', {
|
||||
renoteId: this.appearNote.id
|
||||
});
|
||||
},
|
||||
|
||||
react(viaKeyboard = false) {
|
||||
this.blur();
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.appearNote,
|
||||
showFocus: viaKeyboard,
|
||||
animation: !viaKeyboard,
|
||||
compact: opts.mobile,
|
||||
big: opts.mobile
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
reactDirectly(reaction) {
|
||||
(this as any).api('notes/reactions/create', {
|
||||
noteId: this.appearNote.id,
|
||||
reaction: reaction
|
||||
});
|
||||
},
|
||||
|
||||
favorite() {
|
||||
(this as any).api('notes/favorites/create', {
|
||||
noteId: this.appearNote.id
|
||||
}).then(() => {
|
||||
(this as any).os.new(Ok);
|
||||
});
|
||||
},
|
||||
|
||||
del() {
|
||||
(this as any).api('notes/delete', {
|
||||
noteId: this.appearNote.id
|
||||
});
|
||||
},
|
||||
|
||||
menu(viaKeyboard = false) {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.appearNote,
|
||||
animation: !viaKeyboard,
|
||||
compact: opts.mobile,
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
toggleShowContent() {
|
||||
this.showContent = !this.showContent;
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$el.focus();
|
||||
},
|
||||
|
||||
blur() {
|
||||
this.$el.blur();
|
||||
},
|
||||
|
||||
focusBefore() {
|
||||
focus(this.$el, e => e.previousElementSibling);
|
||||
},
|
||||
|
||||
focusAfter() {
|
||||
focus(this.$el, e => e.nextElementSibling);
|
||||
}
|
||||
}
|
||||
});
|
@ -9,7 +9,7 @@ import MiOS from '../../mios';
|
||||
*/
|
||||
export default class Stream extends EventEmitter {
|
||||
private stream: ReconnectingWebsocket;
|
||||
private state: string;
|
||||
public state: string;
|
||||
private sharedConnectionPools: Pool[] = [];
|
||||
private sharedConnections: SharedConnection[] = [];
|
||||
private nonSharedConnections: NonSharedConnection[] = [];
|
||||
@ -46,6 +46,11 @@ export default class Stream extends EventEmitter {
|
||||
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
|
||||
}
|
||||
|
||||
@autobind
|
||||
public removeSharedConnectionPool(pool: Pool) {
|
||||
this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool);
|
||||
}
|
||||
|
||||
@autobind
|
||||
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
||||
const connection = new NonSharedConnection(this, channel, params);
|
||||
@ -71,9 +76,7 @@ export default class Stream extends EventEmitter {
|
||||
// チャンネル再接続
|
||||
if (isReconnect) {
|
||||
this.sharedConnectionPools.forEach(p => {
|
||||
if (p.users > 0) {
|
||||
p.connect();
|
||||
}
|
||||
p.connect();
|
||||
});
|
||||
this.nonSharedConnections.forEach(c => {
|
||||
c.connect();
|
||||
@ -86,8 +89,10 @@ export default class Stream extends EventEmitter {
|
||||
*/
|
||||
@autobind
|
||||
private onClose() {
|
||||
this.state = 'reconnecting';
|
||||
this.emit('_disconnected_');
|
||||
if (this.state == 'connected') {
|
||||
this.state = 'reconnecting';
|
||||
this.emit('_disconnected_');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,7 +156,14 @@ class Pool {
|
||||
this.channel = channel;
|
||||
this.stream = stream;
|
||||
|
||||
this.id = Math.random().toString();
|
||||
this.id = Math.random().toString().substr(2, 8);
|
||||
|
||||
this.stream.on('_disconnected_', this.onStreamDisconnected);
|
||||
}
|
||||
|
||||
@autobind
|
||||
private onStreamDisconnected() {
|
||||
this.isConnected = false;
|
||||
}
|
||||
|
||||
@autobind
|
||||
@ -185,6 +197,7 @@ class Pool {
|
||||
|
||||
@autobind
|
||||
public connect() {
|
||||
if (this.isConnected) return;
|
||||
this.isConnected = true;
|
||||
this.stream.send('connect', {
|
||||
channel: this.channel,
|
||||
@ -194,9 +207,9 @@ class Pool {
|
||||
|
||||
@autobind
|
||||
private disconnect() {
|
||||
this.isConnected = false;
|
||||
this.disposeTimerId = null;
|
||||
this.stream.off('_disconnected_', this.onStreamDisconnected);
|
||||
this.stream.send('disconnect', { id: this.id });
|
||||
this.stream.removeSharedConnectionPool(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +275,7 @@ class NonSharedConnection extends Connection {
|
||||
super(stream, channel);
|
||||
|
||||
this.params = params;
|
||||
this.id = Math.random().toString();
|
||||
this.id = Math.random().toString().substr(2, 8);
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import noteSkeleton from './note-skeleton.vue';
|
||||
import theme from './theme.vue';
|
||||
import instance from './instance.vue';
|
||||
import cwButton from './cw-button.vue';
|
||||
@ -44,6 +45,7 @@ import uiSelect from './ui/select.vue';
|
||||
import formButton from './ui/form/button.vue';
|
||||
import formRadio from './ui/form/radio.vue';
|
||||
|
||||
Vue.component('mk-note-skeleton', noteSkeleton);
|
||||
Vue.component('mk-theme', theme);
|
||||
Vue.component('mk-instance', instance);
|
||||
Vue.component('mk-cw-button', cwButton);
|
||||
|
@ -354,7 +354,7 @@ export default Vue.extend({
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
padding 0
|
||||
//background rgba(var(--face), 0.95)
|
||||
background var(--messagingRoomBg)
|
||||
background-clip content-box
|
||||
|
||||
> .new-message
|
||||
|
@ -116,16 +116,16 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
case 'mention': {
|
||||
return (createElement as any)('a', {
|
||||
attrs: {
|
||||
href: `${url}/@${getAcct(token)}`,
|
||||
href: `${url}/${token.canonical}`,
|
||||
target: '_blank',
|
||||
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
||||
style: 'color:var(--mfmMention);'
|
||||
},
|
||||
directives: [{
|
||||
name: 'user-preview',
|
||||
value: token.content
|
||||
value: token.canonical
|
||||
}]
|
||||
}, token.content);
|
||||
}, token.canonical);
|
||||
}
|
||||
|
||||
case 'hashtag': {
|
||||
|
@ -22,12 +22,34 @@ export default Vue.extend({
|
||||
icon: '%fa:link%',
|
||||
text: '%i18n:@copy-link%',
|
||||
action: this.copyLink
|
||||
}, null, {
|
||||
icon: '%fa:star%',
|
||||
text: '%i18n:@favorite%',
|
||||
action: this.favorite
|
||||
}];
|
||||
|
||||
if (this.note.uri) {
|
||||
items.push({
|
||||
icon: '%fa:external-link-square-alt%',
|
||||
text: '%i18n:@remote%',
|
||||
action: () => {
|
||||
window.open(this.note.uri, '_blank');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
items.push(null);
|
||||
|
||||
if (this.note.isFavorited) {
|
||||
items.push({
|
||||
icon: '%fa:star%',
|
||||
text: '%i18n:@unfavorite%',
|
||||
action: this.unfavorite
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
icon: '%fa:star%',
|
||||
text: '%i18n:@favorite%',
|
||||
action: this.favorite
|
||||
});
|
||||
}
|
||||
|
||||
if (this.note.userId == this.$store.state.i.id) {
|
||||
if ((this.$store.state.i.pinnedNoteIds || []).includes(this.note.id)) {
|
||||
items.push({
|
||||
@ -45,6 +67,7 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) {
|
||||
items.push(null);
|
||||
items.push({
|
||||
icon: '%fa:trash-alt R%',
|
||||
text: '%i18n:@delete%',
|
||||
@ -52,16 +75,6 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
|
||||
if (this.note.uri) {
|
||||
items.push({
|
||||
icon: '%fa:external-link-square-alt%',
|
||||
text: '%i18n:@remote%',
|
||||
action: () => {
|
||||
window.open(this.note.uri, '_blank');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
},
|
||||
@ -110,6 +123,15 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
unfavorite() {
|
||||
(this as any).api('notes/favorites/delete', {
|
||||
noteId: this.note.id
|
||||
}).then(() => {
|
||||
(this as any).os.new(Ok);
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
closed() {
|
||||
this.$nextTick(() => {
|
||||
this.destroyDom();
|
||||
|
52
src/client/app/common/views/components/note-skeleton.vue
Normal file
52
src/client/app/common/views/components/note-skeleton.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div>
|
||||
<vue-content-loading v-if="width" :width="width" :height="100" :primary="primary" :secondary="secondary">
|
||||
<circle cx="30" cy="30" r="30" />
|
||||
<rect x="75" y="13" rx="4" ry="4" :width="150 + r1" height="15" />
|
||||
<rect x="75" y="39" rx="4" ry="4" :width="260 + r2" height="10" />
|
||||
<rect x="75" y="59" rx="4" ry="4" :width="230 + r3" height="10" />
|
||||
</vue-content-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import VueContentLoading from 'vue-content-loading';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
VueContentLoading,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
width: 0,
|
||||
r1: (Math.random() * 100) - 50,
|
||||
r2: (Math.random() * 100) - 50,
|
||||
r3: (Math.random() * 100) - 50
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
text(): tinycolor.Instance {
|
||||
const text = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text'));
|
||||
return text;
|
||||
},
|
||||
|
||||
primary(): string {
|
||||
return '#' + this.text.clone().toHex();
|
||||
},
|
||||
|
||||
secondary(): string {
|
||||
return '#' + this.text.clone().darken(20).toHex();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
let width = this.$el.clientWidth;
|
||||
if (width < 400) width = 400;
|
||||
this.width = width;
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
import * as getCaretCoordinates from 'textarea-caret';
|
||||
import MkAutocomplete from '../components/autocomplete.vue';
|
||||
import renderAcct from '../../../../../misc/acct/render';
|
||||
import { toASCII } from 'punycode';
|
||||
|
||||
export default {
|
||||
bind(el, binding, vn) {
|
||||
@ -188,7 +188,7 @@ class Autocomplete {
|
||||
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
||||
const after = source.substr(caret);
|
||||
|
||||
const acct = renderAcct(value);
|
||||
const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`;
|
||||
|
||||
// 挿入
|
||||
this.text = `${trimmedBefore}@${acct} ${after}`;
|
||||
|
@ -114,7 +114,7 @@ export default define({
|
||||
this.connection.on('stats', this.onStats);
|
||||
this.connection.on('statsLog', this.onStatsLog);
|
||||
this.connection.send('requestLog',{
|
||||
id: Math.random().toString()
|
||||
id: Math.random().toString().substr(2, 8)
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
@ -92,7 +92,7 @@ export default Vue.extend({
|
||||
this.connection.on('stats', this.onStats);
|
||||
this.connection.on('statsLog', this.onStatsLog);
|
||||
this.connection.send('requestLog', {
|
||||
id: Math.random().toString()
|
||||
id: Math.random().toString().substr(2, 8)
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
@ -6,13 +6,17 @@ export default (os: OS) => opts => {
|
||||
const o = opts || {};
|
||||
if (o.renote) {
|
||||
const vm = os.new(RenoteFormWindow, {
|
||||
note: o.renote
|
||||
note: o.renote,
|
||||
animation: o.animation == null ? true : o.animation
|
||||
});
|
||||
if (o.cb) vm.$once('closed', o.cb);
|
||||
document.body.appendChild(vm.$el);
|
||||
} else {
|
||||
const vm = os.new(PostFormWindow, {
|
||||
reply: o.reply
|
||||
reply: o.reply,
|
||||
animation: o.animation == null ? true : o.animation
|
||||
});
|
||||
if (o.cb) vm.$once('closed', o.cb);
|
||||
document.body.appendChild(vm.$el);
|
||||
}
|
||||
};
|
||||
|
@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<div class="mk-ellipsis-icon">
|
||||
<div></div><div></div><div></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-ellipsis-icon
|
||||
width 70px
|
||||
margin 0 auto
|
||||
text-align center
|
||||
|
||||
> div
|
||||
display inline-block
|
||||
width 18px
|
||||
height 18px
|
||||
background-color rgba(#000, 0.3)
|
||||
border-radius 100%
|
||||
animation bounce 1.4s infinite ease-in-out both
|
||||
|
||||
&:nth-child(1)
|
||||
animation-delay 0s
|
||||
|
||||
&:nth-child(2)
|
||||
margin 0 6px
|
||||
animation-delay 0.16s
|
||||
|
||||
&:nth-child(3)
|
||||
animation-delay 0.32s
|
||||
|
||||
@keyframes bounce
|
||||
0%, 80%, 100%
|
||||
transform scale(0)
|
||||
40%
|
||||
transform scale(1)
|
||||
|
||||
</style>
|
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
|
||||
<template v-if="customize">
|
||||
<x-draggable v-for="place in ['left', 'right']"
|
||||
:list="widgets[place]"
|
||||
@ -359,12 +359,10 @@ export default Vue.extend({
|
||||
box-shadow var(--shadow)
|
||||
border-radius var(--round)
|
||||
|
||||
@media (max-width 700px)
|
||||
padding 0
|
||||
|
||||
> .tl
|
||||
border none
|
||||
border-radius 0
|
||||
&.side
|
||||
> .main
|
||||
width calc(100% - 280px)
|
||||
max-width 680px
|
||||
|
||||
> *:not(.main)
|
||||
width 280px
|
||||
@ -381,14 +379,24 @@ export default Vue.extend({
|
||||
padding-right 16px
|
||||
order 3
|
||||
|
||||
@media (max-width 1100px)
|
||||
> *:not(.main)
|
||||
display none
|
||||
&.side
|
||||
@media (max-width 1000px)
|
||||
> *:not(.main)
|
||||
display none
|
||||
|
||||
> .main
|
||||
float none
|
||||
width 100%
|
||||
max-width 700px
|
||||
margin 0 auto
|
||||
> .main
|
||||
width 100%
|
||||
max-width 700px
|
||||
margin 0 auto
|
||||
|
||||
&:not(.side)
|
||||
@media (max-width 1200px)
|
||||
> *:not(.main)
|
||||
display none
|
||||
|
||||
> .main
|
||||
width 100%
|
||||
max-width 700px
|
||||
margin 0 auto
|
||||
|
||||
</style>
|
||||
|
@ -9,7 +9,6 @@ import subNoteContent from './sub-note-content.vue';
|
||||
import window from './window.vue';
|
||||
import noteFormWindow from './post-form-window.vue';
|
||||
import renoteFormWindow from './renote-form-window.vue';
|
||||
import ellipsisIcon from './ellipsis-icon.vue';
|
||||
import mediaImage from './media-image.vue';
|
||||
import mediaImageDialog from './media-image-dialog.vue';
|
||||
import mediaVideo from './media-video.vue';
|
||||
@ -39,7 +38,6 @@ Vue.component('mk-sub-note-content', subNoteContent);
|
||||
Vue.component('mk-window', window);
|
||||
Vue.component('mk-post-form-window', noteFormWindow);
|
||||
Vue.component('mk-renote-form-window', renoteFormWindow);
|
||||
Vue.component('mk-ellipsis-icon', ellipsisIcon);
|
||||
Vue.component('mk-media-image', mediaImage);
|
||||
Vue.component('mk-media-image-dialog', mediaImageDialog);
|
||||
Vue.component('mk-media-video', mediaVideo);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="note" tabindex="-1" v-hotkey="keymap" :title="title">
|
||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="p.reply"/>
|
||||
<div class="note" v-show="appearNote.deletedAt == null" :tabindex="appearNote.deletedAt == null ? '-1' : null" v-hotkey="keymap" :title="title">
|
||||
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="appearNote.reply"/>
|
||||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
@ -12,228 +12,76 @@
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user"/>
|
||||
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="p"/>
|
||||
<mk-note-header class="header" :note="appearNote"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="p.renote">RP:</a>
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="appearNote.renote">RP:</a>
|
||||
</div>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
<div class="files" v-if="appearNote.files.length > 0">
|
||||
<mk-media-list :media-list="appearNote.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
||||
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
</div>
|
||||
</div>
|
||||
<footer v-if="p.deletedAt == null">
|
||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
||||
<footer>
|
||||
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||
<button class="replyButton" @click="reply()" title="%i18n:@reply%">
|
||||
<template v-if="p.reply">%fa:reply-all%</template>
|
||||
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||
<template v-else>%fa:reply%</template>
|
||||
<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button class="renoteButton" @click="renote()" title="%i18n:@renote%">
|
||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
||||
%fa:retweet%<p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
|
||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
||||
<button class="reactionButton" :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
|
||||
%fa:plus%<p class="count" v-if="appearNote.reactions_count > 0">{{ appearNote.reactions_count }}</p>
|
||||
</button>
|
||||
<button @click="menu()" ref="menuButton">
|
||||
%fa:ellipsis-h%
|
||||
</button>
|
||||
<!-- <button title="%i18n:@detail">
|
||||
<template v-if="!isDetailOpened">%fa:caret-down%</template>
|
||||
<template v-if="isDetailOpened">%fa:caret-up%</template>
|
||||
</button> -->
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
<div class="detail" v-if="isDetailOpened">
|
||||
<mk-note-status-graph width="462" height="130" :note="p"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
|
||||
import MkPostFormWindow from './post-form-window.vue';
|
||||
import MkRenoteFormWindow from './renote-form-window.vue';
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './notes.note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
import noteMixin from '../../../common/scripts/note-mixin';
|
||||
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
||||
|
||||
function focus(el, fn) {
|
||||
const target = fn(el);
|
||||
if (target) {
|
||||
if (target.hasAttribute('tabindex')) {
|
||||
target.focus();
|
||||
} else {
|
||||
focus(target, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XSub
|
||||
},
|
||||
|
||||
mixins: [noteSubscriber('note')],
|
||||
mixins: [
|
||||
noteMixin(),
|
||||
noteSubscriber('note')
|
||||
],
|
||||
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false,
|
||||
isDetailOpened: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'r|left': () => this.reply(true),
|
||||
'e|a|plus': () => this.react(true),
|
||||
'q|right': () => this.renote(true),
|
||||
'ctrl+q|ctrl+right': this.renoteDirectly,
|
||||
'up|k|shift+tab': this.focusBefore,
|
||||
'down|j|tab': this.focusAfter,
|
||||
'esc': this.blur,
|
||||
'm|o': () => this.menu(true),
|
||||
's': this.toggleShowContent,
|
||||
'1': () => this.reactDirectly('like'),
|
||||
'2': () => this.reactDirectly('love'),
|
||||
'3': () => this.reactDirectly('laugh'),
|
||||
'4': () => this.reactDirectly('hmm'),
|
||||
'5': () => this.reactDirectly('surprise'),
|
||||
'6': () => this.reactDirectly('congrats'),
|
||||
'7': () => this.reactDirectly('angry'),
|
||||
'8': () => this.reactDirectly('confused'),
|
||||
'9': () => this.reactDirectly('rip'),
|
||||
'0': () => this.reactDirectly('pudding'),
|
||||
};
|
||||
},
|
||||
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
title(): string {
|
||||
return new Date(this.p.createdAt).toLocaleString();
|
||||
},
|
||||
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reply(viaKeyboard = false) {
|
||||
(this as any).os.new(MkPostFormWindow, {
|
||||
reply: this.p,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
renote(viaKeyboard = false) {
|
||||
(this as any).os.new(MkRenoteFormWindow, {
|
||||
note: this.p,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
renoteDirectly() {
|
||||
(this as any).api('notes/create', {
|
||||
renoteId: this.p.id
|
||||
});
|
||||
},
|
||||
|
||||
react(viaKeyboard = false) {
|
||||
this.blur();
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p,
|
||||
showFocus: viaKeyboard,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
reactDirectly(reaction) {
|
||||
(this as any).api('notes/reactions/create', {
|
||||
noteId: this.p.id,
|
||||
reaction: reaction
|
||||
});
|
||||
},
|
||||
|
||||
menu(viaKeyboard = false) {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
toggleShowContent() {
|
||||
this.showContent = !this.showContent;
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$el.focus();
|
||||
},
|
||||
|
||||
blur() {
|
||||
this.$el.blur();
|
||||
},
|
||||
|
||||
focusBefore() {
|
||||
focus(this.$el, e => e.previousElementSibling);
|
||||
},
|
||||
|
||||
focusAfter() {
|
||||
focus(this.$el, e => e.nextElementSibling);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -445,10 +293,6 @@ export default Vue.extend({
|
||||
&.reacted, &.reacted:hover
|
||||
color var(--noteActionsReactionHover)
|
||||
|
||||
> .detail
|
||||
padding-top 4px
|
||||
background rgba(#000, 0.0125)
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="stylus" module>
|
||||
|
@ -9,6 +9,12 @@
|
||||
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
||||
</div>
|
||||
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
|
||||
<template v-for="(note, i) in _notes">
|
||||
@ -226,6 +232,10 @@ export default Vue.extend({
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
|
||||
> .placeholder
|
||||
padding 32px
|
||||
opacity 0.3
|
||||
|
||||
> .notes
|
||||
> .date
|
||||
display block
|
||||
|
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="mk-notifications">
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="notifications" v-if="notifications.length != 0">
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
|
||||
@ -102,7 +108,6 @@
|
||||
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
||||
</button>
|
||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||
<p class="loading" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -202,6 +207,10 @@ export default Vue.extend({
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
|
||||
> .placeholder
|
||||
padding 16px
|
||||
opacity 0.3
|
||||
|
||||
> .notifications
|
||||
> div
|
||||
> .notification
|
||||
@ -319,13 +328,4 @@ export default Vue.extend({
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .loading
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
||||
|
@ -65,6 +65,7 @@ import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import { toASCII } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -158,14 +159,14 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.user.host != null) {
|
||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
||||
this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.text != null) {
|
||||
const ast = parse(this.reply.text);
|
||||
|
||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
||||
const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
|
||||
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||
|
||||
// 自分は除外
|
||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||
|
@ -21,12 +21,13 @@
|
||||
<ui-button primary @click="save">%i18n:@save%</ui-button>
|
||||
<section>
|
||||
<h2>%i18n:@locked-account%</h2>
|
||||
<ui-switch v-model="$store.state.i.isLocked" @change="onChangeIsLocked">%i18n:@is-locked%</ui-switch>
|
||||
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
||||
<ui-switch v-model="carefulBot" @change="save(false)">%i18n:@careful-bot%</ui-switch>
|
||||
</section>
|
||||
<section>
|
||||
<h2>%i18n:@other%</h2>
|
||||
<ui-switch v-model="$store.state.i.isBot" @change="onChangeIsBot">%i18n:@is-bot%</ui-switch>
|
||||
<ui-switch v-model="$store.state.i.isCat" @change="onChangeIsCat">%i18n:@is-cat%</ui-switch>
|
||||
<ui-switch v-model="isBot" @change="save(false)">%i18n:@is-bot%</ui-switch>
|
||||
<ui-switch v-model="isCat" @change="save(false)">%i18n:@is-cat%</ui-switch>
|
||||
<ui-switch v-model="alwaysMarkNsfw">%i18n:common.always-mark-nsfw%</ui-switch>
|
||||
</section>
|
||||
</div>
|
||||
@ -42,6 +43,10 @@ export default Vue.extend({
|
||||
location: null,
|
||||
description: null,
|
||||
birthday: null,
|
||||
isBot: false,
|
||||
isCat: false,
|
||||
isLocked: false,
|
||||
carefulBot: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -55,34 +60,29 @@ export default Vue.extend({
|
||||
this.location = this.$store.state.i.profile.location;
|
||||
this.description = this.$store.state.i.description;
|
||||
this.birthday = this.$store.state.i.profile.birthday;
|
||||
this.isCat = this.$store.state.i.isCat;
|
||||
this.isBot = this.$store.state.i.isBot;
|
||||
this.isLocked = this.$store.state.i.isLocked;
|
||||
this.carefulBot = this.$store.state.i.carefulBot;
|
||||
},
|
||||
methods: {
|
||||
updateAvatar() {
|
||||
(this as any).apis.updateAvatar();
|
||||
},
|
||||
save() {
|
||||
save(notify) {
|
||||
(this as any).api('i/update', {
|
||||
name: this.name || null,
|
||||
location: this.location || null,
|
||||
description: this.description || null,
|
||||
birthday: this.birthday || null
|
||||
birthday: this.birthday || null,
|
||||
isCat: this.isCat,
|
||||
isBot: this.isBot,
|
||||
isLocked: this.isLocked,
|
||||
carefulBot: this.carefulBot
|
||||
}).then(() => {
|
||||
(this as any).apis.notify('%i18n:@profile-updated%');
|
||||
});
|
||||
},
|
||||
onChangeIsLocked() {
|
||||
(this as any).api('i/update', {
|
||||
isLocked: this.$store.state.i.isLocked
|
||||
});
|
||||
},
|
||||
onChangeIsBot() {
|
||||
(this as any).api('i/update', {
|
||||
isBot: this.$store.state.i.isBot
|
||||
});
|
||||
},
|
||||
onChangeIsCat() {
|
||||
(this as any).api('i/update', {
|
||||
isCat: this.$store.state.i.isCat
|
||||
if (notify) {
|
||||
(this as any).apis.notify('%i18n:@profile-updated%');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,13 @@
|
||||
<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
||||
<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@navbar-position%</header>
|
||||
<ui-radio v-model="navbar" value="top">%i18n:@navbar-position-top%</ui-radio>
|
||||
<ui-radio v-model="navbar" value="left">%i18n:@navbar-position-left%</ui-radio>
|
||||
<ui-radio v-model="navbar" value="right">%i18n:@navbar-position-right%</ui-radio>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
@ -293,6 +300,11 @@ export default Vue.extend({
|
||||
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
|
||||
},
|
||||
|
||||
navbar: {
|
||||
get() { return this.$store.state.device.navbar; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'navbar', value }); }
|
||||
},
|
||||
|
||||
enableSounds: {
|
||||
get() { return this.$store.state.device.enableSounds; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||
|
@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-timeline-core">
|
||||
<mk-friends-maker v-if="src == 'home' && alone"/>
|
||||
<div class="fetching" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||
<p :class="$style.empty" slot="empty">
|
||||
@ -170,15 +167,10 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
.mk-timeline-core
|
||||
> .mk-friends-maker
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> .fetching
|
||||
padding 64px 0
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="stylus" module>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<li @click="list">
|
||||
<p>%fa:list%<span>%i18n:@lists%</span>%fa:angle-right%</p>
|
||||
</li>
|
||||
<li @click="followRequests" v-if="$store.state.i.isLocked">
|
||||
<li @click="followRequests" v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
|
||||
<p>%fa:envelope R%<span>%i18n:@follow-requests%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>%fa:angle-right%</p>
|
||||
</li>
|
||||
</ul>
|
||||
@ -157,6 +157,9 @@ export default Vue.extend({
|
||||
font-family Meiryo, sans-serif
|
||||
text-decoration none
|
||||
|
||||
@media (max-width 1100px)
|
||||
display none
|
||||
|
||||
[data-fa]
|
||||
margin-left 8px
|
||||
|
||||
@ -171,6 +174,9 @@ export default Vue.extend({
|
||||
border-radius 4px
|
||||
transition filter 100ms ease
|
||||
|
||||
@media (max-width 1100px)
|
||||
margin-left 8px
|
||||
|
||||
> .menu
|
||||
$bgcolor = var(--face)
|
||||
display block
|
||||
|
@ -17,8 +17,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
.note
|
||||
display inline-block
|
||||
padding 8px
|
||||
|
@ -29,6 +29,9 @@ export default Vue.extend({
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.search
|
||||
@media (max-width 800px)
|
||||
display none !important
|
||||
|
||||
> [data-fa]
|
||||
display block
|
||||
position absolute
|
||||
@ -58,6 +61,9 @@ export default Vue.extend({
|
||||
transition color 0.5s ease, border 0.5s ease
|
||||
color var(--desktopHeaderSearchFg)
|
||||
|
||||
@media (max-width 1000px)
|
||||
width 10em
|
||||
|
||||
&::placeholder
|
||||
color var(--desktopHeaderFg)
|
||||
|
||||
|
368
src/client/app/desktop/views/components/ui.sidebar.vue
Normal file
368
src/client/app/desktop/views/components/ui.sidebar.vue
Normal file
@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<div class="header" :class="navbar">
|
||||
<div class="body">
|
||||
<div class="post">
|
||||
<button @click="post" title="%i18n:@post%">%fa:pencil-alt%</button>
|
||||
</div>
|
||||
|
||||
<div class="nav" v-if="$store.getters.isSignedIn">
|
||||
<div class="home" :class="{ active: $route.name == 'index' }" @click="goToTop">
|
||||
<router-link to="/">%fa:home%</router-link>
|
||||
</div>
|
||||
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
||||
<router-link to="/deck">%fa:columns%</router-link>
|
||||
</div>
|
||||
<div class="messaging">
|
||||
<a @click="messaging">%fa:comments%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template></a>
|
||||
</div>
|
||||
<div class="game">
|
||||
<a @click="game">%fa:gamepad%<template v-if="hasGameInvitations">%fa:circle%</template></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav bottom" v-if="$store.getters.isSignedIn">
|
||||
<div>
|
||||
<a @click="drive">%fa:cloud%</a>
|
||||
</div>
|
||||
<div ref="notificationsButton" :class="{ active: showNotifications }">
|
||||
<a @click="notifications">%fa:R bell%</a>
|
||||
</div>
|
||||
<div>
|
||||
<a @click="settings">%fa:cog%</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="account">
|
||||
<router-link :to="`/@${ $store.state.i.username }`">
|
||||
<mk-avatar class="avatar" :user="$store.state.i"/>
|
||||
</router-link>
|
||||
|
||||
<div class="nav menu">
|
||||
<div class="signout">
|
||||
<a @click="signout">%fa:power-off%</a>
|
||||
</div>
|
||||
<div>
|
||||
<router-link to="/i/favorites">%fa:star%</router-link>
|
||||
</div>
|
||||
<div v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
|
||||
<a @click="followRequests">%fa:envelope R%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav dark">
|
||||
<div>
|
||||
<a @click="dark"><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition :name="`slide-${navbar}`">
|
||||
<div class="notifications" v-if="showNotifications" ref="notifications" :class="navbar">
|
||||
<mk-notifications/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import MkUserListsWindow from './user-lists-window.vue';
|
||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
||||
import MkSettingsWindow from './settings-window.vue';
|
||||
import MkDriveWindow from './drive-window.vue';
|
||||
import MkMessagingWindow from './messaging-window.vue';
|
||||
import MkGameWindow from './game-window.vue';
|
||||
import contains from '../../../common/scripts/contains';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
hasGameInvitations: false,
|
||||
connection: null,
|
||||
showNotifications: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasUnreadMessagingMessage(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
|
||||
},
|
||||
|
||||
navbar(): string {
|
||||
return this.$store.state.device.navbar;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = (this as any).os.stream.useSharedConnection('main');
|
||||
|
||||
this.connection.on('reversiInvited', this.onReversiInvited);
|
||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.dispose();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitations = true;
|
||||
},
|
||||
|
||||
onReversiNoInvites() {
|
||||
this.hasGameInvitations = false;
|
||||
},
|
||||
|
||||
messaging() {
|
||||
(this as any).os.new(MkMessagingWindow);
|
||||
},
|
||||
|
||||
game() {
|
||||
(this as any).os.new(MkGameWindow);
|
||||
},
|
||||
|
||||
post() {
|
||||
(this as any).apis.post();
|
||||
},
|
||||
|
||||
drive() {
|
||||
(this as any).os.new(MkDriveWindow);
|
||||
},
|
||||
|
||||
list() {
|
||||
const w = (this as any).os.new(MkUserListsWindow);
|
||||
w.$once('choosen', list => {
|
||||
this.$router.push(`i/lists/${ list.id }`);
|
||||
});
|
||||
},
|
||||
|
||||
followRequests() {
|
||||
(this as any).os.new(MkFollowRequestsWindow);
|
||||
},
|
||||
|
||||
settings() {
|
||||
(this as any).os.new(MkSettingsWindow);
|
||||
},
|
||||
|
||||
signout() {
|
||||
(this as any).os.signout();
|
||||
},
|
||||
|
||||
notifications() {
|
||||
this.showNotifications ? this.closeNotifications() : this.openNotifications();
|
||||
},
|
||||
|
||||
openNotifications() {
|
||||
this.showNotifications = true;
|
||||
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||
el.addEventListener('mousedown', this.onMousedown);
|
||||
});
|
||||
},
|
||||
|
||||
closeNotifications() {
|
||||
this.showNotifications = false;
|
||||
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||
el.removeEventListener('mousedown', this.onMousedown);
|
||||
});
|
||||
},
|
||||
|
||||
onMousedown(e) {
|
||||
e.preventDefault();
|
||||
if (
|
||||
!contains(this.$refs.notifications, e.target) &&
|
||||
this.$refs.notifications != e.target &&
|
||||
!contains(this.$refs.notificationsButton, e.target) &&
|
||||
this.$refs.notificationsButton != e.target
|
||||
) {
|
||||
this.closeNotifications();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
dark() {
|
||||
this.$store.commit('device/set', {
|
||||
key: 'darkmode',
|
||||
value: !this.$store.state.device.darkmode
|
||||
});
|
||||
},
|
||||
|
||||
goToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.header
|
||||
$width = 68px
|
||||
|
||||
position fixed
|
||||
top 0
|
||||
z-index 1000
|
||||
width $width
|
||||
height 100%
|
||||
|
||||
&.left
|
||||
left 0
|
||||
box-shadow var(--shadowRight)
|
||||
|
||||
&.right
|
||||
right 0
|
||||
box-shadow var(--shadowLeft)
|
||||
|
||||
> .body
|
||||
position fixed
|
||||
top 0
|
||||
z-index 1
|
||||
width $width
|
||||
height 100%
|
||||
background var(--desktopHeaderBg)
|
||||
|
||||
> .post
|
||||
width $width
|
||||
height $width
|
||||
padding 12px
|
||||
|
||||
> button
|
||||
display inline-block
|
||||
margin 0
|
||||
padding 0
|
||||
height 100%
|
||||
width 100%
|
||||
font-size 1.2em
|
||||
font-weight normal
|
||||
text-decoration none
|
||||
color var(--primaryForeground)
|
||||
background var(--primary) !important
|
||||
outline none
|
||||
border none
|
||||
border-radius 100%
|
||||
transition background 0.1s ease
|
||||
cursor pointer
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
|
||||
&:hover
|
||||
background var(--primaryLighten10) !important
|
||||
|
||||
&:active
|
||||
background var(--primaryDarken10) !important
|
||||
transition background 0s ease
|
||||
|
||||
> .nav.bottom
|
||||
position absolute
|
||||
bottom 128px
|
||||
left 0
|
||||
|
||||
> .account
|
||||
position absolute
|
||||
bottom 64px
|
||||
left 0
|
||||
width $width
|
||||
height $width
|
||||
padding 14px
|
||||
|
||||
> .menu
|
||||
display none
|
||||
position absolute
|
||||
bottom 64px
|
||||
left 0
|
||||
background var(--desktopHeaderBg)
|
||||
|
||||
&:hover
|
||||
> .menu
|
||||
display block
|
||||
|
||||
> *:not(.menu)
|
||||
display block
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
> .avatar
|
||||
pointer-events none
|
||||
width 100%
|
||||
height 100%
|
||||
|
||||
> .dark
|
||||
position absolute
|
||||
bottom 0
|
||||
left 0
|
||||
width $width
|
||||
height $width
|
||||
|
||||
> .notifications
|
||||
position fixed
|
||||
top 0
|
||||
width 350px
|
||||
height 100%
|
||||
overflow auto
|
||||
background var(--face)
|
||||
|
||||
&.left
|
||||
left $width
|
||||
box-shadow var(--shadowRight)
|
||||
|
||||
&.right
|
||||
right $width
|
||||
box-shadow var(--shadowLeft)
|
||||
|
||||
.nav
|
||||
> *
|
||||
> *
|
||||
display block
|
||||
width $width
|
||||
line-height 52px
|
||||
text-align center
|
||||
font-size 18px
|
||||
color var(--desktopHeaderFg)
|
||||
|
||||
&:hover
|
||||
background rgba(0, 0, 0, 0.05)
|
||||
color var(--desktopHeaderHoverFg)
|
||||
text-decoration none
|
||||
|
||||
&:active
|
||||
background rgba(0, 0, 0, 0.1)
|
||||
|
||||
&.left
|
||||
.nav
|
||||
> *
|
||||
&.active
|
||||
box-shadow -4px 0 var(--primary) inset
|
||||
|
||||
&.right
|
||||
.nav
|
||||
> *
|
||||
&.active
|
||||
box-shadow 4px 0 var(--primary) inset
|
||||
|
||||
.slide-left-enter-active,
|
||||
.slide-left-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.slide-left-enter, .slide-left-leave-to {
|
||||
transform: translateX(-16px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-right-enter-active,
|
||||
.slide-right-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.slide-right-enter, .slide-right-leave-to {
|
||||
transform: translateX(16px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="mk-ui" v-hotkey.global="keymap">
|
||||
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
|
||||
<x-header class="header" v-show="!zenMode" ref="header"/>
|
||||
<div class="content">
|
||||
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
|
||||
<x-sidebar class="sidebar" v-if="navbar != 'top'" ref="sidebar"/>
|
||||
<div class="content" :class="[{ sidebar: navbar != 'top' }, navbar]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<mk-stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||
@ -12,10 +13,12 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XHeader from './ui.header.vue';
|
||||
import XSidebar from './ui.sidebar.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XHeader
|
||||
XHeader,
|
||||
XSidebar
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -25,6 +28,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
computed: {
|
||||
navbar(): string {
|
||||
return this.$store.state.device.navbar;
|
||||
},
|
||||
|
||||
style(): any {
|
||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
||||
return {
|
||||
@ -45,6 +52,12 @@ export default Vue.extend({
|
||||
watch: {
|
||||
'$store.state.uiHeaderHeight'() {
|
||||
this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
|
||||
},
|
||||
|
||||
navbar() {
|
||||
if (this.navbar != 'top') {
|
||||
this.$store.commit('setUiHeaderHeight', 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -83,8 +96,10 @@ export default Vue.extend({
|
||||
background-attachment fixed
|
||||
opacity 0.3
|
||||
|
||||
> .header
|
||||
@media (max-width 1000px)
|
||||
display none
|
||||
> .content.sidebar.left
|
||||
padding-left 68px
|
||||
|
||||
> .content.sidebar.right
|
||||
padding-right 68px
|
||||
|
||||
</style>
|
||||
|
@ -78,7 +78,7 @@ export default Vue.extend({
|
||||
this.connection.on('stats', this.onStats);
|
||||
this.connection.on('statsLog', this.onStatsLog);
|
||||
this.connection.send('requestLog', {
|
||||
id: Math.random().toString(),
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
},
|
||||
|
@ -276,13 +276,24 @@ export default Vue.extend({
|
||||
min-width 330px
|
||||
height 100%
|
||||
background var(--face)
|
||||
border-radius 6px
|
||||
//box-shadow 0 2px 16px rgba(#000, 0.1)
|
||||
border-radius var(--round)
|
||||
box-shadow var(--shadow)
|
||||
overflow hidden
|
||||
|
||||
&.draghover
|
||||
box-shadow 0 0 0 2px var(--primaryAlpha08)
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
z-index 1000
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background var(--primaryAlpha02)
|
||||
|
||||
&.dragging
|
||||
box-shadow 0 0 0 2px var(--primaryAlpha04)
|
||||
|
||||
@ -310,7 +321,7 @@ export default Vue.extend({
|
||||
|
||||
> header
|
||||
display flex
|
||||
z-index 1
|
||||
z-index 2
|
||||
line-height $header-height
|
||||
padding 0 16px
|
||||
font-size 14px
|
||||
@ -338,6 +349,7 @@ export default Vue.extend({
|
||||
|
||||
> .toggleActive
|
||||
> .menu
|
||||
padding 0
|
||||
width $header-height
|
||||
line-height $header-height
|
||||
font-size 16px
|
||||
|
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<div v-if="!mediaView" class="zyjjkidcqjnlegkqebitfviomuqmseqk" :class="{ renote: isRenote }">
|
||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="p.reply"/>
|
||||
<div
|
||||
v-if="!mediaView"
|
||||
v-show="appearNote.deletedAt == null"
|
||||
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||
class="zyjjkidcqjnlegkqebitfviomuqmseqk"
|
||||
:class="{ renote: isRenote }"
|
||||
v-hotkey="keymap"
|
||||
:title="title"
|
||||
>
|
||||
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="appearNote.reply"/>
|
||||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
@ -12,43 +20,42 @@
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user"/>
|
||||
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
||||
<mk-note-header class="header" :note="appearNote" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i"/>
|
||||
<a class="rp" v-if="appearNote.renote != null">RP:</a>
|
||||
</div>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
<div class="files" v-if="appearNote.files.length > 0">
|
||||
<mk-media-list :media-list="appearNote.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote" :mini="true"/>
|
||||
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="renote" v-if="appearNote.renote">
|
||||
<mk-note-preview :note="appearNote.renote" :mini="true"/>
|
||||
</div>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="false" :mini="true"/>
|
||||
</div>
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
<span class="app" v-if="appearNote.app">via <b>{{ appearNote.app.name }}</b></span>
|
||||
</div>
|
||||
<footer>
|
||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
||||
<button @click="reply">
|
||||
<template v-if="p.reply">%fa:reply-all%</template>
|
||||
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||
<button @click="reply()">
|
||||
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||
<template v-else>%fa:reply%</template>
|
||||
</button>
|
||||
<button @click="renote" title="Renote">%fa:retweet%</button>
|
||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">%fa:plus%</button>
|
||||
<button class="menu" @click="menu" ref="menuButton">%fa:ellipsis-h%</button>
|
||||
<button @click="renote()" title="Renote">%fa:retweet%</button>
|
||||
<button :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton">%fa:plus%</button>
|
||||
<button class="menu" @click="menu()" ref="menuButton">%fa:ellipsis-h%</button>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
@ -65,11 +72,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parse from '../../../../../../mfm/parse';
|
||||
|
||||
import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../../common/views/components/reaction-picker.vue';
|
||||
import MkPostFormWindow from '../../components/post-form-window.vue';
|
||||
import MkRenoteFormWindow from '../../components/renote-form-window.vue';
|
||||
import XSub from './deck.note.sub.vue';
|
||||
import noteMixin from '../../../../common/scripts/note-mixin';
|
||||
import noteSubscriber from '../../../../common/scripts/note-subscriber';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -77,7 +83,10 @@ export default Vue.extend({
|
||||
XSub
|
||||
},
|
||||
|
||||
mixins: [noteSubscriber('note')],
|
||||
mixins: [
|
||||
noteMixin(),
|
||||
noteSubscriber('note')
|
||||
],
|
||||
|
||||
props: {
|
||||
note: {
|
||||
@ -89,66 +98,6 @@ export default Vue.extend({
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reply() {
|
||||
(this as any).apis.post({
|
||||
reply: this.p
|
||||
});
|
||||
},
|
||||
|
||||
renote() {
|
||||
(this as any).apis.post({
|
||||
renote: this.p
|
||||
});
|
||||
},
|
||||
|
||||
react() {
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
},
|
||||
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -168,6 +117,20 @@ export default Vue.extend({
|
||||
font-size 13px
|
||||
border-bottom solid 1px var(--faceDivider)
|
||||
|
||||
&:focus
|
||||
z-index 1
|
||||
|
||||
&:after
|
||||
content ""
|
||||
pointer-events none
|
||||
position absolute
|
||||
top 2px
|
||||
right 2px
|
||||
bottom 2px
|
||||
left 2px
|
||||
border 2px solid var(--primaryAlpha03)
|
||||
border-radius 4px
|
||||
|
||||
&:last-of-type
|
||||
border-bottom none
|
||||
|
||||
|
@ -2,6 +2,12 @@
|
||||
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
|
||||
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
||||
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="!fetching && requestInitPromise != null">
|
||||
<p>%i18n:@error%</p>
|
||||
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
||||
@ -205,6 +211,10 @@ export default Vue.extend({
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
|
||||
> .placeholder
|
||||
padding 16px
|
||||
opacity 0.3
|
||||
|
||||
> .notes
|
||||
> .date
|
||||
display block
|
||||
|
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
@ -14,7 +20,6 @@
|
||||
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
||||
</button>
|
||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||
<p class="loading" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -161,6 +166,10 @@ export default Vue.extend({
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
|
||||
> .placeholder
|
||||
padding 16px
|
||||
opacity 0.3
|
||||
|
||||
> .notifications
|
||||
|
||||
> .notification:not(:last-child)
|
||||
@ -207,13 +216,4 @@ export default Vue.extend({
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .loading
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -3,9 +3,6 @@
|
||||
<header :class="$style.header">
|
||||
<h1>{{ q }}</h1>
|
||||
</header>
|
||||
<div :class="$style.loading" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
<p :class="$style.notAvailable" v-if="!fetching && notAvailable">%i18n:@not-available%</p>
|
||||
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:not-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:not-found%'.split('{}')[1] }}</p>
|
||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||
@ -119,9 +116,6 @@ export default Vue.extend({
|
||||
border-radius 6px
|
||||
overflow hidden
|
||||
|
||||
.loading
|
||||
padding 64px 0
|
||||
|
||||
.empty
|
||||
display block
|
||||
margin 0 auto
|
||||
|
@ -3,9 +3,6 @@
|
||||
<header :class="$style.header">
|
||||
<h1>#{{ $route.params.tag }}</h1>
|
||||
</header>
|
||||
<div :class="$style.loading" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:no-posts-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:no-posts-found%'.split('{}')[1] }}</p>
|
||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||
</mk-ui>
|
||||
@ -108,9 +105,6 @@ export default Vue.extend({
|
||||
border-radius 6px
|
||||
overflow hidden
|
||||
|
||||
.loading
|
||||
padding 64px 0
|
||||
|
||||
.empty
|
||||
display block
|
||||
margin 0 auto
|
||||
|
@ -8,8 +8,6 @@
|
||||
<div>
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
||||
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
||||
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,6 +16,10 @@
|
||||
<div class="description">
|
||||
<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
||||
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
||||
</div>
|
||||
<div class="status">
|
||||
<span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span>
|
||||
<span class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</span>
|
||||
@ -182,6 +184,14 @@ export default Vue.extend({
|
||||
padding 16px 16px 16px 154px
|
||||
color var(--text)
|
||||
|
||||
> .info
|
||||
margin-top 16px
|
||||
padding-top 16px
|
||||
border-top solid 1px var(--faceDivider)
|
||||
|
||||
> *
|
||||
margin-right 16px
|
||||
|
||||
> .status
|
||||
margin-top 16px
|
||||
padding-top 16px
|
||||
|
@ -5,9 +5,6 @@
|
||||
<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'">%fa:comments% %i18n:@with-replies%</span>
|
||||
<span :data-active="mode == 'with-media'" @click="mode = 'with-media'">%fa:images% %i18n:@with-media%</span>
|
||||
</header>
|
||||
<div class="loading" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||
<p class="empty" slot="empty">%fa:R comments%%i18n:@empty%</p>
|
||||
</mk-notes>
|
||||
@ -152,9 +149,6 @@ export default Vue.extend({
|
||||
&:hover
|
||||
color var(--desktopTimelineSrcHover)
|
||||
|
||||
> .loading
|
||||
padding 64px 0
|
||||
|
||||
> .empty
|
||||
display block
|
||||
margin 0 auto
|
||||
|
@ -124,11 +124,17 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
|
||||
|
||||
//#region shadow
|
||||
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
|
||||
const shadowRight = '4px 0 4px rgba(0, 0, 0, 0.1)';
|
||||
const shadowLeft = '-4px 0 4px rgba(0, 0, 0, 0.1)';
|
||||
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow);
|
||||
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadowRight', shadowRight);
|
||||
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadowLeft', shadowLeft);
|
||||
os.store.watch(s => {
|
||||
return s.settings.useShadow;
|
||||
}, v => {
|
||||
document.documentElement.style.setProperty('--shadow', v ? shadow : 'none');
|
||||
document.documentElement.style.setProperty('--shadowRight', v ? shadowRight : 'none');
|
||||
document.documentElement.style.setProperty('--shadowLeft', v ? shadowLeft : 'none');
|
||||
});
|
||||
//#endregion
|
||||
|
||||
|
@ -443,10 +443,10 @@ export default class MiOS extends EventEmitter {
|
||||
};
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const viaStream = this.stream && this.store.state.device.apiViaStream && !forceFetch;
|
||||
const viaStream = this.stream && this.stream.state == 'connected' && this.store.state.device.apiViaStream && !forceFetch;
|
||||
|
||||
if (viaStream) {
|
||||
const id = Math.random().toString();
|
||||
const id = Math.random().toString().substr(2, 8);
|
||||
|
||||
this.stream.once(`api:${id}`, res => {
|
||||
if (res == null || Object.keys(res).length == 0) {
|
||||
|
@ -18,6 +18,7 @@ export default (os) => (opts) => {
|
||||
}).$mount();
|
||||
vm.$once('cancel', recover);
|
||||
vm.$once('posted', recover);
|
||||
if (o.cb) vm.$once('closed', o.cb);
|
||||
document.body.appendChild(vm.$el);
|
||||
(vm as any).focus();
|
||||
};
|
||||
|
@ -41,6 +41,8 @@
|
||||
<ui-button link :href="`${file.url}?download`" :download="file.name">%fa:download% %i18n:@download%</ui-button>
|
||||
<ui-button @click="rename">%fa:pencil-alt% %i18n:@rename%</ui-button>
|
||||
<ui-button @click="move">%fa:R folder-open% %i18n:@move%</ui-button>
|
||||
<ui-button @click="toggleSensitive" v-if="file.isSensitive">%fa:R eye% %i18n:@unmark-as-sensitive%</ui-button>
|
||||
<ui-button @click="toggleSensitive" v-else>%fa:R eye-slash% %i18n:@mark-as-sensitive%</ui-button>
|
||||
<ui-button @click="del">%fa:trash-alt R% %i18n:@delete%</ui-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -71,25 +73,30 @@ import { gcd } from '../../../../../prelude/math';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['file'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
gcd,
|
||||
exif: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
browser(): any {
|
||||
return this.$parent;
|
||||
},
|
||||
|
||||
kind(): string {
|
||||
return this.file.type.split('/')[0];
|
||||
},
|
||||
|
||||
style(): any {
|
||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
|
||||
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
|
||||
} : {};
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
rename() {
|
||||
const name = window.prompt('%i18n:@rename%', this.file.name);
|
||||
@ -101,6 +108,7 @@ export default Vue.extend({
|
||||
this.browser.cf(this.file, true);
|
||||
});
|
||||
},
|
||||
|
||||
move() {
|
||||
(this as any).apis.chooseDriveFolder().then(folder => {
|
||||
(this as any).api('drive/files/update', {
|
||||
@ -111,6 +119,7 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
del() {
|
||||
(this as any).api('drive/files/delete', {
|
||||
fileId: this.file.id
|
||||
@ -118,9 +127,20 @@ export default Vue.extend({
|
||||
this.browser.cd(this.file.folderId, true);
|
||||
});
|
||||
},
|
||||
|
||||
toggleSensitive() {
|
||||
(this as any).api('drive/files/update', {
|
||||
fileId: this.file.id,
|
||||
isSensitive: !this.file.isSensitive
|
||||
});
|
||||
|
||||
this.file.isSensitive = !this.file.isSensitive;
|
||||
},
|
||||
|
||||
showCreatedAt() {
|
||||
alert(new Date(this.file.createdAt).toLocaleString());
|
||||
},
|
||||
|
||||
onImageLoaded() {
|
||||
const self = this;
|
||||
EXIF.getData(this.$refs.img, function(this: any) {
|
||||
|
@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="note" :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }">
|
||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="p.reply"/>
|
||||
<div
|
||||
class="note"
|
||||
v-show="appearNote.deletedAt == null"
|
||||
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||
:class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }"
|
||||
v-hotkey="keymap"
|
||||
>
|
||||
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="appearNote.reply"/>
|
||||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
@ -12,47 +18,45 @@
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<mk-avatar class="avatar" :user="appearNote.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
||||
<mk-note-header class="header" :note="appearNote" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="appearNote.renote != null">RP:</a>
|
||||
</div>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
<div class="files" v-if="appearNote.files.length > 0">
|
||||
<mk-media-list :media-list="appearNote.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
||||
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
|
||||
</div>
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
<span class="app" v-if="appearNote.app">via <b>{{ appearNote.app.name }}</b></span>
|
||||
</div>
|
||||
<footer v-if="p.deletedAt == null">
|
||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
||||
<button @click="reply">
|
||||
<template v-if="p.reply">%fa:reply-all%</template>
|
||||
<footer>
|
||||
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||
<button @click="reply()">
|
||||
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||
<template v-else>%fa:reply%</template>
|
||||
<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button @click="renote" title="Renote">
|
||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
||||
<button @click="renote()" title="Renote">
|
||||
%fa:retweet%<p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
|
||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
||||
<button :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton">
|
||||
%fa:plus%<p class="count" v-if="appearNote.reactions_count > 0">{{ appearNote.reactions_count }}</p>
|
||||
</button>
|
||||
<button class="menu" @click="menu" ref="menuButton">
|
||||
<button class="menu" @click="menu()" ref="menuButton">
|
||||
%fa:ellipsis-h%
|
||||
</button>
|
||||
</footer>
|
||||
@ -63,12 +67,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
import noteMixin from '../../../common/scripts/note-mixin';
|
||||
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -76,74 +77,17 @@ export default Vue.extend({
|
||||
XSub
|
||||
},
|
||||
|
||||
mixins: [noteSubscriber('note')],
|
||||
mixins: [
|
||||
noteMixin({
|
||||
mobile: true
|
||||
}),
|
||||
noteSubscriber('note')
|
||||
],
|
||||
|
||||
props: ['note'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reply() {
|
||||
(this as any).apis.post({
|
||||
reply: this.p
|
||||
});
|
||||
},
|
||||
|
||||
renote() {
|
||||
(this as any).apis.post({
|
||||
renote: this.p
|
||||
});
|
||||
},
|
||||
|
||||
react() {
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p,
|
||||
compact: true,
|
||||
big: true
|
||||
});
|
||||
},
|
||||
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -154,6 +98,20 @@ export default Vue.extend({
|
||||
font-size 12px
|
||||
border-bottom solid 1px var(--faceDivider)
|
||||
|
||||
&:focus
|
||||
z-index 1
|
||||
|
||||
&:after
|
||||
content ""
|
||||
pointer-events none
|
||||
position absolute
|
||||
top 2px
|
||||
right 2px
|
||||
bottom 2px
|
||||
left 2px
|
||||
border 2px solid var(--primaryAlpha03)
|
||||
border-radius 4px
|
||||
|
||||
&:last-of-type
|
||||
border-bottom none
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
||||
|
||||
<div class="init" v-if="fetching">
|
||||
%fa:spinner .pulse%%i18n:common.loading%
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="!fetching && requestInitPromise != null">
|
||||
@ -251,13 +253,12 @@ export default Vue.extend({
|
||||
[data-fa]
|
||||
margin-right 8px
|
||||
|
||||
> .init
|
||||
padding 64px 0
|
||||
text-align center
|
||||
color #999
|
||||
> .placeholder
|
||||
padding 16px
|
||||
opacity 0.3
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
@media (min-width 500px)
|
||||
padding 32px
|
||||
|
||||
> .empty
|
||||
margin 0 auto
|
||||
|
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="mk-notifications">
|
||||
<div class="placeholder" v-if="fetching">
|
||||
<template v-for="i in 10">
|
||||
<mk-note-skeleton :key="i"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
@ -17,7 +23,6 @@
|
||||
</button>
|
||||
|
||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -179,13 +184,11 @@ export default Vue.extend({
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .fetching
|
||||
margin 0
|
||||
> .placeholder
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
opacity 0.3
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
@media (min-width 500px)
|
||||
padding 32px
|
||||
|
||||
</style>
|
||||
|
@ -62,6 +62,7 @@ import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import { toASCII } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -153,14 +154,14 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.user.host != null) {
|
||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
||||
this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.text != null) {
|
||||
const ast = parse(this.reply.text);
|
||||
|
||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
||||
const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
|
||||
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||
|
||||
// 自分は除外
|
||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||
|
@ -18,7 +18,7 @@
|
||||
<li><router-link to="/" :data-active="$route.name == 'index'">%fa:home%%i18n:@timeline%%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'">%fa:R bell%%i18n:@notifications%<template v-if="hasUnreadNotification">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'">%fa:R comments%%i18n:@messaging%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li v-if="$store.getters.isSignedIn && $store.state.i.isLocked"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/reversi" :data-active="$route.name == 'reversi'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
</ul>
|
||||
<ul>
|
||||
|
@ -8,7 +8,14 @@
|
||||
<x-profile/>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:palette% %i18n:@design%</div>
|
||||
<div slot="title">%fa:palette% %i18n:@theme%</div>
|
||||
<section>
|
||||
<mk-theme/>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:poll-h% %i18n:@design%</div>
|
||||
|
||||
<section>
|
||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||
@ -23,13 +30,6 @@
|
||||
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@theme%</header>
|
||||
<div>
|
||||
<mk-theme/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@timeline%</header>
|
||||
<div>
|
||||
@ -54,7 +54,7 @@
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:cog% %i18n:@behavior%</div>
|
||||
<div slot="title">%fa:sliders-h% %i18n:@behavior%</div>
|
||||
|
||||
<section>
|
||||
<ui-switch v-model="fetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
||||
|
@ -58,6 +58,7 @@
|
||||
|
||||
<div>
|
||||
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
||||
<ui-switch v-model="carefulBot" @change="save(false)">%i18n:@careful-bot%</ui-switch>
|
||||
</div>
|
||||
</section>
|
||||
</ui-card>
|
||||
@ -80,6 +81,7 @@ export default Vue.extend({
|
||||
bannerId: null,
|
||||
isCat: false,
|
||||
isLocked: false,
|
||||
carefulBot: false,
|
||||
saving: false,
|
||||
avatarUploading: false,
|
||||
bannerUploading: false
|
||||
@ -103,6 +105,7 @@ export default Vue.extend({
|
||||
this.bannerId = this.$store.state.i.bannerId;
|
||||
this.isCat = this.$store.state.i.isCat;
|
||||
this.isLocked = this.$store.state.i.isLocked;
|
||||
this.carefulBot = this.$store.state.i.carefulBot;
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -161,7 +164,8 @@ export default Vue.extend({
|
||||
avatarId: this.avatarId,
|
||||
bannerId: this.bannerId,
|
||||
isCat: this.isCat,
|
||||
isLocked: this.isLocked
|
||||
isLocked: this.isLocked,
|
||||
carefulBot: this.carefulBot
|
||||
}).then(i => {
|
||||
this.saving = false;
|
||||
this.$store.state.i.avatarId = i.avatarId;
|
||||
|
@ -56,6 +56,7 @@ const defaultDeviceSettings = {
|
||||
loadRawImages: false,
|
||||
alwaysShowNsfw: false,
|
||||
postStyle: 'standard',
|
||||
navbar: 'top',
|
||||
mobileNotificationPosition: 'bottom'
|
||||
};
|
||||
|
||||
|
@ -62,6 +62,8 @@ export type Source = {
|
||||
*/
|
||||
ghost?: string;
|
||||
|
||||
proxy?: string;
|
||||
|
||||
summalyProxy?: string;
|
||||
|
||||
accesslog?: string;
|
||||
|
@ -30,6 +30,8 @@
|
||||
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">→</kbd></kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
|
||||
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
|
||||
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
|
||||
<tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>お気に入りに登録</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr>
|
||||
<tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>投稿を削除</td><td><b>D</b>elete</tr>
|
||||
<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
|
||||
<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
|
||||
<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
|
||||
|
@ -2,10 +2,12 @@
|
||||
* Mention
|
||||
*/
|
||||
import parseAcct from '../../../misc/acct/parse';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export type TextElementMention = {
|
||||
type: 'mention'
|
||||
content: string
|
||||
canonical: string
|
||||
username: string
|
||||
host: string
|
||||
};
|
||||
@ -15,9 +17,11 @@ export default function(text: string) {
|
||||
if (!match) return null;
|
||||
const mention = match[0];
|
||||
const { username, host } = parseAcct(mention.substr(1));
|
||||
const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;
|
||||
return {
|
||||
type: 'mention',
|
||||
content: mention,
|
||||
canonical,
|
||||
username,
|
||||
host
|
||||
} as TextElementMention;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import { Context } from 'cafy';
|
||||
import isObjectId from './is-objectid';
|
||||
|
||||
export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
|
||||
export const isNotAnId = (x: any) => !isAnId(x);
|
||||
@ -12,7 +13,7 @@ export default class ID extends Context<mongo.ObjectID> {
|
||||
super();
|
||||
|
||||
this.transform = v => {
|
||||
if (isAnId(v) && !mongo.ObjectID.prototype.isPrototypeOf(v)) {
|
||||
if (isAnId(v) && !isObjectId(v)) {
|
||||
return new mongo.ObjectID(v);
|
||||
} else {
|
||||
return v;
|
||||
@ -20,7 +21,7 @@ export default class ID extends Context<mongo.ObjectID> {
|
||||
};
|
||||
|
||||
this.push(v => {
|
||||
if (!mongo.ObjectID.prototype.isPrototypeOf(v) && isNotAnId(v)) {
|
||||
if (!isObjectId(v) && isNotAnId(v)) {
|
||||
return new Error('must-be-an-id');
|
||||
}
|
||||
return true;
|
||||
|
3
src/misc/is-objectid.ts
Normal file
3
src/misc/is-objectid.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function(x: any): boolean {
|
||||
return x.hasOwnProperty('toHexString') || x.hasOwnProperty('_bsontype');
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import isObjectId from './is-objectid';
|
||||
|
||||
function toString(id: any) {
|
||||
return mongo.ObjectID.prototype.isPrototypeOf(id) ? (id as mongo.ObjectID).toHexString() : id;
|
||||
return isObjectId(id) ? (id as mongo.ObjectID).toHexString() : id;
|
||||
}
|
||||
|
||||
export default function(note: any, mutedUserIds: string[]): boolean {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const AccessToken = db.get<IAccessToken>('accessTokens');
|
||||
AccessToken.createIndex('token');
|
||||
@ -22,7 +23,7 @@ export async function deleteAccessToken(accessToken: string | mongo.ObjectID | I
|
||||
let a: IAccessToken;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(accessToken)) {
|
||||
if (isObjectId(accessToken)) {
|
||||
a = await AccessToken.findOne({
|
||||
_id: accessToken
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import AccessToken from './access-token';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import config from '../config';
|
||||
|
||||
const App = db.get<IApp>('apps');
|
||||
@ -43,7 +44,7 @@ export const pack = (
|
||||
let _app: any;
|
||||
|
||||
// Populate the app if 'app' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(app)) {
|
||||
if (isObjectId(app)) {
|
||||
_app = await App.findOne({
|
||||
_id: app
|
||||
});
|
||||
@ -56,7 +57,7 @@ export const pack = (
|
||||
}
|
||||
|
||||
// Me
|
||||
if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) {
|
||||
if (me && !isObjectId(me)) {
|
||||
if (typeof me === 'string') {
|
||||
me = new mongo.ObjectID(me);
|
||||
} else {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packApp } from './app';
|
||||
|
||||
const AuthSession = db.get<IAuthSession>('authSessions');
|
||||
@ -31,7 +32,7 @@ export const pack = (
|
||||
_session = deepcopy(session);
|
||||
|
||||
// Me
|
||||
if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) {
|
||||
if (me && !isObjectId(me)) {
|
||||
if (typeof me === 'string') {
|
||||
me = new mongo.ObjectID(me);
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files');
|
||||
DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true });
|
||||
@ -35,7 +36,7 @@ export async function deleteDriveFileThumbnail(driveFile: string | mongo.ObjectI
|
||||
let d: IDriveFileThumbnail;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(driveFile)) {
|
||||
if (isObjectId(driveFile)) {
|
||||
d = await DriveFileThumbnail.findOne({
|
||||
_id: driveFile
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
|
||||
import { pack as packFolder } from './drive-folder';
|
||||
import config from '../config';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import Note, { deleteNote } from './note';
|
||||
import MessagingMessage, { deleteMessagingMessage } from './messaging-message';
|
||||
import User from './user';
|
||||
@ -11,6 +12,8 @@ import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumb
|
||||
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
||||
DriveFile.createIndex('md5');
|
||||
DriveFile.createIndex('metadata.uri');
|
||||
DriveFile.createIndex('metadata.userId');
|
||||
DriveFile.createIndex('metadata.folderId');
|
||||
export default DriveFile;
|
||||
|
||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
||||
@ -76,7 +79,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv
|
||||
let d: IDriveFile;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(driveFile)) {
|
||||
if (isObjectId(driveFile)) {
|
||||
d = await DriveFile.findOne({
|
||||
_id: driveFile
|
||||
});
|
||||
@ -152,7 +155,7 @@ export const pack = (
|
||||
let _file: any;
|
||||
|
||||
// Populate the file if 'file' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(file)) {
|
||||
if (isObjectId(file)) {
|
||||
_file = await DriveFile.findOne({
|
||||
_id: file
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import DriveFile from './drive-file';
|
||||
|
||||
const DriveFolder = db.get<IDriveFolder>('driveFolders');
|
||||
DriveFolder.createIndex('userId');
|
||||
export default DriveFolder;
|
||||
|
||||
export type IDriveFolder = {
|
||||
@ -28,7 +30,7 @@ export async function deleteDriveFolder(driveFolder: string | mongo.ObjectID | I
|
||||
let d: IDriveFolder;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(driveFolder)) {
|
||||
if (isObjectId(driveFolder)) {
|
||||
d = await DriveFolder.findOne({
|
||||
_id: driveFolder
|
||||
});
|
||||
@ -82,7 +84,7 @@ export const pack = (
|
||||
let _folder: any;
|
||||
|
||||
// Populate the folder if 'folder' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(folder)) {
|
||||
if (isObjectId(folder)) {
|
||||
_folder = await DriveFolder.findOne({ _id: folder });
|
||||
} else if (typeof folder === 'string') {
|
||||
_folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) });
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packNote } from './note';
|
||||
|
||||
const Favorite = db.get<IFavorite>('favorites');
|
||||
@ -21,7 +22,7 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori
|
||||
let f: IFavorite;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) {
|
||||
if (isObjectId(favorite)) {
|
||||
f = await Favorite.findOne({
|
||||
_id: favorite
|
||||
});
|
||||
@ -58,7 +59,7 @@ export const pack = (
|
||||
let _favorite: any;
|
||||
|
||||
// Populate the favorite if 'favorite' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) {
|
||||
if (isObjectId(favorite)) {
|
||||
_favorite = await Favorite.findOne({
|
||||
_id: favorite
|
||||
});
|
||||
@ -75,7 +76,9 @@ export const pack = (
|
||||
delete _favorite._id;
|
||||
|
||||
// Populate note
|
||||
_favorite.note = await packNote(_favorite.noteId, me);
|
||||
_favorite.note = await packNote(_favorite.noteId, me, {
|
||||
detail: true
|
||||
});
|
||||
|
||||
// (データベースの不具合などで)投稿が見つからなかったら
|
||||
if (_favorite.note == null) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
const FollowRequest = db.get<IFollowRequest>('followRequests');
|
||||
@ -12,6 +13,7 @@ export type IFollowRequest = {
|
||||
createdAt: Date;
|
||||
followeeId: mongo.ObjectID;
|
||||
followerId: mongo.ObjectID;
|
||||
requestId?: string; // id of Follow Activity
|
||||
|
||||
// 非正規化
|
||||
_followee: {
|
||||
@ -33,7 +35,7 @@ export async function deleteFollowRequest(followRequest: string | mongo.ObjectID
|
||||
let f: IFollowRequest;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(followRequest)) {
|
||||
if (isObjectId(followRequest)) {
|
||||
f = await FollowRequest.findOne({
|
||||
_id: followRequest
|
||||
});
|
||||
@ -63,7 +65,7 @@ export const pack = (
|
||||
let _request: any;
|
||||
|
||||
// Populate the request if 'request' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(request)) {
|
||||
if (isObjectId(request)) {
|
||||
_request = await FollowRequest.findOne({
|
||||
_id: request
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const FollowedLog = db.get<IFollowedLog>('followedLogs');
|
||||
export default FollowedLog;
|
||||
@ -18,7 +19,7 @@ export async function deleteFollowedLog(followedLog: string | mongo.ObjectID | I
|
||||
let f: IFollowedLog;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(followedLog)) {
|
||||
if (isObjectId(followedLog)) {
|
||||
f = await FollowedLog.findOne({
|
||||
_id: followedLog
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const FollowingLog = db.get<IFollowingLog>('followingLogs');
|
||||
export default FollowingLog;
|
||||
@ -18,7 +19,7 @@ export async function deleteFollowingLog(followingLog: string | mongo.ObjectID |
|
||||
let f: IFollowingLog;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(followingLog)) {
|
||||
if (isObjectId(followingLog)) {
|
||||
f = await FollowingLog.findOne({
|
||||
_id: followingLog
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const Following = db.get<IFollowing>('following');
|
||||
Following.createIndex(['followerId', 'followeeId'], { unique: true });
|
||||
@ -32,7 +33,7 @@ export async function deleteFollowing(following: string | mongo.ObjectID | IFoll
|
||||
let f: IFollowing;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(following)) {
|
||||
if (isObjectId(following)) {
|
||||
f = await Following.findOne({
|
||||
_id: following
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../../../db/mongodb';
|
||||
import isObjectId from '../../../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from '../../user';
|
||||
|
||||
const ReversiGame = db.get<IReversiGame>('reversiGames');
|
||||
@ -62,7 +63,7 @@ export const pack = (
|
||||
let _game: any;
|
||||
|
||||
// Populate the game if 'game' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(game)) {
|
||||
if (isObjectId(game)) {
|
||||
_game = await ReversiGame.findOne({
|
||||
_id: game
|
||||
});
|
||||
@ -76,7 +77,7 @@ export const pack = (
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../../../db/mongodb';
|
||||
import isObjectId from '../../../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from '../../user';
|
||||
|
||||
const Matching = db.get<IMatching>('reversiMatchings');
|
||||
@ -23,7 +24,7 @@ export const pack = (
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const MessagingHistory = db.get<IMessagingHistory>('messagingHistories');
|
||||
export default MessagingHistory;
|
||||
@ -19,7 +20,7 @@ export async function deleteMessagingHistory(messagingHistory: string | mongo.Ob
|
||||
let m: IMessagingHistory;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(messagingHistory)) {
|
||||
if (isObjectId(messagingHistory)) {
|
||||
m = await MessagingHistory.findOne({
|
||||
_id: messagingHistory
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
|
||||
import { pack as packUser } from './user';
|
||||
import { pack as packFile } from './drive-file';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import MessagingHistory, { deleteMessagingHistory } from './messaging-history';
|
||||
import { length } from 'stringz';
|
||||
|
||||
@ -30,7 +31,7 @@ export async function deleteMessagingMessage(messagingMessage: string | mongo.Ob
|
||||
let m: IMessagingMessage;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(messagingMessage)) {
|
||||
if (isObjectId(messagingMessage)) {
|
||||
m = await MessagingMessage.findOne({
|
||||
_id: messagingMessage
|
||||
});
|
||||
@ -72,7 +73,7 @@ export const pack = (
|
||||
let _message: any;
|
||||
|
||||
// Populate the message if 'message' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(message)) {
|
||||
if (isObjectId(message)) {
|
||||
_message = await MessagingMessage.findOne({
|
||||
_id: message
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const Mute = db.get<IMute>('mute');
|
||||
Mute.createIndex(['muterId', 'muteeId'], { unique: true });
|
||||
@ -19,7 +20,7 @@ export async function deleteMute(mute: string | mongo.ObjectID | IMute) {
|
||||
let m: IMute;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(mute)) {
|
||||
if (isObjectId(mute)) {
|
||||
m = await Mute.findOne({
|
||||
_id: mute
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
|
||||
import $ from 'cafy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import Reaction from './note-reaction';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
@ -37,7 +38,7 @@ export async function deleteNoteReaction(noteReaction: string | mongo.ObjectID |
|
||||
let n: INoteReaction;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(noteReaction)) {
|
||||
if (isObjectId(noteReaction)) {
|
||||
n = await NoteReaction.findOne({
|
||||
_id: noteReaction
|
||||
});
|
||||
@ -67,7 +68,7 @@ export const pack = (
|
||||
let _reaction: any;
|
||||
|
||||
// Populate the reaction if 'reaction' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(reaction)) {
|
||||
if (isObjectId(reaction)) {
|
||||
_reaction = await Reaction.findOne({
|
||||
_id: reaction
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const NoteWatching = db.get<INoteWatching>('noteWatching');
|
||||
NoteWatching.createIndex(['userId', 'noteId'], { unique: true });
|
||||
@ -19,7 +20,7 @@ export async function deleteNoteWatching(noteWatching: string | mongo.ObjectID |
|
||||
let n: INoteWatching;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(noteWatching)) {
|
||||
if (isObjectId(noteWatching)) {
|
||||
n = await NoteWatching.findOne({
|
||||
_id: noteWatching
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { length } from 'stringz';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packApp } from './app';
|
||||
@ -107,7 +108,7 @@ export async function deleteNote(note: string | mongo.ObjectID | INote) {
|
||||
let n: INote;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(note)) {
|
||||
if (isObjectId(note)) {
|
||||
n = await Note.findOne({
|
||||
_id: note
|
||||
});
|
||||
@ -259,7 +260,7 @@ export const pack = async (
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
@ -269,7 +270,7 @@ export const pack = async (
|
||||
let _note: any;
|
||||
|
||||
// Populate the note if 'note' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(note)) {
|
||||
if (isObjectId(note)) {
|
||||
_note = await Note.findOne({
|
||||
_id: note
|
||||
});
|
||||
@ -358,8 +359,8 @@ export const pack = async (
|
||||
})(_note.poll);
|
||||
}
|
||||
|
||||
// Fetch my reaction
|
||||
if (meId) {
|
||||
// Fetch my reaction
|
||||
_note.myReaction = (async () => {
|
||||
const reaction = await Reaction
|
||||
.findOne({
|
||||
@ -374,6 +375,19 @@ export const pack = async (
|
||||
|
||||
return null;
|
||||
})();
|
||||
|
||||
// isFavorited
|
||||
_note.isFavorited = (async () => {
|
||||
const favorite = await Favorite
|
||||
.count({
|
||||
userId: meId,
|
||||
noteId: id
|
||||
}, {
|
||||
limit: 1
|
||||
});
|
||||
|
||||
return favorite === 1;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packNote } from './note';
|
||||
|
||||
@ -57,7 +58,7 @@ export async function deleteNotification(notification: string | mongo.ObjectID |
|
||||
let n: INotification;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(notification)) {
|
||||
if (isObjectId(notification)) {
|
||||
n = await Notification.findOne({
|
||||
_id: notification
|
||||
});
|
||||
@ -90,7 +91,7 @@ export const pack = (notification: any) => new Promise<any>(async (resolve, reje
|
||||
let _notification: any;
|
||||
|
||||
// Populate the notification if 'notification' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(notification)) {
|
||||
if (isObjectId(notification)) {
|
||||
_notification = await Notification.findOne({
|
||||
_id: notification
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const PollVote = db.get<IPollVote>('pollVotes');
|
||||
export default PollVote;
|
||||
@ -19,7 +20,7 @@ export async function deletePollVote(pollVote: string | mongo.ObjectID | IPollVo
|
||||
let p: IPollVote;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(pollVote)) {
|
||||
if (isObjectId(pollVote)) {
|
||||
p = await PollVote.findOne({
|
||||
_id: pollVote
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const SwSubscription = db.get<ISwSubscription>('swSubscriptions');
|
||||
export default SwSubscription;
|
||||
@ -19,7 +20,7 @@ export async function deleteSwSubscription(swSubscription: string | mongo.Object
|
||||
let s: ISwSubscription;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(swSubscription)) {
|
||||
if (isObjectId(swSubscription)) {
|
||||
s = await SwSubscription.findOne({
|
||||
_id: swSubscription
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mongo from 'mongodb';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const UserList = db.get<IUserList>('userList');
|
||||
export default UserList;
|
||||
@ -20,7 +21,7 @@ export async function deleteUserList(userList: string | mongo.ObjectID | IUserLi
|
||||
let u: IUserList;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(userList)) {
|
||||
if (isObjectId(userList)) {
|
||||
u = await UserList.findOne({
|
||||
_id: userList
|
||||
});
|
||||
@ -45,7 +46,7 @@ export const pack = (
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _userList: any;
|
||||
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(userList)) {
|
||||
if (isObjectId(userList)) {
|
||||
_userList = await UserList.findOne({
|
||||
_id: userList
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
|
||||
const sequential = require('promise-sequential');
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import Note, { packMany as packNoteMany, deleteNote } from './note';
|
||||
import Following, { deleteFollowing } from './following';
|
||||
import Mute, { deleteMute } from './mute';
|
||||
@ -65,6 +66,16 @@ type IUserBase = {
|
||||
*/
|
||||
isLocked: boolean;
|
||||
|
||||
/**
|
||||
* Botか否か
|
||||
*/
|
||||
isBot: boolean;
|
||||
|
||||
/**
|
||||
* Botからのフォローを承認制にするか
|
||||
*/
|
||||
carefulBot: boolean;
|
||||
|
||||
/**
|
||||
* このアカウントに届いているフォローリクエストの数
|
||||
*/
|
||||
@ -94,7 +105,6 @@ export interface ILocalUser extends IUserBase {
|
||||
tags: string[];
|
||||
};
|
||||
lastUsedAt: Date;
|
||||
isBot: boolean;
|
||||
isCat: boolean;
|
||||
isAdmin?: boolean;
|
||||
isVerified?: boolean;
|
||||
@ -166,7 +176,7 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
|
||||
let u: IUser;
|
||||
|
||||
// Populate
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(user)) {
|
||||
if (isObjectId(user)) {
|
||||
u = await User.findOne({
|
||||
_id: user
|
||||
});
|
||||
@ -331,7 +341,6 @@ export const pack = (
|
||||
includeHasUnreadNotes?: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
includeSecrets: false
|
||||
@ -349,7 +358,7 @@ export const pack = (
|
||||
};
|
||||
|
||||
// Populate the user if 'user' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(user)) {
|
||||
if (isObjectId(user)) {
|
||||
_user = await User.findOne({
|
||||
_id: user
|
||||
}, { fields });
|
||||
@ -369,7 +378,7 @@ export const pack = (
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
|
@ -23,5 +23,5 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||
throw new Error('フォローしようとしているユーザーはローカルユーザーではありません');
|
||||
}
|
||||
|
||||
await follow(actor, followee);
|
||||
await follow(actor, followee, activity.id);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import * as debug from 'debug';
|
||||
|
||||
import uploadFromUrl from '../../../services/drive/upload-from-url';
|
||||
import { IRemoteUser } from '../../../models/user';
|
||||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
||||
import Resolver from '../resolver';
|
||||
|
||||
const log = debug('misskey:activitypub');
|
||||
@ -24,7 +24,22 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
|
||||
|
||||
log(`Creating the Image: ${image.url}`);
|
||||
|
||||
return await uploadFromUrl(image.url, actor, null, image.url, image.sensitive);
|
||||
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive);
|
||||
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||
// URLを更新する
|
||||
if (file.metadata.url !== image.url) {
|
||||
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
|
||||
$set: {
|
||||
'metadata.url': image.url,
|
||||
'metadata.uri': image.url
|
||||
}
|
||||
}, {
|
||||
returnNewDocument: true
|
||||
});
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,8 @@
|
||||
export default (object: any) => ({
|
||||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
|
||||
export default (object: any, user: ILocalUser) => ({
|
||||
type: 'Accept',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
object
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user