feat: Implement replies, quotes and edits

This commit is contained in:
Jesse Wierzbinski 2024-12-01 18:29:54 +01:00
parent a1f0a00892
commit db4cb78f02
No known key found for this signature in database
6 changed files with 88 additions and 21 deletions

View file

@ -1,4 +1,8 @@
<template>
<div v-if="relation" class="rounded border overflow-auto max-h-72">
<Note :note="relation.note" :hide-actions="true" :small-layout="true" />
</div>
<Input v-model:model-value="state.contentWarning" v-if="state.sensitive"
placeholder="Put your content warning here" />
@ -82,7 +86,7 @@
<TooltipProvider>
<Tooltip>
<TooltipTrigger as="div">
<Toggle variant="default" size="sm" @update:pressed="i => state.sensitive = i">
<Toggle variant="default" size="sm" v-model:pressed="state.sensitive">
<TriangleAlert class="!size-5" />
</Toggle>
</TooltipTrigger>
@ -100,7 +104,7 @@
<script lang="ts" setup>
import type { ResponseError } from "@versia/client";
import type { Status } from "@versia/client/types";
import type { Status, StatusSource } from "@versia/client/types";
import {
AtSign,
FilePlus2,
@ -114,6 +118,7 @@ import {
} from "lucide-vue-next";
import { SelectTrigger } from "radix-vue";
import { toast } from "vue-sonner";
import Note from "~/components/notes/note.vue";
import { Select, SelectContent, SelectItem } from "~/components/ui/select";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
@ -136,16 +141,27 @@ const { relation } = defineProps<{
relation?: {
type: "reply" | "quote" | "edit";
note: Status;
source?: StatusSource;
};
}>();
const state = reactive({
content: "",
sensitive: false,
contentWarning: "",
content: relation?.source?.text || "",
sensitive: relation?.type === "edit" ? relation.note.sensitive : false,
contentWarning: relation?.type === "edit" ? relation.note.spoiler_text : "",
contentType: "text/markdown" as "text/markdown" | "text/plain",
visibility: "public" as Status["visibility"],
files: [] as {
visibility: (relation?.type === "edit"
? relation.note.visibility
: "public") as Status["visibility"],
files: (relation?.type === "edit"
? relation.note.media_attachments.map((m) => ({
apiId: m.id,
file: new File([], m.url),
alt: m.description,
uploading: false,
updating: false,
}))
: []) as {
apiId?: string;
file: File;
alt?: string;

View file

@ -1,5 +1,7 @@
<script setup lang="ts">
import { Dialog, DialogContent } from "@/components/ui/dialog";
import type { Status, StatusSource } from "@versia/client/types";
import { toast } from "vue-sonner";
import Composer from "./composer.vue";
useListen("composer:open", () => {
@ -8,17 +10,55 @@ useListen("composer:open", () => {
}
});
useListen("composer:edit", async (note) => {
const id = toast.loading("Loading note data...", {
duration: 0,
});
const { data: source } = await client.value.getStatusSource(note.id);
relation.value = {
type: "edit",
note,
source,
};
open.value = true;
toast.dismiss(id);
});
useListen("composer:reply", (note) => {
relation.value = {
type: "reply",
note,
};
open.value = true;
});
useListen("composer:quote", (note) => {
relation.value = {
type: "quote",
note,
};
open.value = true;
});
useListen("composer:close", () => {
open.value = false;
relation.value = null;
});
const open = ref(false);
const relation = ref(
null as {
type: "reply" | "quote" | "edit";
note: Status;
source?: StatusSource;
} | null,
);
</script>
<template>
<Dialog v-model:open="open">
<Dialog v-model:open="open" @update:open="o => {if (!o) { relation = null}}">
<DialogContent :hide-close="true" class="sm:max-w-xl max-w-full w-full grid-rows-[minmax(0,1fr)_auto] max-h-[90dvh] p-5 pt-6 top-0 sm:top-1/2 translate-y-0 sm:-translate-y-1/2">
<Composer />
<Composer :relation="relation ?? undefined" />
</DialogContent>
</Dialog>
</template>

View file

@ -1,6 +1,6 @@
<template>
<div class="flex flex-row w-full items-stretch justify-around text-sm *:max-w-28 *:w-full *:text-muted-foreground">
<Button variant="ghost">
<Button variant="ghost" @click="emit('reply')">
<Reply class="size-5 text-primary" />
{{ numberFormat(replyCount) }}
</Button>
@ -12,10 +12,10 @@
<Repeat class="size-5 text-primary" />
{{ numberFormat(reblogCount) }}
</Button>
<Button variant="ghost">
<Button variant="ghost" @click="emit('quote')">
<Quote class="size-5 text-primary" />
</Button>
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId">
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')">
<Button variant="ghost">
<Ellipsis class="size-5 text-primary" />
</Button>
@ -39,6 +39,12 @@ defineProps<{
authorId: string;
}>();
const emit = defineEmits<{
edit: [];
reply: [];
quote: [];
}>();
const numberFormat = (number = 0) =>
number !== 0
? new Intl.NumberFormat(undefined, {

View file

@ -11,7 +11,6 @@ import {
} from "@/components/ui/dropdown-menu";
import {
Ban,
Check,
Code,
Delete,
ExternalLink,
@ -22,7 +21,7 @@ import {
} from "lucide-vue-next";
import { toast } from "vue-sonner";
defineProps<{
const { authorId } = defineProps<{
apiNoteString: string;
isRemote: boolean;
url: string;
@ -30,7 +29,13 @@ defineProps<{
authorId: string;
}>();
const emit = defineEmits<{
edit: [];
}>();
const { copy } = useClipboard();
const loggedIn = !!identity.value;
const authorIsMe = loggedIn && authorId === identity.value?.account.id;
const copyText = (text: string) => {
copy(text);
@ -53,7 +58,7 @@ const blockUser = async (id: string) => {
<DropdownMenuLabel>Note Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem as="button">
<DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')">
<Pencil class="mr-2 size-4" />
<span>Edit</span>
<DropdownMenuShortcut>E</DropdownMenuShortcut>
@ -79,8 +84,8 @@ const blockUser = async (id: string) => {
<DropdownMenuShortcut>F</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuSeparator v-if="authorIsMe" />
<DropdownMenuGroup v-if="authorIsMe">
<DropdownMenuItem as="button">
<Delete class="mr-2 size-4" />
<span>Delete and redraft</span>
@ -91,8 +96,8 @@ const blockUser = async (id: string) => {
<DropdownMenuShortcut>D</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuSeparator v-if="loggedIn && !authorIsMe" />
<DropdownMenuGroup v-if="loggedIn && !authorIsMe">
<DropdownMenuItem as="button" :disabled="true">
<MessageSquare class="mr-2 size-4" />
<span>Report</span>

View file

@ -13,7 +13,7 @@
</CardContent>
<CardFooter v-if="!hideActions">
<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" />
: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" @edit="useEvent('composer:edit', note)" @reply="useEvent('composer:reply', note)" @quote="useEvent('composer:quote', note)" />
</CardFooter>
</Card>
</template>

View file

@ -32,7 +32,7 @@ import type { Notification } from "@versia/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import Button from "~/packages/ui/components/buttons/button.vue";
import Note from "../notes/note.vue";
//import Note from "../notes/note.vue";
import SmallCard from "../users/SmallCard.vue";
const props = defineProps<{