From f7b1ef0690b7e30f5918ebf518854b28c0654157 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 18 Apr 2019 03:14:04 +0900
Subject: [PATCH 01/20] =?UTF-8?q?=E3=82=A2=E3=83=B3=E3=82=B1=E3=83=BC?=
 =?UTF-8?q?=E3=83=88=E3=82=A6=E3=82=A3=E3=82=B8=E3=83=83=E3=83=88=E3=81=A7?=
 =?UTF-8?q?=E3=82=82MFM=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20v11=20(#4741)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* MFM in poll

* use mfm
---
 src/client/app/desktop/views/widgets/polls.vue | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/client/app/desktop/views/widgets/polls.vue b/src/client/app/desktop/views/widgets/polls.vue
index 3567cae5ed..c77762ecdf 100644
--- a/src/client/app/desktop/views/widgets/polls.vue
+++ b/src/client/app/desktop/views/widgets/polls.vue
@@ -11,7 +11,9 @@
 
 		<div class="mkw-polls--body">
 			<div class="poll" v-if="!fetching && poll != null">
-				<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p>
+				<p v-if="poll.text"><router-link :to="poll | notePage">
+					<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
+				</router-link></p>
 				<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
 				<mk-poll :note="poll"/>
 			</div>

From 704aabd703de2be4d06b399929e6f33525526b57 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 18 Apr 2019 03:32:45 +0900
Subject: [PATCH 02/20] Use menu instead of prompt Fix #4540, Fix #342 (#4575)

* Use menu instead prompt

* fix

* https://bit.ly/2U0JuVt

* fix
---
 locales/ja-JP.yml                             | 12 ++-
 .../app/mobile/views/components/drive.vue     | 77 +++++++++----------
 src/client/app/mobile/views/pages/drive.vue   | 46 +++++++++--
 3 files changed, 89 insertions(+), 46 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index eb87d4f204..6d68f0ca8c 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1573,12 +1573,11 @@ mobile/views/components/drive.vue:
   file-count: "ファイル"
   nothing-in-drive: "ドライブには何もありません"
   folder-is-empty: "このフォルダは空です"
-  prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
-  deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
   folder-name: "フォルダー名"
   here-is-root: "現在いる場所はルートで、フォルダではありません。"
   url-prompt: "アップロードしたいファイルのURL"
   uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
+  folder-name-cannot-empty: "フォルダ名を空白にすることはできません。"
 
 mobile/views/components/drive-file-chooser.vue:
   select-file: "ファイルを選択"
@@ -1668,6 +1667,15 @@ mobile/views/components/ui.nav.vue:
   admin: "管理"
   about: "Misskeyについて"
 
+mobile/views/pages/drive.vue:
+  contextmenu:
+    upload: "ファイルをアップロード"
+    url-upload: "ファイルをURLでアップロード"
+    create-folder: "フォルダーを作成"
+    rename-folder: "フォルダー名を変更"
+    move-folder: "このフォルダを移動"
+    delete-folder: "このフォルダを削除"
+
 mobile/views/pages/user-lists.vue:
   title: "リスト"
   enter-list-name: "リスト名を入力してください"
diff --git a/src/client/app/mobile/views/components/drive.vue b/src/client/app/mobile/views/components/drive.vue
index 4772333862..b79c0b3806 100644
--- a/src/client/app/mobile/views/components/drive.vue
+++ b/src/client/app/mobile/views/components/drive.vue
@@ -379,43 +379,30 @@ export default Vue.extend({
 			});
 		},
 
-		openContextMenu() {
-			const fn = window.prompt(this.$t('prompt'));
-			if (fn == null || fn == '') return;
-			switch (fn) {
-				case '1':
-					this.selectLocalFile();
-					break;
-				case '2':
-					this.urlUpload();
-					break;
-				case '3':
-					this.createFolder();
-					break;
-				case '4':
-					this.renameFolder();
-					break;
-				case '5':
-					this.moveFolder();
-					break;
-				case '6':
-					this.deleteFolder();
-					break;
-			}
-		},
-
 		selectLocalFile() {
 			(this.$refs.file as any).click();
 		},
 
 		createFolder() {
-			const name = window.prompt(this.$t('folder-name'));
-			if (name == null || name == '') return;
-			this.$root.api('drive/folders/create', {
-				name: name,
-				parentId: this.folder ? this.folder.id : undefined
-			}).then(folder => {
-				this.addFolder(folder, true);
+			this.$root.dialog({
+				title: this.$t('folder-name')
+				input: {
+					default: this.folder.name
+				}
+			}).then(({ result: name }) => {
+				if (!name) {
+					this.$root.dialog({
+						type: 'error',
+						text: this.$t('folder-name-cannot-empty')
+					});
+					return;
+				}
+				this.$root.api('drive/folders/create', {
+					name: name,
+					parentId: this.folder ? this.folder.id : undefined
+				}).then(folder => {
+					this.addFolder(folder, true);
+				});
 			});
 		},
 
@@ -427,13 +414,25 @@ export default Vue.extend({
 				});
 				return;
 			}
-			const name = window.prompt(this.$t('folder-name'), this.folder.name);
-			if (name == null || name == '') return;
-			this.$root.api('drive/folders/update', {
-				name: name,
-				folderId: this.folder.id
-			}).then(folder => {
-				this.cd(folder);
+			this.$root.dialog({
+				title: this.$t('folder-name')
+				input: {
+					default: this.folder.name
+				}
+			}).then(({ result: name }) => {
+				if (!name) {
+					this.$root.dialog({
+						type: 'error',
+						text: this.$t('cannot-empty')
+					});
+					return;
+				}
+				this.$root.api('drive/folders/update', {
+					name: name,
+					folderId: this.folder.id
+				}).then(folder => {
+					this.cd(folder);
+				});
 			});
 		},
 
diff --git a/src/client/app/mobile/views/pages/drive.vue b/src/client/app/mobile/views/pages/drive.vue
index d2f51dc899..05163c6ed9 100644
--- a/src/client/app/mobile/views/pages/drive.vue
+++ b/src/client/app/mobile/views/pages/drive.vue
@@ -5,7 +5,7 @@
 		<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 icon="cloud"/></span>{{ $t('@.drive') }}</template>
 	</template>
-	<template #func><button @click="fn"><fa icon="ellipsis-h"/></button></template>
+	<template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
 	<x-drive
 		ref="browser"
 		:init-folder="initFolder"
@@ -26,9 +26,12 @@
 import Vue from 'vue';
 import i18n from '../../../i18n';
 import Progress from '../../../common/scripts/loading';
+import XMenu from '../../../common/views/components/menu.vue';
+import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
+import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
 
 export default Vue.extend({
-	i18n: i18n(),
+	i18n: i18n('mobile/views/pages/drive.vue'),
 	components: {
 		XDrive: () => import('../components/drive.vue').then(m => m.default),
 	},
@@ -63,9 +66,6 @@ export default Vue.extend({
 				(this.$refs as any).browser.goRoot(true);
 			}
 		},
-		fn() {
-			(this.$refs as any).browser.openContextMenu();
-		},
 		onMoveRoot(silent) {
 			const title = `${this.$root.instanceName} Drive`;
 
@@ -104,6 +104,42 @@ export default Vue.extend({
 
 			this.file = file;
 			this.folder = null;
+		},
+		openContextMenu() {
+			this.$root.new(XMenu, {
+				items: [{
+					type: 'item',
+					text: this.$t('contextmenu.upload'),
+					icon: 'upload',
+					action: this.$refs.browser.selectLocalFile
+				}, {
+					type: 'item',
+					text: this.$t('contextmenu.url-upload'),
+					icon: faCloudUploadAlt,
+					action: this.$refs.browser.urlUpload
+				}, {
+					type: 'item',
+					text: this.$t('contextmenu.create-folder'),
+					icon: ['far', 'folder'],
+					action: this.$refs.browser.createFolder
+				}, ...(this.folder ? [{
+					type: 'item',
+					text: this.$t('contextmenu.rename-folder'),
+					icon: 'i-cursor',
+					action: this.$refs.browser.renameFolder
+				}, {
+					type: 'item',
+					text: this.$t('contextmenu.move-folder'),
+					icon: ['far', 'folder-open'],
+					action: this.$refs.browser.moveFolder
+				}, {
+					type: 'item',
+					text: this.$t('contextmenu.delete-folder'),
+					icon: faTrashAlt,
+					action: this.$refs.browser.deleteFolder
+				}] : [])],
+				source: this.$refs.contextSource,
+			});
 		}
 	}
 });

From adff5382cad8b38381adfbfd0d6b35765948b6b9 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 18 Apr 2019 03:33:51 +0900
Subject: [PATCH 03/20] confirm silence (#4560)

---
 locales/ja-JP.yml                    | 2 ++
 src/client/app/admin/views/users.vue | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6d68f0ca8c..bcdda6c671 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1336,7 +1336,9 @@ admin/views/users.vue:
   unsuspend-confirm: "凍結を解除しますか?"
   unsuspended: "凍結を解除しました"
   make-silence: "サイレンス"
+  silence-confirm: "サイレンスしますか?"
   unmake-silence: "サイレンスの解除"
+  unsilence-confirm: "サイレンスを解除しますか?"
   verify: "公式アカウントにする"
   verify-confirm: "公式アカウントにしますか?"
   verified: "公式アカウントにしました"
diff --git a/src/client/app/admin/views/users.vue b/src/client/app/admin/views/users.vue
index 0f46b564a9..2d6aef3371 100644
--- a/src/client/app/admin/views/users.vue
+++ b/src/client/app/admin/views/users.vue
@@ -232,6 +232,8 @@ export default Vue.extend({
 		},
 
 		async silenceUser() {
+			if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
+
 			const process = async () => {
 				await this.$root.api('admin/silence-user', { userId: this.user.id });
 				this.$root.dialog({
@@ -251,6 +253,8 @@ export default Vue.extend({
 		},
 
 		async unsilenceUser() {
+			if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
+
 			const process = async () => {
 				await this.$root.api('admin/unsilence-user', { userId: this.user.id });
 				this.$root.dialog({

From 9ec6afa37541a307631c2826b7f923160c6d4e0f Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 18 Apr 2019 03:36:06 +0900
Subject: [PATCH 04/20] confirm on user menu (#4553)

---
 locales/ja-JP.yml                             |  8 +++++
 .../app/common/views/components/user-menu.vue | 33 ++++++++++++++++---
 2 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index bcdda6c671..0836386c6c 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -527,8 +527,12 @@ common/views/components/user-menu.vue:
   mention: "メンション"
   mute: "ミュート"
   unmute: "ミュート解除"
+  mute-confirm: "このユーザーをミュートしますか?"
+  unmute-confirm: "このユーザーをミュート解除しますか?"
   block: "ブロック"
   unblock: "ブロック解除"
+  block-confirm: "このユーザーをブロックしますか?"
+  unblock-confirm: "このユーザーをブロック解除しますか?"
   push-to-list: "リストに追加"
   select-list: "リストを選択してください"
   report-abuse: "スパムを報告"
@@ -536,8 +540,12 @@ common/views/components/user-menu.vue:
   report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
   silence: "サイレンス"
   unsilence: "サイレンス解除"
+  silence-confirm: "このユーザーをサイレンスしますか?"
+  unsilence-confirm: "このユーザーをサイレンス解除しますか?"
   suspend: "凍結"
   unsuspend: "凍結解除"
+  suspend-confirm: "このユーザーを凍結しますか?"
+  unsuspend-confirm: "このユーザーを凍結解除しますか?"
 
 common/views/components/poll.vue:
   vote-to: "「{}」に投票する"
diff --git a/src/client/app/common/views/components/user-menu.vue b/src/client/app/common/views/components/user-menu.vue
index a95f7a9225..0af0fdb7e4 100644
--- a/src/client/app/common/views/components/user-menu.vue
+++ b/src/client/app/common/views/components/user-menu.vue
@@ -89,8 +89,10 @@ export default Vue.extend({
 			});
 		},
 
-		toggleMute() {
+		async toggleMute() {
 			if (this.user.isMuted) {
+				if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
+
 				this.$root.api('mute/delete', {
 					userId: this.user.id
 				}).then(() => {
@@ -102,6 +104,8 @@ export default Vue.extend({
 					});
 				});
 			} else {
+				if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
+
 				this.$root.api('mute/create', {
 					userId: this.user.id
 				}).then(() => {
@@ -115,8 +119,10 @@ export default Vue.extend({
 			}
 		},
 
-		toggleBlock() {
+		async toggleBlock() {
 			if (this.user.isBlocking) {
+				if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
+
 				this.$root.api('blocking/delete', {
 					userId: this.user.id
 				}).then(() => {
@@ -128,6 +134,8 @@ export default Vue.extend({
 					});
 				});
 			} else {
+				if (!await this.getConfirmed(this.$t('block-confirm'))) return;
+
 				this.$root.api('blocking/create', {
 					userId: this.user.id
 				}).then(() => {
@@ -164,7 +172,9 @@ export default Vue.extend({
 			});
 		},
 
-		toggleSilence() {
+		async toggleSilence() {
+			if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
+
 			this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
 				userId: this.user.id
 			}).then(() => {
@@ -181,7 +191,9 @@ export default Vue.extend({
 			});
 		},
 
-		toggleSuspend() {
+		async toggleSuspend() {
+			if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
+
 			this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
 				userId: this.user.id
 			}).then(() => {
@@ -196,7 +208,18 @@ export default Vue.extend({
 					text: e
 				});
 			});
-		}
+		},
+
+		async getConfirmed(text: string): Promise<Boolean> {
+			const confirm = await this.$root.dialog({
+				type: 'warning',
+				showCancelButton: true,
+				title: 'confirm',
+				text,
+			});
+
+			return !confirm.canceled;
+		},
 	}
 });
 </script>

From 653b8f635290aa0204d507357e62a353942e9248 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 18 Apr 2019 03:37:49 +0900
Subject: [PATCH 05/20] =?UTF-8?q?=E3=82=B9=E3=83=97=E3=83=A9=E3=83=83?=
 =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=81=8C=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF?=
 =?UTF-8?q?=E3=81=AB=E5=8F=8D=E5=BF=9C=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?=
 =?UTF-8?q?=E3=81=AB=20(#4561)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* confirm silence

* Resolve #4554

* Revert "confirm silence"

This reverts commit e1dbdc2bfc0f41c2b308b142c70e9e4573c98cf9.
---
 src/client/app/common/views/components/dialog.vue | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue
index 6a074769d8..771b1237ee 100644
--- a/src/client/app/common/views/components/dialog.vue
+++ b/src/client/app/common/views/components/dialog.vue
@@ -183,9 +183,6 @@ export default Vue.extend({
 	height 100%
 
 	&.splash
-		&, *
-			pointer-events none !important
-
 		> .main
 			min-width 0
 			width initial

From 683e5b6abec93f2883c4d4dafc130e2a079f3dda Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 14:29:17 +0900
Subject: [PATCH 06/20] Add type annotations

---
 test/streaming.ts | 4 ++--
 test/utils.ts     | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/streaming.ts b/test/streaming.ts
index 7594728618..a29517148a 100644
--- a/test/streaming.ts
+++ b/test/streaming.ts
@@ -32,7 +32,7 @@ describe('Streaming', () => {
 		p.on('message', message => {
 			if (message === 'ok') {
 				(p.channel as any).onread = () => {};
-				initDb(true).then(async connection => {
+				initDb(true).then(async (connection: any) => {
 					Followings = connection.getRepository(Following);
 					done();
 				});
@@ -44,7 +44,7 @@ describe('Streaming', () => {
 		p.kill();
 	});
 
-	const follow = async (follower, followee) => {
+	const follow = async (follower: any, followee: any) => {
 		await Followings.save({
 			id: 'a',
 			createdAt: new Date(),
diff --git a/test/utils.ts b/test/utils.ts
index fbba9a68c9..f67baf0048 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -76,7 +76,7 @@ export const uploadFile = (user: any, path?: string): Promise<any> => new Promis
 	});
 });
 
-export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> {
+export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
 	return new Promise((res, rej) => {
 		const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
 

From d78a5c0863b75ad51f739caf6c6eb129cc958b36 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 14:34:47 +0900
Subject: [PATCH 07/20] Fix #4703

---
 .../api/stream/channels/hybrid-timeline.ts    |  4 +--
 test/streaming.ts                             | 25 +++++++++++++++++++
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts
index 18e6aa8350..5bf23715ef 100644
--- a/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/src/server/api/stream/channels/hybrid-timeline.ts
@@ -20,11 +20,11 @@ export default class extends Channel {
 
 	@autobind
 	private async onNote(note: any) {
-		// 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ
+		// 自分自身の投稿 または その投稿のユーザーをフォローしている または ホームのローカルの投稿 の場合だけ
 		if (!(
 			this.user!.id === note.userId ||
 			this.following.includes(note.userId) ||
-			note.user.host == null
+			(note.user.host == null && note.visibility === 'home')
 		)) return;
 
 		if (['followers', 'specified'].includes(note.visibility)) {
diff --git a/test/streaming.ts b/test/streaming.ts
index a29517148a..a9c90c216f 100644
--- a/test/streaming.ts
+++ b/test/streaming.ts
@@ -484,6 +484,31 @@ describe('Streaming', () => {
 			});
 		}));
 
+		it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
+			const alice = await signup({ username: 'alice' });
+			const bob = await signup({ username: 'bob' });
+
+			let fired = false;
+
+			const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
+				if (type == 'note') {
+					fired = true;
+				}
+			});
+
+			// ホーム投稿
+			post(bob, {
+				text: 'foo',
+				visibility: 'home'
+			});
+
+			setTimeout(() => {
+				assert.strictEqual(fired, false);
+				ws.close();
+				done();
+			}, 3000);
+		}));
+
 		it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
 			const alice = await signup({ username: 'alice' });
 			const bob = await signup({ username: 'bob' });

From 73b683bb4d4cdf7ed0bef59dd1058a6347955d86 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 14:39:49 +0900
Subject: [PATCH 08/20] Add test

---
 test/streaming.ts | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/test/streaming.ts b/test/streaming.ts
index a9c90c216f..2dfede1380 100644
--- a/test/streaming.ts
+++ b/test/streaming.ts
@@ -484,6 +484,31 @@ describe('Streaming', () => {
 			});
 		}));
 
+		it('フォローしているユーザーのホーム投稿が流れる', () => new Promise(async done => {
+			const alice = await signup({ username: 'alice' });
+			const bob = await signup({ username: 'bob' });
+
+			// Alice が Bob をフォロー
+			await request('/following/create', {
+				userId: bob.id
+			}, alice);
+
+			const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
+				if (type == 'note') {
+					assert.deepStrictEqual(body.userId, bob.id);
+					assert.deepStrictEqual(body.text, 'foo');
+					ws.close();
+					done();
+				}
+			});
+
+			// ホーム投稿
+			post(bob, {
+				text: 'foo',
+				visibility: 'home'
+			});
+		}));
+
 		it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
 			const alice = await signup({ username: 'alice' });
 			const bob = await signup({ username: 'bob' });

From 80b83c06248e27e0ea55fe89119d1d6241455411 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 14:41:51 +0900
Subject: [PATCH 09/20] =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=88=E3=81=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/server/api/stream/channels/hybrid-timeline.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts
index 5bf23715ef..a8020bfcfa 100644
--- a/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/src/server/api/stream/channels/hybrid-timeline.ts
@@ -20,11 +20,11 @@ export default class extends Channel {
 
 	@autobind
 	private async onNote(note: any) {
-		// 自分自身の投稿 または その投稿のユーザーをフォローしている または ホームのローカルの投稿 の場合だけ
+		// 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ
 		if (!(
 			this.user!.id === note.userId ||
 			this.following.includes(note.userId) ||
-			(note.user.host == null && note.visibility === 'home')
+			(note.user.host == null && note.visibility === 'public')
 		)) return;
 
 		if (['followers', 'specified'].includes(note.visibility)) {

From cce768aaac9002cf23754b579bea79905fe3ac12 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 14:58:43 +0900
Subject: [PATCH 10/20] Fix API definition

---
 src/server/api/endpoints/notes/hybrid-timeline.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts
index 6dfb143003..effee2b134 100644
--- a/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -17,6 +17,8 @@ export const meta = {
 
 	tags: ['notes'],
 
+	requireCredential: true,
+
 	params: {
 		limit: {
 			validator: $.optional.num.range(1, 100),

From 7827aeb6951bfd0dae1799601dff3f207a3d2363 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 19:40:23 +0900
Subject: [PATCH 11/20] Resolve #4735

---
 locales/ja-JP.yml                             |  1 +
 src/client/app/common/scripts/search.ts       | 64 +++++++++++++++++++
 .../app/common/views/components/dialog.vue    | 50 +++++++++------
 .../views/components/ui.header.search.vue     | 27 ++------
 .../app/desktop/views/home/timeline.core.vue  | 15 +++--
 src/client/app/init.ts                        |  6 +-
 .../app/mobile/views/components/ui.nav.vue    | 26 ++------
 .../app/mobile/views/pages/home.timeline.vue  | 10 +--
 8 files changed, 128 insertions(+), 71 deletions(-)
 create mode 100644 src/client/app/common/scripts/search.ts

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0836386c6c..e9284f89a7 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -35,6 +35,7 @@ common:
   signup: "新規登録"
   signout: "ログアウト"
   reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
+  fetching-as-ap-object: "連合に照会中"
 
   got-it: "わかった"
   customization-tips:
diff --git a/src/client/app/common/scripts/search.ts b/src/client/app/common/scripts/search.ts
new file mode 100644
index 0000000000..c44581817b
--- /dev/null
+++ b/src/client/app/common/scripts/search.ts
@@ -0,0 +1,64 @@
+import { faHistory } from '@fortawesome/free-solid-svg-icons';
+
+export async function search(v: any, q: string) {
+	q = q.trim();
+
+	if (q.startsWith('@')) {
+		v.$router.push(`/${q}`);
+		return;
+	}
+
+	if (q.startsWith('#')) {
+		v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
+		return;
+	}
+
+	// like 2018/03/12
+	if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
+		const date = new Date(q.replace(/-/g, '/'));
+
+		// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
+		// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
+		// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
+		// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
+		if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
+			date.setHours(23, 59, 59, 999);
+		}
+
+		v.$root.$emit('warp', date);
+		v.$root.dialog({
+			icon: faHistory,
+			splash: true,
+		});
+		return;
+	}
+
+	if (q.startsWith('https://')) {
+		const dialog = v.$root.dialog({
+			type: 'waiting',
+			text: v.$t('@.fetching-as-ap-object'),
+			showOkButton: false,
+			showCancelButton: false,
+			cancelableByBgClick: false
+		});
+
+		try {
+			const res = await v.$root.api('ap/show', {
+				uri: q
+			});
+			dialog.close();
+			if (res.type == 'User') {
+				v.$router.push(`/@${res.object.username}@${res.object.host}`);
+			} else if (res.type == 'Note') {
+				v.$router.push(`/notes/${res.object.id}`);
+			}
+		} catch (e) {
+			dialog.close();
+			// TODO: Show error
+		}
+
+		return;
+	}
+
+	v.$router.push(`/search?q=${encodeURIComponent(q)}`);
+}
diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue
index 771b1237ee..c1ee7958c0 100644
--- a/src/client/app/common/views/components/dialog.vue
+++ b/src/client/app/common/views/components/dialog.vue
@@ -6,7 +6,17 @@
 			<mk-signin/>
 		</template>
 		<template v-else>
-			<div class="icon" v-if="!input && !select && !user" :class="type"><fa :icon="icon"/></div>
+			<div class="icon" v-if="icon">
+				<fa :icon="icon"/>
+			</div>
+			<div class="icon" v-else-if="!input && !select && !user" :class="type">
+				<fa icon="check" v-if="type === 'success'"/>
+				<fa :icon="faTimesCircle" v-if="type === 'error'"/>
+				<fa icon="exclamation-triangle" v-if="type === 'warning'"/>
+				<fa icon="info-circle" v-if="type === 'info'"/>
+				<fa :icon="faQuestionCircle" v-if="type === 'question'"/>
+				<fa icon="spinner" pulse v-if="type === 'waiting'"/>
+			</div>
 			<header v-if="title" v-html="title"></header>
 			<div class="body" v-if="text" v-html="text"></div>
 			<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
@@ -14,8 +24,8 @@
 			<ui-select v-if="select" v-model="selectedValue" autofocus>
 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
 			</ui-select>
-			<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash">
-				<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
+			<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
+				<ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
 				<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
 			</ui-horizon-group>
 		</template>
@@ -55,10 +65,21 @@ export default Vue.extend({
 		user: {
 			required: false
 		},
+		icon: {
+			required: false
+		},
+		showOkButton: {
+			type: Boolean,
+			default: true
+		},
 		showCancelButton: {
 			type: Boolean,
 			default: false
 		},
+		cancelableByBgClick: {
+			type: Boolean,
+			default: true
+		},
 		splash: {
 			type: Boolean,
 			default: false
@@ -69,22 +90,11 @@ export default Vue.extend({
 		return {
 			inputValue: this.input && this.input.default ? this.input.default : null,
 			userInputValue: null,
-			selectedValue: null
+			selectedValue: null,
+			faTimesCircle, faQuestionCircle
 		};
 	},
 
-	computed: {
-		icon(): any {
-			switch (this.type) {
-				case 'success': return 'check';
-				case 'error': return faTimesCircle;
-				case 'warning': return 'exclamation-triangle';
-				case 'info': return 'info-circle';
-				case 'question': return faQuestionCircle;
-			}
-		}
-	},
-
 	mounted() {
 		this.$nextTick(() => {
 			(this.$refs.bg as any).style.pointerEvents = 'auto';
@@ -113,6 +123,8 @@ export default Vue.extend({
 
 	methods: {
 		async ok() {
+			if (!this.showOkButton) return;
+
 			if (this.user) {
 				const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
 				if (user) {
@@ -156,7 +168,9 @@ export default Vue.extend({
 		},
 
 		onBgClick() {
-			this.cancel();
+			if (this.cancelableByBgClick) {
+				this.cancel();
+			}
 		},
 
 		onInputKeydown(e) {
@@ -240,7 +254,7 @@ export default Vue.extend({
 				margin-top 8px
 
 		> .body
-			margin 16px 0
+			margin 16px 0 0 0
 
 		> .buttons
 			margin-top 16px
diff --git a/src/client/app/desktop/views/components/ui.header.search.vue b/src/client/app/desktop/views/components/ui.header.search.vue
index 4ade74bc64..0cf5ca6f32 100644
--- a/src/client/app/desktop/views/components/ui.header.search.vue
+++ b/src/client/app/desktop/views/components/ui.header.search.vue
@@ -9,6 +9,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../i18n';
+import { search } from '../../../common/scripts/search';
 
 export default Vue.extend({
 	i18n: i18n('desktop/views/components/ui.header.search.vue'),
@@ -22,29 +23,11 @@ export default Vue.extend({
 		async onSubmit() {
 			if (this.wait) return;
 
-			const q = this.q.trim();
-			if (q.startsWith('@')) {
-				this.$router.push(`/${q}`);
-			} else if (q.startsWith('#')) {
-				this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
-			} else if (q.startsWith('https://')) {
-				this.wait = true;
-				try {
-					const res = await this.$root.api('ap/show', {
-						uri: q
-					});
-					if (res.type == 'User') {
-						this.$router.push(`/@${res.object.username}@${res.object.host}`);
-					} else if (res.type == 'Note') {
-						this.$router.push(`/notes/${res.object.id}`);
-					}
-				} catch (e) {
-					// TODO
-				}
+			this.wait = true;
+			search(this, this.q).finally(() => {
 				this.wait = false;
-			} else {
-				this.$router.push(`/search?q=${encodeURIComponent(q)}`);
-			}
+				this.q = '';
+			});
 		}
 	}
 });
diff --git a/src/client/app/desktop/views/home/timeline.core.vue b/src/client/app/desktop/views/home/timeline.core.vue
index 12806365d4..654a1cc434 100644
--- a/src/client/app/desktop/views/home/timeline.core.vue
+++ b/src/client/app/desktop/views/home/timeline.core.vue
@@ -53,6 +53,12 @@ export default Vue.extend({
 	},
 
 	created() {
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+			this.connection.dispose();
+		});
+
 		const prepend = note => {
 			(this.$refs.timeline as any).prepend(note);
 		};
@@ -124,13 +130,14 @@ export default Vue.extend({
 		});
 	},
 
-	beforeDestroy() {
-		this.connection.dispose();
-	},
-
 	methods: {
 		focus() {
 			(this.$refs.timeline as any).focus();
+		},
+
+		warp(date) {
+			this.date = date;
+			(this.$refs.timeline as any).reload();
 		}
 	}
 });
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index 4b9f341612..230d8c3f1e 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
 					},
 					dialog(opts) {
 						const vm = this.new(Dialog, opts);
-						return new Promise((res) => {
+						const p: any = new Promise((res) => {
 							vm.$once('ok', result => res({ canceled: false, result }));
 							vm.$once('cancel', () => res({ canceled: true }));
 						});
+						p.close = () => {
+							vm.close();
+						};
+						return p;
 					}
 				},
 				router,
diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue
index 169c7fc07d..b26e2380ab 100644
--- a/src/client/app/mobile/views/components/ui.nav.vue
+++ b/src/client/app/mobile/views/components/ui.nav.vue
@@ -66,6 +66,7 @@ import i18n from '../../../i18n';
 import { lang } from '../../../config';
 import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
+import { search } from '../../../common/scripts/search';
 
 export default Vue.extend({
 	i18n: i18n('mobile/views/components/ui.nav.vue'),
@@ -133,29 +134,10 @@ export default Vue.extend({
 			}).then(async ({ canceled, result: query }) => {
 				if (canceled) return;
 
-				const q = query.trim();
-				if (q.startsWith('@')) {
-					this.$router.push(`/${q}`);
-				} else if (q.startsWith('#')) {
-					this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
-				} else if (q.startsWith('https://')) {
-					this.searching = true;
-					try {
-						const res = await this.$root.api('ap/show', {
-							uri: q
-						});
-						if (res.type == 'User') {
-							this.$router.push(`/@${res.object.username}@${res.object.host}`);
-						} else if (res.type == 'Note') {
-							this.$router.push(`/notes/${res.object.id}`);
-						}
-					} catch (e) {
-						// TODO
-					}
+				this.searching = true;
+				search(this, query).finally(() => {
 					this.searching = false;
-				} else {
-					this.$router.push(`/search?q=${encodeURIComponent(q)}`);
-				}
+				});
 			});
 		},
 
diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue
index 809158dd29..e0754e88b4 100644
--- a/src/client/app/mobile/views/pages/home.timeline.vue
+++ b/src/client/app/mobile/views/pages/home.timeline.vue
@@ -54,6 +54,12 @@ export default Vue.extend({
 	},
 
 	created() {
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+			this.connection.dispose();
+		});
+
 		const prepend = note => {
 			(this.$refs.timeline as any).prepend(note);
 		};
@@ -125,10 +131,6 @@ export default Vue.extend({
 		});
 	},
 
-	beforeDestroy() {
-		this.connection.dispose();
-	},
-
 	methods: {
 		focus() {
 			(this.$refs.timeline as any).focus();

From 55f63229cd8f26663a3d9dcc6095997d045f1bd9 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 19:42:40 +0900
Subject: [PATCH 12/20] Update timemachine.vue

---
 src/client/app/desktop/views/widgets/timemachine.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/desktop/views/widgets/timemachine.vue b/src/client/app/desktop/views/widgets/timemachine.vue
index 22a4120403..854b01c13e 100644
--- a/src/client/app/desktop/views/widgets/timemachine.vue
+++ b/src/client/app/desktop/views/widgets/timemachine.vue
@@ -14,7 +14,7 @@ export default define({
 }).extend({
 	methods: {
 		chosen(date) {
-			this.$emit('chosen', date);
+			this.$root.$emit('warp', date);
 		},
 		func() {
 			if (this.props.design == 5) {

From 4beb3e5755b9fe988b7dba9d9d5b1f64f66e3b1a Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 18 Apr 2019 20:46:59 +0900
Subject: [PATCH 13/20] Fix #4734 (#4745)

---
 src/models/repositories/app.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts
index 9266e43cdc..fa5ebf2ac7 100644
--- a/src/models/repositories/app.ts
+++ b/src/models/repositories/app.ts
@@ -26,6 +26,7 @@ export class AppRepository extends Repository<App> {
 			id: app.id,
 			name: app.name,
 			callbackUrl: app.callbackUrl,
+			permission: app.permission,
 			...(opts.includeSecret ? { secret: app.secret } : {}),
 			...(me ? {
 				isAuthorized: await AccessTokens.count({

From 8b92feac7174558ea2e4d2e0c69dffa69c6aac5d Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 21:29:19 +0900
Subject: [PATCH 14/20] Resolve #4732

---
 .../app/common/views/components/settings/api.vue  | 12 ++++++++++--
 .../app/common/views/components/ui/input.vue      | 12 +++++++++++-
 src/server/api/endpoints/endpoints.ts             | 15 +++++++++++++++
 3 files changed, 36 insertions(+), 3 deletions(-)
 create mode 100644 src/server/api/endpoints/endpoints.ts

diff --git a/src/client/app/common/views/components/settings/api.vue b/src/client/app/common/views/components/settings/api.vue
index 4f1b755857..74e3eb0661 100644
--- a/src/client/app/common/views/components/settings/api.vue
+++ b/src/client/app/common/views/components/settings/api.vue
@@ -14,7 +14,7 @@
 
 	<section>
 		<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
-		<ui-input v-model="endpoint">
+		<ui-input v-model="endpoint" :datalist="endpoints">
 			<span>{{ $t('console.endpoint') }}</span>
 		</ui-input>
 		<ui-textarea v-model="body">
@@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
 
 export default Vue.extend({
 	i18n: i18n('common/views/components/api-settings.vue'),
+
 	data() {
 		return {
 			endpoint: '',
 			body: '{}',
 			res: null,
-			sending: false
+			sending: false,
+			endpoints: []
 		};
 	},
 
+	created() {
+		this.$root.api('endpoints').then(endpoints => {
+			this.endpoints = endpoints;
+		});
+	},
+
 	methods: {
 		regenerateToken() {
 			this.$root.dialog({
diff --git a/src/client/app/common/views/components/ui/input.vue b/src/client/app/common/views/components/ui/input.vue
index ae9ce249de..bcb87398ba 100644
--- a/src/client/app/common/views/components/ui/input.vue
+++ b/src/client/app/common/views/components/ui/input.vue
@@ -23,6 +23,7 @@
 				@focus="focused = true"
 				@blur="focused = false"
 				@keydown="$emit('keydown', $event)"
+				:list="id"
 			>
 			<input v-else ref="input"
 				:type="type"
@@ -37,7 +38,11 @@
 				@focus="focused = true"
 				@blur="focused = false"
 				@keydown="$emit('keydown', $event)"
+				:list="id"
 			>
+			<datalist :id="id" v-if="datalist">
+				<option v-for="data in datalist" :value="data"/>
+			</datalist>
 		</template>
 		<template v-else>
 			<input ref="input"
@@ -130,6 +135,10 @@ export default Vue.extend({
 			required: false,
 			default: false
 		},
+		datalist: {
+			type: Array,
+			required: false,
+		},
 		inline: {
 			type: Boolean,
 			required: false,
@@ -147,7 +156,8 @@ export default Vue.extend({
 		return {
 			v: this.value,
 			focused: false,
-			passwordStrength: ''
+			passwordStrength: '',
+			id: Math.random().toString()
 		};
 	},
 	computed: {
diff --git a/src/server/api/endpoints/endpoints.ts b/src/server/api/endpoints/endpoints.ts
new file mode 100644
index 0000000000..d1e4edaa21
--- /dev/null
+++ b/src/server/api/endpoints/endpoints.ts
@@ -0,0 +1,15 @@
+import define from '../define';
+import endpoints from '../endpoints';
+
+export const meta = {
+	requireCredential: false,
+
+	tags: ['meta'],
+
+	params: {
+	},
+};
+
+export default define(meta, async () => {
+	return endpoints.map(x => x.name);
+});

From dab7e527de7aacdd8c4d1755627f96980617e282 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 18 Apr 2019 21:33:24 +0900
Subject: [PATCH 15/20] Improve user lists index (#4605)

* wip

* Revert "wip"

This reverts commit 6212831ce3bdae5ce17f8ace9945710ba7696185.

* improve list index

* Update user-lists.vue
---
 locales/ja-JP.yml                             |  7 +-
 .../common/views/components/user-lists.vue    | 95 +++++++++++++++++++
 .../views/components/ui.header.account.vue    | 10 +-
 .../desktop/views/components/ui.sidebar.vue   |  5 +-
 .../views/components/user-lists-window.vue    | 75 +++------------
 .../app/mobile/views/pages/user-lists.vue     | 36 ++-----
 6 files changed, 123 insertions(+), 105 deletions(-)
 create mode 100644 src/client/app/common/views/components/user-lists.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e9284f89a7..a475bc2c16 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -748,6 +748,10 @@ common/views/components/user-list-editor.vue:
   delete-are-you-sure: "リスト「$1」を削除しますか?"
   deleted: "削除しました"
 
+common/views/components/user-lists.vue:
+  create-list: "リストを作成"
+  list-name: "リスト名"
+
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -1154,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue:
 
 desktop/views/components/user-lists-window.vue:
   title: "リスト"
-  create-list: "リストを作成"
-  list-name: "リスト名"
 
 desktop/views/components/user-preview.vue:
   notes: "投稿"
@@ -1689,7 +1691,6 @@ mobile/views/pages/drive.vue:
 
 mobile/views/pages/user-lists.vue:
   title: "リスト"
-  enter-list-name: "リスト名を入力してください"
 
 mobile/views/pages/signup.vue:
   lets-start: "📦 始めましょう"
diff --git a/src/client/app/common/views/components/user-lists.vue b/src/client/app/common/views/components/user-lists.vue
new file mode 100644
index 0000000000..786a6766d3
--- /dev/null
+++ b/src/client/app/common/views/components/user-lists.vue
@@ -0,0 +1,95 @@
+<template>
+<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
+	<button class="ui" @click="add">{{ $t('create-list') }}</button>
+	<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../i18n';
+
+export default Vue.extend({
+	i18n: i18n('common/views/components/user-lists.vue'),
+	data() {
+		return {
+			fetching: true,
+			lists: []
+		};
+	},
+	mounted() {
+		this.$root.api('users/lists/list').then(lists => {
+			this.fetching = false;
+			this.lists = lists;
+		});
+	},
+	methods: {
+		add() {
+			this.$root.dialog({
+				title: this.$t('list-name'),
+				input: true
+			}).then(async ({ canceled, result: title }) => {
+				if (canceled) return;
+				const list = await this.$root.api('users/lists/create', {
+					title
+				});
+
+				this.lists.push(list)
+				this.$emit('choosen', list);
+			});
+		},
+		choice(list) {
+			this.$emit('choosen', list);
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.xkxvokkjlptzyewouewmceqcxhpgzprp
+	padding 16px
+	background: var(--bg)
+
+	> button
+		display block
+		margin-bottom 16px
+		color var(--primaryForeground)
+		background var(--primary)
+		width 100%
+		border-radius 38px
+		user-select none
+		cursor pointer
+		padding 0 16px
+		min-width 100px
+		line-height 38px
+		font-size 14px
+		font-weight 700
+
+		&:hover
+			background var(--primaryLighten10)
+
+		&:active
+			background var(--primaryDarken10)
+
+	a
+		display block
+		margin 8px 0
+		padding 8px
+		color var(--text)
+		background var(--face)
+		box-shadow 0 2px 16px var(--reversiListItemShadow)
+		border-radius 6px
+		cursor pointer
+		line-height 32px
+
+		*
+			pointer-events none
+			user-select none
+
+		&:hover
+			box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
+
+		&:active
+			box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
+
+</style>
diff --git a/src/client/app/desktop/views/components/ui.header.account.vue b/src/client/app/desktop/views/components/ui.header.account.vue
index effd0ef0fd..7f9decfdcd 100644
--- a/src/client/app/desktop/views/components/ui.header.account.vue
+++ b/src/client/app/desktop/views/components/ui.header.account.vue
@@ -90,9 +90,8 @@
 import Vue from 'vue';
 import i18n from '../../../i18n';
 import MkUserListsWindow from './user-lists-window.vue';
-import MkUserListWindow from './user-list-window.vue';
 import MkFollowRequestsWindow from './received-follow-requests-window.vue';
-import MkSettingsWindow from './settings-window.vue';
+// import MkSettingsWindow from './settings-window.vue';
 import MkDriveWindow from './drive-window.vue';
 import contains from '../../../common/scripts/contains';
 import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
@@ -143,12 +142,7 @@ export default Vue.extend({
 		},
 		list() {
 			this.close();
-			const w = this.$root.new(MkUserListsWindow);
-			w.$once('choosen', list => {
-				this.$root.new(MkUserListWindow, {
-					list
-				});
-			});
+			this.$root.new(MkUserListsWindow);
 		},
 		followRequests() {
 			this.close();
diff --git a/src/client/app/desktop/views/components/ui.sidebar.vue b/src/client/app/desktop/views/components/ui.sidebar.vue
index cb0e059c11..1c01f127b9 100644
--- a/src/client/app/desktop/views/components/ui.sidebar.vue
+++ b/src/client/app/desktop/views/components/ui.sidebar.vue
@@ -148,10 +148,7 @@ export default Vue.extend({
 		},
 
 		list() {
-			const w = this.$root.new(MkUserListsWindow);
-			w.$once('choosen', list => {
-				this.$router.push(`i/lists/${ list.id }`);
-			});
+			this.$root.new(MkUserListsWindow);
 		},
 
 		followRequests() {
diff --git a/src/client/app/desktop/views/components/user-lists-window.vue b/src/client/app/desktop/views/components/user-lists-window.vue
index 7afcd6aa3b..afea01d4a1 100644
--- a/src/client/app/desktop/views/components/user-lists-window.vue
+++ b/src/client/app/desktop/views/components/user-lists-window.vue
@@ -1,85 +1,36 @@
 <template>
 <mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 	<template #header><fa icon="list"/> {{ $t('title') }}</template>
-
-	<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
-		<button class="ui" @click="add">{{ $t('create-list') }}</button>
-		<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
-	</div>
+	<x-lists :class="$style.content" @choosen="choosen"/>
 </mk-window>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../i18n';
+import MkUserListWindow from './user-list-window.vue';
 
 export default Vue.extend({
 	i18n: i18n('desktop/views/components/user-lists-window.vue'),
-	data() {
-		return {
-			fetching: true,
-			lists: []
-		};
-	},
-	mounted() {
-		this.$root.api('users/lists/list').then(lists => {
-			this.fetching = false;
-			this.lists = lists;
-		});
+	components: {
+		XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
 	},
 	methods: {
-		add() {
-			this.$root.dialog({
-				title: this.$t('list-name'),
-				input: true
-			}).then(async ({ canceled, result: title }) => {
-				if (canceled) return;
-				const list = await this.$root.api('users/lists/create', {
-					title
-				});
-
-				this.$emit('choosen', list);
-			});
-		},
-		choice(list) {
-			this.$emit('choosen', list);
-		},
 		close() {
 			(this as any).$refs.window.close();
+		},
+		choosen(list) {
+			this.$root.new(MkUserListWindow, {
+				list
+			});
 		}
 	}
 });
 </script>
 
-<style lang="stylus" scoped>
-.xkxvokkjlptzyewouewmceqcxhpgzprp
-	padding 16px
-
-	> button
-		display block
-		margin-bottom 16px
-		color var(--primaryForeground)
-		background var(--primary)
-		width 100%
-		border-radius 38px
-		user-select none
-		cursor pointer
-		padding 0 16px
-		min-width 100px
-		line-height 38px
-		font-size 14px
-		font-weight 700
-
-		&:hover
-			background var(--primaryLighten10)
-
-		&:active
-			background var(--primaryDarken10)
-
-	> a
-		display block
-		padding 16px
-		border solid 1px var(--faceDivider)
-		border-radius 4px
+<style lang="stylus" module>
+.content
+	height 100%
+	overflow auto
 
 </style>
diff --git a/src/client/app/mobile/views/pages/user-lists.vue b/src/client/app/mobile/views/pages/user-lists.vue
index 49006f41f6..a3e9bd78ba 100644
--- a/src/client/app/mobile/views/pages/user-lists.vue
+++ b/src/client/app/mobile/views/pages/user-lists.vue
@@ -1,20 +1,15 @@
 <template>
 <mk-ui>
 	<template #header><fa icon="list"/>{{ $t('title') }}</template>
-	<template #func><button @click="fn"><fa icon="plus"/></button></template>
+	<template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
 
-	<main>
-		<ul>
-			<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li>
-		</ul>
-	</main>
+	<x-lists ref="lists" @choosen="choosen"/>
 </mk-ui>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../i18n';
-import Progress from '../../../common/scripts/loading';
 
 export default Vue.extend({
 	i18n: i18n('mobile/views/pages/user-lists.vue'),
@@ -24,31 +19,16 @@ export default Vue.extend({
 			lists: []
 		};
 	},
+	components: {
+		XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
+	},
 	mounted() {
 		document.title = this.$t('title');
-
-		Progress.start();
-
-		this.$root.api('users/lists/list').then(lists => {
-			this.fetching = false;
-			this.lists = lists;
-
-			Progress.done();
-		});
 	},
 	methods: {
-		fn() {
-			this.$root.dialog({
-				title: this.$t('enter-list-name'),
-				input: true
-			}).then(async ({ canceled, result: title }) => {
-				if (canceled) return;
-				const list = await this.$root.api('users/lists/create', {
-					title
-				});
-
-				this.$router.push(`/i/lists/${list.id}`);
-			});
+		choosen(list) {
+			if (!list) return;
+			this.$router.push(`/i/lists/${list.id}`);
 		}
 	}
 });

From ae6cc11ad291701966501962c76a1e154ee45dbe Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 21:34:56 +0900
Subject: [PATCH 16/20] Fix icon

---
 src/client/app/common/views/components/messaging-room.vue | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/client/app/common/views/components/messaging-room.vue b/src/client/app/common/views/components/messaging-room.vue
index 83a0c463e0..a8980e068f 100644
--- a/src/client/app/common/views/components/messaging-room.vue
+++ b/src/client/app/common/views/components/messaging-room.vue
@@ -6,7 +6,7 @@
 	<div class="body">
 		<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
 		<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
-		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p>
+		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
 		<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
 			<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
 		</button>
@@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
 import XForm from './messaging-room.form.vue';
 import { url } from '../../../config';
 import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
+import { faFlag } from '@fortawesome/free-regular-svg-icons';
 
 export default Vue.extend({
 	i18n: i18n('common/views/components/messaging-room.vue'),
@@ -54,7 +55,7 @@ export default Vue.extend({
 			connection: null,
 			showIndicator: false,
 			timer: null,
-			faArrowCircleDown
+			faArrowCircleDown, faFlag
 		};
 	},
 

From 4096ddcbd0eb897a75b5dad5b2f389300e653ab8 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 21:36:44 +0900
Subject: [PATCH 17/20] Use -

---
 src/client/app/store.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 44b893835c..a6f2a0b00b 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -358,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({
 					ctx.commit('set', x);
 
 					if (ctx.rootGetters.isSignedIn) {
-						os.api('i/update_client_setting', {
+						os.api('i/update-client-setting', {
 							name: x.key,
 							value: x.value
 						});

From 8aaab195c6f6d763446e0635e2745f3e18fe852a Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 21:40:36 +0900
Subject: [PATCH 18/20] Fix bug

---
 src/client/app/mobile/views/pages/widgets.vue | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue
index 33166825cb..7130fddb34 100644
--- a/src/client/app/mobile/views/pages/widgets.vue
+++ b/src/client/app/mobile/views/pages/widgets.vue
@@ -72,13 +72,13 @@ export default Vue.extend({
 
 	computed: {
 		widgets(): any[] {
-			return this.$store.state.settings.mobileHome;
+			return this.$store.state.device.mobileHome;
 		}
 	},
 
 	created() {
 		if (this.widgets.length == 0) {
-			this.widgets = [{
+			this.$store.commit('device/setMobileHome', [{
 				name: 'calendar',
 				id: 'a', data: {}
 			}, {
@@ -96,8 +96,7 @@ export default Vue.extend({
 			}, {
 				name: 'version',
 				id: 'g', data: {}
-			}];
-			this.saveHome();
+			}]);
 		}
 	},
 
@@ -123,7 +122,7 @@ export default Vue.extend({
 		},
 
 		addWidget() {
-			this.$store.commit('settings/addMobileHomeWidget', {
+			this.$store.commit('device/addMobileHomeWidget', {
 				name: this.widgetAdderSelected,
 				id: uuid(),
 				data: {}
@@ -131,11 +130,11 @@ export default Vue.extend({
 		},
 
 		removeWidget(widget) {
-			this.$store.commit('settings/removeMobileHomeWidget', widget);
+			this.$store.commit('device/removeMobileHomeWidget', widget);
 		},
 
 		saveHome() {
-			this.$store.commit('settings/setMobileHome', this.widgets);
+			this.$store.commit('device/setMobileHome', this.widgets);
 		}
 	}
 });

From fda8cf77ed216567669cf9af5af359f0b33c3e12 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 22:00:11 +0900
Subject: [PATCH 19/20] Improve warp

---
 .../app/desktop/views/components/notes.vue       |  2 +-
 .../views/components/user-list-timeline.vue      | 10 ++++++++++
 .../desktop/views/home/user/user.timeline.vue    | 10 ++++++----
 src/client/app/mobile/views/components/notes.vue |  2 +-
 .../views/components/user-list-timeline.vue      | 12 ++++++++++++
 .../mobile/views/components/user-timeline.vue    | 16 ++++++++++++++++
 src/server/api/endpoints/users/notes.ts          |  2 +-
 7 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue
index 3fad7c9226..9044ad3478 100644
--- a/src/client/app/desktop/views/components/notes.vue
+++ b/src/client/app/desktop/views/components/notes.vue
@@ -123,7 +123,7 @@ export default Vue.extend({
 		},
 
 		fetchMore() {
-			if (!this.more || this.moreFetching) return;
+			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 			this.moreFetching = true;
 			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 				this.notes = this.notes.concat(x.notes);
diff --git a/src/client/app/desktop/views/components/user-list-timeline.vue b/src/client/app/desktop/views/components/user-list-timeline.vue
index 0cbe5ff05a..4437b838f2 100644
--- a/src/client/app/desktop/views/components/user-list-timeline.vue
+++ b/src/client/app/desktop/views/components/user-list-timeline.vue
@@ -18,10 +18,12 @@ export default Vue.extend({
 	data() {
 		return {
 			connection: null,
+			date: null,
 			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 				listId: this.list.id,
 				limit: fetchLimit + 1,
 				untilId: cursor ? cursor : undefined,
+				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@@ -46,6 +48,10 @@ export default Vue.extend({
 	},
 	mounted() {
 		this.init();
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+		});
 	},
 	beforeDestroy() {
 		this.connection.dispose();
@@ -68,6 +74,10 @@ export default Vue.extend({
 		},
 		onUserRemoved() {
 			(this.$refs.timeline as any).reload();
+		},
+		warp(date) {
+			this.date = date;
+			(this.$refs.timeline as any).reload();
 		}
 	}
 });
diff --git a/src/client/app/desktop/views/home/user/user.timeline.vue b/src/client/app/desktop/views/home/user/user.timeline.vue
index 086ad6a329..0435d67dc7 100644
--- a/src/client/app/desktop/views/home/user/user.timeline.vue
+++ b/src/client/app/desktop/views/home/user/user.timeline.vue
@@ -36,6 +36,7 @@ export default Vue.extend({
 				includeReplies: this.mode == 'with-replies',
 				includeMyRenotes: this.mode != 'my-posts',
 				withFiles: this.mode == 'with-media',
+				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 				untilId: cursor ? cursor : undefined
 			}).then(notes => {
 				if (notes.length == fetchLimit + 1) {
@@ -62,10 +63,11 @@ export default Vue.extend({
 
 	mounted() {
 		document.addEventListener('keydown', this.onDocumentKeydown);
-	},
-
-	beforeDestroy() {
-		document.removeEventListener('keydown', this.onDocumentKeydown);
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+			document.removeEventListener('keydown', this.onDocumentKeydown);
+		});
 	},
 
 	methods: {
diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue
index 5f3f5633d4..2e42300717 100644
--- a/src/client/app/mobile/views/components/notes.vue
+++ b/src/client/app/mobile/views/components/notes.vue
@@ -124,7 +124,7 @@ export default Vue.extend({
 		},
 
 		fetchMore() {
-			if (!this.more || this.moreFetching) return;
+			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 			this.moreFetching = true;
 			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 				this.notes = this.notes.concat(x.notes);
diff --git a/src/client/app/mobile/views/components/user-list-timeline.vue b/src/client/app/mobile/views/components/user-list-timeline.vue
index c3f09c9b15..73bc6f3034 100644
--- a/src/client/app/mobile/views/components/user-list-timeline.vue
+++ b/src/client/app/mobile/views/components/user-list-timeline.vue
@@ -15,10 +15,12 @@ export default Vue.extend({
 	data() {
 		return {
 			connection: null,
+			date: null,
 			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 				listId: this.list.id,
 				limit: fetchLimit + 1,
 				untilId: cursor ? cursor : undefined,
+				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@@ -45,6 +47,11 @@ export default Vue.extend({
 
 	mounted() {
 		this.init();
+
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+		});
 	},
 
 	beforeDestroy() {
@@ -73,6 +80,11 @@ export default Vue.extend({
 
 		onUserRemoved() {
 			(this.$refs.timeline as any).reload();
+		},
+
+		warp(date) {
+			this.date = date;
+			(this.$refs.timeline as any).reload();
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue
index 70016a49e3..8c7c8c6d7d 100644
--- a/src/client/app/mobile/views/components/user-timeline.vue
+++ b/src/client/app/mobile/views/components/user-timeline.vue
@@ -17,10 +17,12 @@ export default Vue.extend({
 
 	data() {
 		return {
+			date: null,
 			makePromise: cursor => this.$root.api('users/notes', {
 				userId: this.user.id,
 				limit: fetchLimit + 1,
 				withFiles: this.withMedia,
+				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 				untilId: cursor ? cursor : undefined
 			}).then(notes => {
 				if (notes.length == fetchLimit + 1) {
@@ -37,6 +39,20 @@ export default Vue.extend({
 				}
 			})
 		};
+	},
+
+	created() {
+		this.$root.$on('warp', this.warp);
+		this.$once('hook:beforeDestroy', () => {
+			this.$root.$off('warp', this.warp);
+		});
+	},
+
+	methods: {
+		warp(date) {
+			this.date = date;
+			(this.$refs.timeline as any).reload();
+		}
 	}
 });
 </script>
diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts
index d3f17bd787..4691a99394 100644
--- a/src/server/api/endpoints/users/notes.ts
+++ b/src/server/api/endpoints/users/notes.ts
@@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => {
 	});
 
 	//#region Construct query
-	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
+	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 		.andWhere('note.userId = :userId', { userId: user.id })
 		.leftJoinAndSelect('note.user', 'user');
 

From debe648a98b8ed62822d177bdf7aeb15373cc640 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 18 Apr 2019 22:00:53 +0900
Subject: [PATCH 20/20] 11.2.0

---
 CHANGELOG.md | 16 ++++++++++++++++
 package.json |  2 +-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c45b02b5e..1755ce43b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ If you encounter any problems with updating, please try the following:
 1. `npm run clean` or `npm run cleanall`
 2. Retry update (Don't forget `npm i`)
 
+11.2.0 (2019/04/18)
+-------------------
+### Improvements
+* 検索で日付(日時)を入力するとタイムラインをその時点まで遡るように
+* APIコンソールでエンドポイントをサジェストするように
+* モバイル版でドライブのメニューを使いやすく
+* サイレンス時に確認を表示するように
+* ユーザーメニューでブロックなどの操作を行う時に確認するように
+
+### Fixes
+* アプリケーション連携画面でパーミッションが表示されない問題を修正
+* アンケートウィジットでもMFMを使用するように
+* フォローしてないユーザーのホーム投稿がSTLに流れてくる問題を修正
+* モバイル版でウィジェットを設定できない問題を修正
+* スプラッシュがクリックに反応するように
+
 11.1.6 (2019/04/18)
 -------------------
 ### Fixes
diff --git a/package.json b/package.json
index db2d9d0eac..409bdda154 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "11.1.6",
+	"version": "11.2.0",
 	"codename": "daybreak",
 	"repository": {
 		"type": "git",