From cc441258dbf8c60786c6eb6c70f9b8a45622037f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 23 Dec 2021 16:10:13 +0900
Subject: [PATCH 1/9] enhance(client): tweak channel pages

---
 .../client/src/components/ui/pagination.vue   |  7 +-
 packages/client/src/pages/channel-editor.vue  | 34 ++++-----
 packages/client/src/pages/channel.vue         | 58 ++++++++------
 packages/client/src/pages/channels.vue        | 75 ++++++++++---------
 4 files changed, 97 insertions(+), 77 deletions(-)

diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 00200efd3c..64af4a54f7 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -5,7 +5,12 @@
 	<MkError v-else-if="error" @retry="init()"/>
 
 	<div v-else-if="empty" key="_empty_" class="empty">
-		<slot name="empty"></slot>
+		<slot name="empty">
+			<div class="_fullinfo">
+				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+				<div>{{ $ts.nothing }}</div>
+			</div>
+		</slot>
 	</div>
 
 	<div v-else class="cxiknjgy">
diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue
index 67b839bc3d..58c644be62 100644
--- a/packages/client/src/pages/channel-editor.vue
+++ b/packages/client/src/pages/channel-editor.vue
@@ -1,28 +1,26 @@
 <template>
-<div>
-	<div class="_section">
-		<div class="_content">
-			<MkInput v-model="name">
-				<template #label>{{ $ts.name }}</template>
-			</MkInput>
+<MkSpacer :content-max="700">
+	<div class="_formRoot">
+		<MkInput v-model="name" class="_formBlock">
+			<template #label>{{ $ts.name }}</template>
+		</MkInput>
 
-			<MkTextarea v-model="description">
-				<template #label>{{ $ts.description }}</template>
-			</MkTextarea>
+		<MkTextarea v-model="description" class="_formBlock">
+			<template #label>{{ $ts.description }}</template>
+		</MkTextarea>
 
-			<div class="banner">
-				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
-				<div v-else-if="bannerUrl">
-					<img :src="bannerUrl" style="width: 100%;"/>
-					<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
-				</div>
+		<div class="banner">
+			<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
+			<div v-else-if="bannerUrl">
+				<img :src="bannerUrl" style="width: 100%;"/>
+				<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
 			</div>
 		</div>
-		<div class="_footer">
+		<div class="_formBlock">
 			<MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton>
 		</div>
 	</div>
-</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
@@ -51,9 +49,11 @@ export default defineComponent({
 			[symbols.PAGE_INFO]: computed(() => this.channelId ? {
 				title: this.$ts._channel.edit,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
 			} : {
 				title: this.$ts._channel.create,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
 			}),
 			channel: null,
 			name: null,
diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue
index a328eacb07..67ab2d8981 100644
--- a/packages/client/src/pages/channel.vue
+++ b/packages/client/src/pages/channel.vue
@@ -1,29 +1,31 @@
 <template>
-<div v-if="channel" class="_section">
-	<div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }">
-		<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
-		<button class="_button toggle" @click="() => showBanner = !showBanner">
-			<template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
-			<template v-else><i class="fas fa-angle-down"></i></template>
-		</button>
-		<div v-if="!showBanner" class="hideOverlay">
-		</div>
-		<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
-			<div class="status">
-				<div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
-				<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+<MkSpacer :content-max="700">
+	<div v-if="channel">
+		<div class="wpgynlbz _panel _gap" :class="{ hide: !showBanner }">
+			<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
+			<button class="_button toggle" @click="() => showBanner = !showBanner">
+				<template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
+				<template v-else><i class="fas fa-angle-down"></i></template>
+			</button>
+			<div v-if="!showBanner" class="hideOverlay">
+			</div>
+			<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
+				<div class="status">
+					<div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
+					<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+				</div>
+				<div class="fade"></div>
+			</div>
+			<div v-if="channel.description" class="description">
+				<Mfm :text="channel.description" :is-note="false" :i="$i"/>
 			</div>
-			<div class="fade"></div>
-		</div>
-		<div v-if="channel.description" class="description">
-			<Mfm :text="channel.description" :is-note="false" :i="$i"/>
 		</div>
+
+		<XPostForm v-if="$i" :channel="channel" class="post-form _panel _gap" fixed/>
+
+		<XTimeline :key="channelId" class="_gap" src="channel" :channel="channelId" @before="before" @after="after"/>
 	</div>
-
-	<XPostForm v-if="$i" :channel="channel" class="post-form _content _panel _gap" fixed/>
-
-	<XTimeline :key="channelId" class="_content _gap" src="channel" :channel="channelId" @before="before" @after="after"/>
-</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
@@ -55,6 +57,12 @@ export default defineComponent({
 			[symbols.PAGE_INFO]: computed(() => this.channel ? {
 				title: this.channel.name,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
+				actions: [...(this.$i && this.$i.id === this.channel.userId ? [{
+					icon: 'fas fa-cog',
+					text: this.$ts.edit,
+					handler: this.edit,
+				}] : [])],
 			} : null),
 			channel: null,
 			showBanner: true,
@@ -79,8 +87,10 @@ export default defineComponent({
 		}
 	},
 
-	created() {
-
+	methods: {
+		edit() {
+			this.$router.push(`/channels/${this.channel.id}/edit`);
+		}
 	},
 });
 </script>
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index a08c273279..3bc62b5b56 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -1,58 +1,63 @@
 <template>
-<div>
-	<div v-if="$i" class="_section" style="padding: 0;">
-		<MkTab v-model="tab" class="_content">
-			<option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option>
-			<option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option>
-			<option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option>
-		</MkTab>
+<MkSpacer :content-max="700">
+	<div v-if="tab === 'featured'" class="_content grwlizim featured">
+		<MkPagination v-slot="{items}" :pagination="featuredPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
 	</div>
-
-	<div class="_section">
-		<div v-if="tab === 'featured'" class="_content grwlizim featured">
-			<MkPagination v-slot="{items}" :pagination="featuredPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
-
-		<div v-if="tab === 'following'" class="_content grwlizim following">
-			<MkPagination v-slot="{items}" :pagination="followingPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
-
-		<div v-if="tab === 'owned'" class="_content grwlizim owned">
-			<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
-			<MkPagination v-slot="{items}" :pagination="ownedPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
+	<div v-else-if="tab === 'following'" class="_content grwlizim following">
+		<MkPagination v-slot="{items}" :pagination="followingPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
 	</div>
-</div>
+	<div v-else-if="tab === 'owned'" class="_content grwlizim owned">
+		<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
+		<MkPagination v-slot="{items}" :pagination="ownedPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
+	</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
 import MkChannelPreview from '@/components/channel-preview.vue';
 import MkPagination from '@/components/ui/pagination.vue';
 import MkButton from '@/components/ui/button.vue';
-import MkTab from '@/components/tab.vue';
 import * as symbols from '@/symbols';
 
 export default defineComponent({
 	components: {
-		MkChannelPreview, MkPagination, MkButton, MkTab
+		MkChannelPreview, MkPagination, MkButton,
 	},
 	data() {
 		return {
-			[symbols.PAGE_INFO]: {
+			[symbols.PAGE_INFO]: computed(() => ({
 				title: this.$ts.channel,
 				icon: 'fas fa-satellite-dish',
-				action: {
+				bg: 'var(--bg)',
+				actions: [{
 					icon: 'fas fa-plus',
+					text: this.$ts.create,
 					handler: this.create
-				}
-			},
+				}],
+				tabs: [{
+					active: this.tab === 'featured',
+					title: this.$ts._channel.featured,
+					icon: 'fas fa-fire-alt',
+					onClick: () => { this.tab = 'featured'; },
+				}, {
+					active: this.tab === 'following',
+					title: this.$ts._channel.following,
+					icon: 'fas fa-heart',
+					onClick: () => { this.tab = 'following'; },
+				}, {
+					active: this.tab === 'owned',
+					title: this.$ts._channel.owned,
+					icon: 'fas fa-edit',
+					onClick: () => { this.tab = 'owned'; },
+				},]
+			})),
 			tab: 'featured',
 			featuredPagination: {
 				endpoint: 'channels/featured',

From 3a990dce7520eb1e4fabb2dbdf0860f2c60432e4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 23 Dec 2021 17:05:26 +0900
Subject: [PATCH 2/9] refactor(client): refactor

---
 packages/client/src/components/global/url.vue | 88 +++++--------------
 packages/client/src/pages/channels.vue        |  2 +-
 packages/client/src/scripts/use-tooltip.ts    |  3 +
 3 files changed, 28 insertions(+), 65 deletions(-)

diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue
index 097fcddef6..56a8c3453a 100644
--- a/packages/client/src/components/global/url.vue
+++ b/packages/client/src/components/global/url.vue
@@ -1,7 +1,5 @@
 <template>
-<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
-	@mouseover="onMouseover"
-	@mouseleave="onMouseleave"
+<component :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
 	@contextmenu.stop="() => {}"
 >
 	<template v-if="!self">
@@ -20,11 +18,11 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
 import { url as local } from '@/config';
-import { isTouchUsing } from '@/scripts/touch';
 import * as os from '@/os';
+import { useTooltip } from '@/scripts/use-tooltip';
 
 export default defineComponent({
 	props: {
@@ -35,74 +33,36 @@ export default defineComponent({
 		rel: {
 			type: String,
 			required: false,
+			default: null,
 		}
 	},
-	data() {
-		const self = this.url.startsWith(local);
+	setup(props) {
+		const self = props.url.startsWith(local);
+		const url = new URL(props.url);
+		const el = ref();
+		
+		useTooltip(el, (showing) => {
+			os.popup(import('@/components/url-preview-popup.vue'), {
+				showing,
+				url: props.url,
+				source: el.value,
+			}, {}, 'closed');
+		});
+
 		return {
 			local,
-			schema: null as string | null,
-			hostname: null as string | null,
-			port: null as string | null,
-			pathname: null as string | null,
-			query: null as string | null,
-			hash: null as string | null,
+			schema: url.protocol,
+			hostname: decodePunycode(url.hostname),
+			port: url.port,
+			pathname: decodeURIComponent(url.pathname),
+			query: decodeURIComponent(url.search),
+			hash: decodeURIComponent(url.hash),
 			self: self,
 			attr: self ? 'to' : 'href',
 			target: self ? null : '_blank',
-			showTimer: null,
-			hideTimer: null,
-			checkTimer: null,
-			close: null,
+			el,
 		};
 	},
-	created() {
-		const url = new URL(this.url);
-		this.schema = url.protocol;
-		this.hostname = decodePunycode(url.hostname);
-		this.port = url.port;
-		this.pathname = decodeURIComponent(url.pathname);
-		this.query = decodeURIComponent(url.search);
-		this.hash = decodeURIComponent(url.hash);
-	},
-	methods: {
-		async showPreview() {
-			if (!document.body.contains(this.$el)) return;
-			if (this.close) return;
-
-			const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), {
-				url: this.url,
-				source: this.$el
-			});
-
-			this.close = () => {
-				dispose();
-			};
-
-			this.checkTimer = setInterval(() => {
-				if (!document.body.contains(this.$el)) this.closePreview();
-			}, 1000);
-		},
-		closePreview() {
-			if (this.close) {
-				clearInterval(this.checkTimer);
-				this.close();
-				this.close = null;
-			}
-		},
-		onMouseover() {
-			if (isTouchUsing) return;
-			clearTimeout(this.showTimer);
-			clearTimeout(this.hideTimer);
-			this.showTimer = setTimeout(this.showPreview, 500);
-		},
-		onMouseleave() {
-			if (isTouchUsing) return;
-			clearTimeout(this.showTimer);
-			clearTimeout(this.hideTimer);
-			this.hideTimer = setTimeout(this.closePreview, 500);
-		}
-	}
 });
 </script>
 
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index 3bc62b5b56..48877ab3ec 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -39,7 +39,7 @@ export default defineComponent({
 				actions: [{
 					icon: 'fas fa-plus',
 					text: this.$ts.create,
-					handler: this.create
+					handler: this.create,
 				}],
 				tabs: [{
 					active: this.tab === 'featured',
diff --git a/packages/client/src/scripts/use-tooltip.ts b/packages/client/src/scripts/use-tooltip.ts
index 0df4baca7b..d0c6756eb1 100644
--- a/packages/client/src/scripts/use-tooltip.ts
+++ b/packages/client/src/scripts/use-tooltip.ts
@@ -18,6 +18,9 @@ export function useTooltip(
 	const open = () => {
 		close();
 		if (!isHovering) return;
+		if (elRef.value == null) return;
+		const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el;
+		if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
 
 		const showing = ref(true);
 		onShow(showing);

From 2e80f9dfba1e82b8b649c127138c2553f5cdf760 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 23 Dec 2021 17:05:50 +0900
Subject: [PATCH 3/9] clean up

---
 locales/ja-JP.yml                              | 1 -
 packages/client/src/components/global/a.vue    | 5 -----
 packages/client/src/pages/settings/general.vue | 8 --------
 packages/client/src/store.ts                   | 1 -
 4 files changed, 15 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index f85e8f3e00..62aade568d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -614,7 +614,6 @@ regenerateLoginToken: "ログイントークンを再生成"
 regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
 setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
 fileIdOrUrl: "ファイルIDまたはURL"
-chatOpenBehavior: "チャットを開くときの動作"
 behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue
index 5db61203c6..77ee7525a4 100644
--- a/packages/client/src/components/global/a.vue
+++ b/packages/client/src/components/global/a.vue
@@ -106,11 +106,6 @@ export default defineComponent({
 				return;
 			}
 
-			if (this.to.startsWith('/my/messaging')) {
-				if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window();
-				if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout();
-			}
-
 			if (this.behavior) {
 				if (this.behavior === 'window') {
 					return this.window();
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 83924382d8..734bc78442 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -77,13 +77,6 @@
 		<FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
 	</FormGroup>
 
-	<FormSelect v-model="chatOpenBehavior" class="_formBlock">
-		<template #label>{{ $ts.chatOpenBehavior }}</template>
-		<option value="page">{{ $ts.showInPage }}</option>
-		<option value="window">{{ $ts.openInWindow }}</option>
-		<option value="popout">{{ $ts.popout }}</option>
-	</FormSelect>
-
 	<FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink>
 
 	<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
@@ -149,7 +142,6 @@ export default defineComponent({
 		disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
 		showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
 		defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
-		chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
 		instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
 		enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
 		useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index 19d46bacea..dc9c3b7b9e 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -245,7 +245,6 @@ export class ColdDeviceStorage {
 		lightTheme: require('@/themes/l-light.json5') as Theme,
 		darkTheme: require('@/themes/d-dark.json5') as Theme,
 		syncDeviceDarkMode: true,
-		chatOpenBehavior: 'page' as 'page' | 'window' | 'popout',
 		plugins: [] as Plugin[],
 		mediaVolume: 0.5,
 		sound_masterVolume: 0.3,

From 99ced12ac5c7ef5d4f835cdf054abdd55f888b5a Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 24 Dec 2021 01:07:04 +0900
Subject: [PATCH 4/9] fix(client): fix zindex issue

Fix #8086
---
 CHANGELOG.md                                  | 7 +++++++
 packages/client/src/components/media-list.vue | 9 +++++++++
 2 files changed, 16 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e15c6d6463..743479ae4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@
 
 -->
 
+## 12.x.x (unreleased)
+
+### Improvements
+
+### Bugfixes
+- クライアント: 一部のコンポーネントが裏に隠れるのを修正
+
 ## 12.100.2 (2021/12/18)
 
 ### Bugfixes
diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue
index c987ff5ff1..2970d06c97 100644
--- a/packages/client/src/components/media-list.vue
+++ b/packages/client/src/components/media-list.vue
@@ -105,6 +105,7 @@ export default defineComponent({
 		return {
 			previewable,
 			gallery,
+			pswpZIndex: os.claimZIndex('middle'),
 		};
 	},
 });
@@ -188,3 +189,11 @@ export default defineComponent({
 	}
 }
 </style>
+
+<style lang="scss">
+.pswp {
+	// なぜか機能しない
+  //z-index: v-bind(pswpZIndex);
+	z-index: 2000000;
+}
+</style>

From 1122f7281ec9b95205f4ee8aa8462daae98ebbed Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Fri, 24 Dec 2021 01:16:58 +0900
Subject: [PATCH 5/9] =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=83=9A?=
 =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=81=A7Renote=E3=81=A7=E3=81=82=E3=82=8B?=
 =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=ABnoindex=E3=82=92=E4=BB=98=E5=8A=A0?=
 =?UTF-8?q?=20(#8074)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/backend/src/server/web/views/note.pug | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 7030936975..fce91bdabe 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -4,6 +4,7 @@ block vars
 	- const user = note.user;
 	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
 	- const url = `${config.url}/notes/${note.id}`;
+	- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
 
 block title
 	= `${title} | ${instanceName}`
@@ -19,7 +20,7 @@ block og
 	meta(property='og:image'       content= user.avatarUrl)
 
 block meta
-	if user.host || profile.noCrawle
+	if user.host || isRenote || profile.noCrawle
 		meta(name='robots' content='noindex')
 
 	meta(name='misskey:user-username' content=user.username)

From 2294e9ffdce9091a412cb3a4e1b04e5056c013e6 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 24 Dec 2021 01:31:37 +0900
Subject: [PATCH 6/9] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fe3df853b7..633995c947 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -87,7 +87,7 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows).
 
 ## Vue
 Misskey uses Vue(v3) as its front-end framework.
-**When creating a new component, please use the Composition API instead of the Options API.**
+**When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.**
 Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
 
 ## Adding MisskeyRoom items

From b4636631751449da34e2bad7e7276546c6fd3967 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 24 Dec 2021 12:34:24 +0900
Subject: [PATCH 7/9] enhance(client): tweak ui

---
 .../client/src/components/page-window.vue     | 26 ++++++++++++++++++-
 packages/client/src/components/ui/window.vue  |  5 +++-
 packages/client/src/scripts/use-tooltip.ts    |  7 ++++-
 3 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/packages/client/src/components/page-window.vue b/packages/client/src/components/page-window.vue
index 39c185b3e0..ec7451d5aa 100644
--- a/packages/client/src/components/page-window.vue
+++ b/packages/client/src/components/page-window.vue
@@ -16,7 +16,13 @@
 	<template #headerLeft>
 		<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
 	</template>
-	<div class="yrolvcoq">
+	<template #headerRight>
+		<button v-tooltip="$ts.showInPage" class="_button" @click="expand()"><i class="fas fa-expand-alt"></i></button>
+		<button v-tooltip="$ts.popout" class="_button" @click="popout()"><i class="fas fa-external-link-alt"></i></button>
+		<button class="_button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
+	</template>
+
+	<div class="yrolvcoq" :style="{ background: pageInfo?.bg }">
 		<MkStickyContainer>
 			<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
 			<component :is="component" v-bind="props" :ref="changePage"/>
@@ -33,6 +39,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { resolve } from '@/router';
 import { url } from '@/config';
 import * as symbols from '@/symbols';
+import * as os from '@/os';
 
 export default defineComponent({
 	components: {
@@ -139,6 +146,23 @@ export default defineComponent({
 			this.props = props;
 		},
 
+		menu(ev) {
+			os.popupMenu([{
+				icon: 'fas fa-external-link-alt',
+				text: this.$ts.openInNewTab,
+				action: () => {
+					window.open(this.url, '_blank');
+					this.$refs.window.close();
+				}
+			}, {
+				icon: 'fas fa-link',
+				text: this.$ts.copyLink,
+				action: () => {
+					copyToClipboard(this.url);
+				}
+			}], ev.currentTarget || ev.target);
+		},
+
 		back() {
 			this.navigate(this.history.pop(), false);
 		},
diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue
index d01498d8df..bd33289ccc 100644
--- a/packages/client/src/components/ui/window.vue
+++ b/packages/client/src/components/ui/window.vue
@@ -414,6 +414,10 @@ export default defineComponent({
 				}
 			}
 
+			> .left {
+				min-width: 16px;
+			}
+
 			> .title {
 				flex: 1;
 				position: relative;
@@ -421,7 +425,6 @@ export default defineComponent({
 				white-space: nowrap;
 				overflow: hidden;
 				text-overflow: ellipsis;
-				text-align: center;
 				cursor: move;
 			}
 		}
diff --git a/packages/client/src/scripts/use-tooltip.ts b/packages/client/src/scripts/use-tooltip.ts
index d0c6756eb1..bc8f27a038 100644
--- a/packages/client/src/scripts/use-tooltip.ts
+++ b/packages/client/src/scripts/use-tooltip.ts
@@ -1,4 +1,4 @@
-import { Ref, ref, watch } from 'vue';
+import { Ref, ref, watch, onUnmounted } from 'vue';
 
 export function useTooltip(
 	elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
@@ -72,9 +72,14 @@ export function useTooltip(
 			el.addEventListener('mouseleave', onMouseleave, { passive: true });
 			el.addEventListener('touchstart', onTouchstart, { passive: true });
 			el.addEventListener('touchend', onTouchend, { passive: true });
+			el.addEventListener('click', close, { passive: true });
 		}
 	}, {
 		immediate: true,
 		flush: 'post',
 	});
+
+	onUnmounted(() => {
+		close();
+	});
 }

From 8e7744a69582a05f43944d0db01ec4064988eca8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 24 Dec 2021 23:25:23 +0900
Subject: [PATCH 8/9] =?UTF-8?q?fix(client):=20=E3=83=89=E3=83=AD=E3=83=AF?=
 =?UTF-8?q?=E3=83=BC=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=A7=E3=82=BB?=
 =?UTF-8?q?=E3=83=BC=E3=83=95=E3=82=A8=E3=83=AA=E3=82=A2=E3=82=92=E8=80=83?=
 =?UTF-8?q?=E6=85=AE=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/client/src/components/ui/menu.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index 869709cf21..6f3f277b11 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -284,7 +284,7 @@ export default defineComponent({
 	}
 
 	&.asDrawer {
-		padding: 12px 0;
+		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
 		width: 100%;
 
 		> .item {

From c77fe1f2cf5da7c111e9008d869c702644075d44 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 25 Dec 2021 02:01:35 +0900
Subject: [PATCH 9/9] Truncate push notification message (#8089)

* Truncate push notification message

* fix
---
 .../backend/src/services/push-notification.ts | 27 ++++++++++++++++++-
 .../client/src/sw/compose-notification.ts     | 13 +++++----
 2 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts
index 616bc74411..2133768a96 100644
--- a/packages/backend/src/services/push-notification.ts
+++ b/packages/backend/src/services/push-notification.ts
@@ -3,10 +3,33 @@ import config from '@/config/index';
 import { SwSubscriptions } from '@/models/index';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { Packed } from '@/misc/schema';
+import { getNoteSummary } from '@/misc/get-note-summary';
 
 type notificationType = 'notification' | 'unreadMessagingMessage';
 type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>;
 
+// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
+function truncateNotification(notification: Packed<'Notification'>): any {
+	if (notification.note) {
+		return {
+			...notification,
+			note: {
+				...notification.note,
+				// textをgetNoteSummaryしたものに置き換える
+				text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
+				...{
+					cw: undefined,
+					reply: undefined,
+					renote: undefined,
+					user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
+				}
+			}
+		};
+	}
+
+	return notification;
+}
+
 export default async function(userId: string, type: notificationType, body: notificationBody) {
 	const meta = await fetchMeta();
 
@@ -32,7 +55,9 @@ export default async function(userId: string, type: notificationType, body: noti
 		};
 
 		push.sendNotification(pushSubscription, JSON.stringify({
-			type, body,
+			type,
+			body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body,
+			userId,
 		}), {
 			proxy: config.proxy,
 		}).catch((err: any) => {
diff --git a/packages/client/src/sw/compose-notification.ts b/packages/client/src/sw/compose-notification.ts
index 0aed9610ea..10bd27c9ec 100644
--- a/packages/client/src/sw/compose-notification.ts
+++ b/packages/client/src/sw/compose-notification.ts
@@ -3,7 +3,6 @@
  */
 declare var self: ServiceWorkerGlobalScope;
 
-import { getNoteSummary } from '@/scripts/get-note-summary';
 import * as misskey from 'misskey-js';
 
 function getUserName(user: misskey.entities.User): string {
@@ -26,37 +25,37 @@ export default async function(type, data, i18n): Promise<[string, NotificationOp
 			switch (data.type) {
 				case 'mention':
 					return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'reply':
 					return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'renote':
 					return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'quote':
 					return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'reaction':
 					return [`${data.reaction} ${getUserName(data.user)}`, {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'pollVote':
 					return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];