From ae3abc2126409da909edc1134a057fa13d819ed8 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Tue, 1 Feb 2022 23:48:19 +0900
Subject: [PATCH] fix: Fix Sideview (#8235)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix #7890

* a-

* 3度目の正直

* fix

* :v:

* update CHANGELOG

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                |   1 +
 packages/client/src/components/global/a.vue |   5 +-
 packages/client/src/ui/classic.side.vue     | 162 ++++++-------
 packages/client/src/ui/universal.vue        | 250 +++++++++-----------
 4 files changed, 194 insertions(+), 224 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1725c7703b..a0eef5a6f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 - 投稿フォームのハッシュタグ保持フィールドが動作しない問題を修正
 - Add `img-src` and `media-src` directives to `Content-Security-Policy` for
   files and media proxy
+- サイドビューが動かないのを修正
 - ensure that specified users does not get duplicates
 
 ## 12.102.1 (2022/01/27)
diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue
index b1b6a0cdaf..52fef50f9b 100644
--- a/packages/client/src/components/global/a.vue
+++ b/packages/client/src/components/global/a.vue
@@ -23,8 +23,9 @@ const props = withDefaults(defineProps<{
 	behavior: null,
 });
 
-const navHook = inject('navHook', null);
-const sideViewHook = inject('sideViewHook', null);
+type Navigate = (path: string, record?: boolean) => void;
+const navHook = inject<null | Navigate>('navHook', null);
+const sideViewHook = inject<null | Navigate>('sideViewHook', null);
 
 const active = $computed(() => {
 	if (props.activeClass == null) return false;
diff --git a/packages/client/src/ui/classic.side.vue b/packages/client/src/ui/classic.side.vue
index f816834141..6c2329194e 100644
--- a/packages/client/src/ui/classic.side.vue
+++ b/packages/client/src/ui/classic.side.vue
@@ -4,7 +4,7 @@
 		<header class="header" @contextmenu.prevent.stop="onContextmenu">
 			<button v-if="history.length > 0" class="_button" @click="back()"><i class="fas fa-chevron-left"></i></button>
 			<button v-else class="_button" style="pointer-events: none;"><!-- マージンのバランスを取るためのダミー --></button>
-			<span class="title">{{ pageInfo.title }}</span>
+			<span class="title" v-text="pageInfo?.title" />
 			<button class="_button" @click="close()"><i class="fas fa-times"></i></button>
 		</header>
 		<MkHeader class="pageHeader" :info="pageInfo"/>
@@ -13,99 +13,89 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { provide } from 'vue';
 import * as os from '@/os';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
-import { resolve } from '@/router';
-import { url } from '@/config';
+import { resolve, router } from '@/router';
+import { url as root } from '@/config';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	provide() {
-		return {
-			navHook: (path) => {
-				this.navigate(path);
-			}
-		};
-	},
+provide('navHook', navigate);
 
-	data() {
-		return {
-			path: null,
-			component: null,
-			props: {},
-			pageInfo: null,
-			history: [],
-		};
-	},
+let path: string | null = $ref(null);
+let component: ReturnType<typeof resolve>['component'] | null = $ref(null);
+let props: any | null = $ref(null);
+let pageInfo: any | null = $ref(null);
+let history: string[] = $ref([]);
 
-	computed: {
-		url(): string {
-			return url + this.path;
-		}
-	},
+let url = $computed(() => `${root}${path}`);
 
-	methods: {
-		changePage(page) {
-			if (page == null) return;
-			if (page[symbols.PAGE_INFO]) {
-				this.pageInfo = page[symbols.PAGE_INFO];
-			}
-		},
-
-		navigate(path, record = true) {
-			if (record && this.path) this.history.push(this.path);
-			this.path = path;
-			const { component, props } = resolve(path);
-			this.component = component;
-			this.props = props;
-		},
-
-		back() {
-			this.navigate(this.history.pop(), false);
-		},
-
-		close() {
-			this.path = null;
-			this.component = null;
-			this.props = {};
-		},
-
-		onContextmenu(ev: MouseEvent) {
-			os.contextMenu([{
-				type: 'label',
-				text: this.path,
-			}, {
-				icon: 'fas fa-expand-alt',
-				text: this.$ts.showInPage,
-				action: () => {
-					this.$router.push(this.path);
-					this.close();
-				}
-			}, {
-				icon: 'fas fa-window-maximize',
-				text: this.$ts.openInWindow,
-				action: () => {
-					os.pageWindow(this.path);
-					this.close();
-				}
-			}, null, {
-				icon: 'fas fa-external-link-alt',
-				text: this.$ts.openInNewTab,
-				action: () => {
-					window.open(this.url, '_blank');
-					this.close();
-				}
-			}, {
-				icon: 'fas fa-link',
-				text: this.$ts.copyLink,
-				action: () => {
-					copyToClipboard(this.url);
-				}
-			}], ev);
-		}
+function changePage(page) {
+	if (page == null) return;
+	if (page[symbols.PAGE_INFO]) {
+		pageInfo = page[symbols.PAGE_INFO];
 	}
+}
+
+function navigate(_path: string, record = true) {
+	if (record && path) history.push($$(path).value);
+	path = _path;
+	const resolved = resolve(path);
+	component = resolved.component;
+	props = resolved.props;
+}
+
+function back() {
+	const prev = history.pop();
+	if (prev) navigate(prev, false);
+}
+
+function close() {
+	path = null;
+	component = null;
+	props = {};
+}
+
+function onContextmenu(ev: MouseEvent) {
+	os.contextMenu([{
+		type: 'label',
+		text: path || '',
+	}, {
+		icon: 'fas fa-expand-alt',
+		text: i18n.ts.showInPage,
+		action: () => {
+			if (path) router.push(path);
+			close();
+		}
+	}, {
+		icon: 'fas fa-window-maximize',
+		text: i18n.ts.openInWindow,
+		action: () => {
+			if (path) os.pageWindow(path);
+			close();
+		}
+	}, null, {
+		icon: 'fas fa-external-link-alt',
+		text: i18n.ts.openInNewTab,
+		action: () => {
+			window.open(url, '_blank');
+			close();
+		}
+	}, {
+		icon: 'fas fa-link',
+		text: i18n.ts.copyLink,
+		action: () => {
+			copyToClipboard(url);
+		}
+	}], ev);
+}
+
+defineExpose({
+	navigate,
+	back,
+	close,
 });
 </script>
 
diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue
index 8fe9dcffaf..b0dfc5aadc 100644
--- a/packages/client/src/ui/universal.vue
+++ b/packages/client/src/ui/universal.vue
@@ -20,7 +20,7 @@
 		</main>
 	</div>
 
-	<XSideView v-if="isDesktop" ref="side" class="side"/>
+	<XSideView v-if="isDesktop" ref="sideEl" class="side"/>
 
 	<div v-if="isDesktop" ref="widgetsEl" class="widgets">
 		<XWidgets @mounted="attachSticky"/>
@@ -31,9 +31,9 @@
 	<div v-if="isMobile" class="buttons">
 		<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
 		<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
-		<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
+		<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
 		<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
-		<button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
+		<button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button>
 	</div>
 
 	<transition :name="$store.state.animation ? 'menuDrawer-back' : ''">
@@ -64,155 +64,133 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
 import { instanceName } from '@/config';
 import { StickySidebar } from '@/scripts/sticky-sidebar';
-import XSidebar from '@/ui/_common_/sidebar.vue';
 import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
 import XCommon from './_common_/common.vue';
 import XSideView from './classic.side.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { defaultStore } from '@/store';
-import * as EventEmitter from 'eventemitter3';
 import { menuDef } from '@/menu';
 import { useRoute } from 'vue-router';
 import { i18n } from '@/i18n';
+import { $i } from '@/account';
+const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
+const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/sidebar.vue'));
 
 const DESKTOP_THRESHOLD = 1100;
 const MOBILE_THRESHOLD = 500;
 
-export default defineComponent({
-	components: {
-		XCommon,
-		XSidebar,
-		XDrawerMenu,
-		XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')),
-		XSideView, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
-	},
-
-	setup() {
-		const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
-		const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
-		window.addEventListener('resize', () => {
-			isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
-		});
-
-		const pageInfo = ref();
-		const widgetsEl = ref<HTMLElement>();
-		const widgetsShowing = ref(false);
-
-		const sideViewController = new EventEmitter();
-
-		provide('sideViewHook', isDesktop.value ? (url) => {
-			sideViewController.emit('navigate', url);
-		} : null);
-
-		const menuIndicated = computed(() => {
-			for (const def in menuDef) {
-				if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
-				if (menuDef[def].indicated) return true;
-			}
-			return false;
-		});
-
-		const drawerMenuShowing = ref(false);
-
-		const route = useRoute();
-		watch(route, () => {
-			drawerMenuShowing.value = false;
-		});
-
-		document.documentElement.style.overflowY = 'scroll';
-
-		if (defaultStore.state.widgets.length === 0) {
-			defaultStore.set('widgets', [{
-				name: 'calendar',
-				id: 'a', place: 'right', data: {}
-			}, {
-				name: 'notifications',
-				id: 'b', place: 'right', data: {}
-			}, {
-				name: 'trends',
-				id: 'c', place: 'right', data: {}
-			}]);
-		}
-
-		onMounted(() => {
-			if (!isDesktop.value) {
-				window.addEventListener('resize', () => {
-					if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
-				}, { passive: true });
-			}
-		});
-
-		const changePage = (page) => {
-			if (page == null) return;
-			if (page[symbols.PAGE_INFO]) {
-				pageInfo.value = page[symbols.PAGE_INFO];
-				document.title = `${pageInfo.value.title} | ${instanceName}`;
-			}
-		};
-
-		const onContextmenu = (ev) => {
-			const isLink = (el: HTMLElement) => {
-				if (el.tagName === 'A') return true;
-				if (el.parentElement) {
-					return isLink(el.parentElement);
-				}
-			};
-			if (isLink(ev.target)) return;
-			if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
-			if (window.getSelection().toString() !== '') return;
-			const path = route.path;
-			os.contextMenu([{
-				type: 'label',
-				text: path,
-			}, {
-				icon: 'fas fa-columns',
-				text: i18n.ts.openInSideView,
-				action: () => {
-					this.$refs.side.navigate(path);
-				}
-			}, {
-				icon: 'fas fa-window-maximize',
-				text: i18n.ts.openInWindow,
-				action: () => {
-					os.pageWindow(path);
-				}
-			}], ev);
-		};
-
-		const attachSticky = (el) => {
-			const sticky = new StickySidebar(widgetsEl.value);
-			window.addEventListener('scroll', () => {
-				sticky.calc(window.scrollY);
-			}, { passive: true });
-		};
-
-		return {
-			pageInfo,
-			isDesktop,
-			isMobile,
-			widgetsEl,
-			widgetsShowing,
-			drawerMenuShowing,
-			menuIndicated,
-			wallpaper: localStorage.getItem('wallpaper') != null,
-			changePage,
-			top: () => {
-				window.scroll({ top: 0, behavior: 'smooth' });
-			},
-			onTransition: () => {
-				if (window._scroll) window._scroll();
-			},
-			post: os.post,
-			onContextmenu,
-			attachSticky,
-		};
-	},
+const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
+const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
+window.addEventListener('resize', () => {
+	isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
 });
+
+const pageInfo = ref();
+const widgetsEl = $ref<HTMLElement>();
+const widgetsShowing = ref(false);
+
+let sideEl = $ref<InstanceType<typeof XSideView>>();
+
+provide('sideViewHook', isDesktop.value ? (url) => {
+	sideEl.navigate(url);
+} : null);
+
+const menuIndicated = computed(() => {
+	for (const def in menuDef) {
+		if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
+		if (menuDef[def].indicated) return true;
+	}
+	return false;
+});
+
+const drawerMenuShowing = ref(false);
+
+const route = useRoute();
+watch(route, () => {
+	drawerMenuShowing.value = false;
+});
+
+document.documentElement.style.overflowY = 'scroll';
+
+if (defaultStore.state.widgets.length === 0) {
+	defaultStore.set('widgets', [{
+		name: 'calendar',
+		id: 'a', place: 'right', data: {}
+	}, {
+		name: 'notifications',
+		id: 'b', place: 'right', data: {}
+	}, {
+		name: 'trends',
+		id: 'c', place: 'right', data: {}
+	}]);
+}
+
+onMounted(() => {
+	if (!isDesktop.value) {
+		window.addEventListener('resize', () => {
+			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
+		}, { passive: true });
+	}
+});
+
+const changePage = (page) => {
+	if (page == null) return;
+	if (page[symbols.PAGE_INFO]) {
+		pageInfo.value = page[symbols.PAGE_INFO];
+		document.title = `${pageInfo.value.title} | ${instanceName}`;
+	}
+};
+
+const onContextmenu = (ev) => {
+	const isLink = (el: HTMLElement) => {
+		if (el.tagName === 'A') return true;
+		if (el.parentElement) {
+			return isLink(el.parentElement);
+		}
+	};
+	if (isLink(ev.target)) return;
+	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
+	if (window.getSelection()?.toString() !== '') return;
+	const path = route.path;
+	os.contextMenu([{
+		type: 'label',
+		text: path,
+	}, {
+		icon: 'fas fa-columns',
+		text: i18n.ts.openInSideView,
+		action: () => {
+			sideEl.navigate(path);
+		}
+	}, {
+		icon: 'fas fa-window-maximize',
+		text: i18n.ts.openInWindow,
+		action: () => {
+			os.pageWindow(path);
+		}
+	}], ev);
+};
+
+const attachSticky = (el) => {
+	const sticky = new StickySidebar(widgetsEl);
+	window.addEventListener('scroll', () => {
+		sticky.calc(window.scrollY);
+	}, { passive: true });
+};
+
+function top() {
+	window.scroll({ top: 0, behavior: 'smooth' });
+}
+
+function onTransition() {
+	if (window._scroll) window._scroll();
+}
+
+const wallpaper = localStorage.getItem('wallpaper') != null;
 </script>
 
 <style lang="scss" scoped>