Compare commits
	
		
			105 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					28cb9cae51 | ||
| 
						 | 
					7f2eb64131 | ||
| 
						 | 
					3e5330a92b | ||
| 
						 | 
					93e5e4afc0 | ||
| 
						 | 
					aa5528d11e | ||
| 
						 | 
					251629ab61 | ||
| 
						 | 
					82d94b5963 | ||
| 
						 | 
					8240901332 | ||
| 
						 | 
					0a870b8e7e | ||
| 
						 | 
					88dd653fa5 | ||
| 
						 | 
					b712b70330 | ||
| 
						 | 
					a018c2f09f | ||
| 
						 | 
					04c16e53a5 | ||
| 
						 | 
					5e89e73f76 | ||
| 
						 | 
					2c9432d7a9 | ||
| 
						 | 
					19d1775b36 | ||
| 
						 | 
					ecc235c545 | ||
| 
						 | 
					382b1d2250 | ||
| 
						 | 
					629693355a | ||
| 
						 | 
					00a3f8d392 | ||
| 
						 | 
					80b6e8090e | ||
| 
						 | 
					a5f817d896 | ||
| 
						 | 
					51b0244cf2 | ||
| 
						 | 
					01131e2606 | ||
| 
						 | 
					6283b7668e | ||
| 
						 | 
					d058ecc4ea | ||
| 
						 | 
					77a0450b5d | ||
| 
						 | 
					1dd1b9084f | ||
| 
						 | 
					6341807d02 | ||
| 
						 | 
					51a1f30225 | ||
| 
						 | 
					5422482696 | ||
| 
						 | 
					cd7f8b080e | ||
| 
						 | 
					faf29b768f | ||
| 
						 | 
					7576569dc9 | ||
| 
						 | 
					ea3bcbbc37 | ||
| 
						 | 
					d9f0e158a3 | ||
| 
						 | 
					195f676500 | ||
| 
						 | 
					a9a2f4820b | ||
| 
						 | 
					8414db57f0 | ||
| 
						 | 
					609d68933e | ||
| 
						 | 
					a23b8cebbc | ||
| 
						 | 
					89f6b03cd6 | ||
| 
						 | 
					7bc9de03a6 | ||
| 
						 | 
					3c865d6054 | ||
| 
						 | 
					fd770b008e | ||
| 
						 | 
					b0d60ef2c2 | ||
| 
						 | 
					7b9cea06ef | ||
| 
						 | 
					30608d3e22 | ||
| 
						 | 
					8bf4e55338 | ||
| 
						 | 
					6ead1de383 | ||
| 
						 | 
					3b628ec3c4 | ||
| 
						 | 
					0ed704d173 | ||
| 
						 | 
					87b6ef0ec5 | ||
| 
						 | 
					5184a07cf2 | ||
| 
						 | 
					dba04cc59c | ||
| 
						 | 
					f4045fb5b3 | ||
| 
						 | 
					16c36163b4 | ||
| 
						 | 
					1ac033ff18 | ||
| 
						 | 
					ccfd48232a | ||
| 
						 | 
					429bf179dc | ||
| 
						 | 
					8ba3fb13eb | ||
| 
						 | 
					11496d887e | ||
| 
						 | 
					bec48319ec | ||
| 
						 | 
					71a93b2b43 | ||
| 
						 | 
					6ed3f9e414 | ||
| 
						 | 
					dc8f592c1f | ||
| 
						 | 
					f66c31c771 | ||
| 
						 | 
					55e2ae1408 | ||
| 
						 | 
					19c72627fc | ||
| 
						 | 
					2a4c53c3a4 | ||
| 
						 | 
					1f2ebce8ed | ||
| 
						 | 
					fcea9dacb7 | ||
| 
						 | 
					908872f374 | ||
| 
						 | 
					f688ceafb8 | ||
| 
						 | 
					b47b5d6d8b | ||
| 
						 | 
					31ce3aa312 | ||
| 
						 | 
					5b22d92e99 | ||
| 
						 | 
					df148e25da | ||
| 
						 | 
					4b26df5c3a | ||
| 
						 | 
					e765be4205 | ||
| 
						 | 
					f7d2457063 | ||
| 
						 | 
					6032d803aa | ||
| 
						 | 
					0de371db38 | ||
| 
						 | 
					ce3797c4af | ||
| 
						 | 
					56dd8c298b | ||
| 
						 | 
					3533257efe | ||
| 
						 | 
					dc2f08721d | ||
| 
						 | 
					66608a4131 | ||
| 
						 | 
					2fa90131eb | ||
| 
						 | 
					a51ed28db6 | ||
| 
						 | 
					5ec290663b | ||
| 
						 | 
					1374d6e34d | ||
| 
						 | 
					45ade17c58 | ||
| 
						 | 
					c753e26187 | ||
| 
						 | 
					577929eed1 | ||
| 
						 | 
					1fde8a8fb0 | ||
| 
						 | 
					77e53cbf9e | ||
| 
						 | 
					ab83e08bc7 | ||
| 
						 | 
					2fad6e6d5f | ||
| 
						 | 
					a3604a6c95 | ||
| 
						 | 
					44f6fe6f1f | ||
| 
						 | 
					311b4e90ca | ||
| 
						 | 
					f5a937c523 | ||
| 
						 | 
					0632a3ed3f | ||
| 
						 | 
					481b3f2c58 | 
@@ -78,7 +78,7 @@ gulp.task('build:copy', ['build:copy:views', 'build:copy:lang'], () =>
 | 
			
		||||
	]).pipe(gulp.dest('./built/'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task('test', ['lint', 'mocha']);
 | 
			
		||||
gulp.task('test', ['mocha']);
 | 
			
		||||
 | 
			
		||||
gulp.task('lint', () =>
 | 
			
		||||
	gulp.src('./src/**/*.ts')
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "Global"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Mitteilungen"
 | 
			
		||||
    list: "Listen"
 | 
			
		||||
    swap-left: "Nach links"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "Listen"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ common:
 | 
			
		||||
  verified-user: "Verified account"
 | 
			
		||||
  disable-animated-mfm: "Disable animated texts in a post"
 | 
			
		||||
  always-show-nsfw: "常に閲覧注意のメディアを表示する"
 | 
			
		||||
  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
 | 
			
		||||
  always-mark-nsfw: "Always post with a warning about media attachment"
 | 
			
		||||
  show-full-acct: "Do not omit the hostname from the username"
 | 
			
		||||
  reduce-motion: "Reduce motion in UI"
 | 
			
		||||
  this-setting-is-this-device-only: "Only for this device"
 | 
			
		||||
@@ -155,9 +155,10 @@ common:
 | 
			
		||||
    home: "Home"
 | 
			
		||||
    local: "Local"
 | 
			
		||||
    hybrid: "Social"
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    hashtag: "Hashtag"
 | 
			
		||||
    global: "Global"
 | 
			
		||||
    mentions: "Mentions"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Notifications"
 | 
			
		||||
    list: "Lists"
 | 
			
		||||
    swap-left: "Move to the left"
 | 
			
		||||
@@ -810,11 +811,12 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "Social"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
  mentions: "Mentions"
 | 
			
		||||
  messages: "Messages"
 | 
			
		||||
  list: "Lists"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
  add-list: "リストを追加"
 | 
			
		||||
  list-name: "リスト名"
 | 
			
		||||
  hashtag: "Hashtag"
 | 
			
		||||
  add-tag-timeline: "Add hashtag tl"
 | 
			
		||||
  add-list: "Add list"
 | 
			
		||||
  list-name: "List name"
 | 
			
		||||
desktop/views/components/ui.header.vue:
 | 
			
		||||
  welcome-back: "Welcome back,"
 | 
			
		||||
  adjective: "-san"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "Social"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
  mentions: "Mentions"
 | 
			
		||||
  messages: "Messages"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "No posts \"{}\" found."
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "Global"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Notificaciones"
 | 
			
		||||
    list: "Listado"
 | 
			
		||||
    swap-left: "Desplazar a la izq."
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "Global"
 | 
			
		||||
    mentions: "Mentions"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Notifications"
 | 
			
		||||
    list: "Liste"
 | 
			
		||||
    swap-left: "Déplacer à gauche"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "Social"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
  mentions: "Mentions"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "Listes"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "Social"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
  mentions: "Mentions"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "Pas de message avec un hashtag {} trouvé."
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -778,6 +779,8 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  choose-wallpaper: "壁紙を選択"
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  use-shadow: "UIに影を使用"
 | 
			
		||||
  rounded-corners: "UIの角を丸める"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
@@ -916,6 +919,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1322,6 +1326,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
 
 | 
			
		||||
@@ -13,12 +13,12 @@ common:
 | 
			
		||||
    rich-contents: "投稿"
 | 
			
		||||
    rich-contents-desc: "思っとること、タイガースの実況、他に言いたいことがあればなんでも言ってええで。いろんな構文あるから、好きにつこうてくれや。画像や動画、アンケートも添付できるで。"
 | 
			
		||||
    reaction: "リアクション"
 | 
			
		||||
    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
 | 
			
		||||
    ui: "インターフェース"
 | 
			
		||||
    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
 | 
			
		||||
    reaction-desc: "「何思っとるか言うてみ?」言われても、わからんわ!リアクション使うて、エモーションをダイレクトに伝えるんや!Misskeyはな、他のユーザーの投稿にいろんなリアクション付けられるんや。もう「いいね」とかいうもんだけのSNSには戻れへんわな。551の豚まん食うてみ?もう他の豚まん食えへんで?"
 | 
			
		||||
    ui: "インターフェイス"
 | 
			
		||||
    ui-desc: "このUIええ言うてたで、知らんけど。あんたの好みのUIなんて知ったこっちゃない。Misskeyは好きにいじれるからな、レイアウトやデザイン変えたり、色んなウィジェットひっつけたりして、あんただけのMisskey作って楽しんでな!"
 | 
			
		||||
    drive: "ドライブ"
 | 
			
		||||
    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
 | 
			
		||||
    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
 | 
			
		||||
    drive-desc: "「こないだの画像、どこやったかな…また投稿したいんやけど…」「さっきのファイルあのフォルダに直しといて」そんなこと言わんとって。Misskeyはもとからドライブ機能持っとるさかい、心配あらへん。ファイルの「わけわけ」したってな。"
 | 
			
		||||
    outro: "Misskeyの機能は無限大や!知らんけど。知らん言うとるやんけ、あんたが見に行けや!Misskeyは分散型SNSやから、ここがあかんくても他がある。阪神でもオリックスでもワイは応援するで!"
 | 
			
		||||
  adblock:
 | 
			
		||||
    detected: "広告ブロッカーを無効にしてや"
 | 
			
		||||
    warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
 | 
			
		||||
@@ -84,11 +84,11 @@ common:
 | 
			
		||||
  note-visibility:
 | 
			
		||||
    public: "公開"
 | 
			
		||||
    home: "ホーム"
 | 
			
		||||
    home-desc: "ホームタイムラインにのみ公開"
 | 
			
		||||
    home-desc: "ホームタイムライン以外に見せんとって"
 | 
			
		||||
    followers: "フォロワー"
 | 
			
		||||
    followers-desc: "自分のフォロワーにのみ公開"
 | 
			
		||||
    followers-desc: "自分のフォロワー以外に見せんとって"
 | 
			
		||||
    specified: "ダイレクト"
 | 
			
		||||
    specified-desc: "指定したユーザーにのみ公開"
 | 
			
		||||
    specified-desc: "今から言うユーザー以外に見せんとってや"
 | 
			
		||||
    private: "非公開"
 | 
			
		||||
  note-placeholders:
 | 
			
		||||
    a: "今なにしてん?"
 | 
			
		||||
@@ -109,12 +109,12 @@ common:
 | 
			
		||||
  use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
 | 
			
		||||
  verified-user: "アメちゃん付きアカウント"
 | 
			
		||||
  disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
 | 
			
		||||
  always-show-nsfw: "常に閲覧注意のメディアを表示する"
 | 
			
		||||
  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
 | 
			
		||||
  show-full-acct: "ユーザー名のホストを省略しない"
 | 
			
		||||
  reduce-motion: "UIの動きを減らす"
 | 
			
		||||
  always-show-nsfw: "閲覧注意?見せたらあかん?そんなん知らんわ、見せろや!"
 | 
			
		||||
  always-mark-nsfw: "わからんからとりあえずメディアは見せたらあかん"
 | 
			
		||||
  show-full-acct: "ユーザー名のホストも出したる"
 | 
			
		||||
  reduce-motion: "UI、動き過ぎや、静かにしてや"
 | 
			
		||||
  this-setting-is-this-device-only: "このデバイスのみ"
 | 
			
		||||
  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
 | 
			
		||||
  do-not-use-in-production: '開発ビルドや。本番環境で使わんといて!知らんで!'
 | 
			
		||||
  reversi:
 | 
			
		||||
    drawn: "おあいこ"
 | 
			
		||||
    my-turn: "あんさんのターンや"
 | 
			
		||||
@@ -157,7 +157,8 @@ common:
 | 
			
		||||
    hybrid: "ソーシャル"
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    mentions: "あんた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動や!"
 | 
			
		||||
@@ -261,11 +262,11 @@ common/views/components/connect-failed.troubleshooter.vue:
 | 
			
		||||
  flush: "キャッシュの削除"
 | 
			
		||||
  set-version: "バージョン指定"
 | 
			
		||||
common/views/components/media-banner.vue:
 | 
			
		||||
  sensitive: "閲覧注意"
 | 
			
		||||
  click-to-show: "クリックして表示"
 | 
			
		||||
  sensitive: "見せたらあかん"
 | 
			
		||||
  click-to-show: "押してみ、見せたるわ"
 | 
			
		||||
common/views/components/cw-button.vue:
 | 
			
		||||
  hide: "隠す"
 | 
			
		||||
  show: "もっと見る"
 | 
			
		||||
  hide: "見せへんわ"
 | 
			
		||||
  show: "もっとあるやろ!"
 | 
			
		||||
common/views/components/messaging.vue:
 | 
			
		||||
  search-user: "ユーザーを探す"
 | 
			
		||||
  you: "あんさん"
 | 
			
		||||
@@ -302,7 +303,7 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  pin: "ピン留め"
 | 
			
		||||
  delete: "ほかす"
 | 
			
		||||
  delete-confirm: "この投稿を削除してもええか?"
 | 
			
		||||
  remote: "投稿元で見る"
 | 
			
		||||
  remote: "投稿元に行ってみよか"
 | 
			
		||||
common/views/components/poll.vue:
 | 
			
		||||
  vote-to: "「{}」に投票や!"
 | 
			
		||||
  vote-count: "{}票"
 | 
			
		||||
@@ -317,7 +318,7 @@ common/views/components/poll-editor.vue:
 | 
			
		||||
  add: "+選択肢を追加"
 | 
			
		||||
  destroy: "アンケートをほかそ"
 | 
			
		||||
common/views/components/reaction-picker.vue:
 | 
			
		||||
  choose-reaction: "リアクションを選択"
 | 
			
		||||
  choose-reaction: "リアクション、どれにするんや?"
 | 
			
		||||
common/views/components/signin.vue:
 | 
			
		||||
  username: "ユーザー名"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
@@ -329,7 +330,7 @@ common/views/components/signin.vue:
 | 
			
		||||
  login-failed: "なんかログインできんかったわ。ユーザー名とパスワードとかを確認してや。"
 | 
			
		||||
common/views/components/signup.vue:
 | 
			
		||||
  invitation-code: "招待コード"
 | 
			
		||||
  invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
 | 
			
		||||
  invitation-info: "招待コードをもっとらんのやったら、<a href=\"{}\">管理者</a>まで連絡してや。"
 | 
			
		||||
  username: "ユーザー名"
 | 
			
		||||
  checking: "確認中や…"
 | 
			
		||||
  available: "使えるで"
 | 
			
		||||
@@ -337,7 +338,7 @@ common/views/components/signup.vue:
 | 
			
		||||
  error: "通信あかんわ"
 | 
			
		||||
  invalid-format: "a~z、A~Z、0~9、_が使えるで"
 | 
			
		||||
  too-short: "1文字以上やで!"
 | 
			
		||||
  too-long: "20文字以内でお願いします"
 | 
			
		||||
  too-long: "20文字以内やで"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
  password-placeholder: "8文字以上にしときや"
 | 
			
		||||
  weak-password: "へぼいパスワード"
 | 
			
		||||
@@ -351,8 +352,8 @@ common/views/components/signup.vue:
 | 
			
		||||
  create: "アカウント作成"
 | 
			
		||||
  some-error: "何かよう分からんけど、アカウントの作成に失敗してしもたわ。すまんがもっぺん試してくれへんか?"
 | 
			
		||||
common/views/components/special-message.vue:
 | 
			
		||||
  new-year: "Happy New Year!"
 | 
			
		||||
  christmas: "Merry Christmas!"
 | 
			
		||||
  new-year: "おおきに。今年もよろしゅう。"
 | 
			
		||||
  christmas: "メリークリスマス!"
 | 
			
		||||
common/views/components/stream-indicator.vue:
 | 
			
		||||
  connecting: "つないどるで"
 | 
			
		||||
  reconnecting: "つなぎ直すで"
 | 
			
		||||
@@ -369,19 +370,19 @@ common/views/components/uploader.vue:
 | 
			
		||||
common/views/components/visibility-chooser.vue:
 | 
			
		||||
  public: "公開"
 | 
			
		||||
  home: "ホーム"
 | 
			
		||||
  home-desc: "ホームタイムラインにのみ公開"
 | 
			
		||||
  home-desc: "ホームタイムライン以外に見せんとって"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  followers-desc: "自分のフォロワーにのみ公開"
 | 
			
		||||
  followers-desc: "自分のフォロワー以外に見せんとって"
 | 
			
		||||
  specified: "ダイレクト"
 | 
			
		||||
  specified-desc: "指定したユーザーにのみ公開"
 | 
			
		||||
  specified-desc: "今から言うユーザー以外に見せんとってや"
 | 
			
		||||
  private: "非公開"
 | 
			
		||||
common/views/components/trends.vue:
 | 
			
		||||
  count: "{}人が投稿"
 | 
			
		||||
  empty: "トレンドなし"
 | 
			
		||||
  empty: "流行は自分で作るんや"
 | 
			
		||||
common/views/widgets/broadcast.vue:
 | 
			
		||||
  fetching: "見てみるわ…"
 | 
			
		||||
  no-broadcasts: "お知らせはあらへんで"
 | 
			
		||||
  have-a-nice-day: "良い一日を!"
 | 
			
		||||
  have-a-nice-day: "おおきに!"
 | 
			
		||||
  next: "次"
 | 
			
		||||
common/views/widgets/calendar.vue:
 | 
			
		||||
  year: "{}年"
 | 
			
		||||
@@ -435,21 +436,21 @@ common/views/widgets/tips.vue:
 | 
			
		||||
  tips-line25: "対応ブラウザやったらMisskeyを開いとらんでも通知を受け取れんで"
 | 
			
		||||
common/views/pages/follow.vue:
 | 
			
		||||
  signed-in-as: "{}としてサインイン中"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  following: "フォローしとる"
 | 
			
		||||
  follow: "フォロー"
 | 
			
		||||
  request-pending: "フォロー許可待ち"
 | 
			
		||||
  follow-request: "フォロー申請"
 | 
			
		||||
  request-pending: "フォローの許し待っとる"
 | 
			
		||||
  follow-request: "フォロー許してくれや!言うてみる"
 | 
			
		||||
desktop:
 | 
			
		||||
  banner-crop-title: "バナーとして表示する部分を選択"
 | 
			
		||||
  banner-crop-title: "どこバナーとして出す?"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  uploading-banner: "新しいバナーをアップロードしとるで"
 | 
			
		||||
  banner-updated: "バナーを更新したで"
 | 
			
		||||
  choose-banner: "バナーにする画像選んでや"
 | 
			
		||||
  avatar-crop-title: "どこアバターとして出しとく?"
 | 
			
		||||
  avatar: "アバター"
 | 
			
		||||
  uploading-avatar: "新しいアバターをアップロードしています"
 | 
			
		||||
  avatar-updated: "アバターを更新しました"
 | 
			
		||||
  choose-avatar: "アバターにする画像を選択"
 | 
			
		||||
  uploading-avatar: "新しいアバターをアップロードしとるで"
 | 
			
		||||
  avatar-updated: "アバターを更新したで"
 | 
			
		||||
  choose-avatar: "アバターにする画像選んでや"
 | 
			
		||||
  invalid-filetype: "この形式のファイル無理やねん"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Black ... Total"
 | 
			
		||||
@@ -458,7 +459,7 @@ desktop/views/components/activity.chart.vue:
 | 
			
		||||
  renotes: "Green ... Renotes"
 | 
			
		||||
desktop/views/components/activity.vue:
 | 
			
		||||
  title: "アクティビティ"
 | 
			
		||||
  toggle: "表示を切り替え"
 | 
			
		||||
  toggle: "表示変える"
 | 
			
		||||
desktop/views/components/calendar.vue:
 | 
			
		||||
  title: "{1}年 {2} 月"
 | 
			
		||||
  prev: "前の月"
 | 
			
		||||
@@ -473,10 +474,10 @@ desktop/views/components/charts.vue:
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    notes: "投稿の増減(統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の累計"
 | 
			
		||||
    notes-total: "全部の投稿"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの累計"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
@@ -487,21 +488,21 @@ desktop/views/components/charts.vue:
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
desktop/views/components/choose-file-from-drive-window.vue:
 | 
			
		||||
  choose-file: "ファイル選択中"
 | 
			
		||||
  upload: "PCからドライブにファイルをアップロード"
 | 
			
		||||
  choose-file: "ファイル選択しとる"
 | 
			
		||||
  upload: "PCからドライブにファイル上げる"
 | 
			
		||||
  cancel: "やめとくわ"
 | 
			
		||||
  ok: "決定"
 | 
			
		||||
  choose-prompt: "ファイルを選択"
 | 
			
		||||
  ok: "そうする"
 | 
			
		||||
  choose-prompt: "ファイル選んでや"
 | 
			
		||||
desktop/views/components/choose-folder-from-drive-window.vue:
 | 
			
		||||
  cancel: "やめとくわ"
 | 
			
		||||
  ok: "決定"
 | 
			
		||||
  choose-prompt: "フォルダを選択"
 | 
			
		||||
  ok: "そうする"
 | 
			
		||||
  choose-prompt: "フォルダ選んでや"
 | 
			
		||||
desktop/views/components/crop-window.vue:
 | 
			
		||||
  skip: "クロップをスキップ"
 | 
			
		||||
  skip: "クロップせーへんわ"
 | 
			
		||||
  cancel: "やめとくわ"
 | 
			
		||||
  ok: "決定"
 | 
			
		||||
  ok: "そうする"
 | 
			
		||||
desktop/views/components/drive-window.vue:
 | 
			
		||||
  used: "使用中"
 | 
			
		||||
  used: "使うとる"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
desktop/views/components/drive.file.vue:
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
@@ -537,17 +538,17 @@ desktop/views/components/drive.nav-folder.vue:
 | 
			
		||||
desktop/views/components/drive.vue:
 | 
			
		||||
  search: "検索"
 | 
			
		||||
  load-more: "もっとあらへんのか!"
 | 
			
		||||
  empty-draghover: "ドロップですか?いいですよ、ボクはカワイイですからね"
 | 
			
		||||
  empty-draghover: "ドロップするにゃ!お魚以外なら何でもいいにゃ!"
 | 
			
		||||
  empty-drive: "ドライブには何もあらへんで。"
 | 
			
		||||
  empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。"
 | 
			
		||||
  empty-folder: "このフォルダーは空です"
 | 
			
		||||
  empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできんねん。"
 | 
			
		||||
  empty-folder: "このフォルダーは空や"
 | 
			
		||||
  unable-to-process: "あかん、無理やわ"
 | 
			
		||||
  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
 | 
			
		||||
  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
 | 
			
		||||
  unhandled-error: "ようわからん"
 | 
			
		||||
  url-upload: "URLアップロード"
 | 
			
		||||
  url-of-file: "このURLのファイルをアップロードしたいねん"
 | 
			
		||||
  url-upload-requested: "アップロードしたい言うといたで"
 | 
			
		||||
  may-take-time: "アップロードが完了するまで時間がかかる場合があります。"
 | 
			
		||||
  may-take-time: "アップロード終わるまで時間かかるわ、知らんけど。たこ焼き何個食べれるやろか…"
 | 
			
		||||
  create-folder: "フォルダー作成"
 | 
			
		||||
  folder-name: "フォルダー名"
 | 
			
		||||
  contextmenu:
 | 
			
		||||
@@ -578,7 +579,7 @@ desktop/views/components/friends-maker.vue:
 | 
			
		||||
  empty: "おもろいユーザー居らんかったわ"
 | 
			
		||||
  fetching: "読みこんどるで…"
 | 
			
		||||
  refresh: "もっとあるやろ!"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
  close: "さいなら"
 | 
			
		||||
desktop/views/components/game-window.vue:
 | 
			
		||||
  game: "ゲーム"
 | 
			
		||||
desktop/views/components/home.vue:
 | 
			
		||||
@@ -605,9 +606,9 @@ desktop/views/components/notes.note.vue:
 | 
			
		||||
  reply: "返す"
 | 
			
		||||
  renote: "Renote"
 | 
			
		||||
  add-reaction: "リアクション"
 | 
			
		||||
  detail: "詳細"
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  detail: "もっと"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
desktop/views/components/notes.vue:
 | 
			
		||||
  error: "あかん、読み込めへんわ"
 | 
			
		||||
  retry: "もっぺん"
 | 
			
		||||
@@ -655,11 +656,11 @@ desktop/views/components/renote-form.vue:
 | 
			
		||||
  quote: "持ってくる…"
 | 
			
		||||
  cancel: "やめとくわ"
 | 
			
		||||
  renote: "Renote"
 | 
			
		||||
  reposting: "しています..."
 | 
			
		||||
  success: "Renoteしました!"
 | 
			
		||||
  failure: "Renoteに失敗しました"
 | 
			
		||||
  reposting: "やっとります..."
 | 
			
		||||
  success: "Renoteしたで!"
 | 
			
		||||
  failure: "Renoteでけへん"
 | 
			
		||||
desktop/views/components/renote-form-window.vue:
 | 
			
		||||
  title: "この投稿をRenoteしますか?"
 | 
			
		||||
  title: "この投稿をRenoteしてもええか?"
 | 
			
		||||
desktop/views/components/settings-window.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
desktop/views/components/settings.vue:
 | 
			
		||||
@@ -668,27 +669,27 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  apps: "アプリ"
 | 
			
		||||
  mute: "ミュート"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  security: "セキュリティ"
 | 
			
		||||
  signin: "サインイン履歴"
 | 
			
		||||
  security: "守護神セキュリティ"
 | 
			
		||||
  signin: "こんな感じでサインインしたらしいで"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
  2fa: "二段階認証"
 | 
			
		||||
  other: "その他"
 | 
			
		||||
  license: "ライセンス"
 | 
			
		||||
  behaviour: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
 | 
			
		||||
  behaviour: "動き"
 | 
			
		||||
  fetch-on-scroll: "スクロールしたらもっと見せてや"
 | 
			
		||||
  fetch-on-scroll-desc: "ページを下までスクロールしたときに自動でもっとコンテンツを読み込むで。"
 | 
			
		||||
  note-visibility: "投稿の公開範囲"
 | 
			
		||||
  default-note-visibility: "デフォルトの公開範囲"
 | 
			
		||||
  remember-note-visibility: "投稿の公開範囲を記憶する"
 | 
			
		||||
  default-note-visibility: "もとからの公開範囲"
 | 
			
		||||
  remember-note-visibility: "投稿の公開範囲おぼえといて"
 | 
			
		||||
  auto-popout: "ウィンドウの自動ポップアウト"
 | 
			
		||||
  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
 | 
			
		||||
  advanced: "詳細設定"
 | 
			
		||||
  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトすんで。この設定はブラウザに記憶されんで。"
 | 
			
		||||
  advanced: "もっと設定"
 | 
			
		||||
  api-via-stream: "ストリームを経由したAPIリクエスト"
 | 
			
		||||
  api-via-stream-desc: "この設定をオンにすると、WebSocket接続を経由してAPIリクエストが行われんで(パフォーマンス向上するかも、知らんけど)。オフにすると、ネイティブの fetch API が利用されるで。この設定はこのデバイスのみ有効やで。"
 | 
			
		||||
  display: "デザインと表示"
 | 
			
		||||
  display: "見た感じ"
 | 
			
		||||
  customize: "ホームをカスタマイズ"
 | 
			
		||||
  choose-wallpaper: "壁紙を選択"
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  choose-wallpaper: "壁紙選ぶ"
 | 
			
		||||
  delete-wallpaper: "壁紙ほかす"
 | 
			
		||||
  dark-mode: "夜にすんで"
 | 
			
		||||
  circle-icons: "アイコンもタコ焼きも丸いやんな?"
 | 
			
		||||
  contrasted-acct: "ユーザー名ようわからんし見やすしといて"
 | 
			
		||||
@@ -721,39 +722,39 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  cache-cleared: "キャッシュお掃除したで"
 | 
			
		||||
  cache-cleared-desc: "もっぺんページ読みこみ直してくれや"
 | 
			
		||||
  auto-watch: "投稿勝手にウォッチしといてや"
 | 
			
		||||
  auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。"
 | 
			
		||||
  about: "Misskeyについて"
 | 
			
		||||
  operator: "このサーバーの運営者"
 | 
			
		||||
  auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を勝手に受け取るようにすんで。"
 | 
			
		||||
  about: "Misskeyってなんや?"
 | 
			
		||||
  operator: "このサーバー誰のや"
 | 
			
		||||
  update: "Misskey Update"
 | 
			
		||||
  version: "バージョン:"
 | 
			
		||||
  latest-version: "最新のバージョン:"
 | 
			
		||||
  update-checking: "アップデートを確認中"
 | 
			
		||||
  do-update: "アップデートを確認"
 | 
			
		||||
  update-settings: "詳細設定"
 | 
			
		||||
  prevent-update: "アップデートを延期する(非推奨)"
 | 
			
		||||
  prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
 | 
			
		||||
  no-updates: "利用可能な更新はありません"
 | 
			
		||||
  update-checking: "アップデートはあらへんか…"
 | 
			
		||||
  do-update: "アップデートあるか見てみる"
 | 
			
		||||
  update-settings: "もっと設定"
 | 
			
		||||
  prevent-update: "アップデートしたないわ、また今度や(やめときや)"
 | 
			
		||||
  prevent-update-desc: "この設定をオンにしとってもアップデートが反映される場合があるかも分からん、知らんけど気ぃつけてや。この設定はこのデバイスのみ有効やで。"
 | 
			
		||||
  no-updates: "使える更新はあらへん"
 | 
			
		||||
  no-updates-desc: "つこてるMisskeyは最新や!"
 | 
			
		||||
  update-available: "新しいバージョンが利用可能や"
 | 
			
		||||
  update-available-desc: "ページを再度読み込みすると更新が適用されるで。"
 | 
			
		||||
  advanced-settings: "高度な設定"
 | 
			
		||||
  debug-mode: "デバッグモードを有効にする"
 | 
			
		||||
  debug-mode-desc: "この設定はブラウザに記憶されます。"
 | 
			
		||||
  experimental: "実験的機能を有効にする"
 | 
			
		||||
  experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。"
 | 
			
		||||
  advanced-settings: "ワイにはわからん設定"
 | 
			
		||||
  debug-mode: "デバッグモードにしてみる"
 | 
			
		||||
  debug-mode-desc: "この設定はブラウザに記憶されんで。"
 | 
			
		||||
  experimental: "お試し機能使うてみる"
 | 
			
		||||
  experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になるかも分からん、知らんけど気ぃつけてや。この設定はブラウザに記憶されんで。"
 | 
			
		||||
  tools: "ツール"
 | 
			
		||||
  task-manager: "タスクマネージャ"
 | 
			
		||||
  third-parties: "サードパーティ"
 | 
			
		||||
desktop/views/components/settings.2fa.vue:
 | 
			
		||||
  intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
 | 
			
		||||
  intro: "二段階認証を設定すると、サインイン時にパスワードだけとちゃうくて、予め登録しておいた物理的なデバイス(例えばあんさんのスマートフォンなど)も必要になり、よりセキュリティが向上すんで。"
 | 
			
		||||
  detail: "詳細..."
 | 
			
		||||
  url: "https://www.google.co.jp/intl/ja/landing/2step/"
 | 
			
		||||
  caution: "登録したデバイスを紛失するなどした場合、Misskeyにサインインできなくなりますのでご注意ください。"
 | 
			
		||||
  register: "デバイスを登録する"
 | 
			
		||||
  already-registered: "既に設定は完了しています。"
 | 
			
		||||
  unregister: "設定を解除"
 | 
			
		||||
  unregistered: "二段階認証が無効になりました。"
 | 
			
		||||
  enter-password: "パスワードを入力してください"
 | 
			
		||||
  caution: "登録したデバイスを紛失してもうたら、もうMisskeyにサインインできんくなるで。"
 | 
			
		||||
  register: "デバイス登録する"
 | 
			
		||||
  already-registered: "もう設定終わっとるわ"
 | 
			
		||||
  unregister: "設定をほかす"
 | 
			
		||||
  unregistered: "二段階認証もうせーへんで"
 | 
			
		||||
  enter-password: "パスワードを入れてや"
 | 
			
		||||
  authenticator: "まず、Google Authenticatorとかのをつこてるデバイスにインストールしてや:"
 | 
			
		||||
  howtoinstall: "インストール方法はここやで"
 | 
			
		||||
  scan: "んで、ここに出とるQRコードをスキャンしてな:"
 | 
			
		||||
@@ -778,28 +779,28 @@ desktop/views/components/settings.mute.vue:
 | 
			
		||||
  no-users: "ミュートしているユーザーはおらんで"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
  enter-current-password: "今のパスワードを入れてや"
 | 
			
		||||
  enter-new-password: "さらのパスワード入れてや"
 | 
			
		||||
  enter-new-password-again: "もういっぺんさらのパスワードを入れてや"
 | 
			
		||||
  not-match: "パスワードがおうとらん"
 | 
			
		||||
  changed: "パスワード変えたわ"
 | 
			
		||||
desktop/views/components/settings.profile.vue:
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  choice-avatar: "画像を選択"
 | 
			
		||||
  choice-avatar: "画像選んでや"
 | 
			
		||||
  name: "名前"
 | 
			
		||||
  location: "場所"
 | 
			
		||||
  description: "自己紹介"
 | 
			
		||||
  description: "ワイのこと"
 | 
			
		||||
  birthday: "誕生日"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  locked-account: "アカウントの保護"
 | 
			
		||||
  is-locked: "フォローを承認制にする"
 | 
			
		||||
  locked-account: "アカウント守る"
 | 
			
		||||
  is-locked: "他人のフォローは許してからや!"
 | 
			
		||||
  other: "その他"
 | 
			
		||||
  is-bot: "このアカウントはBotです"
 | 
			
		||||
  is-cat: "このアカウントはCatです"
 | 
			
		||||
  profile-updated: "プロフィールを更新しました"
 | 
			
		||||
  is-bot: "このアカウントはBotやで"
 | 
			
		||||
  is-cat: "このアカウントはCatやで"
 | 
			
		||||
  profile-updated: "プロフィールを更新したで"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
  media-count: "{}つのメディア"
 | 
			
		||||
  poll: "アンケート"
 | 
			
		||||
desktop/views/components/taskmanager.vue:
 | 
			
		||||
@@ -809,26 +810,27 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  local: "ローカル"
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  mentions: "あんた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
  add-list: "リストを追加"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグ増やす"
 | 
			
		||||
  add-list: "リストに入れる"
 | 
			
		||||
  list-name: "リスト名"
 | 
			
		||||
desktop/views/components/ui.header.vue:
 | 
			
		||||
  welcome-back: "おかえり、"
 | 
			
		||||
  adjective: "さん"
 | 
			
		||||
  adjective: "はん"
 | 
			
		||||
desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  profile: "プロフィール"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  favorites: "お気に入り"
 | 
			
		||||
  lists: "リスト"
 | 
			
		||||
  follow-requests: "フォロー申請"
 | 
			
		||||
  follow-requests: "フォロー許してくれや!言うてみる"
 | 
			
		||||
  customize: "ホームをカスタマイズ"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  dark: "闇に飲まれる"
 | 
			
		||||
  signout: "さいなら"
 | 
			
		||||
  dark: "ナイトゲームじゃ!"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  home: "ホーム"
 | 
			
		||||
  deck: "デッキ"
 | 
			
		||||
@@ -841,9 +843,9 @@ desktop/views/components/ui.header.post.vue:
 | 
			
		||||
desktop/views/components/ui.header.search.vue:
 | 
			
		||||
  placeholder: "検索"
 | 
			
		||||
desktop/views/components/received-follow-requests-window.vue:
 | 
			
		||||
  title: "フォロー申請"
 | 
			
		||||
  accept: "承認"
 | 
			
		||||
  reject: "拒否"
 | 
			
		||||
  title: "フォロー許してくれや!言うてみる"
 | 
			
		||||
  accept: "許す"
 | 
			
		||||
  reject: "許さん"
 | 
			
		||||
desktop/views/components/user-lists-window.vue:
 | 
			
		||||
  title: "リスト"
 | 
			
		||||
  create-list: "新しいリストを作成"
 | 
			
		||||
@@ -854,14 +856,14 @@ desktop/views/components/user-preview.vue:
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
desktop/views/components/users-list.vue:
 | 
			
		||||
  all: "すべて"
 | 
			
		||||
  iknow: "知り合い"
 | 
			
		||||
  iknow: "知っとる"
 | 
			
		||||
  load-more: "もっと"
 | 
			
		||||
  fetching: "読み込んでいます"
 | 
			
		||||
  fetching: "読みこんどるで…"
 | 
			
		||||
desktop/views/components/users-list-item.vue:
 | 
			
		||||
  followed: "フォローされています"
 | 
			
		||||
  followed: "フォローされとるで"
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
  close: "さいなら"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
@@ -869,15 +871,15 @@ desktop/views/pages/admin/admin.vue:
 | 
			
		||||
  update: "更新"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-users: "知り合い全員や"
 | 
			
		||||
  original-users: "ここの人らだけ"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  invite: "来てや"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
  suspended: "凍結したで"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
@@ -887,33 +889,33 @@ desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしたで"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウントにせーへん"
 | 
			
		||||
  unverify: "公式アカウントにはさせへんで"
 | 
			
		||||
  unverified: "公式アカウントを解除したで"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
  is-media-only: "メディア投稿だけや"
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
desktop/views/pages/deck/deck.note.vue:
 | 
			
		||||
  reposted-by: "{}がRenote"
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
desktop/views/pages/stats/stats.vue:
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  original-users: "ここの人らだけ"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
  about: "もっと…"
 | 
			
		||||
  gotit: "ほい"
 | 
			
		||||
  signin: "サインイン"
 | 
			
		||||
  signup: "サインアップ"
 | 
			
		||||
  signin-button: "サインイン中…"
 | 
			
		||||
  signup-button: "サインアップ"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  announcements: "知っときや"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  powered-by-misskey: "<b>Misskey</b>のおかげや"
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "ドライブ"
 | 
			
		||||
@@ -922,41 +924,41 @@ desktop/views/pages/favorites.vue:
 | 
			
		||||
desktop/views/pages/home-customize.vue:
 | 
			
		||||
  title: "ホームをカスタマイズ"
 | 
			
		||||
desktop/views/pages/note.vue:
 | 
			
		||||
  prev: "前の投稿"
 | 
			
		||||
  next: "次の投稿"
 | 
			
		||||
  prev: "前のやつ"
 | 
			
		||||
  next: "次のやつ"
 | 
			
		||||
desktop/views/pages/selectdrive.vue:
 | 
			
		||||
  title: "ファイルを選択してください"
 | 
			
		||||
  title: "ファイルを選択してや"
 | 
			
		||||
  ok: "決定"
 | 
			
		||||
  cancel: "やめとくわ"
 | 
			
		||||
  upload: "PCからドライブにファイルをアップロード"
 | 
			
		||||
  upload: "PCからドライブにファイル上げる"
 | 
			
		||||
desktop/views/pages/search.vue:
 | 
			
		||||
  not-available: "検索機能はインスタンスの設定で無効になっています。"
 | 
			
		||||
  not-found: "「{}」に関する投稿は見つかりませんでした。"
 | 
			
		||||
  not-available: "検索機能は使えへんわ。管理者がそう言うとる。"
 | 
			
		||||
  not-found: "「{}」に関する投稿はあらへん。"
 | 
			
		||||
desktop/views/pages/share.vue:
 | 
			
		||||
  share-with: "{}で共有"
 | 
			
		||||
desktop/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへん。"
 | 
			
		||||
desktop/views/pages/user-list.users.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  add-user: "ユーザーを追加"
 | 
			
		||||
  add-user: "ユーザー増やす"
 | 
			
		||||
  username: "ユーザー名"
 | 
			
		||||
desktop/views/pages/user/user.followers-you-know.vue:
 | 
			
		||||
  title: "知り合いのフォロワー"
 | 
			
		||||
  loading: "読み込み中"
 | 
			
		||||
  no-users: "知り合いのフォロワーはいません"
 | 
			
		||||
  title: "知っとるフォロワー"
 | 
			
		||||
  loading: "読み込んどる…"
 | 
			
		||||
  no-users: "フォロワー全員知らんわ"
 | 
			
		||||
desktop/views/pages/user/user.friends.vue:
 | 
			
		||||
  title: "よく話すユーザー"
 | 
			
		||||
  loading: "読み込み中"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
  title: "よう話すツレ"
 | 
			
		||||
  loading: "読み込んどる…"
 | 
			
		||||
  no-users: "よう話すツレは居らん"
 | 
			
		||||
desktop/views/pages/user/user.vue:
 | 
			
		||||
  is-suspended: "このユーザーは凍結されています。"
 | 
			
		||||
  is-remote: "このユーザーはリモートユーザーです。"
 | 
			
		||||
  view-remote: "正確な情報を見る"
 | 
			
		||||
  is-suspended: "このユーザーはあかんわ。凍結されとる。"
 | 
			
		||||
  is-remote: "このユーザーはリモートユーザーや。"
 | 
			
		||||
  view-remote: "ちゃんとした情報を見る"
 | 
			
		||||
desktop/views/pages/user/user.home.vue:
 | 
			
		||||
  last-used-at: "最終アクセス"
 | 
			
		||||
  last-used-at: "最後いつ来た?"
 | 
			
		||||
desktop/views/pages/user/user.photos.vue:
 | 
			
		||||
  title: "写真"
 | 
			
		||||
  loading: "読み込み中"
 | 
			
		||||
  loading: "読み込んどる…"
 | 
			
		||||
  no-photos: "写真はあらへんで"
 | 
			
		||||
desktop/views/pages/user/user.profile.vue:
 | 
			
		||||
  follows-you: "フォローされとるで"
 | 
			
		||||
@@ -972,12 +974,12 @@ desktop/views/pages/user/user.header.vue:
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  is-bot: "このアカウントはBotです"
 | 
			
		||||
  is-bot: "このアカウントはBotや"
 | 
			
		||||
desktop/views/pages/user/user.timeline.vue:
 | 
			
		||||
  default: "投稿"
 | 
			
		||||
  with-replies: "投稿と返信"
 | 
			
		||||
  with-media: "メディア"
 | 
			
		||||
  empty: "このユーザーはまだ何も投稿していないようです。"
 | 
			
		||||
  empty: "このユーザーはまだ何も投稿しとらんようや。"
 | 
			
		||||
desktop/views/widgets/messaging.vue:
 | 
			
		||||
  title: "メッセージ"
 | 
			
		||||
desktop/views/widgets/notifications.vue:
 | 
			
		||||
@@ -991,76 +993,76 @@ desktop/views/widgets/post-form.vue:
 | 
			
		||||
  title: "投稿"
 | 
			
		||||
  note: "投稿"
 | 
			
		||||
desktop/views/widgets/profile.vue:
 | 
			
		||||
  update-banner: "クリックでバナー編集"
 | 
			
		||||
  update-avatar: "クリックでアバター編集"
 | 
			
		||||
  update-banner: "クリックしてバナー編集"
 | 
			
		||||
  update-avatar: "クリックしてアバター編集"
 | 
			
		||||
desktop/views/widgets/trends.vue:
 | 
			
		||||
  title: "トレンド"
 | 
			
		||||
  title: "流行"
 | 
			
		||||
  refresh: "他を見る"
 | 
			
		||||
  nothing: "ありません!"
 | 
			
		||||
  nothing: "あらへん!"
 | 
			
		||||
desktop/views/widgets/users.vue:
 | 
			
		||||
  title: "おすすめユーザー"
 | 
			
		||||
  refresh: "他を見る"
 | 
			
		||||
  no-one: "いません!"
 | 
			
		||||
  no-one: "おらん!"
 | 
			
		||||
mobile/views/components/drive.vue:
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  used: "使用中"
 | 
			
		||||
  used: "使うとる"
 | 
			
		||||
  folder-count: "フォルダ"
 | 
			
		||||
  count-separator: "、"
 | 
			
		||||
  file-count: "ファイル"
 | 
			
		||||
  load-more: "もっと読み込む"
 | 
			
		||||
  load-more: "もっとあらへんのか!"
 | 
			
		||||
  nothing-in-drive: "ドライブには何もあらへんで。"
 | 
			
		||||
  folder-is-empty: "このフォルダは空です"
 | 
			
		||||
  prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
 | 
			
		||||
  deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
 | 
			
		||||
  folder-is-empty: "このフォルダ何もないわ"
 | 
			
		||||
  prompt: "何すんの?(数字を入れてや): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
 | 
			
		||||
  deletion-alert: "フォルダの削除は未実装やねん...。堪忍な!"
 | 
			
		||||
  folder-name: "フォルダー名"
 | 
			
		||||
  root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
 | 
			
		||||
  root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
 | 
			
		||||
  url-prompt: "アップロードしたいファイルのURL"
 | 
			
		||||
  uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
 | 
			
		||||
  root-rename-alert: "現在おる場所はルートで、フォルダとちゃうから名前の変更はできへん。名前を変更したいフォルダに移動してからやってな。"
 | 
			
		||||
  root-move-alert: "現在おる場所はルートで、フォルダとちゃうから移動はできへん。移動したいフォルダに移動してからやってな。"
 | 
			
		||||
  url-prompt: "このURLのファイルをアップロードしたいねん"
 | 
			
		||||
  uploading: "アップロードをリクエストしたで。アップロードが完了するまで時間がかかるかも分からん、知らんけど。"
 | 
			
		||||
mobile/views/components/drive-file-detail.vue:
 | 
			
		||||
  rename: "名前を変更"
 | 
			
		||||
  rename: "名前を変えるで"
 | 
			
		||||
mobile/views/components/drive-file-chooser.vue:
 | 
			
		||||
  select-file: "ファイルを選択"
 | 
			
		||||
  select-file: "ファイル選んでや"
 | 
			
		||||
mobile/views/components/drive-folder-chooser.vue:
 | 
			
		||||
  select-folder: "フォルダーを選択"
 | 
			
		||||
  select-folder: "フォルダ選んでや"
 | 
			
		||||
mobile/views/components/drive.file.vue:
 | 
			
		||||
  nsfw: "閲覧注意"
 | 
			
		||||
  nsfw: "ちょっと見せられへんわ"
 | 
			
		||||
mobile/views/components/drive.file-detail.vue:
 | 
			
		||||
  download: "ダウンロード"
 | 
			
		||||
  rename: "名前を変更"
 | 
			
		||||
  rename: "名前を変えるで"
 | 
			
		||||
  move: "移動"
 | 
			
		||||
  hash: "ハッシュ (md5)"
 | 
			
		||||
  hash: "ハッシュ(md5)"
 | 
			
		||||
  exif: "EXIF"
 | 
			
		||||
  nsfw: "閲覧注意"
 | 
			
		||||
  nsfw: "ちょっと見せられへんわ"
 | 
			
		||||
mobile/views/components/media-image.vue:
 | 
			
		||||
  sensitive: "閲覧注意"
 | 
			
		||||
  click-to-show: "クリックして表示"
 | 
			
		||||
  sensitive: "見たらあかんで"
 | 
			
		||||
  click-to-show: "押してみ、見せたるわ"
 | 
			
		||||
mobile/views/components/media-video.vue:
 | 
			
		||||
  sensitive: "閲覧注意"
 | 
			
		||||
  click-to-show: "クリックして表示"
 | 
			
		||||
  sensitive: "ちょっと見せられへんわ"
 | 
			
		||||
  click-to-show: "押してみ、見せたるわ"
 | 
			
		||||
mobile/views/components/follow-button.vue:
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  following: "フォローしとる"
 | 
			
		||||
  follow: "フォロー"
 | 
			
		||||
  request-pending: "フォロー許可待ち"
 | 
			
		||||
  follow-request: "フォロー申請"
 | 
			
		||||
  request-pending: "フォローの許し待っとる"
 | 
			
		||||
  follow-request: "フォロー許してくれや!言うてみる"
 | 
			
		||||
mobile/views/components/friends-maker.vue:
 | 
			
		||||
  title: "気になるユーザーをフォロー"
 | 
			
		||||
  empty: "おすすめのユーザーは見つかりませんでした。"
 | 
			
		||||
  fetching: "読み込んでいます"
 | 
			
		||||
  refresh: "もっと見る"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
  title: "おもろそうやな"
 | 
			
		||||
  empty: "おすすめのユーザーはおらん。"
 | 
			
		||||
  fetching: "読みこんどるで…"
 | 
			
		||||
  refresh: "もっとあるやろ!"
 | 
			
		||||
  close: "さいなら"
 | 
			
		||||
mobile/views/components/note.vue:
 | 
			
		||||
  reposted-by: "{}がRenote"
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  location: "位置情報"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
  location: "ここおるで:"
 | 
			
		||||
mobile/views/components/note-detail.vue:
 | 
			
		||||
  reply: "返信"
 | 
			
		||||
  reply: "返す"
 | 
			
		||||
  reaction: "リアクション"
 | 
			
		||||
  reposted-by: "{}がRenote"
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  location: "位置情報"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
  location: "ここおるで:"
 | 
			
		||||
mobile/views/components/note-preview.vue:
 | 
			
		||||
  admin: "admin"
 | 
			
		||||
  bot: "bot"
 | 
			
		||||
@@ -1070,55 +1072,55 @@ mobile/views/components/note-sub.vue:
 | 
			
		||||
  bot: "bot"
 | 
			
		||||
  cat: "cat"
 | 
			
		||||
mobile/views/components/notes.vue:
 | 
			
		||||
  failed: "読み込みに失敗しました。"
 | 
			
		||||
  retry: "リトライ"
 | 
			
		||||
  failed: "あかん、読み込めへんわ"
 | 
			
		||||
  retry: "もっぺん"
 | 
			
		||||
mobile/views/components/notifications.vue:
 | 
			
		||||
  more: "もっと見る"
 | 
			
		||||
  empty: "ありません!"
 | 
			
		||||
  more: "もっとあるやろ!"
 | 
			
		||||
  empty: "あらへん!"
 | 
			
		||||
mobile/views/components/post-form.vue:
 | 
			
		||||
  add-visible-user: "ユーザーを追加"
 | 
			
		||||
  add-visible-user: "ユーザー増やす"
 | 
			
		||||
  submit: "投稿"
 | 
			
		||||
  reply: "返信"
 | 
			
		||||
  reply: "返す"
 | 
			
		||||
  renote: "Renote"
 | 
			
		||||
  quote-placeholder: "この投稿を引用... (オプション)"
 | 
			
		||||
  quote-placeholder: "この投稿を持ってくる(オプション)"
 | 
			
		||||
  reply-placeholder: "この投稿への返信..."
 | 
			
		||||
  cw-placeholder: "内容への注釈 (オプション)"
 | 
			
		||||
  location-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。"
 | 
			
		||||
  error: "エラー"
 | 
			
		||||
  username-prompt: "ユーザー名を入力してや"
 | 
			
		||||
mobile/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
  media-count: "{}つのメディア"
 | 
			
		||||
  poll: "アンケート"
 | 
			
		||||
mobile/views/components/timeline.vue:
 | 
			
		||||
  empty: "投稿がありません"
 | 
			
		||||
  empty: "投稿はあらへん"
 | 
			
		||||
  load-more: "もっと"
 | 
			
		||||
mobile/views/components/ui.header.vue:
 | 
			
		||||
  welcome-back: "おかえりなさい、"
 | 
			
		||||
  adjective: "さん"
 | 
			
		||||
  welcome-back: "おかえり、"
 | 
			
		||||
  adjective: "はん"
 | 
			
		||||
mobile/views/components/ui.nav.vue:
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  messaging: "メッセージ"
 | 
			
		||||
  follow-requests: "フォロー申請"
 | 
			
		||||
  follow-requests: "フォロー許してくれや!言うてみる"
 | 
			
		||||
  search: "検索"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  favorites: "お気に入り"
 | 
			
		||||
  user-lists: "リスト"
 | 
			
		||||
  widgets: "ウィジェット"
 | 
			
		||||
  game: "ゲーム"
 | 
			
		||||
  darkmode: "ダークモード"
 | 
			
		||||
  darkmode: "ナイトゲームや"
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  about: "Misskeyについて"
 | 
			
		||||
  about: "Misskeyってなんや?"
 | 
			
		||||
mobile/views/components/user-timeline.vue:
 | 
			
		||||
  no-notes: "このユーザーは投稿していないようです。"
 | 
			
		||||
  no-notes-with-media: "メディア付き投稿はありません。"
 | 
			
		||||
  no-notes: "このユーザーは投稿しとらんようや。"
 | 
			
		||||
  no-notes-with-media: "メディア付き投稿はあらへん。"
 | 
			
		||||
  load-more: "もっと"
 | 
			
		||||
mobile/views/components/users-list.vue:
 | 
			
		||||
  all: "すべて"
 | 
			
		||||
  known: "知り合い"
 | 
			
		||||
  known: "知っとる"
 | 
			
		||||
  load-more: "もっと"
 | 
			
		||||
mobile/views/pages/favorites.vue:
 | 
			
		||||
  title: "お気に入り"
 | 
			
		||||
@@ -1127,9 +1129,9 @@ mobile/views/pages/user-lists.vue:
 | 
			
		||||
  enter-list-name: "リスト名を入力してや"
 | 
			
		||||
mobile/views/pages/drive.vue:
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  more: "もっと見る"
 | 
			
		||||
  more: "もっとあるやろ!"
 | 
			
		||||
mobile/views/pages/signup.vue:
 | 
			
		||||
  lets-start: "📦 始めましょう"
 | 
			
		||||
  lets-start: "📦 始めようや"
 | 
			
		||||
mobile/views/pages/followers.vue:
 | 
			
		||||
  followers-of: "{}のフォロワー"
 | 
			
		||||
mobile/views/pages/following.vue:
 | 
			
		||||
@@ -1139,35 +1141,36 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  local: "ローカル"
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  mentions: "あんた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへんで。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
  signup: "新規登録"
 | 
			
		||||
mobile/views/pages/widgets.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  widgets-hints: "ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。"
 | 
			
		||||
  add-widget: "追加"
 | 
			
		||||
  widgets-hints: "ウィジェットを追加/削除したり並べ替えたりできんで。ウィジェットを移動するんやったら「三」をドラッグしてや。ウィジェットを削除するんやったら「x」をタップしてや。いくつかのウィジェットはタップしたったら表示を変更できるかも分からん、知らんけど。"
 | 
			
		||||
  add-widget: "増やす"
 | 
			
		||||
  customization-tips: "カスタマイズのヒント"
 | 
			
		||||
mobile/views/pages/widgets/activity.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  activity: "やっとること"
 | 
			
		||||
mobile/views/pages/share.vue:
 | 
			
		||||
  share-with: "{}で共有"
 | 
			
		||||
  share-with: "{}で「わけわけ」"
 | 
			
		||||
mobile/views/pages/messaging.vue:
 | 
			
		||||
  messaging: "メッセージ"
 | 
			
		||||
mobile/views/pages/messaging-room.vue:
 | 
			
		||||
  messaging: "メッセージ"
 | 
			
		||||
mobile/views/pages/received-follow-requests.vue:
 | 
			
		||||
  title: "フォロー申請"
 | 
			
		||||
  accept: "承認"
 | 
			
		||||
  reject: "拒否"
 | 
			
		||||
  title: "フォロー許してくれや!"
 | 
			
		||||
  accept: "許す"
 | 
			
		||||
  reject: "許さん"
 | 
			
		||||
mobile/views/pages/note.vue:
 | 
			
		||||
  title: "投稿"
 | 
			
		||||
  prev: "前の投稿"
 | 
			
		||||
  next: "次の投稿"
 | 
			
		||||
  prev: "前のやつ"
 | 
			
		||||
  next: "次のやつ"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  read-all: "すべての通知を既読にしますか?"
 | 
			
		||||
  read-all: "通知全部読んだか?"
 | 
			
		||||
mobile/views/pages/games/reversi.vue:
 | 
			
		||||
  reversi: "リバーシ"
 | 
			
		||||
mobile/views/pages/settings/settings.profile.vue:
 | 
			
		||||
@@ -1175,12 +1178,12 @@ mobile/views/pages/settings/settings.profile.vue:
 | 
			
		||||
  name: "名前"
 | 
			
		||||
  account: "アカウント"
 | 
			
		||||
  location: "場所"
 | 
			
		||||
  description: "自己紹介"
 | 
			
		||||
  description: "ワイのこと"
 | 
			
		||||
  birthday: "誕生日"
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  is-cat: "このアカウントはCatです"
 | 
			
		||||
  is-locked: "フォローを承認制にする"
 | 
			
		||||
  is-cat: "このアカウントはCatや"
 | 
			
		||||
  is-locked: "他人のフォローは許してからや!"
 | 
			
		||||
  advanced: "その他"
 | 
			
		||||
  privacy: "プライバシー"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "글로벌"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "통지"
 | 
			
		||||
    list: "목록"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "Algemeen"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "Lijsten"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "Globalne"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Powiadomienia"
 | 
			
		||||
    list: "Listy"
 | 
			
		||||
    swap-left: "Przesuń w lewo"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "Społeczność"
 | 
			
		||||
  global: "Globalne"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "Listy"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "Społeczność"
 | 
			
		||||
  global: "Globalne"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "Nie znaleziono wpisów zawierających „{}”."
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "Global"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "Notificações"
 | 
			
		||||
    list: "Listas"
 | 
			
		||||
    swap-left: "Mover para a esquerda"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,7 @@ common:
 | 
			
		||||
    hashtag: "ハッシュタグ"
 | 
			
		||||
    global: "グローバル"
 | 
			
		||||
    mentions: "あなた宛て"
 | 
			
		||||
    direct: "ダイレクト投稿"
 | 
			
		||||
    notifications: "通知"
 | 
			
		||||
    list: "リスト"
 | 
			
		||||
    swap-left: "左に移動"
 | 
			
		||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
  list: "リスト"
 | 
			
		||||
  hashtag: "ハッシュタグ"
 | 
			
		||||
  add-tag-timeline: "ハッシュタグを追加"
 | 
			
		||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  hybrid: "ソーシャル"
 | 
			
		||||
  global: "グローバル"
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
mobile/views/pages/welcome.vue:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "8.45.1",
 | 
			
		||||
	"clientVersion": "1.0.9840",
 | 
			
		||||
	"version": "8.58.0",
 | 
			
		||||
	"clientVersion": "1.0.9945",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
		"@koa/cors": "2.2.2",
 | 
			
		||||
		"@prezzemolo/rap": "0.1.2",
 | 
			
		||||
		"@prezzemolo/zip": "0.0.3",
 | 
			
		||||
		"@types/bcryptjs": "2.4.1",
 | 
			
		||||
		"@types/bcryptjs": "2.4.2",
 | 
			
		||||
		"@types/dateformat": "1.0.1",
 | 
			
		||||
		"@types/debug": "0.0.30",
 | 
			
		||||
		"@types/deep-equal": "1.0.1",
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
		"@types/koa-logger": "3.1.0",
 | 
			
		||||
		"@types/koa-mount": "3.0.1",
 | 
			
		||||
		"@types/koa-multer": "1.0.0",
 | 
			
		||||
		"@types/koa-router": "7.0.31",
 | 
			
		||||
		"@types/koa-router": "7.0.32",
 | 
			
		||||
		"@types/koa-send": "4.1.1",
 | 
			
		||||
		"@types/koa-views": "2.0.3",
 | 
			
		||||
		"@types/koa__cors": "2.2.3",
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
		"@types/mocha": "5.2.3",
 | 
			
		||||
		"@types/mongodb": "3.1.7",
 | 
			
		||||
		"@types/ms": "0.7.30",
 | 
			
		||||
		"@types/node": "10.9.4",
 | 
			
		||||
		"@types/node": "10.10.3",
 | 
			
		||||
		"@types/portscanner": "2.1.0",
 | 
			
		||||
		"@types/pug": "2.0.4",
 | 
			
		||||
		"@types/qrcode": "1.2.0",
 | 
			
		||||
@@ -77,7 +77,7 @@
 | 
			
		||||
		"@types/systeminformation": "3.23.0",
 | 
			
		||||
		"@types/tmp": "0.0.33",
 | 
			
		||||
		"@types/uuid": "3.4.4",
 | 
			
		||||
		"@types/webpack": "4.4.11",
 | 
			
		||||
		"@types/webpack": "4.4.12",
 | 
			
		||||
		"@types/webpack-stream": "3.2.10",
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
@@ -217,11 +217,11 @@
 | 
			
		||||
		"vuewordcloud": "18.7.11",
 | 
			
		||||
		"vuex": "3.0.1",
 | 
			
		||||
		"vuex-persistedstate": "2.5.4",
 | 
			
		||||
		"web-push": "3.3.2",
 | 
			
		||||
		"web-push": "3.3.3",
 | 
			
		||||
		"webfinger.js": "2.6.6",
 | 
			
		||||
		"webpack": "4.19.0",
 | 
			
		||||
		"webpack": "4.19.1",
 | 
			
		||||
		"webpack-cli": "3.1.0",
 | 
			
		||||
		"websocket": "1.0.26",
 | 
			
		||||
		"websocket": "1.0.28",
 | 
			
		||||
		"ws": "6.0.0",
 | 
			
		||||
		"xev": "2.0.1"
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,24 @@
 | 
			
		||||
<template>
 | 
			
		||||
<router-view id="app"></router-view>
 | 
			
		||||
<router-view id="app" v-hotkey.global="keymap"></router-view>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { url, lang } from './config';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'h|slash': this.help
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		help() {
 | 
			
		||||
			window.open(`${url}/docs/${lang}/keyboard-shortcut`, '_blank');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								src/client/app/common/hotkey.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/client/app/common/hotkey.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
import keyCode from './keycode';
 | 
			
		||||
import { concat } from '../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
type pattern = {
 | 
			
		||||
	which: string[];
 | 
			
		||||
	ctrl?: boolean;
 | 
			
		||||
	shift?: boolean;
 | 
			
		||||
	alt?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type action = {
 | 
			
		||||
	patterns: pattern[];
 | 
			
		||||
 | 
			
		||||
	callback: Function;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
 | 
			
		||||
	const result = {
 | 
			
		||||
		patterns: [],
 | 
			
		||||
		callback: callback
 | 
			
		||||
	} as action;
 | 
			
		||||
 | 
			
		||||
	result.patterns = patterns.split('|').map(part => {
 | 
			
		||||
		const pattern = {
 | 
			
		||||
			which: [],
 | 
			
		||||
			ctrl: false,
 | 
			
		||||
			alt: false,
 | 
			
		||||
			shift: false
 | 
			
		||||
		} as pattern;
 | 
			
		||||
 | 
			
		||||
		part.trim().split('+').forEach(key => {
 | 
			
		||||
			key = key.trim().toLowerCase();
 | 
			
		||||
			switch (key) {
 | 
			
		||||
				case 'ctrl': pattern.ctrl = true; break;
 | 
			
		||||
				case 'alt': pattern.alt = true; break;
 | 
			
		||||
				case 'shift': pattern.shift = true; break;
 | 
			
		||||
				default: pattern.which = keyCode(key).map(k => k.toLowerCase());
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return pattern;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const ignoreElemens = ['input', 'textarea'];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
	install(Vue) {
 | 
			
		||||
		Vue.directive('hotkey', {
 | 
			
		||||
			bind(el, binding) {
 | 
			
		||||
				el._hotkey_global = binding.modifiers.global === true;
 | 
			
		||||
 | 
			
		||||
				const actions = getKeyMap(binding.value);
 | 
			
		||||
 | 
			
		||||
				// flatten
 | 
			
		||||
				const reservedKeys = concat(concat(actions.map(a => a.patterns.map(p => p.which))));
 | 
			
		||||
 | 
			
		||||
				el.dataset.reservedKeys = reservedKeys.map(key => `'${key}'`).join(' ');
 | 
			
		||||
 | 
			
		||||
				el._keyHandler = e => {
 | 
			
		||||
					const key = e.code.toLowerCase();
 | 
			
		||||
 | 
			
		||||
					const targetReservedKeys = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '' : '';
 | 
			
		||||
					if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
 | 
			
		||||
 | 
			
		||||
					for (const action of actions) {
 | 
			
		||||
						if (el._hotkey_global && targetReservedKeys.includes(`'${key}'`)) break;
 | 
			
		||||
 | 
			
		||||
						const matched = action.patterns.some(pattern => {
 | 
			
		||||
							const matched = pattern.which.includes(key) &&
 | 
			
		||||
								pattern.ctrl == e.ctrlKey &&
 | 
			
		||||
								pattern.shift == e.shiftKey &&
 | 
			
		||||
								pattern.alt == e.altKey;
 | 
			
		||||
 | 
			
		||||
							if (matched) {
 | 
			
		||||
								e.preventDefault();
 | 
			
		||||
								e.stopPropagation();
 | 
			
		||||
								action.callback(e);
 | 
			
		||||
								return true;
 | 
			
		||||
							} else {
 | 
			
		||||
								return false;
 | 
			
		||||
							}
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						if (matched) {
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				if (el._hotkey_global) {
 | 
			
		||||
					document.addEventListener('keydown', el._keyHandler);
 | 
			
		||||
				} else {
 | 
			
		||||
					el.addEventListener('keydown', el._keyHandler);
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			unbind(el) {
 | 
			
		||||
				if (el._hotkey_global) {
 | 
			
		||||
					document.removeEventListener('keydown', el._keyHandler);
 | 
			
		||||
				} else {
 | 
			
		||||
					el.removeEventListener('keydown', el._keyHandler);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								src/client/app/common/keycode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/client/app/common/keycode.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
export default (input: string): string[] => {
 | 
			
		||||
	if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
 | 
			
		||||
		const codes = aliases[input];
 | 
			
		||||
		return Array.isArray(codes) ? codes : [codes];
 | 
			
		||||
	} else {
 | 
			
		||||
		return [input];
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const aliases = {
 | 
			
		||||
	'esc': 'Escape',
 | 
			
		||||
	'enter': ['Enter', 'NumpadEnter'],
 | 
			
		||||
	'up': 'ArrowUp',
 | 
			
		||||
	'down': 'ArrowDown',
 | 
			
		||||
	'left': 'ArrowLeft',
 | 
			
		||||
	'right': 'ArrowRight',
 | 
			
		||||
	'plus': ['NumpadAdd', 'Semicolon'],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
* Programatically add the following
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// lower case chars
 | 
			
		||||
for (let i = 97; i < 123; i++) {
 | 
			
		||||
	const char = String.fromCharCode(i);
 | 
			
		||||
	aliases[char] = `Key${char.toUpperCase()}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// numbers
 | 
			
		||||
for (let i = 0; i < 10; i++) {
 | 
			
		||||
	aliases[i] = [`Numpad${i}`, `Digit${i}`];
 | 
			
		||||
}
 | 
			
		||||
@@ -50,6 +50,30 @@ export class HomeStream extends Stream {
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('unreadMention', () => {
 | 
			
		||||
			os.store.dispatch('mergeMe', {
 | 
			
		||||
				hasUnreadMentions: true
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('readAllUnreadMentions', () => {
 | 
			
		||||
			os.store.dispatch('mergeMe', {
 | 
			
		||||
				hasUnreadMentions: false
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('unreadSpecifiedNote', () => {
 | 
			
		||||
			os.store.dispatch('mergeMe', {
 | 
			
		||||
				hasUnreadSpecifiedNotes: true
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('readAllUnreadSpecifiedNotes', () => {
 | 
			
		||||
			os.store.dispatch('mergeMe', {
 | 
			
		||||
				hasUnreadSpecifiedNotes: false
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.on('clientSettingUpdated', x => {
 | 
			
		||||
			os.store.commit('settings/set', {
 | 
			
		||||
				key: x.key,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@
 | 
			
		||||
<div class="onchrpzrvnoruiaenfcqvccjfuupzzwv">
 | 
			
		||||
	<div class="backdrop" ref="backdrop" @click="close"></div>
 | 
			
		||||
	<div class="popover" :class="{ hukidasi }" ref="popover">
 | 
			
		||||
		<template v-for="item in items">
 | 
			
		||||
		<template v-for="item, i in items">
 | 
			
		||||
			<div v-if="item === null"></div>
 | 
			
		||||
			<button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text"></button>
 | 
			
		||||
			<button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text" :tabindex="i"></button>
 | 
			
		||||
		</template>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,12 +33,16 @@ export default Vue.extend({
 | 
			
		||||
					text: '%i18n:@pin%',
 | 
			
		||||
					action: this.pin
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) {
 | 
			
		||||
				items.push({
 | 
			
		||||
					icon: '%fa:trash-alt R%',
 | 
			
		||||
					text: '%i18n:@delete%',
 | 
			
		||||
					action: this.del
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (this.note.uri) {
 | 
			
		||||
				items.push({
 | 
			
		||||
					icon: '%fa:external-link-square-alt%',
 | 
			
		||||
@@ -48,6 +52,7 @@ export default Vue.extend({
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return items;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-reaction-picker">
 | 
			
		||||
<div class="mk-reaction-picker" v-hotkey.global="keymap">
 | 
			
		||||
	<div class="backdrop" ref="backdrop" @click="close"></div>
 | 
			
		||||
	<div class="popover" :class="{ compact, big }" ref="popover">
 | 
			
		||||
		<p v-if="!compact">{{ title }}</p>
 | 
			
		||||
		<div>
 | 
			
		||||
		<div ref="buttons" :class="{ showFocus }">
 | 
			
		||||
			<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
 | 
			
		||||
			<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
 | 
			
		||||
			<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
 | 
			
		||||
@@ -31,30 +31,84 @@ export default Vue.extend({
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		source: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		compact: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		cb: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		big: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		showFocus: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		animation: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			title: placeholder
 | 
			
		||||
			title: placeholder,
 | 
			
		||||
			focus: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'esc': this.close,
 | 
			
		||||
				'enter|space|plus': this.choose,
 | 
			
		||||
				'up|k': this.focusUp,
 | 
			
		||||
				'left|h|shift+tab': this.focusLeft,
 | 
			
		||||
				'right|l|tab': this.focusRight,
 | 
			
		||||
				'down|j': this.focusDown,
 | 
			
		||||
				'1': () => this.react('like'),
 | 
			
		||||
				'2': () => this.react('love'),
 | 
			
		||||
				'3': () => this.react('laugh'),
 | 
			
		||||
				'4': () => this.react('hmm'),
 | 
			
		||||
				'5': () => this.react('surprise'),
 | 
			
		||||
				'6': () => this.react('congrats'),
 | 
			
		||||
				'7': () => this.react('angry'),
 | 
			
		||||
				'8': () => this.react('confused'),
 | 
			
		||||
				'9': () => this.react('rip'),
 | 
			
		||||
				'0': () => this.react('pudding'),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		focus(i) {
 | 
			
		||||
			this.$refs.buttons.children[i].focus();
 | 
			
		||||
 | 
			
		||||
			if (this.showFocus) {
 | 
			
		||||
				this.title = this.$refs.buttons.children[i].title;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			this.focus = 0;
 | 
			
		||||
 | 
			
		||||
			const popover = this.$refs.popover as any;
 | 
			
		||||
 | 
			
		||||
			const rect = this.source.getBoundingClientRect();
 | 
			
		||||
@@ -76,7 +130,7 @@ export default Vue.extend({
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.backdrop,
 | 
			
		||||
				opacity: 1,
 | 
			
		||||
				duration: 100,
 | 
			
		||||
				duration: this.animation ? 100 : 0,
 | 
			
		||||
				easing: 'linear'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
@@ -84,10 +138,11 @@ export default Vue.extend({
 | 
			
		||||
				targets: this.$refs.popover,
 | 
			
		||||
				opacity: 1,
 | 
			
		||||
				scale: [0.5, 1],
 | 
			
		||||
				duration: 500
 | 
			
		||||
				duration: this.animation ? 500 : 0
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		react(reaction) {
 | 
			
		||||
			(this as any).api('notes/reactions/create', {
 | 
			
		||||
@@ -95,21 +150,25 @@ export default Vue.extend({
 | 
			
		||||
				reaction: reaction
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				if (this.cb) this.cb();
 | 
			
		||||
				this.$emit('closed');
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onMouseover(e) {
 | 
			
		||||
			this.title = e.target.title;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onMouseout(e) {
 | 
			
		||||
			this.title = placeholder;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		close() {
 | 
			
		||||
			(this.$refs.backdrop as any).style.pointerEvents = 'none';
 | 
			
		||||
			anime({
 | 
			
		||||
				targets: this.$refs.backdrop,
 | 
			
		||||
				opacity: 0,
 | 
			
		||||
				duration: 200,
 | 
			
		||||
				duration: this.animation ? 200 : 0,
 | 
			
		||||
				easing: 'linear'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
@@ -118,10 +177,33 @@ export default Vue.extend({
 | 
			
		||||
				targets: this.$refs.popover,
 | 
			
		||||
				opacity: 0,
 | 
			
		||||
				scale: 0.5,
 | 
			
		||||
				duration: 200,
 | 
			
		||||
				duration: this.animation ? 200 : 0,
 | 
			
		||||
				easing: 'easeInBack',
 | 
			
		||||
				complete: () => this.destroyDom()
 | 
			
		||||
				complete: () => {
 | 
			
		||||
					this.$emit('closed');
 | 
			
		||||
					this.destroyDom();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focusUp() {
 | 
			
		||||
			this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focusDown() {
 | 
			
		||||
			this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focusRight() {
 | 
			
		||||
			this.focus = this.focus == 9 ? 0 : (this.focus + 1);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focusLeft() {
 | 
			
		||||
			this.focus = this.focus == 0 ? 9 : (this.focus - 1);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		choose() {
 | 
			
		||||
			this.$refs.buttons.childNodes[this.focus].click();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -207,6 +289,21 @@ root(isDark)
 | 
			
		||||
			width 240px
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			&.showFocus
 | 
			
		||||
				> button:focus
 | 
			
		||||
					z-index 1
 | 
			
		||||
 | 
			
		||||
					&:after
 | 
			
		||||
						content ""
 | 
			
		||||
						pointer-events none
 | 
			
		||||
						position absolute
 | 
			
		||||
						top 0
 | 
			
		||||
						right 0
 | 
			
		||||
						bottom 0
 | 
			
		||||
						left 0
 | 
			
		||||
						border 2px solid rgba($theme-color, 0.3)
 | 
			
		||||
						border-radius 4px
 | 
			
		||||
 | 
			
		||||
			> button
 | 
			
		||||
				padding 0
 | 
			
		||||
				width 40px
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi"
 | 
			
		||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi">
 | 
			
		||||
	<mk-widget-container :show-header="false" :naked="props.design == 1">
 | 
			
		||||
		<div class="anltbovirfeutcigvwgmgxipejaeozxi-body"
 | 
			
		||||
			:data-found="announcements && announcements.length != 0"
 | 
			
		||||
			:data-melt="props.design == 1"
 | 
			
		||||
			:data-mobile="platform == 'mobile'"
 | 
			
		||||
>
 | 
			
		||||
			:data-darkmode="$store.state.device.darkmode"
 | 
			
		||||
		>
 | 
			
		||||
			<div class="icon">
 | 
			
		||||
				<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">
 | 
			
		||||
					<path class="tower" d="M16.04,11.24c1.79,0,3.239-1.45,3.239-3.24S17.83,4.76,16.04,4.76c-1.79,0-3.24,1.45-3.24,3.24 C12.78,9.78,14.24,11.24,16.04,11.24z M16.04,13.84c-0.82,0-1.66-0.2-2.4-0.6L7.34,29.98h2.98l1.72-2h8l1.681,2H24.7L18.42,13.24 C17.66,13.64,16.859,13.84,16.04,13.84z M16.02,14.8l2.02,7.2h-4L16.02,14.8z M12.04,25.98l2-2h4l2,2H12.04z"></path>
 | 
			
		||||
@@ -20,6 +23,8 @@
 | 
			
		||||
				<template v-if="announcements.length == 0">%i18n:@have-a-nice-day%</template>
 | 
			
		||||
			</p>
 | 
			
		||||
			<a v-if="announcements.length > 1" @click="next">%i18n:@next% >></a>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -68,11 +73,10 @@ export default define({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	padding 10px
 | 
			
		||||
	border solid 1px #4078c0
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	background isDark ? #253a50 : #f3f9ff
 | 
			
		||||
 | 
			
		||||
	&[data-melt]
 | 
			
		||||
		border none
 | 
			
		||||
		background transparent
 | 
			
		||||
 | 
			
		||||
	&[data-found]
 | 
			
		||||
		padding-left 50px
 | 
			
		||||
@@ -133,7 +137,7 @@ root(isDark)
 | 
			
		||||
		z-index 1
 | 
			
		||||
		margin 0
 | 
			
		||||
		font-size 0.7em
 | 
			
		||||
		color isDark ? #fff : #555
 | 
			
		||||
		color isDark ? #fff : #57616f
 | 
			
		||||
 | 
			
		||||
		&.fetching
 | 
			
		||||
			text-align center
 | 
			
		||||
@@ -146,10 +150,10 @@ root(isDark)
 | 
			
		||||
		> p
 | 
			
		||||
			color #fff
 | 
			
		||||
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi[data-darkmode]
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi-body[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi:not([data-darkmode])
 | 
			
		||||
.anltbovirfeutcigvwgmgxipejaeozxi-body:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -133,8 +133,8 @@ export default Vue.extend({
 | 
			
		||||
root(isDark)
 | 
			
		||||
	color isDark ? #c5ced6 : #777
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	&[data-melt]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header">
 | 
			
		||||
		<span v-html="title" :class="$style.title"></span>
 | 
			
		||||
		<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header">
 | 
			
		||||
		<span v-html="title" :class="$style.title"></span>
 | 
			
		||||
	</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" @closed="$destroy" width="800px" height="500px" :popout-url="popout">
 | 
			
		||||
<mk-window ref="window" @closed="destroyDom" width="800px" height="500px" :popout-url="popout">
 | 
			
		||||
	<template slot="header">
 | 
			
		||||
		<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p>
 | 
			
		||||
		<span :class="$style.title">%fa:cloud%%i18n:@drive%</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window width="400px" height="550px" @closed="$destroy">
 | 
			
		||||
<mk-window width="400px" height="550px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">
 | 
			
		||||
		<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
 | 
			
		||||
	</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window width="400px" height="550px" @closed="$destroy">
 | 
			
		||||
<mk-window width="400px" height="550px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">
 | 
			
		||||
		<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
 | 
			
		||||
	</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
 | 
			
		||||
	<mk-reversi :class="$style.content" @gamed="g => game = g"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
 
 | 
			
		||||
@@ -237,6 +237,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		warp(date) {
 | 
			
		||||
			(this.$refs.tl as any).warp(date);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$refs.tl as any).focus();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -336,7 +340,7 @@ root(isDark)
 | 
			
		||||
		display flex
 | 
			
		||||
		justify-content center
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		max-width 1220px
 | 
			
		||||
		max-width 1240px
 | 
			
		||||
 | 
			
		||||
		> *
 | 
			
		||||
			.customize-container
 | 
			
		||||
@@ -351,7 +355,7 @@ root(isDark)
 | 
			
		||||
 | 
			
		||||
		> .main
 | 
			
		||||
			padding 16px
 | 
			
		||||
			width calc(100% - 275px * 2)
 | 
			
		||||
			width calc(100% - 280px * 2)
 | 
			
		||||
			order 2
 | 
			
		||||
 | 
			
		||||
			> .form
 | 
			
		||||
@@ -367,7 +371,7 @@ root(isDark)
 | 
			
		||||
					border-radius 0
 | 
			
		||||
 | 
			
		||||
		> *:not(.main)
 | 
			
		||||
			width 275px
 | 
			
		||||
			width 280px
 | 
			
		||||
			padding 16px 0 16px 0
 | 
			
		||||
 | 
			
		||||
			> *:not(:last-child)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">
 | 
			
		||||
		%fa:i-cursor%{{ title }}
 | 
			
		||||
	</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
 | 
			
		||||
	<mk-messaging-room :user="user" :class="$style.content"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:comments%%i18n:@title%</span>
 | 
			
		||||
	<mk-messaging :class="$style.content" @navigate="navigate"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
 
 | 
			
		||||
@@ -231,8 +231,8 @@ root(isDark)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	text-align left
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.1)
 | 
			
		||||
	border-radius 8px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
 | 
			
		||||
	> .read-more
 | 
			
		||||
		display block
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="note" tabindex="-1" :title="title" @keydown="onKeydown">
 | 
			
		||||
<div class="note" tabindex="-1" v-hotkey="keymap" :title="title">
 | 
			
		||||
	<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
 | 
			
		||||
		<x-sub :note="p.reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -40,18 +40,18 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<footer>
 | 
			
		||||
				<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
 | 
			
		||||
				<button class="replyButton" @click="reply" title="%i18n:@reply%">
 | 
			
		||||
				<button class="replyButton" @click="reply()" title="%i18n:@reply%">
 | 
			
		||||
					<template v-if="p.reply">%fa:reply-all%</template>
 | 
			
		||||
					<template v-else>%fa:reply%</template>
 | 
			
		||||
					<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button class="renoteButton" @click="renote" title="%i18n:@renote%">
 | 
			
		||||
				<button class="renoteButton" @click="renote()" title="%i18n:@renote%">
 | 
			
		||||
					%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:@add-reaction%">
 | 
			
		||||
				<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
 | 
			
		||||
					%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
 | 
			
		||||
				</button>
 | 
			
		||||
				<button @click="menu" ref="menuButton">
 | 
			
		||||
				<button @click="menu()" ref="menuButton">
 | 
			
		||||
					%fa:ellipsis-h%
 | 
			
		||||
				</button>
 | 
			
		||||
				<!-- <button title="%i18n:@detail">
 | 
			
		||||
@@ -111,6 +111,30 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'r|left': () => this.reply(true),
 | 
			
		||||
				'e|a|plus': () => this.react(true),
 | 
			
		||||
				'q|right': () => this.renote(true),
 | 
			
		||||
				'ctrl+q|ctrl+right': this.renoteDirectly,
 | 
			
		||||
				'up|k|shift+tab': this.focusBefore,
 | 
			
		||||
				'down|j|tab': this.focusAfter,
 | 
			
		||||
				'esc': this.blur,
 | 
			
		||||
				'm|o': () => this.menu(true),
 | 
			
		||||
				's': this.toggleShowContent,
 | 
			
		||||
				'1': () => this.reactDirectly('like'),
 | 
			
		||||
				'2': () => this.reactDirectly('love'),
 | 
			
		||||
				'3': () => this.reactDirectly('laugh'),
 | 
			
		||||
				'4': () => this.reactDirectly('hmm'),
 | 
			
		||||
				'5': () => this.reactDirectly('surprise'),
 | 
			
		||||
				'6': () => this.reactDirectly('congrats'),
 | 
			
		||||
				'7': () => this.reactDirectly('angry'),
 | 
			
		||||
				'8': () => this.reactDirectly('confused'),
 | 
			
		||||
				'9': () => this.reactDirectly('rip'),
 | 
			
		||||
				'0': () => this.reactDirectly('pudding'),
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		isRenote(): boolean {
 | 
			
		||||
			return (this.note.renote &&
 | 
			
		||||
				this.note.text == null &&
 | 
			
		||||
@@ -189,10 +213,14 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		capture(withHandler = false) {
 | 
			
		||||
			if (this.$store.getters.isSignedIn) {
 | 
			
		||||
				this.connection.send({
 | 
			
		||||
				const data = {
 | 
			
		||||
					type: 'capture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				} as any;
 | 
			
		||||
				if ((this.p.visibleUserIds || []).includes(this.$store.state.i.id) || (this.p.mentions || []).includes(this.$store.state.i.id)) {
 | 
			
		||||
					data.read = true;
 | 
			
		||||
				}
 | 
			
		||||
				this.connection.send(data);
 | 
			
		||||
				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
@@ -220,67 +248,69 @@ export default Vue.extend({
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		reply() {
 | 
			
		||||
		reply(viaKeyboard = false) {
 | 
			
		||||
			(this as any).os.new(MkPostFormWindow, {
 | 
			
		||||
				reply: this.p
 | 
			
		||||
			});
 | 
			
		||||
				reply: this.p,
 | 
			
		||||
				animation: !viaKeyboard
 | 
			
		||||
			}).$once('closed', this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renote() {
 | 
			
		||||
		renote(viaKeyboard = false) {
 | 
			
		||||
			(this as any).os.new(MkRenoteFormWindow, {
 | 
			
		||||
				note: this.p
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				animation: !viaKeyboard
 | 
			
		||||
			}).$once('closed', this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renoteDirectly() {
 | 
			
		||||
			(this as any).api('notes/create', {
 | 
			
		||||
				renoteId: this.p.id
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		react() {
 | 
			
		||||
		react(viaKeyboard = false) {
 | 
			
		||||
			this.blur();
 | 
			
		||||
			(this as any).os.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
				note: this.p
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				showFocus: viaKeyboard,
 | 
			
		||||
				animation: !viaKeyboard
 | 
			
		||||
			}).$once('closed', this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		reactDirectly(reaction) {
 | 
			
		||||
			(this as any).api('notes/reactions/create', {
 | 
			
		||||
				noteId: this.p.id,
 | 
			
		||||
				reaction: reaction
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		menu() {
 | 
			
		||||
		menu(viaKeyboard = false) {
 | 
			
		||||
			(this as any).os.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				note: this.p
 | 
			
		||||
			});
 | 
			
		||||
				note: this.p,
 | 
			
		||||
				animation: !viaKeyboard
 | 
			
		||||
			}).$once('closed', this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onKeydown(e) {
 | 
			
		||||
			let shouldBeCancel = true;
 | 
			
		||||
		toggleShowContent() {
 | 
			
		||||
			this.showContent = !this.showContent;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
			switch (true) {
 | 
			
		||||
				case e.which == 38: // [↑]
 | 
			
		||||
				case e.which == 74: // [j]
 | 
			
		||||
				case e.which == 9 && e.shiftKey: // [Shift] + [Tab]
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$el.focus();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		blur() {
 | 
			
		||||
			this.$el.blur();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focusBefore() {
 | 
			
		||||
			focus(this.$el, e => e.previousElementSibling);
 | 
			
		||||
					break;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
				case e.which == 40: // [↓]
 | 
			
		||||
				case e.which == 75: // [k]
 | 
			
		||||
				case e.which == 9: // [Tab]
 | 
			
		||||
		focusAfter() {
 | 
			
		||||
			focus(this.$el, e => e.nextElementSibling);
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 81: // [q]
 | 
			
		||||
				case e.which == 69: // [e]
 | 
			
		||||
					this.renote();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 70: // [f]
 | 
			
		||||
				case e.which == 76: // [l]
 | 
			
		||||
					//this.like();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case e.which == 82: // [r]
 | 
			
		||||
					this.reply();
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				default:
 | 
			
		||||
					shouldBeCancel = false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (shouldBeCancel) e.preventDefault();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,9 @@
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
			
		||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
 | 
			
		||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
 | 
			
		||||
		<template v-for="(note, i) in _notes">
 | 
			
		||||
			<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
 | 
			
		||||
			<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" ref="note"/>
 | 
			
		||||
			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
			
		||||
				<span>%fa:angle-up%{{ note._datetext }}</span>
 | 
			
		||||
				<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
 | 
			
		||||
@@ -89,7 +89,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$el as any).children[0].focus();
 | 
			
		||||
			(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNoteUpdated(i, note) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window class="mk-post-form-window" ref="window" is-modal @closed="$destroy">
 | 
			
		||||
<mk-window class="mk-post-form-window" ref="window" is-modal @closed="onWindowClosed" :animation="animation">
 | 
			
		||||
	<span slot="header" class="mk-post-form-window--header">
 | 
			
		||||
		<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
 | 
			
		||||
		<span v-if="!reply">%i18n:@note%</span>
 | 
			
		||||
@@ -25,7 +25,19 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['reply'],
 | 
			
		||||
	props: {
 | 
			
		||||
		reply: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		animation: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			uploadings: [],
 | 
			
		||||
@@ -33,11 +45,13 @@ export default Vue.extend({
 | 
			
		||||
			geo: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			(this.$refs.form as any).focus();
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onChangeUploadings(files) {
 | 
			
		||||
			this.uploadings = files;
 | 
			
		||||
@@ -53,6 +67,10 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
		onPosted() {
 | 
			
		||||
			(this.$refs.window as any).close();
 | 
			
		||||
		},
 | 
			
		||||
		onWindowClosed() {
 | 
			
		||||
			this.$emit('closed');
 | 
			
		||||
			this.destroyDom();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header">{{ title }}<mk-ellipsis/></span>
 | 
			
		||||
	<div :class="$style.body">
 | 
			
		||||
		<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header">%fa:envelope R% %i18n:@title%</span>
 | 
			
		||||
 | 
			
		||||
	<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal @closed="onWindowClosed" :animation="animation">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:retweet%%i18n:@title%</span>
 | 
			
		||||
	<mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/>
 | 
			
		||||
	<mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled" v-hotkey.global="keymap"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -9,26 +9,48 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.addEventListener('keydown', this.onDocumentKeydown);
 | 
			
		||||
	props: {
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		document.removeEventListener('keydown', this.onDocumentKeydown);
 | 
			
		||||
 | 
			
		||||
		animation: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'esc': this.close,
 | 
			
		||||
				'enter': this.post,
 | 
			
		||||
				'q': this.quote,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onDocumentKeydown(e) {
 | 
			
		||||
			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
 | 
			
		||||
				if (e.which == 27) { // Esc
 | 
			
		||||
		post() {
 | 
			
		||||
			(this.$refs.form as any).ok();
 | 
			
		||||
		},
 | 
			
		||||
		quote() {
 | 
			
		||||
			(this.$refs.form as any).onQuote();
 | 
			
		||||
		},
 | 
			
		||||
		close() {
 | 
			
		||||
			(this.$refs.window as any).close();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onPosted() {
 | 
			
		||||
			(this.$refs.window as any).close();
 | 
			
		||||
		},
 | 
			
		||||
		onCanceled() {
 | 
			
		||||
			(this.$refs.window as any).close();
 | 
			
		||||
		},
 | 
			
		||||
		onWindowClosed() {
 | 
			
		||||
			this.$emit('closed');
 | 
			
		||||
			this.destroyDom();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
 | 
			
		||||
	<mk-settings :initial-page="initialPage" @done="close"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,8 @@
 | 
			
		||||
				<button class="ui" @click="updateWallpaper">%i18n:@choose-wallpaper%</button>
 | 
			
		||||
				<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
 | 
			
		||||
				<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
 | 
			
		||||
				<mk-switch v-model="useShadow" text="%i18n:@use-shadow%"/>
 | 
			
		||||
				<mk-switch v-model="roundedCorners" text="%i18n:@rounded-corners%"/>
 | 
			
		||||
				<mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/>
 | 
			
		||||
				<mk-switch v-model="reduceMotion" text="%i18n:common.reduce-motion%"/>
 | 
			
		||||
				<mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/>
 | 
			
		||||
@@ -316,6 +318,16 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		useShadow: {
 | 
			
		||||
			get() { return this.$store.state.settings.useShadow; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'useShadow', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		roundedCorners: {
 | 
			
		||||
			get() { return this.$store.state.settings.roundedCorners; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'roundedCorners', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchOnScroll: {
 | 
			
		||||
			get() { return this.$store.state.settings.fetchOnScroll; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,14 @@ export default Vue.extend({
 | 
			
		||||
			streamManager: null,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			date: null
 | 
			
		||||
			date: null,
 | 
			
		||||
			baseQuery: {
 | 
			
		||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
			
		||||
			},
 | 
			
		||||
			query: {},
 | 
			
		||||
			endpoint: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -47,84 +54,109 @@ export default Vue.extend({
 | 
			
		||||
			return this.$store.state.i.followingCount == 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		endpoint(): string {
 | 
			
		||||
			switch (this.src) {
 | 
			
		||||
				case 'home': return 'notes/timeline';
 | 
			
		||||
				case 'local': return 'notes/local-timeline';
 | 
			
		||||
				case 'hybrid': return 'notes/hybrid-timeline';
 | 
			
		||||
				case 'global': return 'notes/global-timeline';
 | 
			
		||||
				case 'mentions': return 'notes/mentions';
 | 
			
		||||
				case 'tag': return 'notes/search_by_tag';
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		canFetchMore(): boolean {
 | 
			
		||||
			return !this.moreFetching && !this.fetching && this.existMore;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		const prepend = note => {
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (this.src == 'tag') {
 | 
			
		||||
			this.endpoint = 'notes/search_by_tag';
 | 
			
		||||
			this.query = {
 | 
			
		||||
				query: this.tagTl.query
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.connection.close();
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'home') {
 | 
			
		||||
			this.endpoint = 'notes/timeline';
 | 
			
		||||
			const onChangeFollowing = () => {
 | 
			
		||||
				this.fetch();
 | 
			
		||||
			};
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.on('unfollow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.connection.on('follow', onChangeFollowing);
 | 
			
		||||
			this.connection.on('unfollow', onChangeFollowing);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.connection.off('follow', onChangeFollowing);
 | 
			
		||||
				this.connection.off('unfollow', onChangeFollowing);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'local') {
 | 
			
		||||
			this.endpoint = 'notes/local-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.localTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'hybrid') {
 | 
			
		||||
			this.endpoint = 'notes/hybrid-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.hybridTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'global') {
 | 
			
		||||
			this.endpoint = 'notes/global-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.globalTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'mentions') {
 | 
			
		||||
			this.endpoint = 'notes/mentions';
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('mention', this.onNote);
 | 
			
		||||
			this.connection.on('mention', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('mention', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'messages') {
 | 
			
		||||
			this.endpoint = 'notes/mentions';
 | 
			
		||||
			this.query = {
 | 
			
		||||
				visibility: 'specified'
 | 
			
		||||
			};
 | 
			
		||||
			const onNote = note => {
 | 
			
		||||
				if (note.visibility == 'specified') {
 | 
			
		||||
					prepend(note);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('mention', onNote);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('mention', onNote);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		document.addEventListener('keydown', this.onKeydown);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		if (this.src == 'tag') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.connection.close();
 | 
			
		||||
		} else if (this.src == 'home') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.connection.off('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.off('unfollow', this.onChangeFollowing);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'local') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'hybrid') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'global') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'mentions') {
 | 
			
		||||
			this.connection.off('mention', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		document.removeEventListener('keydown', this.onKeydown);
 | 
			
		||||
		this.$emit('beforeDestroy');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
@@ -132,14 +164,10 @@ export default Vue.extend({
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
				(this as any).api(this.endpoint, {
 | 
			
		||||
				(this as any).api(this.endpoint, Object.assign({
 | 
			
		||||
					limit: fetchLimit + 1,
 | 
			
		||||
					untilDate: this.date ? this.date.getTime() : undefined,
 | 
			
		||||
					includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
					includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
					query: this.tagTl ? this.tagTl.query : undefined
 | 
			
		||||
				}).then(notes => {
 | 
			
		||||
					untilDate: this.date ? this.date.getTime() : undefined
 | 
			
		||||
				}, this.baseQuery, this.query)).then(notes => {
 | 
			
		||||
					if (notes.length == fetchLimit + 1) {
 | 
			
		||||
						notes.pop();
 | 
			
		||||
						this.existMore = true;
 | 
			
		||||
@@ -156,14 +184,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
 | 
			
		||||
			const promise = (this as any).api(this.endpoint, {
 | 
			
		||||
			const promise = (this as any).api(this.endpoint, Object.assign({
 | 
			
		||||
				limit: fetchLimit + 1,
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id,
 | 
			
		||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
				query: this.tagTl ? this.tagTl.query : undefined
 | 
			
		||||
			});
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id
 | 
			
		||||
			}, this.baseQuery, this.query));
 | 
			
		||||
 | 
			
		||||
			promise.then(notes => {
 | 
			
		||||
				if (notes.length == fetchLimit + 1) {
 | 
			
		||||
@@ -178,15 +202,6 @@ export default Vue.extend({
 | 
			
		||||
			return promise;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeFollowing() {
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$refs.timeline as any).focus();
 | 
			
		||||
		},
 | 
			
		||||
@@ -194,14 +209,6 @@ export default Vue.extend({
 | 
			
		||||
		warp(date) {
 | 
			
		||||
			this.date = date;
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onKeydown(e) {
 | 
			
		||||
			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
 | 
			
		||||
				if (e.which == 84) { // t
 | 
			
		||||
					this.focus();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,11 @@
 | 
			
		||||
		<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
 | 
			
		||||
		<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
 | 
			
		||||
		<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
 | 
			
		||||
		<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
 | 
			
		||||
		<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl">%fa:hashtag% {{ tagTl.title }}</span>
 | 
			
		||||
		<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
 | 
			
		||||
		<div class="buttons">
 | 
			
		||||
			<button :data-active="src == 'mentions'" @click="src = 'mentions'" title="%i18n:@mentions%">%fa:at%<i class="badge" v-if="$store.state.i.hasUnreadMentions">%fa:circle%</i></button>
 | 
			
		||||
			<button :data-active="src == 'messages'" @click="src = 'messages'" title="%i18n:@messages%">%fa:envelope R%<i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes">%fa:circle%</i></button>
 | 
			
		||||
			<button @click="chooseTag" title="%i18n:@hashtag%" ref="tagButton">%fa:hashtag%</button>
 | 
			
		||||
			<button @click="chooseList" title="%i18n:@list%" ref="listButton">%fa:list%</button>
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -18,6 +19,7 @@
 | 
			
		||||
	<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
 | 
			
		||||
	<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
 | 
			
		||||
	<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
 | 
			
		||||
	<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
 | 
			
		||||
	<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
 | 
			
		||||
	<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -90,6 +92,10 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$refs.tl as any).focus();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		warp(date) {
 | 
			
		||||
			(this.$refs.tl as any).warp(date);
 | 
			
		||||
		},
 | 
			
		||||
@@ -173,14 +179,14 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		padding 0 8px
 | 
			
		||||
		z-index 10
 | 
			
		||||
		background isDark ? #313543 : #fff
 | 
			
		||||
		border-radius 6px 6px 0 0
 | 
			
		||||
		box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08)
 | 
			
		||||
 | 
			
		||||
		> .buttons
 | 
			
		||||
@@ -196,12 +202,33 @@ root(isDark)
 | 
			
		||||
				line-height 42px
 | 
			
		||||
				color isDark ? #9baec8 : #ccc
 | 
			
		||||
 | 
			
		||||
				> .badge
 | 
			
		||||
					position absolute
 | 
			
		||||
					top -4px
 | 
			
		||||
					right 4px
 | 
			
		||||
					font-size 10px
 | 
			
		||||
					color $theme-color
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color isDark ? #b2c1d5 : #aaa
 | 
			
		||||
 | 
			
		||||
				&:active
 | 
			
		||||
					color isDark ? #b2c1d5 : #999
 | 
			
		||||
 | 
			
		||||
				&[data-active]
 | 
			
		||||
					color $theme-color
 | 
			
		||||
					cursor default
 | 
			
		||||
 | 
			
		||||
					&:before
 | 
			
		||||
						content ""
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						bottom 0
 | 
			
		||||
						left 0
 | 
			
		||||
						width 100%
 | 
			
		||||
						height 2px
 | 
			
		||||
						background $theme-color
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			display inline-block
 | 
			
		||||
			padding 0 10px
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="account">
 | 
			
		||||
<div class="account" v-hotkey.global="keymap">
 | 
			
		||||
	<button class="header" :data-active="isOpen" @click="toggle">
 | 
			
		||||
		<span class="username">{{ $store.state.i.username }}<template v-if="!isOpen">%fa:angle-down%</template><template v-if="isOpen">%fa:angle-up%</template></span>
 | 
			
		||||
		<mk-avatar class="avatar" :user="$store.state.i"/>
 | 
			
		||||
@@ -63,6 +63,13 @@ export default Vue.extend({
 | 
			
		||||
			isOpen: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'a|m': this.toggle
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.close();
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="notifications">
 | 
			
		||||
<div class="notifications" v-hotkey.global="keymap">
 | 
			
		||||
	<button :data-active="isOpen" @click="toggle" title="%i18n:@title%">
 | 
			
		||||
		%fa:R bell%<template v-if="hasUnreadNotification">%fa:circle%</template>
 | 
			
		||||
	</button>
 | 
			
		||||
@@ -19,11 +19,19 @@ export default Vue.extend({
 | 
			
		||||
			isOpen: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		hasUnreadNotification(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'shift+n': this.toggle
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggle() {
 | 
			
		||||
			this.isOpen ? this.close() : this.open();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="header">
 | 
			
		||||
<div class="header" :style="style">
 | 
			
		||||
	<p class="warn" v-if="env != 'production'">%i18n:common.do-not-use-in-production%</p>
 | 
			
		||||
	<mk-special-message/>
 | 
			
		||||
	<div class="main" ref="main">
 | 
			
		||||
@@ -54,8 +54,16 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		style(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'box-shadow': this.$store.state.settings.useShadow ? '0 0px 8px rgba(0, 0, 0, 0.2)' : 'none'
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$store.commit('setUiHeaderHeight', 48);
 | 
			
		||||
		this.$store.commit('setUiHeaderHeight', this.$el.offsetHeight);
 | 
			
		||||
 | 
			
		||||
		if (this.$store.getters.isSignedIn) {
 | 
			
		||||
			const ago = (new Date().getTime() - new Date(this.$store.state.i.lastUsedAt).getTime()) / 1000;
 | 
			
		||||
@@ -120,12 +128,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	position -webkit-sticky
 | 
			
		||||
	position sticky
 | 
			
		||||
	position fixed
 | 
			
		||||
	top 0
 | 
			
		||||
	z-index 1000
 | 
			
		||||
	width 100%
 | 
			
		||||
	box-shadow 0 1px 1px rgba(#000, 0.075)
 | 
			
		||||
 | 
			
		||||
	> .warn
 | 
			
		||||
		display block
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-ui" :style="style">
 | 
			
		||||
	<x-header class="header" v-show="!zenMode"/>
 | 
			
		||||
<div class="mk-ui" v-hotkey.global="keymap">
 | 
			
		||||
	<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
 | 
			
		||||
	<x-header class="header" v-show="!zenMode" ref="header"/>
 | 
			
		||||
	<div class="content">
 | 
			
		||||
		<slot></slot>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -16,11 +17,13 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XHeader
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			zenMode: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		style(): any {
 | 
			
		||||
			if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
 | 
			
		||||
@@ -28,27 +31,37 @@ export default Vue.extend({
 | 
			
		||||
				backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null,
 | 
			
		||||
				backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'p': this.post,
 | 
			
		||||
				'n': this.post,
 | 
			
		||||
				'z': this.toggleZenMode
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		'$store.state.uiHeaderHeight'() {
 | 
			
		||||
			this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.addEventListener('keydown', this.onKeydown);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		document.removeEventListener('keydown', this.onKeydown);
 | 
			
		||||
		this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onKeydown(e) {
 | 
			
		||||
			if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA') return;
 | 
			
		||||
 | 
			
		||||
			if (e.which == 80 || e.which == 78) { // p or n
 | 
			
		||||
				e.preventDefault();
 | 
			
		||||
		post() {
 | 
			
		||||
			(this as any).apis.post();
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
			if (e.which == 90) { // z
 | 
			
		||||
				e.preventDefault();
 | 
			
		||||
		toggleZenMode() {
 | 
			
		||||
			this.zenMode = !this.zenMode;
 | 
			
		||||
			}
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				this.$store.commit('setUiHeaderHeight', this.$refs.header.$el.offsetHeight);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -56,20 +69,22 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-ui
 | 
			
		||||
	display flex
 | 
			
		||||
	flex-direction column
 | 
			
		||||
	flex 1
 | 
			
		||||
	min-height 100vh
 | 
			
		||||
	padding-top 48px
 | 
			
		||||
 | 
			
		||||
	> .bg
 | 
			
		||||
		position fixed
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100vh
 | 
			
		||||
		background-size cover
 | 
			
		||||
		background-position center
 | 
			
		||||
		background-attachment fixed
 | 
			
		||||
		opacity 0.3
 | 
			
		||||
 | 
			
		||||
	> .header
 | 
			
		||||
		@media (max-width 1000px)
 | 
			
		||||
			display none
 | 
			
		||||
 | 
			
		||||
	> .content
 | 
			
		||||
		display flex
 | 
			
		||||
		flex-direction column
 | 
			
		||||
		flex 1
 | 
			
		||||
		overflow hidden
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
 | 
			
		||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
 | 
			
		||||
	<span slot="header">%fa:list% %i18n:@title%</span>
 | 
			
		||||
 | 
			
		||||
	<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
 
 | 
			
		||||
@@ -36,13 +36,13 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, isDark ? 0.2 : 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	&.naked
 | 
			
		||||
		background transparent !important
 | 
			
		||||
		border none !important
 | 
			
		||||
		box-shadow none !important
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		background isDark ? #313543 : #fff
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,11 @@ export default Vue.extend({
 | 
			
		||||
		name: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			default: null
 | 
			
		||||
		},
 | 
			
		||||
		animation: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -142,7 +147,7 @@ export default Vue.extend({
 | 
			
		||||
				anime({
 | 
			
		||||
					targets: bg,
 | 
			
		||||
					opacity: 1,
 | 
			
		||||
					duration: 100,
 | 
			
		||||
					duration: this.animation ? 100 : 0,
 | 
			
		||||
					easing: 'linear'
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
@@ -152,7 +157,7 @@ export default Vue.extend({
 | 
			
		||||
				targets: main,
 | 
			
		||||
				opacity: 1,
 | 
			
		||||
				scale: [1.1, 1],
 | 
			
		||||
				duration: 200,
 | 
			
		||||
				duration: this.animation ? 200 : 0,
 | 
			
		||||
				easing: 'easeOutQuad'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
@@ -160,7 +165,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			setTimeout(() => {
 | 
			
		||||
				this.$emit('opened');
 | 
			
		||||
			}, 300);
 | 
			
		||||
			}, this.animation ? 300 : 0);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		close() {
 | 
			
		||||
@@ -174,7 +179,7 @@ export default Vue.extend({
 | 
			
		||||
				anime({
 | 
			
		||||
					targets: bg,
 | 
			
		||||
					opacity: 0,
 | 
			
		||||
					duration: 300,
 | 
			
		||||
					duration: this.animation ? 300 : 0,
 | 
			
		||||
					easing: 'linear'
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
@@ -185,14 +190,14 @@ export default Vue.extend({
 | 
			
		||||
				targets: main,
 | 
			
		||||
				opacity: 0,
 | 
			
		||||
				scale: 0.8,
 | 
			
		||||
				duration: 300,
 | 
			
		||||
				duration: this.animation ? 300 : 0,
 | 
			
		||||
				easing: [0.5, -0.5, 1, 0.5]
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			setTimeout(() => {
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
				this.$emit('closed');
 | 
			
		||||
			}, 300);
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
			}, this.animation ? 300 : 0);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		popout() {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,14 @@
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="form">
 | 
			
		||||
		<div>
 | 
			
		||||
			<label>
 | 
			
		||||
				<p>%i18n:@banner-url%</p>
 | 
			
		||||
				<input v-model="bannerUrl">
 | 
			
		||||
			</label>
 | 
			
		||||
			<button class="ui" @click="updateMeta">%i18n:@save%</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<label>
 | 
			
		||||
				<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
 | 
			
		||||
@@ -46,6 +54,7 @@ export default Vue.extend({
 | 
			
		||||
			stats: null,
 | 
			
		||||
			disableRegistration: false,
 | 
			
		||||
			disableLocalTimeline: false,
 | 
			
		||||
			bannerUrl: null,
 | 
			
		||||
			inviteCode: null,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
@@ -58,6 +67,7 @@ export default Vue.extend({
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.disableRegistration = meta.disableRegistration;
 | 
			
		||||
			this.disableLocalTimeline = meta.disableLocalTimeline;
 | 
			
		||||
			this.bannerUrl = meta.bannerUrl;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
@@ -76,7 +86,8 @@ export default Vue.extend({
 | 
			
		||||
		updateMeta() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				disableRegistration: this.disableRegistration,
 | 
			
		||||
				disableLocalTimeline: this.disableLocalTimeline
 | 
			
		||||
				disableLocalTimeline: this.disableLocalTimeline,
 | 
			
		||||
				bannerUrl: this.bannerUrl
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -114,6 +125,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	> .form
 | 
			
		||||
		> div
 | 
			
		||||
			padding 16px
 | 
			
		||||
			border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
 | 
			
		||||
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/>
 | 
			
		||||
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/>
 | 
			
		||||
<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked"/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -16,13 +17,15 @@ import XTlColumn from './deck.tl-column.vue';
 | 
			
		||||
import XNotificationsColumn from './deck.notifications-column.vue';
 | 
			
		||||
import XWidgetsColumn from './deck.widgets-column.vue';
 | 
			
		||||
import XMentionsColumn from './deck.mentions-column.vue';
 | 
			
		||||
import XDirectColumn from './deck.direct-column.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XTlColumn,
 | 
			
		||||
		XNotificationsColumn,
 | 
			
		||||
		XWidgetsColumn,
 | 
			
		||||
		XMentionsColumn
 | 
			
		||||
		XMentionsColumn,
 | 
			
		||||
		XDirectColumn
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
 
 | 
			
		||||
@@ -279,7 +279,7 @@ root(isDark)
 | 
			
		||||
	height 100%
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow 0 2px 16px rgba(#000, 0.1)
 | 
			
		||||
	//box-shadow 0 2px 16px rgba(#000, 0.1)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	&.draghover
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
			
		||||
	<span slot="header">%fa:envelope R%{{ name }}</span>
 | 
			
		||||
 | 
			
		||||
	<x-direct/>
 | 
			
		||||
</x-column>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import XDirect from './deck.direct.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XColumn,
 | 
			
		||||
		XDirect
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		column: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		isStacked: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		name(): string {
 | 
			
		||||
			if (this.column.name) return this.column.name;
 | 
			
		||||
			return '%i18n:common.deck.direct%';
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										97
									
								
								src/client/app/desktop/views/pages/deck/deck.direct.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/client/app/desktop/views/pages/deck/deck.direct.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<x-notes ref="timeline" :more="existMore ? more : null"/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XNotes from './deck.notes.vue';
 | 
			
		||||
 | 
			
		||||
const fetchLimit = 10;
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XNotes
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			moreFetching: false,
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('mention', this.onNote);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('mention', this.onNote);
 | 
			
		||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
				(this as any).api('notes/mentions', {
 | 
			
		||||
					limit: fetchLimit + 1,
 | 
			
		||||
					includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
					includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
					visibility: 'specified'
 | 
			
		||||
				}).then(notes => {
 | 
			
		||||
					if (notes.length == fetchLimit + 1) {
 | 
			
		||||
						notes.pop();
 | 
			
		||||
						this.existMore = true;
 | 
			
		||||
					}
 | 
			
		||||
					res(notes);
 | 
			
		||||
					this.fetching = false;
 | 
			
		||||
					this.$emit('loaded');
 | 
			
		||||
				}, rej);
 | 
			
		||||
			}));
 | 
			
		||||
		},
 | 
			
		||||
		more() {
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
 | 
			
		||||
			const promise = (this as any).api('notes/mentions', {
 | 
			
		||||
				limit: fetchLimit + 1,
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id,
 | 
			
		||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
				visibility: 'specified'
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			promise.then(notes => {
 | 
			
		||||
				if (notes.length == fetchLimit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				notes.forEach(n => (this.$refs.timeline as any).append(n));
 | 
			
		||||
				this.moreFetching = false;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return promise;
 | 
			
		||||
		},
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			if (note.visibility == 'specified') {
 | 
			
		||||
				(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -147,10 +147,14 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		capture(withHandler = false) {
 | 
			
		||||
			if (this.$store.getters.isSignedIn) {
 | 
			
		||||
				this.connection.send({
 | 
			
		||||
				const data = {
 | 
			
		||||
					type: 'capture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				} as any;
 | 
			
		||||
				if ((this.p.visibleUserIds || []).includes(this.$store.state.i.id) || (this.p.mentions || []).includes(this.$store.state.i.id)) {
 | 
			
		||||
					data.read = true;
 | 
			
		||||
				}
 | 
			
		||||
				this.connection.send(data);
 | 
			
		||||
				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui :class="$style.root">
 | 
			
		||||
	<div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
	<div class="qlvquzbjribqcaozciifydkngcwtyzje" :data-darkmode="$store.state.device.darkmode" :style="style">
 | 
			
		||||
		<template v-for="ids in layout">
 | 
			
		||||
			<div v-if="ids.length > 1" class="folder">
 | 
			
		||||
				<template v-for="id, i in ids">
 | 
			
		||||
@@ -35,6 +35,11 @@ export default Vue.extend({
 | 
			
		||||
			if (this.$store.state.settings.deck == null) return [];
 | 
			
		||||
			if (this.$store.state.settings.deck.layout == null) return this.$store.state.settings.deck.columns.map(c => [c.id]);
 | 
			
		||||
			return this.$store.state.settings.deck.layout;
 | 
			
		||||
		},
 | 
			
		||||
		style(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				height: `calc(100vh - ${this.$store.state.uiHeaderHeight}px)`
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -147,6 +152,15 @@ export default Vue.extend({
 | 
			
		||||
							type: 'mentions'
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: '%fa:envelope R%',
 | 
			
		||||
					text: '%i18n:common.deck.direct%',
 | 
			
		||||
					action: () => {
 | 
			
		||||
						this.$store.dispatch('settings/addDeckColumn', {
 | 
			
		||||
							id: uuid(),
 | 
			
		||||
							type: 'direct'
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: '%fa:list%',
 | 
			
		||||
					text: '%i18n:common.deck.list%',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<mk-home :mode="mode" @loaded="loaded"/>
 | 
			
		||||
	<mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +15,13 @@ export default Vue.extend({
 | 
			
		||||
			default: 'timeline'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				't': this.focus
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = (this as any).os.instanceName;
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +30,9 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		loaded() {
 | 
			
		||||
			Progress.done();
 | 
			
		||||
		},
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$refs.home.focus();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,8 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
 | 
			
		||||
	> .title
 | 
			
		||||
		z-index 1
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,8 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	> .title
 | 
			
		||||
 
 | 
			
		||||
@@ -104,8 +104,8 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border 1px solid rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	&[data-is-dark-background]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
 | 
			
		||||
	<div class="stream" v-if="!fetching && images.length > 0">
 | 
			
		||||
		<div v-for="image in images" class="img"
 | 
			
		||||
			:style="`background-image: url(${image.url})`"
 | 
			
		||||
			:style="`background-image: url(${image.thumbnailUrl})`"
 | 
			
		||||
		></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
 | 
			
		||||
@@ -41,8 +41,8 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	> .title
 | 
			
		||||
 
 | 
			
		||||
@@ -87,8 +87,8 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
 | 
			
		||||
	> *:first-child
 | 
			
		||||
		border-top none !important
 | 
			
		||||
 
 | 
			
		||||
@@ -116,12 +116,13 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		padding 0 8px
 | 
			
		||||
		z-index 10
 | 
			
		||||
		background isDark ? #313543 : #fff
 | 
			
		||||
		border-radius 6px 6px 0 0
 | 
			
		||||
		box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08)
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
		<main>
 | 
			
		||||
			<div class="main">
 | 
			
		||||
				<x-header :user="user"/>
 | 
			
		||||
				<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
 | 
			
		||||
				<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
 | 
			
		||||
				<x-timeline class="timeline" ref="tl" :user="user"/>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="side">
 | 
			
		||||
@@ -28,7 +28,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import parseAcct from '../../../../../../misc/acct/parse';
 | 
			
		||||
import getUserName from '../../../../../../misc/get-user-name';
 | 
			
		||||
import Progress from '../../../../common/scripts/loading';
 | 
			
		||||
import XHeader from './user.header.vue';
 | 
			
		||||
import XTimeline from './user.timeline.vue';
 | 
			
		||||
@@ -89,17 +88,16 @@ root(isDark)
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
		padding 14px 16px
 | 
			
		||||
		font-size 14px
 | 
			
		||||
		border-radius 6px
 | 
			
		||||
		box-shadow var(--shadow)
 | 
			
		||||
		border-radius var(--round)
 | 
			
		||||
 | 
			
		||||
		&.is-suspended
 | 
			
		||||
			color isDark ? #ffb4b4 : #570808
 | 
			
		||||
			background isDark ? #611d1d : #ffdbdb
 | 
			
		||||
			border solid 1px isDark ? #d64a4a : #e09696
 | 
			
		||||
 | 
			
		||||
		&.is-remote
 | 
			
		||||
			color isDark ? #ffbd3e : #573c08
 | 
			
		||||
			background isDark ? #42321c : #fff0db
 | 
			
		||||
			border solid 1px isDark ? #90733c : #dcbb7b
 | 
			
		||||
 | 
			
		||||
		> a
 | 
			
		||||
			font-weight bold
 | 
			
		||||
@@ -119,8 +117,7 @@ root(isDark)
 | 
			
		||||
			margin-right 16px
 | 
			
		||||
 | 
			
		||||
			> .timeline
 | 
			
		||||
				border 1px solid rgba(#000, 0.075)
 | 
			
		||||
				border-radius 6px
 | 
			
		||||
				box-shadow var(--shadow)
 | 
			
		||||
 | 
			
		||||
		> .side
 | 
			
		||||
			width 275px
 | 
			
		||||
@@ -139,8 +136,8 @@ root(isDark)
 | 
			
		||||
				font-size 12px
 | 
			
		||||
				color #aaa
 | 
			
		||||
				background isDark ? #21242f : #fff
 | 
			
		||||
				border solid 1px rgba(#000, 0.075)
 | 
			
		||||
				border-radius 6px
 | 
			
		||||
				box-shadow var(--shadow)
 | 
			
		||||
				border-radius var(--round)
 | 
			
		||||
 | 
			
		||||
				a
 | 
			
		||||
					color #999
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-welcome">
 | 
			
		||||
	<div class="banner" :style="{ backgroundImage: banner ? `url(${banner})` : null }"></div>
 | 
			
		||||
 | 
			
		||||
	<button @click="dark">
 | 
			
		||||
		<template v-if="$store.state.device.darkmode">%fa:moon%</template>
 | 
			
		||||
		<template v-else>%fa:R moon%</template>
 | 
			
		||||
@@ -154,6 +156,7 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			meta: null,
 | 
			
		||||
			stats: null,
 | 
			
		||||
			banner: null,
 | 
			
		||||
			copyright,
 | 
			
		||||
			host,
 | 
			
		||||
			name: 'Misskey',
 | 
			
		||||
@@ -169,6 +172,7 @@ export default Vue.extend({
 | 
			
		||||
			this.name = meta.name;
 | 
			
		||||
			this.description = meta.description;
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
			this.banner = meta.bannerUrl;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
@@ -308,6 +312,26 @@ root(isDark)
 | 
			
		||||
	//background-position center
 | 
			
		||||
	//background-size cover
 | 
			
		||||
 | 
			
		||||
	> .banner
 | 
			
		||||
		position absolute
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 400px
 | 
			
		||||
		background-position center
 | 
			
		||||
		background-size cover
 | 
			
		||||
		opacity 0.7
 | 
			
		||||
 | 
			
		||||
		&:after
 | 
			
		||||
			content ""
 | 
			
		||||
			display block
 | 
			
		||||
			position absolute
 | 
			
		||||
			bottom 0
 | 
			
		||||
			left 0
 | 
			
		||||
			width 100%
 | 
			
		||||
			height 100px
 | 
			
		||||
			background linear-gradient(transparent, isDark ? #191b22 : #f7f7f7)
 | 
			
		||||
 | 
			
		||||
	> .forkit
 | 
			
		||||
		position absolute
 | 
			
		||||
		top 0
 | 
			
		||||
@@ -331,7 +355,7 @@ root(isDark)
 | 
			
		||||
		.block
 | 
			
		||||
			color isDark ? #fff : #444
 | 
			
		||||
			background isDark ? #282C37 : #fff
 | 
			
		||||
			box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
 | 
			
		||||
			box-shadow var(--shadow)
 | 
			
		||||
			//border-radius 8px
 | 
			
		||||
			overflow auto
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-profile"
 | 
			
		||||
<div class="egwyvoaaryotefqhqtmiyawwefemjfsd">
 | 
			
		||||
	<mk-widget-container :show-header="false" :naked="props.design == 2">
 | 
			
		||||
		<div class="egwyvoaaryotefqhqtmiyawwefemjfsd-body"
 | 
			
		||||
			:data-compact="props.design == 1 || props.design == 2"
 | 
			
		||||
			:data-melt="props.design == 2"
 | 
			
		||||
>
 | 
			
		||||
			:data-darkmode="$store.state.device.darkmode"
 | 
			
		||||
		>
 | 
			
		||||
			<div class="banner"
 | 
			
		||||
				:style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''"
 | 
			
		||||
				title="%i18n:@update-banner%"
 | 
			
		||||
@@ -15,6 +18,8 @@
 | 
			
		||||
			/>
 | 
			
		||||
			<router-link class="name" :to="$store.state.i | userPage">{{ $store.state.i | userName }}</router-link>
 | 
			
		||||
			<p class="username">@{{ $store.state.i | acct }}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -42,10 +47,6 @@ export default define({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	background isDark ? #282c37 : #fff
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
 | 
			
		||||
	&[data-compact]
 | 
			
		||||
		> .banner:before
 | 
			
		||||
@@ -75,9 +76,6 @@ root(isDark)
 | 
			
		||||
			display none
 | 
			
		||||
 | 
			
		||||
	&[data-melt]
 | 
			
		||||
		background transparent !important
 | 
			
		||||
		border none !important
 | 
			
		||||
 | 
			
		||||
		> .banner
 | 
			
		||||
			visibility hidden
 | 
			
		||||
 | 
			
		||||
@@ -120,10 +118,10 @@ root(isDark)
 | 
			
		||||
		font-size 0.9em
 | 
			
		||||
		color isDark ? #606984 : #999
 | 
			
		||||
 | 
			
		||||
.mkw-profile[data-darkmode]
 | 
			
		||||
.egwyvoaaryotefqhqtmiyawwefemjfsd-body[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.mkw-profile:not([data-darkmode])
 | 
			
		||||
.egwyvoaaryotefqhqtmiyawwefemjfsd-body:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
 | 
			
		||||
import * as TreeView from 'vue-json-tree-view';
 | 
			
		||||
import VAnimateCss from 'v-animate-css';
 | 
			
		||||
import VModal from 'vue-js-modal';
 | 
			
		||||
import VueHotkey from './common/hotkey';
 | 
			
		||||
 | 
			
		||||
import App from './app.vue';
 | 
			
		||||
import checkForUpdate from './common/scripts/check-for-update';
 | 
			
		||||
@@ -19,6 +20,7 @@ Vue.use(VueRouter);
 | 
			
		||||
Vue.use(TreeView);
 | 
			
		||||
Vue.use(VAnimateCss);
 | 
			
		||||
Vue.use(VModal);
 | 
			
		||||
Vue.use(VueHotkey);
 | 
			
		||||
 | 
			
		||||
// Register global directives
 | 
			
		||||
require('./common/views/directives');
 | 
			
		||||
@@ -123,6 +125,26 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
 | 
			
		||||
			});
 | 
			
		||||
			//#endregion
 | 
			
		||||
 | 
			
		||||
			//#region shadow
 | 
			
		||||
			const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
 | 
			
		||||
			if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow);
 | 
			
		||||
			os.store.watch(s => {
 | 
			
		||||
				return s.settings.useShadow;
 | 
			
		||||
			}, v => {
 | 
			
		||||
				document.documentElement.style.setProperty('--shadow', v ? shadow : 'none');
 | 
			
		||||
			});
 | 
			
		||||
			//#endregion
 | 
			
		||||
 | 
			
		||||
			//#region rounded corners
 | 
			
		||||
			const round = '6px';
 | 
			
		||||
			if (os.store.state.settings.roundedCorners) document.documentElement.style.setProperty('--round', round);
 | 
			
		||||
			os.store.watch(s => {
 | 
			
		||||
				return s.settings.roundedCorners;
 | 
			
		||||
			}, v => {
 | 
			
		||||
				document.documentElement.style.setProperty('--round', v ? round : '0');
 | 
			
		||||
			});
 | 
			
		||||
			//#endregion
 | 
			
		||||
 | 
			
		||||
			Vue.mixin({
 | 
			
		||||
				data() {
 | 
			
		||||
					return {
 | 
			
		||||
 
 | 
			
		||||
@@ -160,10 +160,14 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		capture(withHandler = false) {
 | 
			
		||||
			if (this.$store.getters.isSignedIn) {
 | 
			
		||||
				this.connection.send({
 | 
			
		||||
				const data = {
 | 
			
		||||
					type: 'capture',
 | 
			
		||||
					id: this.p.id
 | 
			
		||||
				});
 | 
			
		||||
				} as any;
 | 
			
		||||
				if ((this.p.visibleUserIds || []).includes(this.$store.state.i.id) || (this.p.mentions || []).includes(this.$store.state.i.id)) {
 | 
			
		||||
					data.read = true;
 | 
			
		||||
				}
 | 
			
		||||
				this.connection.send(data);
 | 
			
		||||
				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -188,9 +188,6 @@ root(isDark)
 | 
			
		||||
				overflow hidden
 | 
			
		||||
				text-overflow ellipsis
 | 
			
		||||
 | 
			
		||||
				[data-fa], [data-icon]
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
 | 
			
		||||
				> img
 | 
			
		||||
					display inline-block
 | 
			
		||||
					vertical-align bottom
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">
 | 
			
		||||
		<template v-if="folder">%fa:R folder-open%{{ folder.name }}</template>
 | 
			
		||||
		<template v-if="file"><mk-file-type-icon data-icon :type="file.type"/>{{ file.name }}</template>
 | 
			
		||||
		<template v-if="!folder && !file">%fa:cloud%%i18n:@drive%</template>
 | 
			
		||||
		<template v-if="folder"><span style="margin-right:4px;">%fa:R folder-open%</span>{{ folder.name }}</template>
 | 
			
		||||
		<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
 | 
			
		||||
		<template v-if="!folder && !file"><span style="margin-right:4px;">%fa:cloud%</span>%i18n:@drive%</template>
 | 
			
		||||
	</span>
 | 
			
		||||
	<template slot="func"><button @click="fn">%fa:ellipsis-h%</button></template>
 | 
			
		||||
	<mk-drive
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:star%%i18n:@title%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:star%</span>%i18n:@title%</span>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
		<template v-for="favorite in favorites">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:gamepad%%i18n:@reversi%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:gamepad%</span>%i18n:@reversi%</span>
 | 
			
		||||
	<mk-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,14 @@ export default Vue.extend({
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			unreadCount: 0,
 | 
			
		||||
			date: null
 | 
			
		||||
			date: null,
 | 
			
		||||
			baseQuery: {
 | 
			
		||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
			
		||||
			},
 | 
			
		||||
			query: {},
 | 
			
		||||
			endpoint: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -46,80 +53,109 @@ export default Vue.extend({
 | 
			
		||||
			return this.$store.state.i.followingCount == 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		endpoint(): string {
 | 
			
		||||
			switch (this.src) {
 | 
			
		||||
				case 'home': return 'notes/timeline';
 | 
			
		||||
				case 'local': return 'notes/local-timeline';
 | 
			
		||||
				case 'hybrid': return 'notes/hybrid-timeline';
 | 
			
		||||
				case 'global': return 'notes/global-timeline';
 | 
			
		||||
				case 'mentions': return 'notes/mentions';
 | 
			
		||||
				case 'tag': return 'notes/search_by_tag';
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		canFetchMore(): boolean {
 | 
			
		||||
			return !this.moreFetching && !this.fetching && this.existMore;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		const prepend = note => {
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (this.src == 'tag') {
 | 
			
		||||
			this.endpoint = 'notes/search_by_tag';
 | 
			
		||||
			this.query = {
 | 
			
		||||
				query: this.tagTl.query
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.connection.close();
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'home') {
 | 
			
		||||
			this.endpoint = 'notes/timeline';
 | 
			
		||||
			const onChangeFollowing = () => {
 | 
			
		||||
				this.fetch();
 | 
			
		||||
			};
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.on('unfollow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.connection.on('follow', onChangeFollowing);
 | 
			
		||||
			this.connection.on('unfollow', onChangeFollowing);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.connection.off('follow', onChangeFollowing);
 | 
			
		||||
				this.connection.off('unfollow', onChangeFollowing);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'local') {
 | 
			
		||||
			this.endpoint = 'notes/local-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.localTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'hybrid') {
 | 
			
		||||
			this.endpoint = 'notes/hybrid-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.hybridTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'global') {
 | 
			
		||||
			this.endpoint = 'notes/global-timeline';
 | 
			
		||||
			this.streamManager = (this as any).os.streams.globalTimelineStream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('note', this.onNote);
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('note', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'mentions') {
 | 
			
		||||
			this.endpoint = 'notes/mentions';
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('mention', this.onNote);
 | 
			
		||||
			this.connection.on('mention', prepend);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('mention', prepend);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		} else if (this.src == 'messages') {
 | 
			
		||||
			this.endpoint = 'notes/mentions';
 | 
			
		||||
			this.query = {
 | 
			
		||||
				visibility: 'specified'
 | 
			
		||||
			};
 | 
			
		||||
			const onNote = note => {
 | 
			
		||||
				if (note.visibility == 'specified') {
 | 
			
		||||
					prepend(note);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			this.streamManager = (this as any).os.stream;
 | 
			
		||||
			this.connection = this.streamManager.getConnection();
 | 
			
		||||
			this.connectionId = this.streamManager.use();
 | 
			
		||||
			this.connection.on('mention', onNote);
 | 
			
		||||
			this.$once('beforeDestroy', () => {
 | 
			
		||||
				this.connection.off('mention', onNote);
 | 
			
		||||
				this.streamManager.dispose(this.connectionId);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		if (this.src == 'tag') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.connection.close();
 | 
			
		||||
		} else if (this.src == 'home') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.connection.off('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.off('unfollow', this.onChangeFollowing);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'local') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'hybrid') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'global') {
 | 
			
		||||
			this.connection.off('note', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		} else if (this.src == 'mentions') {
 | 
			
		||||
			this.connection.off('mention', this.onNote);
 | 
			
		||||
			this.streamManager.dispose(this.connectionId);
 | 
			
		||||
		}
 | 
			
		||||
		this.$emit('beforeDestroy');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
@@ -127,14 +163,10 @@ export default Vue.extend({
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
				(this as any).api(this.endpoint, {
 | 
			
		||||
				(this as any).api(this.endpoint, Object.assign({
 | 
			
		||||
					limit: fetchLimit + 1,
 | 
			
		||||
					untilDate: this.date ? this.date.getTime() : undefined,
 | 
			
		||||
					includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
					includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
					query: this.tagTl ? this.tagTl.query : undefined
 | 
			
		||||
				}).then(notes => {
 | 
			
		||||
					untilDate: this.date ? this.date.getTime() : undefined
 | 
			
		||||
				}, this.baseQuery, this.query)).then(notes => {
 | 
			
		||||
					if (notes.length == fetchLimit + 1) {
 | 
			
		||||
						notes.pop();
 | 
			
		||||
						this.existMore = true;
 | 
			
		||||
@@ -151,14 +183,10 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
 | 
			
		||||
			const promise = (this as any).api(this.endpoint, {
 | 
			
		||||
			const promise = (this as any).api(this.endpoint, Object.assign({
 | 
			
		||||
				limit: fetchLimit + 1,
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id,
 | 
			
		||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
			
		||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
			
		||||
				query: this.tagTl ? this.tagTl.query : undefined
 | 
			
		||||
			});
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id
 | 
			
		||||
			}, this.baseQuery, this.query));
 | 
			
		||||
 | 
			
		||||
			promise.then(notes => {
 | 
			
		||||
				if (notes.length == fetchLimit + 1) {
 | 
			
		||||
@@ -173,15 +201,6 @@ export default Vue.extend({
 | 
			
		||||
			return promise;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeFollowing() {
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$refs.timeline as any).focus();
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header" @click="showNav = true">
 | 
			
		||||
		<span>
 | 
			
		||||
		<span :class="$style.title">
 | 
			
		||||
			<span v-if="src == 'home'">%fa:home%%i18n:@home%</span>
 | 
			
		||||
			<span v-if="src == 'local'">%fa:R comments%%i18n:@local%</span>
 | 
			
		||||
			<span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span>
 | 
			
		||||
			<span v-if="src == 'global'">%fa:globe%%i18n:@global%</span>
 | 
			
		||||
			<span v-if="src == 'mentions'">%fa:at%%i18n:@mentions%</span>
 | 
			
		||||
			<span v-if="src == 'messages'">%fa:envelope R%%i18n:@messages%</span>
 | 
			
		||||
			<span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
 | 
			
		||||
			<span v-if="src == 'tag'">%fa:hashtag%{{ tagTl.title }}</span>
 | 
			
		||||
		</span>
 | 
			
		||||
@@ -14,6 +15,7 @@
 | 
			
		||||
			<template v-if="!showNav">%fa:angle-down%</template>
 | 
			
		||||
			<template v-else>%fa:angle-up%</template>
 | 
			
		||||
		</span>
 | 
			
		||||
		<i :class="$style.badge" v-if="$store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes">%fa:circle%</i>
 | 
			
		||||
	</span>
 | 
			
		||||
 | 
			
		||||
	<template slot="func">
 | 
			
		||||
@@ -23,16 +25,21 @@
 | 
			
		||||
	<main :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
		<div class="nav" v-if="showNav">
 | 
			
		||||
			<div class="bg" @click="showNav = false"></div>
 | 
			
		||||
			<div class="pointer"></div>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<div>
 | 
			
		||||
					<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
 | 
			
		||||
					<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
 | 
			
		||||
					<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
 | 
			
		||||
					<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
 | 
			
		||||
					<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
 | 
			
		||||
					<div class="hr"></div>
 | 
			
		||||
					<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%<i class="badge" v-if="$store.state.i.hasUnreadMentions">%fa:circle%</i></span>
 | 
			
		||||
					<span :data-active="src == 'messages'" @click="src = 'messages'">%fa:envelope R% %i18n:@messages%<i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes">%fa:circle%</i></span>
 | 
			
		||||
					<template v-if="lists">
 | 
			
		||||
						<div class="hr" v-if="lists.length > 0"></div>
 | 
			
		||||
						<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
 | 
			
		||||
					</template>
 | 
			
		||||
					<div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
 | 
			
		||||
					<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id">%fa:hashtag% {{ tl.title }}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
@@ -44,6 +51,7 @@
 | 
			
		||||
			<x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
 | 
			
		||||
			<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
 | 
			
		||||
			<x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
 | 
			
		||||
			<x-tl v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
 | 
			
		||||
			<x-tl v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
 | 
			
		||||
			<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -150,6 +158,26 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
root(isDark)
 | 
			
		||||
	> .nav
 | 
			
		||||
		> .pointer
 | 
			
		||||
			position fixed
 | 
			
		||||
			z-index 10002
 | 
			
		||||
			top 56px
 | 
			
		||||
			left 0
 | 
			
		||||
			right 0
 | 
			
		||||
 | 
			
		||||
			$size = 16px
 | 
			
		||||
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				position absolute
 | 
			
		||||
				top -($size * 2)
 | 
			
		||||
				left s('calc(50% - %s)', $size)
 | 
			
		||||
				border-top solid $size transparent
 | 
			
		||||
				border-left solid $size transparent
 | 
			
		||||
				border-right solid $size transparent
 | 
			
		||||
				border-bottom solid $size isDark ? #272f3a : #fff
 | 
			
		||||
 | 
			
		||||
		> .bg
 | 
			
		||||
			position fixed
 | 
			
		||||
			z-index 10000
 | 
			
		||||
@@ -166,28 +194,22 @@ root(isDark)
 | 
			
		||||
			left 0
 | 
			
		||||
			right 0
 | 
			
		||||
			width 300px
 | 
			
		||||
			max-height calc(100% - 70px)
 | 
			
		||||
			margin 0 auto
 | 
			
		||||
			overflow auto
 | 
			
		||||
			-webkit-overflow-scrolling touch
 | 
			
		||||
			background isDark ? #272f3a : #fff
 | 
			
		||||
			border-radius 8px
 | 
			
		||||
			box-shadow 0 0 16px rgba(#000, 0.1)
 | 
			
		||||
 | 
			
		||||
			$balloon-size = 16px
 | 
			
		||||
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				position absolute
 | 
			
		||||
				top -($balloon-size * 2) + 1.5px
 | 
			
		||||
				left s('calc(50% - %s)', $balloon-size)
 | 
			
		||||
				border-top solid $balloon-size transparent
 | 
			
		||||
				border-left solid $balloon-size transparent
 | 
			
		||||
				border-right solid $balloon-size transparent
 | 
			
		||||
				border-bottom solid $balloon-size isDark ? #272f3a : #fff
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				padding 8px 0
 | 
			
		||||
 | 
			
		||||
				> *
 | 
			
		||||
				> .hr
 | 
			
		||||
					margin 8px 0
 | 
			
		||||
					border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
 | 
			
		||||
 | 
			
		||||
				> *:not(.hr)
 | 
			
		||||
					display block
 | 
			
		||||
					padding 8px 16px
 | 
			
		||||
					color isDark ? #cdd0d8 : #666
 | 
			
		||||
@@ -199,6 +221,11 @@ root(isDark)
 | 
			
		||||
					&:not([data-active]):hover
 | 
			
		||||
						background isDark ? #353e4a : #eee
 | 
			
		||||
 | 
			
		||||
					> .badge
 | 
			
		||||
						margin-left 6px
 | 
			
		||||
						font-size 10px
 | 
			
		||||
						color $theme-color
 | 
			
		||||
 | 
			
		||||
	> .tl
 | 
			
		||||
		max-width 680px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
@@ -217,3 +244,18 @@ main:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.title
 | 
			
		||||
	i
 | 
			
		||||
		margin-right 4px
 | 
			
		||||
 | 
			
		||||
.badge
 | 
			
		||||
	margin-left 6px
 | 
			
		||||
	font-size 10px
 | 
			
		||||
	color $theme-color
 | 
			
		||||
	vertical-align middle
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">
 | 
			
		||||
		<template v-if="user">%fa:R comments%{{ user | userName }}</template>
 | 
			
		||||
		<template v-if="user"><span style="margin-right:4px;">%fa:R comments%</span>{{ user | userName }}</template>
 | 
			
		||||
		<template v-else><mk-ellipsis/></template>
 | 
			
		||||
	</span>
 | 
			
		||||
	<mk-messaging-room v-if="!fetching" :user="user" :is-naked="true"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:R comments%%i18n:@messaging%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:R comments%</span>%i18n:@messaging%</span>
 | 
			
		||||
	<mk-messaging @navigate="navigate" :header-top="48"/>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:R sticky-note%%i18n:@title%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:R sticky-note%</span>%i18n:@title%</span>
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<div>
 | 
			
		||||
			<mk-note-detail :note="note"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:R bell%%i18n:@notifications%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:R bell%</span>%i18n:@notifications%</span>
 | 
			
		||||
	<template slot="func"><button @click="fn">%fa:check%</button></template>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:cog%%i18n:@settings%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:cog%</span>%i18n:@settings%</span>
 | 
			
		||||
	<main :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
		<div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', `<b>${name}</b>`)"></div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:hashtag%{{ $route.params.tag }}</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:hashtag%</span>{{ $route.params.tag }}</span>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
		<p v-if="!fetching && empty">%fa:search% {{ '%i18n:no-posts-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:no-posts-found%'.split('{}')[1] }}</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
	<div class="stream" v-if="!fetching && images.length > 0">
 | 
			
		||||
		<a v-for="image in images"
 | 
			
		||||
			class="img"
 | 
			
		||||
			:style="`background-image: url(${image.media.url})`"
 | 
			
		||||
			:style="`background-image: url(${image.media.thumbnailUrl})`"
 | 
			
		||||
			:href="image.note | notePage"
 | 
			
		||||
		></a>
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="root home">
 | 
			
		||||
	<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
 | 
			
		||||
	<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
 | 
			
		||||
	<section class="recent-notes">
 | 
			
		||||
		<h2>%fa:R comments%%i18n:@recent-notes%</h2>
 | 
			
		||||
		<div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="wgwfgvvimdjvhjfwxropcwksnzftjqes">
 | 
			
		||||
	<div class="banner" :style="{ backgroundImage: banner ? `url(${banner})` : null }"></div>
 | 
			
		||||
 | 
			
		||||
	<div>
 | 
			
		||||
		<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name">
 | 
			
		||||
		<p class="host">{{ host }}</p>
 | 
			
		||||
@@ -80,6 +82,7 @@ export default Vue.extend({
 | 
			
		||||
			meta: null,
 | 
			
		||||
			copyright,
 | 
			
		||||
			stats: null,
 | 
			
		||||
			banner: null,
 | 
			
		||||
			host,
 | 
			
		||||
			name: 'Misskey',
 | 
			
		||||
			description: '',
 | 
			
		||||
@@ -93,6 +96,7 @@ export default Vue.extend({
 | 
			
		||||
			this.name = meta.name;
 | 
			
		||||
			this.description = meta.description;
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
			this.banner = meta.bannerUrl;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
@@ -121,7 +125,27 @@ root(isDark)
 | 
			
		||||
	text-align center
 | 
			
		||||
	//background #fff
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
	> .banner
 | 
			
		||||
		position absolute
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 300px
 | 
			
		||||
		background-position center
 | 
			
		||||
		background-size cover
 | 
			
		||||
		opacity 0.7
 | 
			
		||||
 | 
			
		||||
		&:after
 | 
			
		||||
			content ""
 | 
			
		||||
			display block
 | 
			
		||||
			position absolute
 | 
			
		||||
			bottom 0
 | 
			
		||||
			left 0
 | 
			
		||||
			width 100%
 | 
			
		||||
			height 100px
 | 
			
		||||
			background linear-gradient(transparent, isDark ? #191b22 : #f7f7f7)
 | 
			
		||||
 | 
			
		||||
	> div:not(.banner)
 | 
			
		||||
		padding 32px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		max-width 500px
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:home%%i18n:@dashboard%</span>
 | 
			
		||||
	<span slot="header"><span style="margin-right:4px;">%fa:home%</span>%i18n:@dashboard%</span>
 | 
			
		||||
	<template slot="func">
 | 
			
		||||
		<button @click="customizing = !customizing">%fa:cog%</button>
 | 
			
		||||
	</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@ const defaultSettings = {
 | 
			
		||||
	showPostFormOnTopOfTl: false,
 | 
			
		||||
	suggestRecentHashtags: true,
 | 
			
		||||
	showClockOnHeader: true,
 | 
			
		||||
	useShadow: true,
 | 
			
		||||
	roundedCorners: false,
 | 
			
		||||
	circleIcons: true,
 | 
			
		||||
	contrastedAcct: true,
 | 
			
		||||
	showFullAcct: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -101,15 +101,15 @@ props:
 | 
			
		||||
      ja-JP: "投稿の数"
 | 
			
		||||
      en-US: "The number of the notes of this user"
 | 
			
		||||
 | 
			
		||||
  pinnedNote:
 | 
			
		||||
    type: "entity(Note)"
 | 
			
		||||
  pinnedNotes:
 | 
			
		||||
    type: "entity(Note)[]"
 | 
			
		||||
    optional: true
 | 
			
		||||
    desc:
 | 
			
		||||
      ja-JP: "ピン留めされた投稿"
 | 
			
		||||
      en-US: "The pinned note of this user"
 | 
			
		||||
 | 
			
		||||
  pinnedNoteId:
 | 
			
		||||
    type: "id(Note)"
 | 
			
		||||
  pinnedNoteIds:
 | 
			
		||||
    type: "id(Note)[]"
 | 
			
		||||
    optional: true
 | 
			
		||||
    desc:
 | 
			
		||||
      ja-JP: "ピン留めされた投稿のID"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								src/docs/keyboard-shortcut.ja-JP.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/docs/keyboard-shortcut.ja-JP.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
# Misskeyキーボードショートカットまとめ
 | 
			
		||||
 | 
			
		||||
## グローバル
 | 
			
		||||
これらのショートカットは基本的にどこでも使えます。
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
 | 
			
		||||
		<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">A</kbd>, <kbd class="key">M</kbd></td><td>アカウントメニューを表示/隠す</td><td><b>A</b>ccount, <b>M</b>y, <b>M</b>e, <b>M</b>enu</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Z</kbd></td><td>上部のバーを隠す</td><td><b>Z</b>en</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## 投稿にフォーカスされた状態
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">↑</kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上の投稿にフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">↓</kbd>, <kbd class="key">J</kbd>, <kbd class="key">Tab</kbd></td><td>下の投稿にフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">←</kbd>, <kbd class="key">R</kbd></td><td>返信フォームを開く</td><td><b>R</b>eply</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">→</kbd>, <kbd class="key">Q</kbd></td><td>Renoteフォームを開く</td><td><b>Q</b>uote</td></tr>
 | 
			
		||||
		<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">→</kbd></kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## Renoteフォーム
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">Enter</kbd></td><td>Renoteする</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Q</kbd></td><td>フォームを展開する</td><td><b>Q</b>uote</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉じる</td><td>-</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## リアクションフォーム
 | 
			
		||||
デフォルトで「👍」にフォーカスが当たっている状態です。
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">↑</kbd>, <kbd class="key">K</kbd></td><td>上のリアクションにフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">↓</kbd>, <kbd class="key">J</kbd></td><td>下のリアクションにフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">←</kbd>, <kbd class="key">H</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>左のリアクションにフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">→</kbd>, <kbd class="key">L</kbd>, <kbd class="key">Tab</kbd></td><td>右のリアクションにフォーカスを移動</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Enter</kbd>, <kbd class="key">Space</kbd>, <kbd class="key">+</kbd></td><td>リアクション確定</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションで確定(対応については後述)</td><td>-</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">Esc</kbd></td><td>リアクションするのをやめる</td><td>-</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## リアクションと数字キーの対応
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>数字キー</th><th>リアクション</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">1</kbd></td><td>👍</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">2</kbd></td><td>❤️</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">3</kbd></td><td>😆</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">4</kbd></td><td>🤔</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">5</kbd></td><td>😮</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">6</kbd></td><td>🎉</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">7</kbd></td><td>💢</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">8</kbd></td><td>😥</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">9</kbd></td><td>😇</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">0</kbd></td><td>🍮 or 🍣</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
# 例
 | 
			
		||||
<table>
 | 
			
		||||
	<thead>
 | 
			
		||||
		<tr><th>ショートカット</th><th>動作</th></tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
		<tr><td><kbd class="key">t</kbd><kbd class="key">+</kbd><kbd class="key">+</kbd></td><td>タイムラインの最新の投稿に👍する</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">t</kbd><kbd class="key">1</kbd></td><td>タイムラインの最新の投稿に👍する</td></tr>
 | 
			
		||||
		<tr><td><kbd class="key">t</kbd><kbd class="key">0</kbd></td><td>タイムラインの最新の投稿に🍮する</td></tr>
 | 
			
		||||
	</tbody>
 | 
			
		||||
</table>
 | 
			
		||||
@@ -128,3 +128,24 @@ pre
 | 
			
		||||
	> code
 | 
			
		||||
		display block
 | 
			
		||||
		padding 16px
 | 
			
		||||
 | 
			
		||||
kbd.group
 | 
			
		||||
	display inline-block
 | 
			
		||||
	padding 4px
 | 
			
		||||
	background #fbfbfb
 | 
			
		||||
	border 1px solid #d6d6d6
 | 
			
		||||
	border-radius 4px
 | 
			
		||||
	box-shadow 0 1px 1px rgba(0, 0, 0, 0.1)
 | 
			
		||||
 | 
			
		||||
kbd.key
 | 
			
		||||
	display inline-block
 | 
			
		||||
	padding 6px 8px
 | 
			
		||||
	background #fff
 | 
			
		||||
	border solid 1px #cecece
 | 
			
		||||
	border-radius 4px
 | 
			
		||||
	box-shadow 0 1px 1px rgba(0, 0, 0, 0.1)
 | 
			
		||||
 | 
			
		||||
td
 | 
			
		||||
	> kbd.group,
 | 
			
		||||
	> kbd.key
 | 
			
		||||
		margin 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,9 @@ export type TextElementHashtag = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string, i: number) {
 | 
			
		||||
	if (!(/^\s#[^\s]+/.test(text) || (i == 0 && /^#[^\s]+/.test(text)))) return null;
 | 
			
		||||
	if (!(/^\s#[^\s\.,]+/.test(text) || (i == 0 && /^#[^\s\.,]+/.test(text)))) return null;
 | 
			
		||||
	const isHead = text.startsWith('#');
 | 
			
		||||
	const hashtag = text.match(/^\s?#[^\s]+/)[0];
 | 
			
		||||
	const hashtag = text.match(/^\s?#[^\s\.,]+/)[0];
 | 
			
		||||
	const res: any[] = !isHead ? [{
 | 
			
		||||
		type: 'text',
 | 
			
		||||
		content: text[0]
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,20 @@ export type TextElementQuote = {
 | 
			
		||||
	quote: string
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
	const match = text.match(/^"([\s\S]+?)\n"/);
 | 
			
		||||
export default function(text: string, index: number) {
 | 
			
		||||
	const match = text.match(/^"([\s\S]+?)\n"/) || text.match(/^\n>([\s\S]+?)(\n\n|$)/) ||
 | 
			
		||||
		(index == 0 ? text.match(/^>([\s\S]+?)(\n\n|$)/) : null);
 | 
			
		||||
 | 
			
		||||
	if (!match) return null;
 | 
			
		||||
	const quote = match[0];
 | 
			
		||||
 | 
			
		||||
	const quote = match[1]
 | 
			
		||||
		.split('\n')
 | 
			
		||||
		.map(line => line.replace(/^>+/g, '').trim())
 | 
			
		||||
		.join('\n');
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		type: 'quote',
 | 
			
		||||
		content: quote,
 | 
			
		||||
		quote: match[1].trim(),
 | 
			
		||||
		content: match[0],
 | 
			
		||||
		quote: quote,
 | 
			
		||||
	} as TextElementQuote;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/misc/should-mute-this-note.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/misc/should-mute-this-note.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
 | 
			
		||||
function toString(id: any) {
 | 
			
		||||
	return mongo.ObjectID.prototype.isPrototypeOf(id) ? (id as mongo.ObjectID).toHexString() : id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function(note: any, mutedUserIds: string[]): boolean {
 | 
			
		||||
	if (mutedUserIds.includes(toString(note.userId))) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (note.reply != null && mutedUserIds.includes(toString(note.reply.userId))) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (note.renote != null && mutedUserIds.includes(toString(note.renote.userId))) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
@@ -14,4 +14,5 @@ export type IMeta = {
 | 
			
		||||
	disableRegistration?: boolean;
 | 
			
		||||
	disableLocalTimeline?: boolean;
 | 
			
		||||
	hidedTags?: string[];
 | 
			
		||||
	bannerUrl?: string;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								src/models/note-unread.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/models/note-unread.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import db from '../db/mongodb';
 | 
			
		||||
 | 
			
		||||
const NoteUnread = db.get<INoteUnread>('noteUnreads');
 | 
			
		||||
NoteUnread.createIndex(['userId', 'noteId'], { unique: true });
 | 
			
		||||
export default NoteUnread;
 | 
			
		||||
 | 
			
		||||
export interface INoteUnread {
 | 
			
		||||
	_id: mongo.ObjectID;
 | 
			
		||||
	noteId: mongo.ObjectID;
 | 
			
		||||
	userId: mongo.ObjectID;
 | 
			
		||||
	isSpecified: boolean;
 | 
			
		||||
 | 
			
		||||
	_note: {
 | 
			
		||||
		userId: mongo.ObjectID;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
@@ -196,7 +196,7 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
 | 
			
		||||
			hide = false;
 | 
			
		||||
		} else {
 | 
			
		||||
			// 指定されているかどうか
 | 
			
		||||
			const specified = packedNote.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId));
 | 
			
		||||
			const specified = packedNote.visibleUserIds.some((id: any) => meId.equals(id));
 | 
			
		||||
 | 
			
		||||
			if (specified) {
 | 
			
		||||
				hide = false;
 | 
			
		||||
@@ -295,8 +295,8 @@ export const pack = async (
 | 
			
		||||
 | 
			
		||||
	delete _note._user;
 | 
			
		||||
	delete _note._reply;
 | 
			
		||||
	delete _note.repost;
 | 
			
		||||
	delete _note.mentions;
 | 
			
		||||
	delete _note._renote;
 | 
			
		||||
	delete _note._files;
 | 
			
		||||
	if (_note.geo) delete _note.geo.type;
 | 
			
		||||
 | 
			
		||||
	// Populate user
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,28 @@ User.createIndex('uri', { sparse: true, unique: true });
 | 
			
		||||
 | 
			
		||||
export default User;
 | 
			
		||||
 | 
			
		||||
// 後方互換性のため
 | 
			
		||||
User.findOne({
 | 
			
		||||
	pinnedNoteId: { $exists: true }
 | 
			
		||||
}).then(async x => {
 | 
			
		||||
	if (x == null) return;
 | 
			
		||||
 | 
			
		||||
	const users = await User.find({
 | 
			
		||||
		pinnedNoteId: { $exists: true }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	users.forEach(u => {
 | 
			
		||||
		User.update({ _id: u._id }, {
 | 
			
		||||
			$set: {
 | 
			
		||||
				pinnedNoteIds: [(u as any).pinnedNoteId]
 | 
			
		||||
			},
 | 
			
		||||
			$unset: {
 | 
			
		||||
				pinnedNoteId: ''
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
type IUserBase = {
 | 
			
		||||
	_id: mongo.ObjectID;
 | 
			
		||||
	createdAt: Date;
 | 
			
		||||
@@ -53,7 +75,7 @@ type IUserBase = {
 | 
			
		||||
	wallpaperUrl?: string;
 | 
			
		||||
	data: any;
 | 
			
		||||
	description: string;
 | 
			
		||||
	pinnedNoteId: mongo.ObjectID;
 | 
			
		||||
	pinnedNoteIds: mongo.ObjectID[];
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 凍結されているか否か
 | 
			
		||||
@@ -326,7 +348,8 @@ export const pack = (
 | 
			
		||||
	me?: string | mongo.ObjectID | IUser,
 | 
			
		||||
	options?: {
 | 
			
		||||
		detail?: boolean,
 | 
			
		||||
		includeSecrets?: boolean
 | 
			
		||||
		includeSecrets?: boolean,
 | 
			
		||||
		includeHasUnreadNotes?: boolean
 | 
			
		||||
	}
 | 
			
		||||
) => new Promise<any>(async (resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
@@ -464,11 +487,11 @@ export const pack = (
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (opts.detail) {
 | 
			
		||||
		if (_user.pinnedNoteId) {
 | 
			
		||||
			// Populate pinned note
 | 
			
		||||
			_user.pinnedNote = packNote(_user.pinnedNoteId, meId, {
 | 
			
		||||
		if (_user.pinnedNoteIds) {
 | 
			
		||||
			// Populate pinned notes
 | 
			
		||||
			_user.pinnedNotes = Promise.all(_user.pinnedNoteIds.map((id: mongo.ObjectId) => packNote(id, meId, {
 | 
			
		||||
				detail: true
 | 
			
		||||
			});
 | 
			
		||||
			})));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (meId && !meId.equals(_user.id)) {
 | 
			
		||||
@@ -488,6 +511,11 @@ export const pack = (
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!opts.includeHasUnreadNotes) {
 | 
			
		||||
		delete _user.hasUnreadSpecifiedNotes;
 | 
			
		||||
		delete _user.hasUnreadMentions;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// resolve promises in _user object
 | 
			
		||||
	_user = await rap(_user);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user