From 2cfed3395e50712c73248512fe7723394a299517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?=
 <root@acid-chicken.com>
Date: Fri, 5 May 2023 08:16:55 +0900
Subject: [PATCH] feat: condense acct (#10753)

* feat: condense acct

* fix: watch parent element size

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 .../components/global/MkAcct.stories.impl.ts  | 16 ++++++
 .../frontend/src/components/global/MkAcct.vue |  5 +-
 .../global/MkCondensedLine.stories.impl.ts    | 39 +++++++++++++
 .../src/components/global/MkCondensedLine.vue | 56 +++++++++++++++++++
 packages/frontend/src/components/index.ts     |  3 +
 5 files changed, 117 insertions(+), 2 deletions(-)
 create mode 100644 packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
 create mode 100644 packages/frontend/src/components/global/MkCondensedLine.vue

diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index d5e3fc3568..68202bb705 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -41,3 +41,19 @@ export const Detail = {
 		detail: true,
 	},
 } satisfies StoryObj<typeof MkAcct>;
+export const Long = {
+	...Default,
+	args: {
+		...Default.args,
+		user: {
+			...userDetailed(),
+			username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc',
+			host: 'nostr.example',
+		},
+	},
+	decorators: [
+		() => ({
+			template: '<div style="width: 360px;"><story/></div>',
+		}),
+	],
+} satisfies StoryObj<typeof MkAcct>;
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 2b9f892fc6..8a93a5adf7 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,13 +1,14 @@
 <template>
-<span>
+<MkCondensedLine>
 	<span>@{{ user.username }}</span>
 	<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
-</span>
+</MkCondensedLine>
 </template>
 
 <script lang="ts" setup>
 import * as misskey from 'misskey-js';
 import { toUnicode } from 'punycode/';
+import MkCondensedLine from './MkCondensedLine.vue';
 import { host as hostRaw } from '@/config';
 import { defaultStore } from '@/store';
 
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
new file mode 100644
index 0000000000..ce985bc59f
--- /dev/null
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -0,0 +1,39 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import MkCondensedLine from './MkCondensedLine.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkCondensedLine,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkCondensedLine>{{ props.text }}</MkCondensedLine>',
+		};
+	},
+	args: {
+		text: 'This is a condensed line.',
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkCondensedLine>;
+export const ContainerIs100px = {
+	...Default,
+	decorators: [
+		() => ({
+			template: '<div style="width: 100px;"><story/></div>',
+		}),
+	],
+} satisfies StoryObj<typeof MkCondensedLine>;
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
new file mode 100644
index 0000000000..e3c0a866b7
--- /dev/null
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -0,0 +1,56 @@
+<template>
+<span :class="$style.container">
+	<span ref="content" :class="$style.content">
+		<slot/>
+	</span>
+</span>
+</template>
+
+<script lang="ts">
+const contentSymbol = Symbol();
+const observer = new ResizeObserver((entries) => {
+	for (const entry of entries) {
+		const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement;
+		const container = content.parentElement as HTMLSpanElement;
+		const contentWidth = content.getBoundingClientRect().width;
+		const containerWidth = container.getBoundingClientRect().width;
+		container.style.transform = `scaleX(${Math.min(1, containerWidth / contentWidth)})`;
+	}
+});
+</script>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+
+const content = ref<HTMLSpanElement>();
+
+watch(content, (value, oldValue) => {
+	if (oldValue) {
+		delete oldValue[contentSymbol];
+		observer.unobserve(oldValue);
+		if (oldValue.parentElement) {
+			observer.unobserve(oldValue.parentElement);
+		}
+	}
+	if (value) {
+		value[contentSymbol] = contentSymbol;
+		observer.observe(value);
+		if (value.parentElement) {
+			observer.observe(value.parentElement);
+		}
+	}
+});
+</script>
+
+<style module lang="scss">
+.container {
+	display: inline-block;
+	width: 100%;
+	transform-origin: 0;
+}
+
+.content {
+	display: inline-block;
+	white-space: nowrap;
+}
+</style>
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 63e8fc225c..4ef8111da9 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -5,6 +5,7 @@ import MkA from './global/MkA.vue';
 import MkAcct from './global/MkAcct.vue';
 import MkAvatar from './global/MkAvatar.vue';
 import MkEmoji from './global/MkEmoji.vue';
+import MkCondensedLine from './global/MkCondensedLine.vue';
 import MkCustomEmoji from './global/MkCustomEmoji.vue';
 import MkUserName from './global/MkUserName.vue';
 import MkEllipsis from './global/MkEllipsis.vue';
@@ -33,6 +34,7 @@ export const components = {
 	MkAcct: MkAcct,
 	MkAvatar: MkAvatar,
 	MkEmoji: MkEmoji,
+	MkCondensedLine: MkCondensedLine,
 	MkCustomEmoji: MkCustomEmoji,
 	MkUserName: MkUserName,
 	MkEllipsis: MkEllipsis,
@@ -55,6 +57,7 @@ declare module '@vue/runtime-core' {
 		MkAcct: typeof MkAcct;
 		MkAvatar: typeof MkAvatar;
 		MkEmoji: typeof MkEmoji;
+		MkCondensedLine: typeof MkCondensedLine;
 		MkCustomEmoji: typeof MkCustomEmoji;
 		MkUserName: typeof MkUserName;
 		MkEllipsis: typeof MkEllipsis;