From f3aef8df756668847461a40110b44955090cb987 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 20 Feb 2021 22:28:53 +0900
Subject: [PATCH] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4?=
 =?UTF-8?q?=E3=83=B3=E3=82=92=E7=89=B9=E5=AE=9A=E3=81=AE=E6=97=A5=E4=BB=98?=
 =?UTF-8?q?=E3=81=AB=E3=82=B8=E3=83=A3=E3=83=B3=E3=83=97=E3=81=99=E3=82=8B?=
 =?UTF-8?q?=E6=A9=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/ja-JP.yml                             |  3 +++
 src/client/ui/chat/index.vue                  | 23 +++++++++++++++----
 src/client/ui/chat/timeline.vue               | 17 +++++++++++++-
 src/server/api/endpoints/channels/timeline.ts |  2 +-
 4 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e7057e3f89..e5700fe059 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -707,6 +707,9 @@ emailNotification: "メール通知"
 inChannelSearch: "チャンネル内検索"
 useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
 typingUsers: "{users}が入力中"
+jumpToSpecifiedDate: "特定の日付にジャンプ"
+showingPastTimeline: "過去のタイムラインを表示しています"
+clear: "クリア"
 
 _email:
   _follow:
diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue
index bd0fd324fa..26c81a1aa9 100644
--- a/src/client/ui/chat/index.vue
+++ b/src/client/ui/chat/index.vue
@@ -99,6 +99,9 @@
 			<div class="right">
 				<div class="instance">{{ instanceName }}</div>
 				<XHeaderClock class="clock"/>
+				<button class="_button button timetravel" @click="timetravel" v-tooltip="$ts.jumpToSpecifiedDate">
+					<Fa :icon="faCalendarAlt"/>
+				</button>
 				<button class="_button button search" v-if="tl.startsWith('channel:') && currentChannel" @click="inChannelSearch" v-tooltip="$ts.inChannelSearch">
 					<Fa :icon="faSearch"/>
 				</button>
@@ -115,8 +118,8 @@
 			</div>
 		</header>
 
-		<XTimeline class="body" v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/>
-		<XTimeline class="body" v-else :src="tl" :key="tl"/>
+		<XTimeline class="body" ref="tl" v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/>
+		<XTimeline class="body" ref="tl" v-else :src="tl" :key="tl"/>
 	</main>
 
 	<XSide class="side" ref="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/>
@@ -131,7 +134,7 @@
 <script lang="ts">
 import { defineComponent, defineAsyncComponent } from 'vue';
 import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, faAt, faLink, faEllipsisH, faGlobe } from '@fortawesome/free-solid-svg-icons';
-import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
+import { faBell, faStar as farStar, faEnvelope, faComments, faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
 import { instanceName, url } from '@/config';
 import XSidebar from '@/components/sidebar.vue';
 import XWidgets from './widgets.vue';
@@ -192,7 +195,7 @@ export default defineComponent({
 			menuDef: sidebarDef,
 			sideViewOpening: false,
 			instanceName,
-			faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope,
+			faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope, faCalendarAlt,
 		};
 	},
 
@@ -244,6 +247,18 @@ export default defineComponent({
 			os.post();
 		},
 
+		async timetravel() {
+			const { canceled, result: date } = await os.dialog({
+				title: this.$ts.date,
+				input: {
+					type: 'date'
+				}
+			});
+			if (canceled) return;
+
+			this.$refs.tl.timetravel(new Date(date));
+		},
+
 		search() {
 			search();
 		},
diff --git a/src/client/ui/chat/timeline.vue b/src/client/ui/chat/timeline.vue
index 2f0e571ef0..232e749c1c 100644
--- a/src/client/ui/chat/timeline.vue
+++ b/src/client/ui/chat/timeline.vue
@@ -1,4 +1,7 @@
 <template>
+<div class="dbiokgaf info" v-if="date">
+	<MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo>
+</div>
 <div class="dbiokgaf top" v-if="['home', 'local', 'social', 'global'].includes(src)">
 	<XPostForm/>
 </div>
@@ -27,11 +30,13 @@ import * as sound from '@/scripts/sound';
 import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
 import follow from '@/directives/follow-append';
 import XPostForm from './post-form.vue';
+import MkInfo from '@/components/ui/info.vue';
 
 export default defineComponent({
 	components: {
 		XNotes,
 		XPostForm,
+		MkInfo,
 	},
 
 	directives: {
@@ -81,6 +86,7 @@ export default defineComponent({
 			top: 0,
 			bottom: 0,
 			typers: [],
+			date: null
 		};
 	},
 
@@ -186,7 +192,7 @@ export default defineComponent({
 			reversed,
 			limit: 10,
 			params: init => ({
-				untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
+				untilDate: this.date?.getTime(),
 				...this.baseQuery, ...this.query
 			})
 		};
@@ -220,11 +226,20 @@ export default defineComponent({
 			}
 			this.queue = q;
 		},
+
+		timetravel(date?: Date) {
+			this.date = date;
+			this.$refs.tl.reload();
+		}
 	}
 });
 </script>
 
 <style lang="scss" scoped>
+.dbiokgaf.info{
+	padding: 16px 16px 0 16px;
+}
+
 .dbiokgaf.top {
 	padding: 16px 16px 0 16px;
 }
diff --git a/src/server/api/endpoints/channels/timeline.ts b/src/server/api/endpoints/channels/timeline.ts
index 3ae28fc67a..acb34f124d 100644
--- a/src/server/api/endpoints/channels/timeline.ts
+++ b/src/server/api/endpoints/channels/timeline.ts
@@ -85,7 +85,7 @@ export default define(meta, async (ps, user) => {
 	}
 
 	//#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.channelId = :channelId', { channelId: channel.id })
 		.leftJoinAndSelect('note.user', 'user')
 		.leftJoinAndSelect('note.channel', 'channel');
-- 
GitLab