Compare commits

...

83 Commits

Author SHA1 Message Date
1bec4e2d12 10.20.0 2018-10-16 11:44:35 +09:00
03cd1d27bf New Crowdin translations (#2910)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)
2018-10-16 11:38:43 +09:00
9427a756c9 Update mongodb 2018-10-16 11:38:09 +09:00
d32b2a8ce5 fix(package): update @types/node to version 10.12.0 (#2912) 2018-10-16 10:46:45 +09:00
15473b4368 fix(package): update @types/webpack to version 4.4.17 (#2911) 2018-10-16 10:46:38 +09:00
54de0dc4a7 Update config for CI 2018-10-16 10:36:27 +09:00
0162eaf826 Update signin.ts 2018-10-16 10:33:05 +09:00
572cfafbe1 Add some API tests 2018-10-16 10:18:47 +09:00
4d6335ce9a Add some tests and fix 2018-10-16 09:45:36 +09:00
1c9c4af9f1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-16 08:58:54 +09:00
a6844ebc9d Add some tests 2018-10-16 08:58:45 +09:00
072492c29b Implement /api/v1/instance/peers (#2913)
* Implement /api/v1/instance/peers

* Use punycode

* Remove Cache-Control

* Rename
2018-10-16 08:55:55 +09:00
99da4f9839 Add some tests and some fixes 2018-10-16 08:54:36 +09:00
88664486af Refactor 2018-10-16 08:27:20 +09:00
80daf7c749 Implement API tests 2018-10-16 06:37:21 +09:00
beb2f7e558 fix(package): update @types/sharp to version 0.21.0 (#2908) 2018-10-16 05:21:07 +09:00
6243184c95 fix(package): update @types/gulp-uglify to version 3.0.6 (#2906) 2018-10-16 05:20:57 +09:00
1b3baef966 Merge pull request #2898 from syuilo/l10n_develop
New Crowdin translations
2018-10-16 05:20:40 +09:00
98f38ee29b fix(package): update vue-svg-inline-loader to version 1.2.1 (#2909) 2018-10-16 05:20:17 +09:00
09b82bfea4 fix(package): update chart.js to version 2.7.3 (#2907) 2018-10-16 05:20:02 +09:00
937f686264 New translations ja-JP.yml (Russian) 2018-10-15 20:53:38 +09:00
9bc9cbac21 New translations ja-JP.yml (English) 2018-10-15 18:22:42 +09:00
6024550158 New translations ja-JP.yml (Norwegian) 2018-10-15 18:14:29 +09:00
4ae5f82171 New translations ja-JP.yml (Dutch) 2018-10-15 18:14:23 +09:00
6d2c9dcee9 New translations ja-JP.yml (Japanese, Kansai) 2018-10-15 18:14:18 +09:00
0f1b0e1870 New translations ja-JP.yml (Spanish) 2018-10-15 18:14:11 +09:00
81c682cdc8 New translations ja-JP.yml (Russian) 2018-10-15 18:14:05 +09:00
ab9fa67d9f New translations ja-JP.yml (Portuguese) 2018-10-15 18:14:00 +09:00
9537fce335 New translations ja-JP.yml (Polish) 2018-10-15 18:13:53 +09:00
9d97e7e348 New translations ja-JP.yml (Korean) 2018-10-15 18:13:48 +09:00
ebe7939412 New translations ja-JP.yml (Italian) 2018-10-15 18:13:43 +09:00
807e3e8ca7 New translations ja-JP.yml (German) 2018-10-15 18:13:37 +09:00
a59faf9117 New translations ja-JP.yml (French) 2018-10-15 18:13:32 +09:00
d786036155 New translations ja-JP.yml (English) 2018-10-15 18:13:25 +09:00
61d6ed5489 New translations ja-JP.yml (Chinese Simplified) 2018-10-15 18:13:21 +09:00
b38200d48a New translations ja-JP.yml (Catalan) 2018-10-15 18:13:16 +09:00
a0c396a842 10.19.0 2018-10-15 18:03:28 +09:00
88fbc53e37 Resolve #2314 2018-10-15 18:02:57 +09:00
a2206b2d52 🎨 2018-10-15 17:55:59 +09:00
a95ff447d7 🎨 2018-10-15 17:43:25 +09:00
49dbd7f9d2 Fix following from Preroma does not complete (#2905)
* In Follow Accept/Reject, send previous received id

* In Follow Accept/Reject, send Activity.actor
2018-10-15 16:51:22 +09:00
2ad2779096 10.18.0 2018-10-15 06:03:50 +09:00
23045369aa 🎨 2018-10-15 06:03:15 +09:00
116faf26e6 10.17.0 2018-10-15 05:29:58 +09:00
2582b8d132 🎨 2018-10-15 05:28:35 +09:00
63f7941073 🎨 2018-10-15 05:18:39 +09:00
676f026085 🎨 2018-10-15 04:36:31 +09:00
a13319fd86 New translations ja-JP.yml (Norwegian) 2018-10-14 19:52:13 +09:00
be8765278c New translations ja-JP.yml (Dutch) 2018-10-14 19:52:08 +09:00
c8bb3dc209 New translations ja-JP.yml (Japanese, Kansai) 2018-10-14 19:52:03 +09:00
ea16befb73 New translations ja-JP.yml (Spanish) 2018-10-14 19:51:59 +09:00
20b1bb7681 New translations ja-JP.yml (Russian) 2018-10-14 19:51:55 +09:00
bd10eb50eb New translations ja-JP.yml (Portuguese) 2018-10-14 19:51:50 +09:00
d47c0eb31a New translations ja-JP.yml (Polish) 2018-10-14 19:51:45 +09:00
177e8bb19f New translations ja-JP.yml (Korean) 2018-10-14 19:51:41 +09:00
d156111637 New translations ja-JP.yml (Italian) 2018-10-14 19:51:37 +09:00
8c13d3e50b New translations ja-JP.yml (German) 2018-10-14 19:51:33 +09:00
6ff01016f0 New translations ja-JP.yml (French) 2018-10-14 19:51:30 +09:00
5d659da012 New translations ja-JP.yml (English) 2018-10-14 19:51:26 +09:00
28e7552a1a New translations ja-JP.yml (Chinese Simplified) 2018-10-14 19:51:21 +09:00
53d264814b New translations ja-JP.yml (Catalan) 2018-10-14 19:51:15 +09:00
2d6b20d34b 10.16.0 2018-10-14 19:45:51 +09:00
99073b56df Resolve #2900 2018-10-14 19:44:30 +09:00
5dce81c0db 非ASCIIなドメインへのメンションの修正 (#2903)
* punycodeでされたmentionのラベルをunicodeとして表示する

* post-form mentionはpunycodeにする

* mentionの表示はURLもAPI向けもunicodeにする
2018-10-14 16:56:19 +09:00
be82d845a4 expose user recommendation config in /api/meta (#2902) 2018-10-14 16:54:09 +09:00
f49ccd0cd3 10.15.0 2018-10-14 10:17:04 +09:00
69d83f535d Clean up 2018-10-14 10:16:07 +09:00
c7988fb6f5 🎨 2018-10-14 10:16:02 +09:00
3961fd08c9 Fix #2901 2018-10-14 10:06:10 +09:00
935b074a7a New translations ja-JP.yml (Norwegian) 2018-10-13 20:12:19 +09:00
9d9c609bfb New translations ja-JP.yml (Dutch) 2018-10-13 20:12:15 +09:00
f6a664f181 New translations ja-JP.yml (Japanese, Kansai) 2018-10-13 20:12:10 +09:00
fce68d1f75 New translations ja-JP.yml (Spanish) 2018-10-13 20:12:06 +09:00
88739c2444 New translations ja-JP.yml (Russian) 2018-10-13 20:12:02 +09:00
7e2f10fce3 New translations ja-JP.yml (Portuguese) 2018-10-13 20:11:58 +09:00
a494c3a5cc New translations ja-JP.yml (Polish) 2018-10-13 20:11:53 +09:00
d6bb702883 New translations ja-JP.yml (Korean) 2018-10-13 20:11:48 +09:00
d15a972c68 New translations ja-JP.yml (Italian) 2018-10-13 20:11:44 +09:00
2ae7d31725 New translations ja-JP.yml (German) 2018-10-13 20:11:40 +09:00
2e329b1888 New translations ja-JP.yml (French) 2018-10-13 20:11:36 +09:00
522d40328b New translations ja-JP.yml (English) 2018-10-13 20:11:32 +09:00
2ecbff45bf New translations ja-JP.yml (Chinese Simplified) 2018-10-13 20:11:28 +09:00
b6f7282c13 New translations ja-JP.yml (Catalan) 2018-10-13 20:11:24 +09:00
89 changed files with 1992 additions and 203 deletions

View File

@ -1,12 +1,8 @@
maintainer: '@syuilo' maintainer:
url: 'https://misskey.xyz' name: syuilo
secondary_url: 'https://himasaku.net' url: 'https://syuilo.com'
url: 'http://misskey.local'
port: 80 port: 80
https:
enable: false
key: null
cert: null
ca: null
mongodb: mongodb:
host: localhost host: localhost
port: 27017 port: 27017
@ -21,6 +17,3 @@ elasticsearch:
host: localhost host: localhost
port: 9200 port: 9200
pass: '' pass: ''
recaptcha:
site_key: hima
secret_key: saku

View File

@ -1,12 +1,8 @@
maintainer: '@syuilo' maintainer:
url: 'https://misskey.xyz' name: syuilo
secondary_url: 'https://himasaku.net' url: 'https://syuilo.com'
url: 'http://misskey.local'
port: 80 port: 80
https:
enable: false
key: null
cert: null
ca: null
mongodb: mongodb:
host: localhost host: localhost
port: 27017 port: 27017
@ -21,6 +17,3 @@ elasticsearch:
host: localhost host: localhost
port: 9200 port: 9200
pass: '' pass: ''
recaptcha:
site_key: hima
secret_key: saku

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -3,16 +3,16 @@ meta:
lang: "Deutsch" lang: "Deutsch"
divider: "" divider: ""
common: common:
misskey: "A ⭐ of fediverse" misskey: "Ein ⭐ des Fediversums"
about-title: "A ⭐ of fediverse." about-title: "Ein ⭐ des Fediversums."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
intro: intro:
title: "Misskeyって?" title: "Was ist Misskey?"
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。" about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
features: "特徴" features: "Funktionen"
rich-contents: "投稿" rich-contents: "Notizen"
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。" rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
reaction: "リアクション" reaction: "Reaktionen"
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。" reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
ui: "インターフェース" ui: "インターフェース"
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。" ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
@ -23,7 +23,7 @@ common:
detected: "広告ブロッカーを無効にしてください" detected: "広告ブロッカーを無効にしてください"
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。" warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
application-authorization: "アプリの連携" application-authorization: "アプリの連携"
close: "閉じる" close: "Schließen"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。" do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
got-it: "わかった" got-it: "わかった"
customization-tips: customization-tips:
@ -34,13 +34,13 @@ common:
paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。" paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
gotit: "Got it!" gotit: "Got it!"
notification: notification:
file-uploaded: "ファイルがアップロードされました" file-uploaded: "Datei hochgeladen!"
message-from: "{}さんからメッセージ:" message-from: "{}さんからメッセージ:"
reversi-invited: "対局への招待があります" reversi-invited: "対局への招待があります"
reversi-invited-by: "{}さんから" reversi-invited-by: "{}さんから"
notified-by: "{}さんから" notified-by: "Benachrichtigt von {}:"
reply-from: "{}さんから返信:" reply-from: "Antwort von {}:"
quoted-by: "{}さんが引用:" quoted-by: "Zitiert von {}:"
time: time:
unknown: "Unbekannt" unknown: "Unbekannt"
future: "Zukunft" future: "Zukunft"
@ -53,7 +53,7 @@ common:
months_ago: "vor {0} Monat{0:en}" months_ago: "vor {0} Monat{0:en}"
years_ago: "vor {} Jahr{0:en}" years_ago: "vor {} Jahr{0:en}"
month-and-day: "{month}月 {day}日" month-and-day: "{month}月 {day}日"
trash: "ゴミ箱" trash: "Papierkorb"
weekday-short: weekday-short:
sunday: "So" sunday: "So"
monday: "Mo" monday: "Mo"
@ -63,13 +63,13 @@ common:
friday: "Fr" friday: "Fr"
saturday: "Sa" saturday: "Sa"
weekday: weekday:
sunday: "日曜日" sunday: "Sonntag"
monday: "月曜日" monday: "Montag"
tuesday: "火曜日" tuesday: "Dienstag"
wednesday: "水曜日" wednesday: "Mittwoch"
thursday: "木曜日" thursday: "Donnerstag"
friday: "金曜日" friday: "Freitag"
saturday: "土曜日" saturday: "Samstag"
reactions: reactions:
like: "いいね" like: "いいね"
love: "Lieben" love: "Lieben"
@ -82,10 +82,10 @@ common:
rip: "RIP" rip: "RIP"
pudding: "Pudding" pudding: "Pudding"
note-visibility: note-visibility:
public: "公開" public: "Öffentlich"
home: "ホーム" home: "ホーム"
home-desc: "ホームタイムラインにのみ公開" home-desc: "ホームタイムラインにのみ公開"
followers: "フォロワー" followers: "Abonnenten"
followers-desc: "自分のフォロワーにのみ公開" followers-desc: "自分のフォロワーにのみ公開"
specified: "ダイレクト" specified: "ダイレクト"
specified-desc: "指定したユーザーにのみ公開" specified-desc: "指定したユーザーにのみ公開"
@ -122,9 +122,9 @@ common:
turn-of: "{}のターンです" turn-of: "{}のターンです"
past-turn-of: "{}のターン" past-turn-of: "{}のターン"
won: "{}の勝ち" won: "{}の勝ち"
black: "" black: "Schwarz"
white: "" white: "Weiß"
total: "合計" total: "Gesamt"
this-turn: "{}ターン目" this-turn: "{}ターン目"
widgets: widgets:
analog-clock: "Analoge Uhr" analog-clock: "Analoge Uhr"
@ -142,23 +142,23 @@ common:
broadcast: "ブロードキャスト" broadcast: "ブロードキャスト"
notifications: "Benachrichtigungen" notifications: "Benachrichtigungen"
users: "Empfohlene Benutzer" users: "Empfohlene Benutzer"
polls: "アンケート" polls: "Umfrage"
post-form: "Beitragsform" post-form: "Beitragsform"
messaging: "Nachrichten" messaging: "Nachrichten"
server: "Server-Info" server: "Server-Info"
donation: "Spenden" donation: "Spenden"
nav: "Navigation" nav: "Navigation"
tips: "Tipps" tips: "Tipps"
hashtags: "ハッシュタグ" hashtags: "Hashtags"
deck: deck:
widgets: "Widget hinzufügen:" widgets: "Widget hinzufügen:"
home: "Startseite" home: "Startseite"
local: "Lokal" local: "Lokal"
hybrid: "ソーシャル" hybrid: "ソーシャル"
hashtag: "ハッシュタグ" hashtag: "Hashtag"
global: "Global" global: "Global"
mentions: "あなた宛て" mentions: "Erwähnungen"
direct: "ダイレクト投稿" direct: "Direktnachrichten"
notifications: "Mitteilungen" notifications: "Mitteilungen"
list: "Listen" list: "Listen"
swap-left: "Nach links" swap-left: "Nach links"
@ -182,10 +182,10 @@ auth/views/form.vue:
drive-write: "ドライブを操作する。" drive-write: "ドライブを操作する。"
notification-read: "通知を見る。" notification-read: "通知を見る。"
notification-write: "通知を操作する。" notification-write: "通知を操作する。"
cancel: "キャンセル" cancel: "Abbrechen"
accept: "アクセスを許可" accept: "Zugriff erlauben."
auth/views/index.vue: auth/views/index.vue:
loading: "読み込み中" loading: "Lädt"
denied: "アプリケーションの連携をキャンセルしました。" denied: "アプリケーションの連携をキャンセルしました。"
denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。" denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
already-authorized: "このアプリは既に連携済みです" already-authorized: "このアプリは既に連携済みです"
@ -196,10 +196,10 @@ auth/views/index.vue:
sign-in: "サインインしてください" sign-in: "サインインしてください"
common/views/components/games/reversi/reversi.vue: common/views/components/games/reversi/reversi.vue:
matching: matching:
waiting-for: "{}を待っています" waiting-for: "Warten auf {}"
cancel: "キャンセル" cancel: "Abbrechen"
common/views/components/games/reversi/reversi.game.vue: common/views/components/games/reversi/reversi.game.vue:
surrender: "投了" surrender: "Aufgeben"
surrendered: "投了により" surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)" is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ" looped-map: "ループマップ"
@ -208,9 +208,9 @@ common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi" title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう" sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
invite: "招待" invite: "招待"
rule: "遊び方" rule: "Spielanleitung"
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。" rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
mode-invite: "招待" mode-invite: "Einladen"
mode-invite-desc: "指定したユーザーと対戦するモードです。" mode-invite-desc: "指定したユーザーと対戦するモードです。"
invitations: "対局の招待があります!" invitations: "対局の招待があります!"
my-games: "自分の対局" my-games: "自分の対局"
@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Werkzeuge" tools: "Werkzeuge"
task-manager: "Taskmanager" task-manager: "Taskmanager"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Profil aktualisieren" save: "Profil aktualisieren"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Tools" tools: "Tools"
task-manager: "Task Manager" task-manager: "Task Manager"
third-parties: "Third-parties" 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: 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." 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…" detail: "Details…"
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Update profile" save: "Update profile"
locked-account: "Protect your account" locked-account: "Protect your account"
is-locked: "Follow request needs approval" is-locked: "Follow request needs approval"
careful-bot: "Botからのフォローだけ承認制にする"
other: "Other" other: "Other"
is-bot: "This account is a Bot" is-bot: "This account is a Bot"
is-cat: "This account is a Cat" is-cat: "This account is a Cat"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "NSFW" nsfw: "NSFW"
mark-as-sensitive: "Mark as 'sensitive'"
unmark-as-sensitive: "Unmark as 'sensitive'"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "NSFW" sensitive: "NSFW"
click-to-show: "Click to show" click-to-show: "Click to show"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Banner" banner: "Banner"
is-cat: "This account is a Cat" is-cat: "This account is a Cat"
is-locked: "Follow request needs approval" is-locked: "Follow request needs approval"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "Advanced" advanced: "Advanced"
privacy: "Privacy" privacy: "Privacy"
save: "Update profile" save: "Update profile"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Herramientas" tools: "Herramientas"
task-manager: "Navegador de tareas" task-manager: "Navegador de tareas"
third-parties: "Servicios externos" third-parties: "Servicios externos"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "Ver detalles..." detail: "Ver detalles..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Perfil actualizado" save: "Perfil actualizado"
locked-account: "Protege tu cuenta" locked-account: "Protege tu cuenta"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Outils" tools: "Outils"
task-manager: "Gestionnaire de tâches" task-manager: "Gestionnaire de tâches"
third-parties: "Services tiers" third-parties: "Services tiers"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "Si vous configurez la vérication en deux étapes vous aurez non seulement besoin de votre mot de passe mais aussi un appareil déjà pré-enregistré(tel que votre smartphone) ce qui ameliora grandement la sécurité de votre compte." intro: "Si vous configurez la vérication en deux étapes vous aurez non seulement besoin de votre mot de passe mais aussi un appareil déjà pré-enregistré(tel que votre smartphone) ce qui ameliora grandement la sécurité de votre compte."
detail: "Voir les détails..." detail: "Voir les détails..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Mettre à jour le profil" save: "Mettre à jour le profil"
locked-account: "Protéger votre compte" locked-account: "Protéger votre compte"
is-locked: "Demande dabonnement en attente dapprobation" is-locked: "Demande dabonnement en attente dapprobation"
careful-bot: "Botからのフォローだけ承認制にする"
other: "Autre" other: "Autre"
is-bot: "Ce compte est un Bot" is-bot: "Ce compte est un Bot"
is-cat: "Ce compte est un Chat" is-cat: "Ce compte est un Chat"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "CW" nsfw: "CW"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "Le contenu est NSFW" sensitive: "Le contenu est NSFW"
click-to-show: "Cliquer pour afficher" click-to-show: "Cliquer pour afficher"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Bannière" banner: "Bannière"
is-cat: "Ce compte est un Bot" is-cat: "Ce compte est un Bot"
is-locked: "Demande dabonnement en attente dapprobation" is-locked: "Demande dabonnement en attente dapprobation"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "Avancé" advanced: "Avancé"
privacy: "Vie privée" privacy: "Vie privée"
save: "Mettre à jour le profil" save: "Mettre à jour le profil"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -883,6 +883,11 @@ desktop/views/components/settings.vue:
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -1234,6 +1239,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけとちゃうくて、予め登録しておいた物理的なデバイス(例えばあんさんのスマートフォンなど)も必要になり、よりセキュリティが向上すんで。" intro: "二段階認証を設定すると、サインイン時にパスワードだけとちゃうくて、予め登録しておいた物理的なデバイス(例えばあんさんのスマートフォンなど)も必要になり、よりセキュリティが向上すんで。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウント守る" locked-account: "アカウント守る"
is-locked: "他人のフォローは許可してからや!" is-locked: "他人のフォローは許可してからや!"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotやで" is-bot: "このアカウントはBotやで"
is-cat: "このアカウントはCatやで" is-cat: "このアカウントはCatやで"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ(md5)" hash: "ハッシュ(md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "ちょっと見せられへんわ" nsfw: "ちょっと見せられへんわ"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "見たらあかんで" sensitive: "見たらあかんで"
click-to-show: "押してみ、見せたるわ" click-to-show: "押してみ、見せたるわ"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatや" is-cat: "このアカウントはCatや"
is-locked: "他人のフォローは許可してからや!" is-locked: "他人のフォローは許可してからや!"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシーってなんや?オカンの年齢か?" privacy: "プライバシーってなんや?オカンの年齢か?"
save: "保存" save: "保存"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Hulpmiddelen" tools: "Hulpmiddelen"
task-manager: "Taakbeheer" task-manager: "Taakbeheer"
third-parties: "Derde partij" third-parties: "Derde partij"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: 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. " 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..." detail: "Details bekijken..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Profiel bijwerken" save: "Profiel bijwerken"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "Dit account is een Bot" is-bot: "Dit account is een Bot"
is-cat: "Dit account is een Kat" is-cat: "Dit account is een Kat"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Omslagfoto" banner: "Omslagfoto"
is-cat: "Dit account is een Kat" is-cat: "Dit account is een Kat"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "Profiel bijwerken" save: "Profiel bijwerken"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Verktøy" tools: "Verktøy"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "Detaljer..." detail: "Detaljer..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Lagre profilen" save: "Lagre profilen"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "Annet" other: "Annet"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "NSFW" nsfw: "NSFW"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "NSFW" sensitive: "NSFW"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Banner" banner: "Banner"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "Avansert" advanced: "Avansert"
privacy: "プライバシー" privacy: "プライバシー"
save: "Lagre profilen" save: "Lagre profilen"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "Narzędzia" tools: "Narzędzia"
task-manager: "Menedżer zadań" task-manager: "Menedżer zadań"
third-parties: "Autorzy trzeci" third-parties: "Autorzy trzeci"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: 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." 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…" detail: "Zobacz szczegóły…"
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "Aktualizuj profil" save: "Aktualizuj profil"
locked-account: "Zabezpiecz swoje konto" locked-account: "Zabezpiecz swoje konto"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "Inne" other: "Inne"
is-bot: "To konto jest prowadzone przez bota" is-bot: "To konto jest prowadzone przez bota"
is-cat: "To konto jest prowadzone przez kota" is-cat: "To konto jest prowadzone przez kota"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "To jest zawartość NSFW" sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić" click-to-show: "Naciśnij aby wyświetlić"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Baner" banner: "Baner"
is-cat: "To konto jest prowadzone przez kota" is-cat: "To konto jest prowadzone przez kota"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "Aktualizuj profil" save: "Aktualizuj profil"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "Capa" banner: "Capa"
is-cat: "Esta conta é gato" is-cat: "Esta conta é gato"
is-locked: "Pedido para seguir precisa ser aprovado" is-locked: "Pedido para seguir precisa ser aprovado"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "Avançado" advanced: "Avançado"
privacy: "Provacidade" privacy: "Provacidade"
save: "Atualizar perfil" save: "Atualizar perfil"

View File

@ -3,9 +3,9 @@ meta:
lang: "Русский язык" lang: "Русский язык"
divider: "" divider: ""
common: common:
misskey: "A ⭐ of fediverse" misskey: "Мы — ⭐ fediverse"
about-title: "A ⭐ of fediverse." about-title: "Мы — ⭐ fediverse"
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Спасибо, что нашли Misskey. Misskey — это <b>децентрализованная платформа для микроблоггинга</b> родом с планеты Земля. Поскольку она существует внутри Fediverse (вселенной различных социальных платформ), она связана с другими платформами. Отдохните от шума большого города — и познакомьтесь с новым интернетом."
intro: intro:
title: "Misskeyって" title: "Misskeyって"
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。" about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -785,6 +785,10 @@ desktop/views/components/settings.vue:
tools: "ツール" tools: "ツール"
task-manager: "タスクマネージャ" task-manager: "タスクマネージャ"
third-parties: "サードパーティ" third-parties: "サードパーティ"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: "上"
navbar-position-left: "左"
navbar-position-right: "右"
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。" intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "詳細..." detail: "詳細..."
@ -834,6 +838,7 @@ desktop/views/components/settings.profile.vue:
save: "保存" save: "保存"
locked-account: "アカウントの保護" locked-account: "アカウントの保護"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
other: "その他" other: "その他"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
@ -1074,6 +1079,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
nsfw: "閲覧注意" nsfw: "閲覧注意"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue: mobile/views/components/media-image.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
@ -1225,6 +1232,7 @@ mobile/views/pages/settings/settings.profile.vue:
banner: "バナー" banner: "バナー"
is-cat: "このアカウントはCatです" is-cat: "このアカウントはCatです"
is-locked: "フォローを承認制にする" is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他" advanced: "その他"
privacy: "プライバシー" privacy: "プライバシー"
save: "保存" save: "保存"

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.14.0", "version": "10.20.0",
"clientVersion": "1.0.10524", "clientVersion": "1.0.10607",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -28,6 +28,7 @@
"@prezzemolo/rap": "0.1.2", "@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/chai-http": "3.0.5",
"@types/dateformat": "1.0.1", "@types/dateformat": "1.0.1",
"@types/debug": "0.0.31", "@types/debug": "0.0.31",
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
@ -39,7 +40,7 @@
"@types/gulp-mocha": "0.0.32", "@types/gulp-mocha": "0.0.32",
"@types/gulp-rename": "0.0.33", "@types/gulp-rename": "0.0.33",
"@types/gulp-replace": "0.0.31", "@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/gulp-util": "3.0.34",
"@types/is-root": "1.0.0", "@types/is-root": "1.0.0",
"@types/is-url": "1.2.28", "@types/is-url": "1.2.28",
@ -60,7 +61,7 @@
"@types/mocha": "5.2.3", "@types/mocha": "5.2.3",
"@types/mongodb": "3.1.12", "@types/mongodb": "3.1.12",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "10.11.7", "@types/node": "10.12.0",
"@types/portscanner": "2.1.0", "@types/portscanner": "2.1.0",
"@types/pug": "2.0.4", "@types/pug": "2.0.4",
"@types/qrcode": "1.3.0", "@types/qrcode": "1.3.0",
@ -70,7 +71,7 @@
"@types/request-promise-native": "1.0.15", "@types/request-promise-native": "1.0.15",
"@types/rimraf": "2.0.2", "@types/rimraf": "2.0.2",
"@types/seedrandom": "2.4.27", "@types/seedrandom": "2.4.27",
"@types/sharp": "0.17.10", "@types/sharp": "0.21.0",
"@types/showdown": "1.7.5", "@types/showdown": "1.7.5",
"@types/single-line-log": "1.1.0", "@types/single-line-log": "1.1.0",
"@types/speakeasy": "2.0.2", "@types/speakeasy": "2.0.2",
@ -78,7 +79,7 @@
"@types/tinycolor2": "1.4.1", "@types/tinycolor2": "1.4.1",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
"@types/uuid": "3.4.4", "@types/uuid": "3.4.4",
"@types/webpack": "4.4.16", "@types/webpack": "4.4.17",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/ws": "6.0.1", "@types/ws": "6.0.1",
@ -90,8 +91,10 @@
"bee-queue": "1.2.2", "bee-queue": "1.2.2",
"bootstrap-vue": "2.0.0-rc.11", "bootstrap-vue": "2.0.0-rc.11",
"cafy": "11.3.0", "cafy": "11.3.0",
"chai": "4.2.0",
"chai-http": "4.2.0",
"chalk": "2.4.1", "chalk": "2.4.1",
"chart.js": "2.7.2", "chart.js": "2.7.3",
"commander": "2.19.0", "commander": "2.19.0",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "1.0.0", "css-loader": "1.0.0",
@ -157,7 +160,7 @@
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.2.0", "mocha": "5.2.0",
"moji": "0.5.1", "moji": "0.5.1",
"mongodb": "3.1.1", "mongodb": "3.1.8",
"monk": "6.0.6", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.1",
"nan": "2.11.1", "nan": "2.11.1",
@ -220,7 +223,7 @@
"vue-loader": "15.4.2", "vue-loader": "15.4.2",
"vue-router": "3.0.1", "vue-router": "3.0.1",
"vue-style-loader": "4.1.2", "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-sweetalert2": "1.5.5",
"vue-template-compiler": "2.5.17", "vue-template-compiler": "2.5.17",
"vuedraggable": "2.16.0", "vuedraggable": "2.16.0",

View File

@ -116,16 +116,16 @@ export default Vue.component('misskey-flavored-markdown', {
case 'mention': { case 'mention': {
return (createElement as any)('a', { return (createElement as any)('a', {
attrs: { attrs: {
href: `${url}/@${getAcct(token)}`, href: `${url}/${token.canonical}`,
target: '_blank', target: '_blank',
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token), dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
style: 'color:var(--mfmMention);' style: 'color:var(--mfmMention);'
}, },
directives: [{ directives: [{
name: 'user-preview', name: 'user-preview',
value: token.content value: token.canonical
}] }]
}, token.content); }, token.canonical);
} }
case 'hashtag': { case 'hashtag': {

View File

@ -1,6 +1,6 @@
import * as getCaretCoordinates from 'textarea-caret'; import * as getCaretCoordinates from 'textarea-caret';
import MkAutocomplete from '../components/autocomplete.vue'; import MkAutocomplete from '../components/autocomplete.vue';
import renderAcct from '../../../../../misc/acct/render'; import { toASCII } from 'punycode';
export default { export default {
bind(el, binding, vn) { bind(el, binding, vn) {
@ -188,7 +188,7 @@ class Autocomplete {
const trimmedBefore = before.substring(0, before.lastIndexOf('@')); const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
const after = source.substr(caret); 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}`; this.text = `${trimmedBefore}@${acct} ${after}`;

View File

@ -38,7 +38,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="main"> <div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
<template v-if="customize"> <template v-if="customize">
<x-draggable v-for="place in ['left', 'right']" <x-draggable v-for="place in ['left', 'right']"
:list="widgets[place]" :list="widgets[place]"
@ -359,12 +359,10 @@ export default Vue.extend({
box-shadow var(--shadow) box-shadow var(--shadow)
border-radius var(--round) border-radius var(--round)
@media (max-width 700px) &.side
padding 0 > .main
width calc(100% - 280px)
> .tl max-width 680px
border none
border-radius 0
> *:not(.main) > *:not(.main)
width 280px width 280px
@ -381,14 +379,24 @@ export default Vue.extend({
padding-right 16px padding-right 16px
order 3 order 3
@media (max-width 1100px) &.side
> *:not(.main) @media (max-width 1000px)
display none > *:not(.main)
display none
> .main > .main
float none width 100%
width 100% max-width 700px
max-width 700px margin 0 auto
margin 0 auto
&:not(.side)
@media (max-width 1200px)
> *:not(.main)
display none
> .main
width 100%
max-width 700px
margin 0 auto
</style> </style>

View File

@ -9,7 +9,7 @@
<button @click="resolveInitPromise">%i18n:@retry%</button> <button @click="resolveInitPromise">%i18n:@retry%</button>
</div> </div>
<div class="skeleton" v-if="fetching"> <div class="placeholder" v-if="fetching">
<template v-for="i in 10"> <template v-for="i in 10">
<mk-note-skeleton :key="i"/> <mk-note-skeleton :key="i"/>
</template> </template>
@ -232,7 +232,7 @@ export default Vue.extend({
> * > *
transition transform .3s ease, opacity .3s ease transition transform .3s ease, opacity .3s ease
> .skeleton > .placeholder
padding 32px padding 32px
opacity 0.3 opacity 0.3

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="mk-notifications"> <div class="mk-notifications">
<div class="skeleton" v-if="fetching"> <div class="placeholder" v-if="fetching">
<template v-for="i in 10"> <template v-for="i in 10">
<mk-note-skeleton :key="i"/> <mk-note-skeleton :key="i"/>
</template> </template>
@ -207,7 +207,7 @@ export default Vue.extend({
> * > *
transition transform .3s ease, opacity .3s ease transition transform .3s ease, opacity .3s ease
> .skeleton > .placeholder
padding 16px padding 16px
opacity 0.3 opacity 0.3

View File

@ -65,6 +65,7 @@ import { host } from '../../../config';
import { erase, unique } from '../../../../../prelude/array'; import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz'; import { length } from 'stringz';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import { toASCII } from 'punycode';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -158,14 +159,14 @@ export default Vue.extend({
} }
if (this.reply && this.reply.user.host != null) { 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) { if (this.reply && this.reply.text != null) {
const ast = parse(this.reply.text); const ast = parse(this.reply.text);
ast.filter(t => t.type == 'mention').forEach(x => { 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; if (this.$store.state.i.username == x.username && x.host == null) return;

View File

@ -88,6 +88,13 @@
<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> <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_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</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>
<section class="web" v-show="page == 'web'"> <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 }); } 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: { enableSounds: {
get() { return this.$store.state.device.enableSounds; }, get() { return this.$store.state.device.enableSounds; },
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); } set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }

View File

@ -157,6 +157,9 @@ export default Vue.extend({
font-family Meiryo, sans-serif font-family Meiryo, sans-serif
text-decoration none text-decoration none
@media (max-width 1100px)
display none
[data-fa] [data-fa]
margin-left 8px margin-left 8px
@ -171,6 +174,9 @@ export default Vue.extend({
border-radius 4px border-radius 4px
transition filter 100ms ease transition filter 100ms ease
@media (max-width 1100px)
margin-left 8px
> .menu > .menu
$bgcolor = var(--face) $bgcolor = var(--face)
display block display block

View File

@ -29,6 +29,9 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
.search .search
@media (max-width 800px)
display none !important
> [data-fa] > [data-fa]
display block display block
position absolute position absolute
@ -58,6 +61,9 @@ export default Vue.extend({
transition color 0.5s ease, border 0.5s ease transition color 0.5s ease, border 0.5s ease
color var(--desktopHeaderSearchFg) color var(--desktopHeaderSearchFg)
@media (max-width 1000px)
width 10em
&::placeholder &::placeholder
color var(--desktopHeaderFg) color var(--desktopHeaderFg)

View 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>

View File

@ -1,8 +1,9 @@
<template> <template>
<div class="mk-ui" v-hotkey.global="keymap"> <div class="mk-ui" v-hotkey.global="keymap">
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div> <div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
<x-header class="header" v-show="!zenMode" ref="header"/> <x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
<div class="content"> <x-sidebar class="sidebar" v-if="navbar != 'top'" ref="sidebar"/>
<div class="content" :class="[{ sidebar: navbar != 'top' }, navbar]">
<slot></slot> <slot></slot>
</div> </div>
<mk-stream-indicator v-if="$store.getters.isSignedIn"/> <mk-stream-indicator v-if="$store.getters.isSignedIn"/>
@ -12,10 +13,12 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XHeader from './ui.header.vue'; import XHeader from './ui.header.vue';
import XSidebar from './ui.sidebar.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XHeader XHeader,
XSidebar
}, },
data() { data() {
@ -25,6 +28,10 @@ export default Vue.extend({
}, },
computed: { computed: {
navbar(): string {
return this.$store.state.device.navbar;
},
style(): any { style(): any {
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {}; if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
return { return {
@ -45,6 +52,12 @@ export default Vue.extend({
watch: { watch: {
'$store.state.uiHeaderHeight'() { '$store.state.uiHeaderHeight'() {
this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px'; 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 background-attachment fixed
opacity 0.3 opacity 0.3
> .header > .content.sidebar.left
@media (max-width 1000px) padding-left 68px
display none
> .content.sidebar.right
padding-right 68px
</style> </style>

View File

@ -276,13 +276,24 @@ export default Vue.extend({
min-width 330px min-width 330px
height 100% height 100%
background var(--face) background var(--face)
border-radius 6px border-radius var(--round)
//box-shadow 0 2px 16px rgba(#000, 0.1) box-shadow var(--shadow)
overflow hidden overflow hidden
&.draghover &.draghover
box-shadow 0 0 0 2px var(--primaryAlpha08) 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 &.dragging
box-shadow 0 0 0 2px var(--primaryAlpha04) box-shadow 0 0 0 2px var(--primaryAlpha04)
@ -338,6 +349,7 @@ export default Vue.extend({
> .toggleActive > .toggleActive
> .menu > .menu
padding 0
width $header-height width $header-height
line-height $header-height line-height $header-height
font-size 16px font-size 16px

View File

@ -2,6 +2,12 @@
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu"> <div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> <slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
<div class="placeholder" v-if="fetching">
<template v-for="i in 10">
<mk-note-skeleton :key="i"/>
</template>
</div>
<div v-if="!fetching && requestInitPromise != null"> <div v-if="!fetching && requestInitPromise != null">
<p>%i18n:@error%</p> <p>%i18n:@error%</p>
<button @click="resolveInitPromise">%i18n:@retry%</button> <button @click="resolveInitPromise">%i18n:@retry%</button>
@ -205,6 +211,10 @@ export default Vue.extend({
> * > *
transition transform .3s ease, opacity .3s ease transition transform .3s ease, opacity .3s ease
> .placeholder
padding 16px
opacity 0.3
> .notes > .notes
> .date > .date
display block display block

View File

@ -1,5 +1,11 @@
<template> <template>
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl"> <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"> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
<template v-for="(notification, i) in _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%' }} <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
</button> </button>
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p> <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> </div>
</template> </template>
@ -161,6 +166,10 @@ export default Vue.extend({
> * > *
transition transform .3s ease, opacity .3s ease transition transform .3s ease, opacity .3s ease
> .placeholder
padding 16px
opacity 0.3
> .notifications > .notifications
> .notification:not(:last-child) > .notification:not(:last-child)
@ -207,13 +216,4 @@ export default Vue.extend({
text-align center text-align center
color #aaa color #aaa
> .loading
margin 0
padding 16px
text-align center
color #aaa
> [data-fa]
margin-right 4px
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/> <x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -124,11 +124,17 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
//#region shadow //#region shadow
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)'; 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('--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 => { os.store.watch(s => {
return s.settings.useShadow; return s.settings.useShadow;
}, v => { }, v => {
document.documentElement.style.setProperty('--shadow', v ? shadow : 'none'); 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 //#endregion

View File

@ -41,6 +41,8 @@
<ui-button link :href="`${file.url}?download`" :download="file.name">%fa:download% %i18n:@download%</ui-button> <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="rename">%fa:pencil-alt% %i18n:@rename%</ui-button>
<ui-button @click="move">%fa:R folder-open% %i18n:@move%</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> <ui-button @click="del">%fa:trash-alt R% %i18n:@delete%</ui-button>
</div> </div>
</div> </div>
@ -71,25 +73,30 @@ import { gcd } from '../../../../../prelude/math';
export default Vue.extend({ export default Vue.extend({
props: ['file'], props: ['file'],
data() { data() {
return { return {
gcd, gcd,
exif: null exif: null
}; };
}, },
computed: { computed: {
browser(): any { browser(): any {
return this.$parent; return this.$parent;
}, },
kind(): string { kind(): string {
return this.file.type.split('/')[0]; return this.file.type.split('/')[0];
}, },
style(): any { style(): any {
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? { return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })` 'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
} : {}; } : {};
} }
}, },
methods: { methods: {
rename() { rename() {
const name = window.prompt('%i18n:@rename%', this.file.name); const name = window.prompt('%i18n:@rename%', this.file.name);
@ -101,6 +108,7 @@ export default Vue.extend({
this.browser.cf(this.file, true); this.browser.cf(this.file, true);
}); });
}, },
move() { move() {
(this as any).apis.chooseDriveFolder().then(folder => { (this as any).apis.chooseDriveFolder().then(folder => {
(this as any).api('drive/files/update', { (this as any).api('drive/files/update', {
@ -111,6 +119,7 @@ export default Vue.extend({
}); });
}); });
}, },
del() { del() {
(this as any).api('drive/files/delete', { (this as any).api('drive/files/delete', {
fileId: this.file.id fileId: this.file.id
@ -118,9 +127,20 @@ export default Vue.extend({
this.browser.cd(this.file.folderId, true); 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() { showCreatedAt() {
alert(new Date(this.file.createdAt).toLocaleString()); alert(new Date(this.file.createdAt).toLocaleString());
}, },
onImageLoaded() { onImageLoaded() {
const self = this; const self = this;
EXIF.getData(this.$refs.img, function(this: any) { EXIF.getData(this.$refs.img, function(this: any) {

View File

@ -4,7 +4,7 @@
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> <slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
<div class="skeleton" v-if="fetching"> <div class="placeholder" v-if="fetching">
<template v-for="i in 10"> <template v-for="i in 10">
<mk-note-skeleton :key="i"/> <mk-note-skeleton :key="i"/>
</template> </template>
@ -253,7 +253,7 @@ export default Vue.extend({
[data-fa] [data-fa]
margin-right 8px margin-right 8px
> .skeleton > .placeholder
padding 16px padding 16px
opacity 0.3 opacity 0.3

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="mk-notifications"> <div class="mk-notifications">
<div class="skeleton" v-if="fetching"> <div class="placeholder" v-if="fetching">
<template v-for="i in 10"> <template v-for="i in 10">
<mk-note-skeleton :key="i"/> <mk-note-skeleton :key="i"/>
</template> </template>
@ -184,7 +184,7 @@ export default Vue.extend({
text-align center text-align center
color #aaa color #aaa
> .skeleton > .placeholder
padding 16px padding 16px
opacity 0.3 opacity 0.3

View File

@ -62,6 +62,7 @@ import { host } from '../../../config';
import { erase, unique } from '../../../../../prelude/array'; import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz'; import { length } from 'stringz';
import parseAcct from '../../../../../misc/acct/parse'; import parseAcct from '../../../../../misc/acct/parse';
import { toASCII } from 'punycode';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -153,14 +154,14 @@ export default Vue.extend({
} }
if (this.reply && this.reply.user.host != null) { 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) { if (this.reply && this.reply.text != null) {
const ast = parse(this.reply.text); const ast = parse(this.reply.text);
ast.filter(t => t.type == 'mention').forEach(x => { 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; if (this.$store.state.i.username == x.username && x.host == null) return;

View File

@ -56,6 +56,7 @@ const defaultDeviceSettings = {
loadRawImages: false, loadRawImages: false,
alwaysShowNsfw: false, alwaysShowNsfw: false,
postStyle: 'standard', postStyle: 'standard',
navbar: 'top',
mobileNotificationPosition: 'bottom' mobileNotificationPosition: 'bottom'
}; };

View File

@ -2,10 +2,12 @@
* Mention * Mention
*/ */
import parseAcct from '../../../misc/acct/parse'; import parseAcct from '../../../misc/acct/parse';
import { toUnicode } from 'punycode';
export type TextElementMention = { export type TextElementMention = {
type: 'mention' type: 'mention'
content: string content: string
canonical: string
username: string username: string
host: string host: string
}; };
@ -15,9 +17,11 @@ export default function(text: string) {
if (!match) return null; if (!match) return null;
const mention = match[0]; const mention = match[0];
const { username, host } = parseAcct(mention.substr(1)); const { username, host } = parseAcct(mention.substr(1));
const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;
return { return {
type: 'mention', type: 'mention',
content: mention, content: mention,
canonical,
username, username,
host host
} as TextElementMention; } as TextElementMention;

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import { Context } from 'cafy'; import { Context } from 'cafy';
import isObjectId from './is-objectid';
export const isAnId = (x: any) => mongo.ObjectID.isValid(x); export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
export const isNotAnId = (x: any) => !isAnId(x); export const isNotAnId = (x: any) => !isAnId(x);
@ -12,7 +13,7 @@ export default class ID extends Context<mongo.ObjectID> {
super(); super();
this.transform = v => { this.transform = v => {
if (isAnId(v) && !mongo.ObjectID.prototype.isPrototypeOf(v)) { if (isAnId(v) && !isObjectId(v)) {
return new mongo.ObjectID(v); return new mongo.ObjectID(v);
} else { } else {
return v; return v;
@ -20,7 +21,7 @@ export default class ID extends Context<mongo.ObjectID> {
}; };
this.push(v => { this.push(v => {
if (!mongo.ObjectID.prototype.isPrototypeOf(v) && isNotAnId(v)) { if (!isObjectId(v) && isNotAnId(v)) {
return new Error('must-be-an-id'); return new Error('must-be-an-id');
} }
return true; return true;

3
src/misc/is-objectid.ts Normal file
View File

@ -0,0 +1,3 @@
export default function(x: any): boolean {
return x.hasOwnProperty('toHexString') || x.hasOwnProperty('_bsontype');
}

View File

@ -1,7 +1,8 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import isObjectId from './is-objectid';
function toString(id: any) { 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 { export default function(note: any, mutedUserIds: string[]): boolean {

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const AccessToken = db.get<IAccessToken>('accessTokens'); const AccessToken = db.get<IAccessToken>('accessTokens');
AccessToken.createIndex('token'); AccessToken.createIndex('token');
@ -22,7 +23,7 @@ export async function deleteAccessToken(accessToken: string | mongo.ObjectID | I
let a: IAccessToken; let a: IAccessToken;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(accessToken)) { if (isObjectId(accessToken)) {
a = await AccessToken.findOne({ a = await AccessToken.findOne({
_id: accessToken _id: accessToken
}); });

View File

@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import AccessToken from './access-token'; import AccessToken from './access-token';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import config from '../config'; import config from '../config';
const App = db.get<IApp>('apps'); const App = db.get<IApp>('apps');
@ -43,7 +44,7 @@ export const pack = (
let _app: any; let _app: any;
// Populate the app if 'app' is ID // Populate the app if 'app' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(app)) { if (isObjectId(app)) {
_app = await App.findOne({ _app = await App.findOne({
_id: app _id: app
}); });
@ -56,7 +57,7 @@ export const pack = (
} }
// Me // Me
if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) { if (me && !isObjectId(me)) {
if (typeof me === 'string') { if (typeof me === 'string') {
me = new mongo.ObjectID(me); me = new mongo.ObjectID(me);
} else { } else {

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import { pack as packApp } from './app'; import { pack as packApp } from './app';
const AuthSession = db.get<IAuthSession>('authSessions'); const AuthSession = db.get<IAuthSession>('authSessions');
@ -31,7 +32,7 @@ export const pack = (
_session = deepcopy(session); _session = deepcopy(session);
// Me // Me
if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) { if (me && !isObjectId(me)) {
if (typeof me === 'string') { if (typeof me === 'string') {
me = new mongo.ObjectID(me); me = new mongo.ObjectID(me);
} else { } else {

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import monkDb, { nativeDbConn } from '../db/mongodb'; import monkDb, { nativeDbConn } from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files'); const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files');
DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true }); DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true });
@ -35,7 +36,7 @@ export async function deleteDriveFileThumbnail(driveFile: string | mongo.ObjectI
let d: IDriveFileThumbnail; let d: IDriveFileThumbnail;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(driveFile)) { if (isObjectId(driveFile)) {
d = await DriveFileThumbnail.findOne({ d = await DriveFileThumbnail.findOne({
_id: driveFile _id: driveFile
}); });

View File

@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
import { pack as packFolder } from './drive-folder'; import { pack as packFolder } from './drive-folder';
import config from '../config'; import config from '../config';
import monkDb, { nativeDbConn } from '../db/mongodb'; import monkDb, { nativeDbConn } from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Note, { deleteNote } from './note'; import Note, { deleteNote } from './note';
import MessagingMessage, { deleteMessagingMessage } from './messaging-message'; import MessagingMessage, { deleteMessagingMessage } from './messaging-message';
import User from './user'; import User from './user';
@ -78,7 +79,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv
let d: IDriveFile; let d: IDriveFile;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(driveFile)) { if (isObjectId(driveFile)) {
d = await DriveFile.findOne({ d = await DriveFile.findOne({
_id: driveFile _id: driveFile
}); });
@ -154,7 +155,7 @@ export const pack = (
let _file: any; let _file: any;
// Populate the file if 'file' is ID // Populate the file if 'file' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(file)) { if (isObjectId(file)) {
_file = await DriveFile.findOne({ _file = await DriveFile.findOne({
_id: file _id: file
}); });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import DriveFile from './drive-file'; import DriveFile from './drive-file';
const DriveFolder = db.get<IDriveFolder>('driveFolders'); const DriveFolder = db.get<IDriveFolder>('driveFolders');
@ -29,7 +30,7 @@ export async function deleteDriveFolder(driveFolder: string | mongo.ObjectID | I
let d: IDriveFolder; let d: IDriveFolder;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(driveFolder)) { if (isObjectId(driveFolder)) {
d = await DriveFolder.findOne({ d = await DriveFolder.findOne({
_id: driveFolder _id: driveFolder
}); });
@ -83,7 +84,7 @@ export const pack = (
let _folder: any; let _folder: any;
// Populate the folder if 'folder' is ID // Populate the folder if 'folder' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(folder)) { if (isObjectId(folder)) {
_folder = await DriveFolder.findOne({ _id: folder }); _folder = await DriveFolder.findOne({ _id: folder });
} else if (typeof folder === 'string') { } else if (typeof folder === 'string') {
_folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) }); _folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import { pack as packNote } from './note'; import { pack as packNote } from './note';
const Favorite = db.get<IFavorite>('favorites'); const Favorite = db.get<IFavorite>('favorites');
@ -21,7 +22,7 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori
let f: IFavorite; let f: IFavorite;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) { if (isObjectId(favorite)) {
f = await Favorite.findOne({ f = await Favorite.findOne({
_id: favorite _id: favorite
}); });
@ -58,7 +59,7 @@ export const pack = (
let _favorite: any; let _favorite: any;
// Populate the favorite if 'favorite' is ID // Populate the favorite if 'favorite' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) { if (isObjectId(favorite)) {
_favorite = await Favorite.findOne({ _favorite = await Favorite.findOne({
_id: favorite _id: favorite
}); });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import { pack as packUser } from './user'; import { pack as packUser } from './user';
const FollowRequest = db.get<IFollowRequest>('followRequests'); const FollowRequest = db.get<IFollowRequest>('followRequests');
@ -12,6 +13,7 @@ export type IFollowRequest = {
createdAt: Date; createdAt: Date;
followeeId: mongo.ObjectID; followeeId: mongo.ObjectID;
followerId: mongo.ObjectID; followerId: mongo.ObjectID;
requestId?: string; // id of Follow Activity
// 非正規化 // 非正規化
_followee: { _followee: {
@ -33,7 +35,7 @@ export async function deleteFollowRequest(followRequest: string | mongo.ObjectID
let f: IFollowRequest; let f: IFollowRequest;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(followRequest)) { if (isObjectId(followRequest)) {
f = await FollowRequest.findOne({ f = await FollowRequest.findOne({
_id: followRequest _id: followRequest
}); });
@ -63,7 +65,7 @@ export const pack = (
let _request: any; let _request: any;
// Populate the request if 'request' is ID // Populate the request if 'request' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(request)) { if (isObjectId(request)) {
_request = await FollowRequest.findOne({ _request = await FollowRequest.findOne({
_id: request _id: request
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const FollowedLog = db.get<IFollowedLog>('followedLogs'); const FollowedLog = db.get<IFollowedLog>('followedLogs');
export default FollowedLog; export default FollowedLog;
@ -18,7 +19,7 @@ export async function deleteFollowedLog(followedLog: string | mongo.ObjectID | I
let f: IFollowedLog; let f: IFollowedLog;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(followedLog)) { if (isObjectId(followedLog)) {
f = await FollowedLog.findOne({ f = await FollowedLog.findOne({
_id: followedLog _id: followedLog
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const FollowingLog = db.get<IFollowingLog>('followingLogs'); const FollowingLog = db.get<IFollowingLog>('followingLogs');
export default FollowingLog; export default FollowingLog;
@ -18,7 +19,7 @@ export async function deleteFollowingLog(followingLog: string | mongo.ObjectID |
let f: IFollowingLog; let f: IFollowingLog;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(followingLog)) { if (isObjectId(followingLog)) {
f = await FollowingLog.findOne({ f = await FollowingLog.findOne({
_id: followingLog _id: followingLog
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const Following = db.get<IFollowing>('following'); const Following = db.get<IFollowing>('following');
Following.createIndex(['followerId', 'followeeId'], { unique: true }); Following.createIndex(['followerId', 'followeeId'], { unique: true });
@ -32,7 +33,7 @@ export async function deleteFollowing(following: string | mongo.ObjectID | IFoll
let f: IFollowing; let f: IFollowing;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(following)) { if (isObjectId(following)) {
f = await Following.findOne({ f = await Following.findOne({
_id: following _id: following
}); });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../../../db/mongodb'; import db from '../../../db/mongodb';
import isObjectId from '../../../misc/is-objectid';
import { IUser, pack as packUser } from '../../user'; import { IUser, pack as packUser } from '../../user';
const ReversiGame = db.get<IReversiGame>('reversiGames'); const ReversiGame = db.get<IReversiGame>('reversiGames');
@ -62,7 +63,7 @@ export const pack = (
let _game: any; let _game: any;
// Populate the game if 'game' is ID // Populate the game if 'game' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(game)) { if (isObjectId(game)) {
_game = await ReversiGame.findOne({ _game = await ReversiGame.findOne({
_id: game _id: game
}); });
@ -76,7 +77,7 @@ export const pack = (
// Me // Me
const meId: mongo.ObjectID = me const meId: mongo.ObjectID = me
? mongo.ObjectID.prototype.isPrototypeOf(me) ? isObjectId(me)
? me as mongo.ObjectID ? me as mongo.ObjectID
: typeof me === 'string' : typeof me === 'string'
? new mongo.ObjectID(me) ? new mongo.ObjectID(me)

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../../../db/mongodb'; import db from '../../../db/mongodb';
import isObjectId from '../../../misc/is-objectid';
import { IUser, pack as packUser } from '../../user'; import { IUser, pack as packUser } from '../../user';
const Matching = db.get<IMatching>('reversiMatchings'); const Matching = db.get<IMatching>('reversiMatchings');
@ -23,7 +24,7 @@ export const pack = (
// Me // Me
const meId: mongo.ObjectID = me const meId: mongo.ObjectID = me
? mongo.ObjectID.prototype.isPrototypeOf(me) ? isObjectId(me)
? me as mongo.ObjectID ? me as mongo.ObjectID
: typeof me === 'string' : typeof me === 'string'
? new mongo.ObjectID(me) ? new mongo.ObjectID(me)

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const MessagingHistory = db.get<IMessagingHistory>('messagingHistories'); const MessagingHistory = db.get<IMessagingHistory>('messagingHistories');
export default MessagingHistory; export default MessagingHistory;
@ -19,7 +20,7 @@ export async function deleteMessagingHistory(messagingHistory: string | mongo.Ob
let m: IMessagingHistory; let m: IMessagingHistory;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(messagingHistory)) { if (isObjectId(messagingHistory)) {
m = await MessagingHistory.findOne({ m = await MessagingHistory.findOne({
_id: messagingHistory _id: messagingHistory
}); });

View File

@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
import { pack as packUser } from './user'; import { pack as packUser } from './user';
import { pack as packFile } from './drive-file'; import { pack as packFile } from './drive-file';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; import MessagingHistory, { deleteMessagingHistory } from './messaging-history';
import { length } from 'stringz'; import { length } from 'stringz';
@ -30,7 +31,7 @@ export async function deleteMessagingMessage(messagingMessage: string | mongo.Ob
let m: IMessagingMessage; let m: IMessagingMessage;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(messagingMessage)) { if (isObjectId(messagingMessage)) {
m = await MessagingMessage.findOne({ m = await MessagingMessage.findOne({
_id: messagingMessage _id: messagingMessage
}); });
@ -72,7 +73,7 @@ export const pack = (
let _message: any; let _message: any;
// Populate the message if 'message' is ID // Populate the message if 'message' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(message)) { if (isObjectId(message)) {
_message = await MessagingMessage.findOne({ _message = await MessagingMessage.findOne({
_id: message _id: message
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const Mute = db.get<IMute>('mute'); const Mute = db.get<IMute>('mute');
Mute.createIndex(['muterId', 'muteeId'], { unique: true }); Mute.createIndex(['muterId', 'muteeId'], { unique: true });
@ -19,7 +20,7 @@ export async function deleteMute(mute: string | mongo.ObjectID | IMute) {
let m: IMute; let m: IMute;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(mute)) { if (isObjectId(mute)) {
m = await Mute.findOne({ m = await Mute.findOne({
_id: mute _id: mute
}); });

View File

@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
import $ from 'cafy'; import $ from 'cafy';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Reaction from './note-reaction'; import Reaction from './note-reaction';
import { pack as packUser } from './user'; import { pack as packUser } from './user';
@ -37,7 +38,7 @@ export async function deleteNoteReaction(noteReaction: string | mongo.ObjectID |
let n: INoteReaction; let n: INoteReaction;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(noteReaction)) { if (isObjectId(noteReaction)) {
n = await NoteReaction.findOne({ n = await NoteReaction.findOne({
_id: noteReaction _id: noteReaction
}); });
@ -67,7 +68,7 @@ export const pack = (
let _reaction: any; let _reaction: any;
// Populate the reaction if 'reaction' is ID // Populate the reaction if 'reaction' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(reaction)) { if (isObjectId(reaction)) {
_reaction = await Reaction.findOne({ _reaction = await Reaction.findOne({
_id: reaction _id: reaction
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const NoteWatching = db.get<INoteWatching>('noteWatching'); const NoteWatching = db.get<INoteWatching>('noteWatching');
NoteWatching.createIndex(['userId', 'noteId'], { unique: true }); NoteWatching.createIndex(['userId', 'noteId'], { unique: true });
@ -19,7 +20,7 @@ export async function deleteNoteWatching(noteWatching: string | mongo.ObjectID |
let n: INoteWatching; let n: INoteWatching;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(noteWatching)) { if (isObjectId(noteWatching)) {
n = await NoteWatching.findOne({ n = await NoteWatching.findOne({
_id: noteWatching _id: noteWatching
}); });

View File

@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import { length } from 'stringz'; import { length } from 'stringz';
import { IUser, pack as packUser } from './user'; import { IUser, pack as packUser } from './user';
import { pack as packApp } from './app'; import { pack as packApp } from './app';
@ -107,7 +108,7 @@ export async function deleteNote(note: string | mongo.ObjectID | INote) {
let n: INote; let n: INote;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(note)) { if (isObjectId(note)) {
n = await Note.findOne({ n = await Note.findOne({
_id: note _id: note
}); });
@ -259,7 +260,7 @@ export const pack = async (
// Me // Me
const meId: mongo.ObjectID = me const meId: mongo.ObjectID = me
? mongo.ObjectID.prototype.isPrototypeOf(me) ? isObjectId(me)
? me as mongo.ObjectID ? me as mongo.ObjectID
: typeof me === 'string' : typeof me === 'string'
? new mongo.ObjectID(me) ? new mongo.ObjectID(me)
@ -269,7 +270,7 @@ export const pack = async (
let _note: any; let _note: any;
// Populate the note if 'note' is ID // Populate the note if 'note' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(note)) { if (isObjectId(note)) {
_note = await Note.findOne({ _note = await Note.findOne({
_id: note _id: note
}); });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import { IUser, pack as packUser } from './user'; import { IUser, pack as packUser } from './user';
import { pack as packNote } from './note'; import { pack as packNote } from './note';
@ -57,7 +58,7 @@ export async function deleteNotification(notification: string | mongo.ObjectID |
let n: INotification; let n: INotification;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(notification)) { if (isObjectId(notification)) {
n = await Notification.findOne({ n = await Notification.findOne({
_id: notification _id: notification
}); });
@ -90,7 +91,7 @@ export const pack = (notification: any) => new Promise<any>(async (resolve, reje
let _notification: any; let _notification: any;
// Populate the notification if 'notification' is ID // Populate the notification if 'notification' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(notification)) { if (isObjectId(notification)) {
_notification = await Notification.findOne({ _notification = await Notification.findOne({
_id: notification _id: notification
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const PollVote = db.get<IPollVote>('pollVotes'); const PollVote = db.get<IPollVote>('pollVotes');
export default PollVote; export default PollVote;
@ -19,7 +20,7 @@ export async function deletePollVote(pollVote: string | mongo.ObjectID | IPollVo
let p: IPollVote; let p: IPollVote;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(pollVote)) { if (isObjectId(pollVote)) {
p = await PollVote.findOne({ p = await PollVote.findOne({
_id: pollVote _id: pollVote
}); });

View File

@ -1,5 +1,6 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const SwSubscription = db.get<ISwSubscription>('swSubscriptions'); const SwSubscription = db.get<ISwSubscription>('swSubscriptions');
export default SwSubscription; export default SwSubscription;
@ -19,7 +20,7 @@ export async function deleteSwSubscription(swSubscription: string | mongo.Object
let s: ISwSubscription; let s: ISwSubscription;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(swSubscription)) { if (isObjectId(swSubscription)) {
s = await SwSubscription.findOne({ s = await SwSubscription.findOne({
_id: swSubscription _id: swSubscription
}); });

View File

@ -1,6 +1,7 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
const deepcopy = require('deepcopy'); const deepcopy = require('deepcopy');
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const UserList = db.get<IUserList>('userList'); const UserList = db.get<IUserList>('userList');
export default UserList; export default UserList;
@ -20,7 +21,7 @@ export async function deleteUserList(userList: string | mongo.ObjectID | IUserLi
let u: IUserList; let u: IUserList;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(userList)) { if (isObjectId(userList)) {
u = await UserList.findOne({ u = await UserList.findOne({
_id: userList _id: userList
}); });
@ -45,7 +46,7 @@ export const pack = (
) => new Promise<any>(async (resolve, reject) => { ) => new Promise<any>(async (resolve, reject) => {
let _userList: any; let _userList: any;
if (mongo.ObjectID.prototype.isPrototypeOf(userList)) { if (isObjectId(userList)) {
_userList = await UserList.findOne({ _userList = await UserList.findOne({
_id: userList _id: userList
}); });

View File

@ -3,6 +3,7 @@ const deepcopy = require('deepcopy');
const sequential = require('promise-sequential'); const sequential = require('promise-sequential');
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import db from '../db/mongodb'; import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Note, { packMany as packNoteMany, deleteNote } from './note'; import Note, { packMany as packNoteMany, deleteNote } from './note';
import Following, { deleteFollowing } from './following'; import Following, { deleteFollowing } from './following';
import Mute, { deleteMute } from './mute'; import Mute, { deleteMute } from './mute';
@ -175,7 +176,7 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
let u: IUser; let u: IUser;
// Populate // Populate
if (mongo.ObjectID.prototype.isPrototypeOf(user)) { if (isObjectId(user)) {
u = await User.findOne({ u = await User.findOne({
_id: user _id: user
}); });
@ -340,7 +341,6 @@ export const pack = (
includeHasUnreadNotes?: boolean includeHasUnreadNotes?: boolean
} }
) => new Promise<any>(async (resolve, reject) => { ) => new Promise<any>(async (resolve, reject) => {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
includeSecrets: false includeSecrets: false
@ -358,7 +358,7 @@ export const pack = (
}; };
// Populate the user if 'user' is ID // Populate the user if 'user' is ID
if (mongo.ObjectID.prototype.isPrototypeOf(user)) { if (isObjectId(user)) {
_user = await User.findOne({ _user = await User.findOne({
_id: user _id: user
}, { fields }); }, { fields });
@ -378,7 +378,7 @@ export const pack = (
// Me // Me
const meId: mongo.ObjectID = me const meId: mongo.ObjectID = me
? mongo.ObjectID.prototype.isPrototypeOf(me) ? isObjectId(me)
? me as mongo.ObjectID ? me as mongo.ObjectID
: typeof me === 'string' : typeof me === 'string'
? new mongo.ObjectID(me) ? new mongo.ObjectID(me)

View File

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

View 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', type: 'Accept',
actor: `${config.url}/users/${user._id}`,
object object
}); });

View File

@ -1,8 +1,14 @@
import config from '../../../config'; import config from '../../../config';
import { IUser, isLocalUser } from '../../../models/user'; import { IUser, isLocalUser } from '../../../models/user';
export default (follower: IUser, followee: IUser) => ({ export default (follower: IUser, followee: IUser, requestId?: string) => {
type: 'Follow', const follow = {
actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri, type: 'Follow',
object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri,
}); object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri
} as any;
if (requestId) follow.id = requestId;
return follow;
};

View 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: 'Reject', type: 'Reject',
actor: `${config.url}/users/${user._id}`,
object object
}); });

View File

@ -1,4 +1,5 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import isObjectId from '../../../misc/is-objectid';
import Message from '../../../models/messaging-message'; import Message from '../../../models/messaging-message';
import { IMessagingMessage as IMessage } from '../../../models/messaging-message'; import { IMessagingMessage as IMessage } from '../../../models/messaging-message';
import { publishMainStream } from '../../../stream'; import { publishMainStream } from '../../../stream';
@ -15,21 +16,21 @@ export default (
message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[] message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[]
) => new Promise<any>(async (resolve, reject) => { ) => new Promise<any>(async (resolve, reject) => {
const userId = mongo.ObjectID.prototype.isPrototypeOf(user) const userId = isObjectId(user)
? user ? user
: new mongo.ObjectID(user); : new mongo.ObjectID(user);
const otherpartyId = mongo.ObjectID.prototype.isPrototypeOf(otherparty) const otherpartyId = isObjectId(otherparty)
? otherparty ? otherparty
: new mongo.ObjectID(otherparty); : new mongo.ObjectID(otherparty);
const ids: mongo.ObjectID[] = Array.isArray(message) const ids: mongo.ObjectID[] = Array.isArray(message)
? mongo.ObjectID.prototype.isPrototypeOf(message[0]) ? isObjectId(message[0])
? (message as mongo.ObjectID[]) ? (message as mongo.ObjectID[])
: typeof message[0] === 'string' : typeof message[0] === 'string'
? (message as string[]).map(m => new mongo.ObjectID(m)) ? (message as string[]).map(m => new mongo.ObjectID(m))
: (message as IMessage[]).map(m => m._id) : (message as IMessage[]).map(m => m._id)
: mongo.ObjectID.prototype.isPrototypeOf(message) : isObjectId(message)
? [(message as mongo.ObjectID)] ? [(message as mongo.ObjectID)]
: typeof message === 'string' : typeof message === 'string'
? [new mongo.ObjectID(message)] ? [new mongo.ObjectID(message)]

View File

@ -1,4 +1,5 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import isObjectId from '../../../misc/is-objectid';
import { default as Notification, INotification } from '../../../models/notification'; import { default as Notification, INotification } from '../../../models/notification';
import { publishMainStream } from '../../../stream'; import { publishMainStream } from '../../../stream';
import Mute from '../../../models/mute'; import Mute from '../../../models/mute';
@ -12,17 +13,17 @@ export default (
message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[]
) => new Promise<any>(async (resolve, reject) => { ) => new Promise<any>(async (resolve, reject) => {
const userId = mongo.ObjectID.prototype.isPrototypeOf(user) const userId = isObjectId(user)
? user ? user
: new mongo.ObjectID(user); : new mongo.ObjectID(user);
const ids: mongo.ObjectID[] = Array.isArray(message) const ids: mongo.ObjectID[] = Array.isArray(message)
? mongo.ObjectID.prototype.isPrototypeOf(message[0]) ? isObjectId(message[0])
? (message as mongo.ObjectID[]) ? (message as mongo.ObjectID[])
: typeof message[0] === 'string' : typeof message[0] === 'string'
? (message as string[]).map(m => new mongo.ObjectID(m)) ? (message as string[]).map(m => new mongo.ObjectID(m))
: (message as INotification[]).map(m => m._id) : (message as INotification[]).map(m => m._id)
: mongo.ObjectID.prototype.isPrototypeOf(message) : isObjectId(message)
? [(message as mongo.ObjectID)] ? [(message as mongo.ObjectID)]
: typeof message === 'string' : typeof message === 'string'
? [new mongo.ObjectID(message)] ? [new mongo.ObjectID(message)]

View File

@ -100,8 +100,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
}).then(notes => { }).then(notes => {
notes.forEach(note => { notes.forEach(note => {
note._files[note._files.findIndex(f => f._id.equals(file._id))] = file; note._files[note._files.findIndex(f => f._id.equals(file._id))] = file;
Note.findOneAndUpdate({ _id: note._id }, { Note.update({ _id: note._id }, {
_files: note._files $set: {
_files: note._files
}
}); });
}); });
}); });

View File

@ -101,7 +101,7 @@ export const meta = {
export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params); const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr; if (psErr) return rej(psErr);
const isSecure = user != null && app == null; const isSecure = user != null && app == null;

View File

@ -55,7 +55,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
recaptcha: config.recaptcha ? true : false, recaptcha: config.recaptcha ? true : false,
objectStorage: config.drive && config.drive.storage === 'minio', objectStorage: config.drive && config.drive.storage === 'minio',
twitter: config.twitter ? true : false, twitter: config.twitter ? true : false,
serviceWorker: config.sw ? true : false serviceWorker: config.sw ? true : false,
userRecommendation: config.user_recommendation ? config.user_recommendation : {}
} }
}); });
}); });

View File

@ -46,6 +46,8 @@ router.post('/signin', require('./private/signin').default);
router.use(require('./service/github').routes()); router.use(require('./service/github').routes());
router.use(require('./service/twitter').routes()); router.use(require('./service/twitter').routes());
router.use(require('./mastodon').routes());
// Return 404 for unknown API // Return 404 for unknown API
router.all('*', async ctx => { router.all('*', async ctx => {
ctx.status = 404; ctx.status = 404;
@ -54,4 +56,4 @@ router.all('*', async ctx => {
// Register router // Register router
app.use(router.routes()); app.use(router.routes());
module.exports = app; export default app;

View File

@ -0,0 +1,14 @@
import * as Router from 'koa-router';
import User from '../../models/user';
import { toASCII } from 'punycode';
// Init router
const router = new Router();
router.get('/v1/instance/peers', async ctx => {
const peers = await User.distinct('host', { host: { $ne: null } }) as any as string[];
const punyCodes = peers.map(peer => toASCII(peer));
ctx.body = punyCodes;
});
module.exports = router;

View File

@ -12,9 +12,8 @@ export default async (ctx: Koa.Context) => {
ctx.set('Access-Control-Allow-Credentials', 'true'); ctx.set('Access-Control-Allow-Credentials', 'true');
const body = ctx.request.body as any; const body = ctx.request.body as any;
// See: https://github.com/syuilo/misskey/issues/2384 const username = body['username'];
const username = body['username'] || body['x']; const password = body['password'];
const password = body['password'] || body['y'];
const token = body['token']; const token = body['token'];
if (typeof username != 'string') { if (typeof username != 'string') {

View File

@ -132,6 +132,12 @@ export default async (ctx: Koa.Context) => {
updateUserStats(account, true); updateUserStats(account, true);
// Response const res = await pack(account, account, {
ctx.body = await pack(account); detail: true,
includeSecrets: true
});
res.token = secret;
ctx.body = res;
}; };

View File

@ -18,6 +18,7 @@ import activityPub from './activitypub';
import webFinger from './webfinger'; import webFinger from './webfinger';
import config from '../config'; import config from '../config';
import { updateNetworkStats } from '../services/update-chart'; import { updateNetworkStats } from '../services/update-chart';
import apiServer from './api';
// Init app // Init app
const app = new Koa(); const app = new Koa();
@ -47,7 +48,7 @@ if (config.url.startsWith('https')) {
}); });
} }
app.use(mount('/api', require('./api'))); app.use(mount('/api', apiServer));
app.use(mount('/files', require('./file'))); app.use(mount('/files', require('./file')));
// Init router // Init router

View File

@ -10,13 +10,13 @@ import renderAccept from '../../remote/activitypub/renderer/accept';
import { deliver } from '../../queue'; import { deliver } from '../../queue';
import createFollowRequest from './requests/create'; import createFollowRequest from './requests/create';
export default async function(follower: IUser, followee: IUser) { export default async function(follower: IUser, followee: IUser, requestId?: string) {
// フォロー対象が鍵アカウントである or // フォロー対象が鍵アカウントである or
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
if (followee.isLocked || (followee.carefulBot && follower.isBot) || (isLocalUser(follower) && isRemoteUser(followee))) { if (followee.isLocked || (followee.carefulBot && follower.isBot) || (isLocalUser(follower) && isRemoteUser(followee))) {
await createFollowRequest(follower, followee); await createFollowRequest(follower, followee, requestId);
return; return;
} }
@ -79,7 +79,7 @@ export default async function(follower: IUser, followee: IUser) {
} }
if (isRemoteUser(follower) && isLocalUser(followee)) { if (isRemoteUser(follower) && isLocalUser(followee)) {
const content = pack(renderAccept(renderFollow(follower, followee))); const content = pack(renderAccept(renderFollow(follower, followee, requestId), followee));
deliver(followee, content, follower.inbox); deliver(followee, content, follower.inbox);
} }
} }

View File

@ -29,7 +29,12 @@ export default async function(followee: IUser, follower: IUser) {
}); });
if (isRemoteUser(follower)) { if (isRemoteUser(follower)) {
const content = pack(renderAccept(renderFollow(follower, followee))); const request = await FollowRequest.findOne({
followeeId: followee._id,
followerId: follower._id
});
const content = pack(renderAccept(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
deliver(followee as ILocalUser, content, follower.inbox); deliver(followee as ILocalUser, content, follower.inbox);
} }

View File

@ -6,11 +6,12 @@ import renderFollow from '../../../remote/activitypub/renderer/follow';
import { deliver } from '../../../queue'; import { deliver } from '../../../queue';
import FollowRequest from '../../../models/follow-request'; import FollowRequest from '../../../models/follow-request';
export default async function(follower: IUser, followee: IUser) { export default async function(follower: IUser, followee: IUser, requestId?: string) {
await FollowRequest.insert({ await FollowRequest.insert({
createdAt: new Date(), createdAt: new Date(),
followerId: follower._id, followerId: follower._id,
followeeId: followee._id, followeeId: followee._id,
requestId,
// 非正規化 // 非正規化
_follower: { _follower: {

View File

@ -8,7 +8,12 @@ import { publishMainStream } from '../../../stream';
export default async function(followee: IUser, follower: IUser) { export default async function(followee: IUser, follower: IUser) {
if (isRemoteUser(follower)) { if (isRemoteUser(follower)) {
const content = pack(renderReject(renderFollow(follower, followee))); const request = await FollowRequest.findOne({
followeeId: followee._id,
followerId: follower._id
});
const content = pack(renderReject(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
deliver(followee as ILocalUser, content, follower.inbox); deliver(followee as ILocalUser, content, follower.inbox);
} }

View File

@ -26,7 +26,7 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
} catch (e) { } catch (e) {
// duplicate key error // duplicate key error
if (e.code === 11000) { if (e.code === 11000) {
return res(null); return rej('already reacted');
} }
console.error(e); console.error(e);

View File

@ -1,4 +1,5 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import isObjectId from '../../misc/is-objectid';
import { publishMainStream } from '../../stream'; import { publishMainStream } from '../../stream';
import User from '../../models/user'; import User from '../../models/user';
import NoteUnread from '../../models/note-unread'; import NoteUnread from '../../models/note-unread';
@ -11,11 +12,11 @@ export default (
note: string | mongo.ObjectID note: string | mongo.ObjectID
) => new Promise<any>(async (resolve, reject) => { ) => new Promise<any>(async (resolve, reject) => {
const userId: mongo.ObjectID = mongo.ObjectID.prototype.isPrototypeOf(user) const userId: mongo.ObjectID = isObjectId(user)
? user as mongo.ObjectID ? user as mongo.ObjectID
: new mongo.ObjectID(user); : new mongo.ObjectID(user);
const noteId: mongo.ObjectID = mongo.ObjectID.prototype.isPrototypeOf(note) const noteId: mongo.ObjectID = isObjectId(note)
? note as mongo.ObjectID ? note as mongo.ObjectID
: new mongo.ObjectID(note); : new mongo.ObjectID(note);

1117
test/api.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,7 @@
/*
* Tests of MFM
*/
import * as assert from 'assert'; import * as assert from 'assert';
import analyze from '../src/mfm/parse'; import analyze from '../src/mfm/parse';
@ -8,9 +12,9 @@ describe('Text', () => {
it('can be analyzed', () => { it('can be analyzed', () => {
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr'); const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
assert.deepEqual([ assert.deepEqual([
{ type: 'mention', content: '@himawari', username: 'himawari', host: null }, { type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
{ type: 'text', content: ' '}, { type: 'text', content: ' '},
{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }, { type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
{ type: 'text', content: ' お腹ペコい ' }, { type: 'text', content: ' お腹ペコい ' },
{ type: 'emoji', content: ':cat:', emoji: 'cat'}, { type: 'emoji', content: ':cat:', emoji: 'cat'},
{ type: 'text', content: ' '}, { type: 'text', content: ' '},
@ -58,7 +62,7 @@ describe('Text', () => {
it('local', () => { it('local', () => {
const tokens = analyze('@himawari お腹ペコい'); const tokens = analyze('@himawari お腹ペコい');
assert.deepEqual([ assert.deepEqual([
{ type: 'mention', content: '@himawari', username: 'himawari', host: null }, { type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
{ type: 'text', content: ' お腹ペコい' } { type: 'text', content: ' お腹ペコい' }
], tokens); ], tokens);
}); });
@ -66,7 +70,15 @@ describe('Text', () => {
it('remote', () => { it('remote', () => {
const tokens = analyze('@hima_sub@namori.net お腹ペコい'); const tokens = analyze('@hima_sub@namori.net お腹ペコい');
assert.deepEqual([ assert.deepEqual([
{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }, { type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
{ type: 'text', content: ' お腹ペコい' }
], tokens);
});
it('remote punycode', () => {
const tokens = analyze('@hima_sub@xn--q9j5bya.xn--zckzah お腹ペコい');
assert.deepEqual([
{ type: 'mention', content: '@hima_sub@xn--q9j5bya.xn--zckzah', canonical: '@hima_sub@なもり.テスト', username: 'hima_sub', host: 'xn--q9j5bya.xn--zckzah' },
{ type: 'text', content: ' お腹ペコい' } { type: 'text', content: ' お腹ペコい' }
], tokens); ], tokens);
}); });