refactor: ♻️ Replace HeadlessUI with Ark UI, improve UI

This commit is contained in:
Jesse Wierzbinski 2024-06-04 14:03:15 -10:00
parent d109e09a72
commit 3c68c2e788
No known key found for this signature in database
15 changed files with 231 additions and 242 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -1,7 +1,6 @@
<template>
<ButtonsBase
class="bg-white/10 hover:bg-white/20 !text-left flex flex-row gap-x-3 !rounded-none !ring-0 !p-4 sm:!p-3">
<iconify-icon :icon="icon" width="1.25rem" height="1.25rem" class="text-gray-200" aria-hidden="true" />
<ButtonsBase class="hover:bg-white/20 !rounded-sm !text-left flex flex-row gap-x-3 !ring-0 !p-4 sm:!p-2">
<iconify-icon :icon="icon" width="none" class="text-gray-200 size-5" aria-hidden="true" />
<slot />
</ButtonsBase>
</template>

View file

@ -1,21 +1,12 @@
<template>
<div v-if="respondingTo" class="mb-4">
<OverlayScrollbarsComponent :defer="true" class="max-h-72 overflow-y-auto">
<LazySocialElementsNotesNote :note="respondingTo" :small="true" :disabled="true"
class="!rounded-none !bg-pink-500/10" />
</OverlayScrollbarsComponent>
</div>
<div class="px-6 py-4">
<div class="py-2 relative">
<div v-if="respondingTo" class="mb-4">
<span v-if="respondingType === 'reply'" class="text-gray-400 uppercase text-xs font-semibold">
<iconify-icon width="1rem" height="1rem" icon="tabler:arrow-back-up" class="text-gray-400 mb-0.5"
aria-hidden="true" />
Replying to
</span>
<span v-else-if="respondingType === 'quote'" class="text-gray-400 uppercase text-xs font-semibold">
<iconify-icon width="1rem" height="1rem" icon="tabler:quote" class="text-gray-400"
aria-hidden="true" />
Quoting
</span>
<OverlayScrollbarsComponent :defer="true" class="mt-2 max-h-72 overflow-y-auto">
<LazySocialElementsNotesNote :note="respondingTo" :small="true" :disabled="true" />
</OverlayScrollbarsComponent>
</div>
<div class="pb-2 relative">
<textarea :disabled="submitting" ref="textarea" v-model="content" :placeholder="chosenSplash"
class="resize-none min-h-48 prose prose-invert max-h-[70dvh] w-full p-0 focus:!ring-0 !ring-none !border-none !outline-none placeholder:text-zinc-500 bg-transparent appearance-none focus:!border-none focus:!outline-none disabled:cursor-not-allowed"></textarea>
<div

View file

@ -0,0 +1,52 @@
<template>
<HeadlessTransitionRoot as="template" :show="open">
<Dialog.Root v-model:open="open" :close-on-escape="true" :close-on-interact-outside="true"
@update:open="o => open = o">
<Teleport to="body">
<Dialog.Positioner
class="flex min-h-full items-start z-50 justify-center p-4 text-center sm:items-center sm:p-0 fixed inset-0 w-screen overflow-y-auto">
<HeadlessTransitionChild as="template" enter="ease-out duration-200" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100"
leave-to="opacity-0">
<Dialog.Backdrop class="fixed inset-0 bg-black/70" @click="open = false" />
</HeadlessTransitionChild>
<HeadlessTransitionChild as="template" enter="ease-out duration-200"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<Dialog.Content
class="relative transform overflow-hidden rounded-lg bg-dark-700 ring-1 ring-dark-800 text-left shadow-xl transition-all sm:my-8 w-full max-w-xl">
<Composer v-if="instance" :instance="instance" />
</Dialog.Content>
</HeadlessTransitionChild>
</Dialog.Positioner>
</Teleport>
</Dialog.Root>
</HeadlessTransitionRoot>
</template>
<script lang="ts" setup>
import { Dialog } from "@ark-ui/vue";
const open = ref(false);
useListen("note:reply", async (note) => {
open.value = true;
await nextTick();
useEvent("composer:reply", note);
});
useListen("note:quote", async (note) => {
open.value = true;
await nextTick();
useEvent("composer:quote", note);
});
useListen("composer:open", () => {
if (tokenData.value) open.value = true;
});
useListen("composer:close", () => {
open.value = false;
});
const log = console.log;
const tokenData = useTokenData();
const instance = useInstance();
</script>

View file

@ -1,48 +0,0 @@
<template>
<HeadlessTransitionRoot as="template" :show="open">
<HeadlessDialog as="div" class="relative z-50" @close="useEvent('composer:close')">
<HeadlessTransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<div class="fixed inset-0 bg-black/70 transition-opacity" />
</HeadlessTransitionChild>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-start justify-center p-4 text-center sm:items-center sm:p-0">
<HeadlessTransitionChild as="template" enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<HeadlessDialogPanel
class="relative transform rounded-lg bg-dark-700 ring-1 ring-dark-800 text-left shadow-xl transition-all sm:my-8 w-full max-w-xl">
<Composer v-if="instance" :instance="instance" />
</HeadlessDialogPanel>
</HeadlessTransitionChild>
</div>
</div>
</HeadlessDialog>
</HeadlessTransitionRoot>
</template>
<script lang="ts" setup>
const open = ref(false);
useListen("note:reply", async (note) => {
open.value = true;
await nextTick();
useEvent("composer:reply", note);
});
useListen("note:quote", async (note) => {
open.value = true;
await nextTick();
useEvent("composer:quote", note);
});
useListen("composer:open", () => {
if (tokenData.value) open.value = true;
});
useListen("composer:close", () => {
open.value = false;
});
const client = useMegalodon();
const tokenData = useTokenData();
const instance = useInstance();
</script>

View file

@ -1,29 +1,34 @@
<template>
<HeadlessMenu v-slot="{ close }" v-bind="$props">
<slot name="button"></slot>
<HeadlessMenuItems @click="close" class="fixed z-20 inset-0 z-5 bg-black/50">
</HeadlessMenuItems>
<transition enter-active-class="transition ease-in duration-100"
enter-from-class="transform opacity-0 translate-y-full sm:translate-y-0 scale-95"
enter-to-class="transform translate-y-0 opacity-100 scale-100"
leave-active-class="transition ease-out duration-75" leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95">
<HeadlessMenuItems
:class="['z-20 mt-2 rounded overflow-hidden bg-dark-900 shadow-lg ring-1 ring-white/10 focus:outline-none',
isSmallScreen ? 'bottom-0 fixed inset-x-0 w-full origin-bottom' : 'absolute right-0 origin-top-right top-full min-w-56']">
<div v-if="isSmallScreen" class="w-full bg-white/10 py-2">
<div class="rounded-full h-1 bg-gray-400 w-12 mx-auto"></div>
</div>
<slot name="items"></slot>
</HeadlessMenuItems>
</transition>
</HeadlessMenu>
<Menu.Root :positioning="{
strategy: 'fixed',
}" @update:open="(o) => open = o" :open="open">
<Menu.Trigger>
<slot name="button"></slot>
</Menu.Trigger>
<div @mousedown="open = false" @touchstart="open = false" v-if="open" class="fixed inset-0 z-10 bg-black/50">
</div>
<Menu.Positioner :class="isSmallScreen && '!bottom-0 !top-[unset] fixed inset-x-0 w-full !translate-y-0'">
<transition enter-active-class="transition ease-in duration-100"
enter-from-class="transform opacity-0 translate-y-full sm:translate-y-0 scale-95"
enter-to-class="transform translate-y-0 opacity-100 scale-100"
leave-active-class="transition ease-out duration-75" leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95">
<Menu.Content v-if="open"
:class="['z-20 mt-2 rounded overflow-hidden p-1 space-y-1 bg-dark-700 shadow-lg ring-1 ring-white/10 focus:outline-none min-w-56']">
<div v-if="isSmallScreen" class="w-full py-2">
<div class="rounded-full h-1 bg-gray-400 w-12 mx-auto"></div>
</div>
<slot name="items"></slot>
</Menu.Content>
</transition>
</Menu.Positioner>
</Menu.Root>
</template>
<script setup lang="ts">
import { Menu } from "@ark-ui/vue";
const { width } = useWindowSize();
const isSmallScreen = computed(() => width.value < 768);
const open = ref(false);
</script>

View file

@ -1,79 +0,0 @@
<template>
<header class="absolute inset-x-0 top-0 z-50">
<div
class="relative isolate warning-background flex items-center gap-x-6 overflow-hidden bg-dark-900 px-6 py-2.5 sm:px-3.5 sm:before:flex-1">
<div class="flex flex-wrap justify-center gap-x-4 gap-y-2 w-full">
<p class="text-sm text-gray-50 bg-dark-900 px-2 rounded py-1">
<strong class="font-semibold">Warning!</strong>&nbsp;&nbsp;This is a testing site used for
development, not a finished page.
</p>
</div>
<div class="flex justify-end">
</div>
</div>
<nav class="flex items-center justify-between p-6 lg:px-8" aria-label="Global">
<div class="flex lg:flex-1">
<NuxtLink href="/" class="-m-1.5 p-1.5">
<img crossorigin="anonymous" class="h-8 w-auto" src="/logo.webp" alt="Lysand logo" />
</NuxtLink>
</div>
<div class="flex lg:hidden">
<button type="button"
class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-200"
@click="mobileMenuOpen = true">
<span class="sr-only">Open main menu</span>
<iconify-icon icon="tabler:menu-2" width="1.5rem" height="1.5rem" aria-hidden="true" />
</button>
</div>
<div class="hidden lg:flex lg:gap-x-12">
<NuxtLink v-for="item in navigation" :key="item.name" :href="item.href"
class="text-sm font-semibold leading-6 text-gray-50">{{ item.name }}</NuxtLink>
</div>
<div class="hidden lg:flex lg:flex-1 lg:justify-end">
</div>
</nav>
<HeadlessDialog as="div" class="lg:hidden" @close="mobileMenuOpen = false" :open="mobileMenuOpen">
<div class="fixed inset-0 z-50" />
<HeadlessDialogPanel
class="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-dark-800 px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-50/10">
<div class="flex items-center justify-between">
<NuxtLink href="/" class="-m-1.5 p-1.5">
<img crossorigin="anonymous" class="h-8 w-auto" src="/logo.webp" alt="Lysand Logo" />
</NuxtLink>
<button type="button" class="-m-2.5 rounded-md p-2.5 text-gray-200" @click="mobileMenuOpen = false">
<span class="sr-only">Close menu</span>
<iconify-icon icon="tabler:x" width="1.5rem" height="1.5rem" aria-hidden="true" />
</button>
</div>
<div class="mt-6 flow-root">
<div class="-my-6 divide-y divide-gray-400/10">
<div class="space-y-2 py-6">
<NuxtLink v-for="item in navigation" :key="item.name" :href="item.href"
class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-50 hover:bg-gray-900">
{{
item.name }}</NuxtLink>
</div>
</div>
</div>
</HeadlessDialogPanel>
</HeadlessDialog>
</header>
</template>
<script lang="ts" setup>
const navigation = [
{ name: "About", href: "/" },
{ name: "Roadmap", href: "/" },
{ name: "Documentation", href: "/" },
{ name: "Team", href: "/" },
];
const mobileMenuOpen = ref(false);
</script>
<style>
.warning-background {
background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23f9d63d' fill-opacity='0.4' fill-rule='evenodd'%3E%3Cpath d='M0 40L40 0H20L0 20M40 40V20L20 40'/%3E%3C/g%3E%3C/svg%3E");
}
</style>

View file

@ -70,23 +70,20 @@
<ClientOnly>
<DropdownsAdaptiveDropdown>
<template #button>
<HeadlessMenuButton class="flex flex-col items-center justify-center p-2 rounded">
<button class="flex flex-col items-center justify-center p-2 rounded">
<iconify-icon icon="tabler:home" class="text-2xl" />
<span class="text-xs">Timelines</span>
</HeadlessMenuButton>
</button>
</template>
<template #items>
<ClientOnly>
<HeadlessMenuItem v-for="timeline in visibleTimelines" :key="timeline.href"
:href="timeline.href">
<NuxtLink>
<ButtonsDropdownElement :icon="timeline.icon" class="w-full">
{{ timeline.name }}
</ButtonsDropdownElement>
</NuxtLink>
</HeadlessMenuItem>
</ClientOnly>
<Menu.Item value="" v-for="timeline in visibleTimelines" :key="timeline.href">
<NuxtLink :href="timeline.href">
<ButtonsDropdownElement :icon="timeline.icon" class="w-full">
{{ timeline.name }}
</ButtonsDropdownElement>
</NuxtLink>
</Menu.Item>
</template>
</DropdownsAdaptiveDropdown>
<NuxtLink href="/notifications" class="flex flex-col items-center justify-center p-2 rounded">
@ -102,33 +99,33 @@
</ClientOnly>
<DropdownsAdaptiveDropdown v-else>
<template #button>
<HeadlessMenuButton class="flex flex-col items-center justify-center p-2 rounded">
<button class="flex flex-col items-center justify-center p-2 rounded">
<iconify-icon icon="tabler:user" class="text-2xl" />
<span class="text-xs">Account</span>
</HeadlessMenuButton>
</button>
</template>
<template #items>
<ClientOnly>
<HeadlessMenuItem v-if="tokenData">
<Menu.Item value="" v-if="tokenData">
<ButtonsDropdownElement icon="tabler:logout" class="w-full"
@click="signOut().finally(() => loadingAuth = false)" :loading="loadingAuth">
Sign Out
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem v-if="!tokenData">
</Menu.Item>
<Menu.Item value="" v-if="!tokenData">
<ButtonsDropdownElement icon="tabler:login" class="w-full"
@click="signIn().finally(() => loadingAuth = false)" :loading="loadingAuth">
Sign In
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem v-if="!tokenData">
</Menu.Item>
<Menu.Item value="" v-if="!tokenData">
<NuxtLink href="/register">
<ButtonsDropdownElement icon="tabler:certificate" class="w-full">
Register
</ButtonsDropdownElement>
</NuxtLink>
</HeadlessMenuItem>
</Menu.Item>
</ClientOnly>
</template>
</DropdownsAdaptiveDropdown>
@ -142,6 +139,7 @@
</template>
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
const { $pwa } = useNuxtApp();
const timelines = ref([
{
@ -167,6 +165,7 @@ const timelines = ref([
requiresAuth: true,
},
]);
const log = console.log;
const visibleTimelines = computed(() =>
timelines.value.filter(

View file

@ -0,0 +1,57 @@
<template>
<HeadlessTransitionRoot as="template" :show="lightbox">
<Dialog.Root v-model:open="lightbox" :close-on-escape="true" :close-on-interact-outside="true"
@update:open="o => lightbox = o">
<Teleport to="body">
<Dialog.Positioner class="z-50">
<HeadlessTransitionChild as="template" enter="ease-out duration-200" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100"
leave-to="opacity-0">
<Dialog.Backdrop class="fixed inset-0 bg-black/70 !z-40" @click="lightbox = false" />
</HeadlessTransitionChild>
<Dialog.Content
class="w-screen h-screen flex !z-50 justify-center items-center flex-col overflow-hidden p-10 fixed inset-0">
<div class="w-full absolute inset-x-0 top-0 p-10 shrink text-gray-400 flex flex-row gap-3">
<a @click.stop :href="attachment?.url" target="_blank" download class="ml-auto">
<iconify-icon icon="tabler:download" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</a>
<button @click.stop="lightbox = false">
<iconify-icon icon="tabler:x" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</button>
</div>
<HeadlessTransitionChild as="template" enter="ease-out duration-200"
enter-from="opacity-0 sm:scale-95" enter-to="opacity-100 sm:scale-100"
leave="ease-in duration-200" leave-from="opacity-100 sm:scale-100"
leave-to="opacity-0 sm:scale-95">
<img @click.stop v-if="attachment?.type === 'image'"
class="rounded max-w-full min-w-[30%] max-h-[70%]" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
</HeadlessTransitionChild>
<span @click.stop v-if="attachment?.description"
class="text-gray-300 rounded mt-6 -mb-20 px-4 py-2 max-w-xl ring-1 ring-white/5 bg-dark-900 max-h-40 overflow-y-auto">
{{ attachment.description }}
</span>
</Dialog.Content>
</Dialog.Positioner>
</Teleport>
</Dialog.Root>
</HeadlessTransitionRoot>
</template>t
<script lang="ts" setup>
import { Dialog } from "@ark-ui/vue";
import type { Attachment } from "~/types/mastodon/attachment";
const lightbox = ref(false);
const attachment = ref<Attachment | null>(null);
useListen("attachment:view", async (a) => {
attachment.value = a;
await nextTick();
lightbox.value = true;
});
</script>

View file

@ -1,55 +1,26 @@
<template>
<div @click="lightbox = true" tabindex="0" aria-label="Open attachment in lightbox" @keydown="lightbox = true"
<div tabindex="0" aria-label="Open attachment in lightbox"
class="aspect-video w-full rounded ring-white/5 shadow overflow-hidden ring-1 hover:ring-2 duration-100">
<img v-if="attachment.type === 'image'" tabindex="-1"
class="object-cover w-full h-full rounded duration-150 hover:scale-[102%] ease-in-out" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" @click="openLightbox"
@keydown="openLightbox" />
<video v-else-if="attachment.type === 'video'" class="object-cover w-full h-full rounded" controls
:alt="attachment.description ?? ''" :title="attachment.description ?? ''">
<source :src="attachment.url" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
<HeadlessTransitionRoot appear :show="lightbox" as="template">
<HeadlessDialog @close="lightbox = false">
<div class="fixed inset-0 overflow-y-auto z-50 bg-black/70">
<div class="flex min-h-full items-center justify-center text-center">
<HeadlessTransitionChild as="template" enter="duration-100 ease-out" enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100">
<HeadlessDialogPanel
class="w-screen h-screen flex justify-center items-center flex-col relative overflow-hidden p-10"
@click="lightbox = false">
<div class="w-full absolute inset-x-0 top-0 p-10 shrink text-gray-400 flex flex-row gap-3">
<a @click.stop :href="attachment.url" target="_blank" download class="ml-auto">
<iconify-icon icon="tabler:download" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</a>
<button @click.stop="lightbox = false">
<iconify-icon icon="tabler:x" width="1.5rem" height="1.5rem" />
<span class="sr-only">Close</span>
</button>
</div>
<img @click.stop v-if="attachment.type === 'image'"
class="rounded max-w-full min-w-[30%] max-h-[70%]" :src="attachment.url"
:alt="attachment.description ?? ''" :title="attachment.description ?? ''" />
<span @click.stop v-if="attachment.description"
class="text-gray-300 rounded mt-6 -mb-20 px-4 py-2 max-w-xl ring-1 ring-white/5 bg-dark-900 max-h-40 overflow-y-auto">
{{ attachment.description }}
</span>
</HeadlessDialogPanel>
</HeadlessTransitionChild>
</div>
</div>
</HeadlessDialog>
</HeadlessTransitionRoot>
</template>
<script lang="ts" setup>
import type { Attachment } from "~/types/mastodon/attachment";
const lightbox = ref(false);
defineProps<{
const props = defineProps<{
attachment: Attachment;
}>();
const openLightbox = () => {
useEvent("attachment:view", props.attachment);
};
</script>

View file

@ -24,17 +24,17 @@
class="text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.replies_count) }}</span>
</button>
<button class="group" :disabled="!isSignedIn">
<button class="group" @click="likeFn" :disabled="!isSignedIn">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart" v-if="!note?.favourited"
class="size-5 text-gray-200 group-hover:group-enabled:text-pink-600" aria-hidden="true" />
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart-filled" v-else
class="size-5 text-pink-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.favourites_count) }}</span>
</button>
<button class="group" :disabled="!isSignedIn">
<button class="group" @click="reblogFn" :disabled="!isSignedIn">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-if="!note?.reblogged"
class="size-5 text-gray-200 group-hover:group-enabled:text-green-600" aria-hidden="true" />
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat-off" v-else
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-else
class="size-5 text-green-600 group-hover:group-enabled:text-gray-200" aria-hidden="true" />
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(note?.reblogs_count) }}</span>
</button>
@ -45,32 +45,30 @@
</button>
<DropdownsAdaptiveDropdown>
<template #button>
<HeadlessMenuButton>
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots"
class="size-5 text-gray-200" aria-hidden="true" />
<span class="sr-only">Open menu</span>
</HeadlessMenuButton>
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
aria-hidden="true" />
<span class="sr-only">Open menu</span>
</template>
<template #items>
<HeadlessMenuItem>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(JSON.stringify(note, null, 4))" icon="tabler:code"
class="w-full">
Copy API
Response
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(url)" icon="tabler:link" class="w-full">
Copy Link
</ButtonsDropdownElement>
</HeadlessMenuItem>
<HeadlessMenuItem>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="remove" icon="tabler:backspace" :disabled="!isSignedIn"
class="w-full border-r-2 border-red-500">
Delete
</ButtonsDropdownElement>
</HeadlessMenuItem>
</Menu.Item>
</template>
</DropdownsAdaptiveDropdown>
</div>
@ -79,6 +77,7 @@
</template>
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import type { Status } from "~/types/mastodon/status";
@ -94,6 +93,8 @@ const props = withDefaults(
},
);
const noteRef = ref(props.note);
const tokenData = useTokenData();
const isSignedIn = useSignedIn();
const client = useMegalodon(tokenData);
@ -108,7 +109,7 @@ const {
reblog,
isReply,
reblogDisplayName,
} = useNoteData(ref(props.note), client);
} = useNoteData(noteRef, client);
const { copy } = useClipboard();
const numberFormat = (number = 0) =>
@ -117,4 +118,38 @@ const numberFormat = (number = 0) =>
compactDisplay: "short",
maximumFractionDigits: 1,
}).format(number);
const likeFn = async () => {
if (!note.value) return;
if (note.value.favourited) {
const output = await client.value?.unfavouriteStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
} else {
const output = await client.value?.favouriteStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
}
};
const reblogFn = async () => {
if (!note.value) return;
if (note.value?.reblogged) {
const output = await client.value?.unreblogStatus(note.value.id);
if (output?.data) {
noteRef.value = output.data;
}
} else {
const output = await client.value?.reblogStatus(note.value.id);
if (output?.data.reblog) {
noteRef.value = output.data.reblog;
}
}
};
</script>

View file

@ -1,4 +1,5 @@
import mitt from "mitt";
import type { Attachment } from "~/types/mastodon/attachment";
import type { Status } from "~/types/mastodon/status";
export type NotificationEvent = {
@ -24,6 +25,7 @@ type ApplicationEvents = {
"composer:send": Status;
"composer:close": undefined;
"notification:new": NotificationEvent;
"attachment:view": Attachment;
};
const emitter = mitt<ApplicationEvents>();

View file

@ -45,8 +45,11 @@ export const useNoteData = (
: null,
);
const url = computed(
() => `/@${renderedNote.value?.account.acct}/${renderedNote.value?.id}`,
const url = computed(() =>
new URL(
`/@${renderedNote.value?.account.acct}/${renderedNote.value?.id}`,
window.location.origin,
).toString(),
);
const remove = async () => {

View file

@ -38,6 +38,7 @@
</div>
</div>
<LazyComposerModal />
<LazySocialElementsNotesAttachmentDialog />
</template>
<script setup lang="ts">

View file

@ -28,6 +28,7 @@
"lint": "bunx @biomejs/biome check ."
},
"dependencies": {
"@ark-ui/vue": "^3.1.0",
"@nuxt/fonts": "^0.7.0",
"@tailwindcss/typography": "^0.5.13",
"@vee-validate/nuxt": "^4.13.0",