feat: Give more functionality to note menu

This commit is contained in:
Jesse Wierzbinski 2024-11-30 18:21:40 +01:00
parent 97566289cd
commit 49d356e2ab
No known key found for this signature in database
12 changed files with 95 additions and 35 deletions

View file

@ -15,7 +15,7 @@
<Button variant="ghost">
<Quote class="size-5 text-primary" />
</Button>
<Menu>
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId">
<Button variant="ghost">
<Ellipsis class="size-5 text-primary" />
</Button>
@ -32,6 +32,11 @@ defineProps<{
replyCount: number;
likeCount: number;
reblogCount: number;
apiNoteString: string;
isRemote: boolean;
url: string;
remoteUrl: string;
authorId: string;
}>();
const numberFormat = (number = 0) =>

View file

@ -4,36 +4,31 @@
<slot />
</span>
<span class="hidden group-hover:inline">
<span @click="copyText" v-if="!hasCopied"
<span @click="copyText"
class="select-none cursor-pointer space-x-1">
<Clipboard class="size-4 -translate-y-0.5 inline" />
Click to copy
</span>
<span v-else class="select-none space-x-1">
<Check class="size-4 -translate-y-0.5 inline" />
Copied!
</span>
</span>
</span>
</template>
<script lang="ts" setup>
<script lang="tsx" setup>
import { cn } from "@/lib/utils";
import { Check, Clipboard } from "lucide-vue-next";
import type { HTMLAttributes } from "vue";
import { toast } from "vue-sonner";
const { text } = defineProps<{
text: string;
class?: HTMLAttributes["class"];
}>();
const hasCopied = ref(false);
const { copy } = useClipboard();
const copyText = () => {
copy(text);
hasCopied.value = true;
setTimeout(() => {
hasCopied.value = false;
}, 2000);
toast("Copied to clipboard", {
icon: <Check class="size-5 text-green-500" />,
});
};
</script>

View file

@ -1,6 +1,6 @@
<template>
<NuxtLink :href="url" class="rounded flex flex-row items-center gap-3">
<div :class="cn('relative size-14', smallLayout && 'size-6')">
<div class="rounded flex flex-row items-center gap-3">
<NuxtLink :href="url" :class="cn('relative size-14', smallLayout && 'size-6')">
<Avatar :class="cn('size-14 rounded-md border border-card', smallLayout && 'size-6')">
<AvatarImage :src="avatar" alt="" />
<AvatarFallback class="rounded-lg"> AA </AvatarFallback>
@ -9,7 +9,7 @@
<AvatarImage :src="cornerAvatar" alt="" />
<AvatarFallback class="rounded-lg"> AA </AvatarFallback>
</Avatar>
</div>
</NuxtLink>
<div :class="cn('flex flex-col gap-0.5 justify-center flex-1 text-left leading-tight', smallLayout && 'flex-row justify-start items-center gap-2')">
<span class="truncate font-semibold">{{
displayName
@ -31,7 +31,7 @@
<component :is="visibilities[visibility].icon" class="size-5" />
</span>
</div>
</NuxtLink>
</div>
</template>
<script lang="ts" setup>

View file

@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="tsx">
import {
DropdownMenu,
DropdownMenuContent,
@ -11,6 +11,7 @@ import {
} from "@/components/ui/dropdown-menu";
import {
Ban,
Check,
Code,
Delete,
ExternalLink,
@ -19,6 +20,32 @@ import {
Pencil,
Trash,
} from "lucide-vue-next";
import { toast } from "vue-sonner";
defineProps<{
apiNoteString: string;
isRemote: boolean;
url: string;
remoteUrl: string;
authorId: string;
}>();
const { copy } = useClipboard();
const copyText = (text: string) => {
copy(text);
toast("Copied to clipboard", {
icon: <Check class="size-5 text-green-500" />,
});
};
const blockUser = async (id: string) => {
await client.value.blockAccount(id);
toast("User blocked", {
icon: <Ban class="size-5 text-destructive" />,
});
};
</script>
<template>
@ -30,27 +57,27 @@ import {
<DropdownMenuLabel>Note Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<DropdownMenuItem as="button">
<Pencil class="mr-2 size-4" />
<span>Edit</span>
<DropdownMenuShortcut>E</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(apiNoteString)">
<Code class="mr-2 size-4" />
<span>Copy API data</span>
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(url)">
<Link class="mr-2 size-4" />
<span>Copy link</span>
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="button" v-if="isRemote" @click="copyText(remoteUrl)">
<Link class="mr-2 size-4" />
<span>Copy link (origin)</span>
<DropdownMenuShortcut>K</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="remoteUrl">
<ExternalLink class="mr-2 size-4" />
<span>Open on remote</span>
<DropdownMenuShortcut>F</DropdownMenuShortcut>
@ -58,11 +85,11 @@ import {
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<DropdownMenuItem as="button">
<Delete class="mr-2 size-4" />
<span>Delete and redraft</span>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="button">
<Trash class="mr-2 size-4" />
<span>Delete</span>
<DropdownMenuShortcut>D</DropdownMenuShortcut>
@ -70,11 +97,11 @@ import {
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<DropdownMenuItem as="button" :disabled="true">
<MessageSquare class="mr-2 size-4" />
<span>Report</span>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem as="button" @click="blockUser(authorId)">
<Ban class="mr-2 size-4" />
<span>Block user</span>
</DropdownMenuItem>

View file

@ -1,18 +1,19 @@
<template>
<Card as="article" class="rounded-none border-0 duration-200 shadow-none">
<CardHeader class="pb-4" as="header">
<ReblogHeader v-if="note.reblog" :avatar="note.account.avatar"
:display-name="note.account.display_name" :url="reblogAccountUrl" />
<ReblogHeader v-if="note.reblog" :avatar="note.account.avatar" :display-name="note.account.display_name"
:url="reblogAccountUrl" />
<Header :avatar="noteToUse.account.avatar" :corner-avatar="note.reblog ? note.account.avatar : undefined"
:acct="noteToUse.account.acct" :display-name="noteToUse.account.display_name"
:visibility="noteToUse.visibility" :url="accountUrl" :created-at="new Date(noteToUse.created_at)" :small-layout="smallLayout" />
:visibility="noteToUse.visibility" :url="accountUrl" :created-at="new Date(noteToUse.created_at)"
:small-layout="smallLayout" />
</CardHeader>
<CardContent>
<Content :content="noteToUse.content" :quote="note.quote ?? undefined" />
</CardContent>
<CardFooter v-if="!hideActions">
<Actions :reply-count="noteToUse.replies_count" :like-count="noteToUse.favourites_count"
:reblog-count="noteToUse.reblogs_count" />
<Actions :reply-count="noteToUse.replies_count" :like-count="noteToUse.favourites_count" :url="url"
:api-note-string="JSON.stringify(note, null, 4)" :reblog-count="noteToUse.reblogs_count" :remote-url="noteToUse.url" :is-remote="isRemote" :author-id="noteToUse.account.id" />
</CardFooter>
</Card>
</template>
@ -34,7 +35,8 @@ const { note } = defineProps<{
// Notes can be reblogs, in which case the actual thing to render is inside the reblog property
const noteToUse = note.reblog ? note.reblog : note;
const url = `/@${noteToUse.account.acct}/${noteToUse.id}`;
const accountUrl = `/@${noteToUse.account.acct}`;
const reblogAccountUrl = `/@${note.account.acct}`;
const url = wrapUrl(`/@${noteToUse.account.acct}/${noteToUse.id}`);
const accountUrl = wrapUrl(`/@${noteToUse.account.acct}`);
const reblogAccountUrl = wrapUrl(`/@${note.account.acct}`);
const isRemote = noteToUse.account.acct.includes("@");
</script>