Skip to content

Commit c188d97

Browse files
committed
refactor: ♻️ Improve Note rendering structure
1 parent d6fe8db commit c188d97

File tree

5 files changed

+255
-190
lines changed

5 files changed

+255
-190
lines changed

components/dropdowns/AdaptiveDropdown.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Menu.Root :positioning="{
33
strategy: 'fixed',
44
}" @update:open="(o) => open = o" :open="open">
5-
<Menu.Trigger>
5+
<Menu.Trigger :as-child="true">
66
<slot name="button"></slot>
77
</Menu.Trigger>
88

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<button class="group disabled:opacity-70 max-w-28 disabled:cursor-not-allowed hover:enabled:bg-dark-800 duration-200 rounded flex flex-1 flex-row items-center justify-center">
3+
<slot />
4+
</button>
5+
</template>
6+
7+
<script lang="ts" setup>
8+
9+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<div
3+
class="mt-6 flex flex-row items-stretch relative justify-around text-sm h-10">
4+
<InteractionButton @click="useEvent('note:reply', note)"
5+
:disabled="!identity">
6+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:arrow-back-up"
7+
class="text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
8+
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note.replies_count) }}</span>
9+
</InteractionButton>
10+
<InteractionButton @click="likeFn" :disabled="!identity">
11+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart" v-if="!note.favourited"
12+
class="size-5 text-gray-200 group-hover:group-enabled:text-primary-600" aria-hidden="true" />
13+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart-filled" v-else
14+
class="size-5 text-primary-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
15+
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note.favourites_count) }}</span>
16+
</InteractionButton>
17+
<InteractionButton @click="reblogFn" :disabled="!identity">
18+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-if="!note.reblogged"
19+
class="size-5 text-gray-200 group-hover:group-enabled:text-green-600" aria-hidden="true" />
20+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-else
21+
class="size-5 text-green-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
22+
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note.reblogs_count) }}</span>
23+
</InteractionButton>
24+
<InteractionButton @click="useEvent('note:quote', note)"
25+
:disabled="!identity">
26+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:quote"
27+
class="size-5 text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
28+
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(0) }}</span>
29+
</InteractionButton>
30+
<NoteMenu v-model:note="note" :url="url" :remove="remove" />
31+
</div>
32+
</template>
33+
34+
<script lang="ts" setup>
35+
import type { Status } from "@versia/client/types";
36+
import NoteMenu from "../note-menu.vue";
37+
import InteractionButton from "./button.vue";
38+
39+
defineProps<{
40+
url: string;
41+
remove: () => Promise<void>;
42+
}>();
43+
44+
const note = defineModel<Status>("note", {
45+
required: true,
46+
});
47+
48+
const numberFormat = (number = 0) =>
49+
new Intl.NumberFormat(undefined, {
50+
notation: "compact",
51+
compactDisplay: "short",
52+
maximumFractionDigits: 1,
53+
}).format(number);
54+
55+
const likeFn = async () => {
56+
if (note.value.favourited) {
57+
const output = await client.value.unfavouriteStatus(note.value.id);
58+
59+
if (output?.data) {
60+
note.value = output.data;
61+
}
62+
} else {
63+
const output = await client.value.favouriteStatus(note.value.id);
64+
65+
if (output?.data) {
66+
note.value = output.data;
67+
}
68+
}
69+
};
70+
71+
const reblogFn = async () => {
72+
if (note.value?.reblogged) {
73+
const output = await client.value.unreblogStatus(note.value.id);
74+
75+
if (output?.data) {
76+
note.value = output.data;
77+
}
78+
} else {
79+
const output = await client.value.reblogStatus(note.value.id);
80+
81+
if (output?.data.reblog) {
82+
note.value = output.data.reblog;
83+
}
84+
}
85+
};
86+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<template>
2+
<AdaptiveDropdown>
3+
<template #button>
4+
<InteractionButton>
5+
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
6+
aria-hidden="true" />
7+
<span class="sr-only">Open menu</span>
8+
</InteractionButton>
9+
</template>
10+
11+
<template #items>
12+
<Menu.ItemGroup>
13+
<Menu.Item value="" v-if="isMyAccount">
14+
<ButtonDropdown @click="note && useEvent('note:edit', note)" icon="tabler:pencil" class="w-full">
15+
Edit
16+
</ButtonDropdown>
17+
</Menu.Item>
18+
<Menu.Item value="">
19+
<ButtonDropdown @click="copy(JSON.stringify(note, null, 4))" icon="tabler:code"
20+
class="w-full">
21+
Copy API
22+
response
23+
</ButtonDropdown>
24+
</Menu.Item>
25+
<Menu.Item value="">
26+
<ButtonDropdown @click="copy(url)" icon="tabler:link" class="w-full">
27+
Copy link
28+
</ButtonDropdown>
29+
</Menu.Item>
30+
<Menu.Item value="" v-if="note?.url && isRemote">
31+
<ButtonDropdown @click="copy(note.url)" icon="tabler:link" class="w-full">
32+
Copy link (origin)
33+
</ButtonDropdown>
34+
</Menu.Item>
35+
<Menu.Item value="" v-if="note?.url && isRemote">
36+
<ButtonDropdown @click="openBlank(note.url)" icon="tabler:external-link" class="w-full">
37+
View on remote
38+
</ButtonDropdown>
39+
</Menu.Item>
40+
<Menu.Item value="" v-if="isMyAccount">
41+
<ButtonDropdown @click="remove" icon="tabler:backspace" :disabled="!identity"
42+
class="w-full border-r-2 border-red-500">
43+
Delete
44+
</ButtonDropdown>
45+
</Menu.Item>
46+
</Menu.ItemGroup>
47+
<hr class="border-white/10 rounded" v-if="identity" />
48+
<Menu.ItemGroup v-if="identity">
49+
<Menu.Item value="">
50+
<ButtonDropdown @click="note && useEvent('note:reply', note)" icon="tabler:arrow-back-up"
51+
class="w-full">
52+
Reply
53+
</ButtonDropdown>
54+
</Menu.Item>
55+
<Menu.Item value="">
56+
<ButtonDropdown @click="likeFn" icon="tabler:heart" class="w-full" v-if="!note?.favourited">
57+
Like
58+
</ButtonDropdown>
59+
<ButtonDropdown @click="likeFn" icon="tabler:heart-filled" class="w-full" v-else>
60+
Unlike
61+
</ButtonDropdown>
62+
</Menu.Item>
63+
<Menu.Item value="">
64+
<ButtonDropdown @click="reblogFn" icon="tabler:repeat" class="w-full" v-if="!note?.reblogged">
65+
Reblog
66+
</ButtonDropdown>
67+
<ButtonDropdown @click="reblogFn" icon="tabler:repeat" class="w-full" v-else>
68+
Unreblog
69+
</ButtonDropdown>
70+
</Menu.Item>
71+
<Menu.Item value="">
72+
<ButtonDropdown @click="note && useEvent('note:quote', note)" icon="tabler:quote" class="w-full">
73+
Quote
74+
</ButtonDropdown>
75+
</Menu.Item>
76+
</Menu.ItemGroup>
77+
<hr class="border-white/10 rounded" v-if="identity" />
78+
<Menu.ItemGroup v-if="identity">
79+
<Menu.Item value="">
80+
<ButtonDropdown @click="note && useEvent('note:report', note)" icon="tabler:flag" class="w-full"
81+
:disabled="!permissions.includes(RolePermission.ManageOwnReports)">
82+
Report
83+
</ButtonDropdown>
84+
</Menu.Item>
85+
<Menu.Item value="" v-if="permissions.includes(RolePermission.ManageAccounts)">
86+
<ButtonDropdown icon="tabler:shield-bolt" class="w-full">
87+
Open moderation panel
88+
</ButtonDropdown>
89+
</Menu.Item>
90+
</Menu.ItemGroup>
91+
</template>
92+
</AdaptiveDropdown>
93+
</template>
94+
95+
<script lang="ts" setup>
96+
import { Menu } from "@ark-ui/vue";
97+
import { RolePermission, type Status } from "@versia/client/types";
98+
import ButtonDropdown from "~/components/buttons/button-dropdown.vue";
99+
import AdaptiveDropdown from "~/components/dropdowns/AdaptiveDropdown.vue";
100+
import InteractionButton from "./interactions/button.vue";
101+
102+
defineProps<{
103+
url: string;
104+
remove: () => Promise<void>;
105+
}>();
106+
107+
const note = defineModel<Status>("note", {
108+
required: true,
109+
});
110+
111+
const openBlank = (url: string) => window.open(url, "_blank");
112+
const { copy } = useClipboard();
113+
const isMyAccount = computed(
114+
() => identity.value?.account.id === note.value?.account.id,
115+
);
116+
const isRemote = computed(() => note.value?.account.acct.includes("@"));
117+
const permissions = usePermissions();
118+
119+
const likeFn = async () => {
120+
if (!note.value) {
121+
return;
122+
}
123+
if (note.value.favourited) {
124+
const output = await client.value.unfavouriteStatus(note.value.id);
125+
126+
if (output.data) {
127+
note.value = output.data;
128+
}
129+
} else {
130+
const output = await client.value.favouriteStatus(note.value.id);
131+
132+
if (output.data) {
133+
note.value = output.data;
134+
}
135+
}
136+
};
137+
138+
const reblogFn = async () => {
139+
if (!note.value) {
140+
return;
141+
}
142+
if (note.value.reblogged) {
143+
const output = await client.value.unreblogStatus(note.value.id);
144+
145+
if (output.data) {
146+
note.value = output.data;
147+
}
148+
} else {
149+
const output = await client.value.reblogStatus(note.value.id);
150+
151+
if (output.data.reblog) {
152+
note.value = output.data.reblog;
153+
}
154+
}
155+
};
156+
</script>

0 commit comments

Comments
 (0)