mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
feat: ✨ Implement replies, quotes and edits
This commit is contained in:
parent
a1f0a00892
commit
db4cb78f02
|
|
@ -1,4 +1,8 @@
|
||||||
<template>
|
<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"
|
<Input v-model:model-value="state.contentWarning" v-if="state.sensitive"
|
||||||
placeholder="Put your content warning here" />
|
placeholder="Put your content warning here" />
|
||||||
|
|
||||||
|
|
@ -82,7 +86,7 @@
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger as="div">
|
<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" />
|
<TriangleAlert class="!size-5" />
|
||||||
</Toggle>
|
</Toggle>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
|
@ -100,7 +104,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ResponseError } from "@versia/client";
|
import type { ResponseError } from "@versia/client";
|
||||||
import type { Status } from "@versia/client/types";
|
import type { Status, StatusSource } from "@versia/client/types";
|
||||||
import {
|
import {
|
||||||
AtSign,
|
AtSign,
|
||||||
FilePlus2,
|
FilePlus2,
|
||||||
|
|
@ -114,6 +118,7 @@ import {
|
||||||
} from "lucide-vue-next";
|
} from "lucide-vue-next";
|
||||||
import { SelectTrigger } from "radix-vue";
|
import { SelectTrigger } from "radix-vue";
|
||||||
import { toast } from "vue-sonner";
|
import { toast } from "vue-sonner";
|
||||||
|
import Note from "~/components/notes/note.vue";
|
||||||
import { Select, SelectContent, SelectItem } from "~/components/ui/select";
|
import { Select, SelectContent, SelectItem } from "~/components/ui/select";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
|
@ -136,16 +141,27 @@ const { relation } = defineProps<{
|
||||||
relation?: {
|
relation?: {
|
||||||
type: "reply" | "quote" | "edit";
|
type: "reply" | "quote" | "edit";
|
||||||
note: Status;
|
note: Status;
|
||||||
|
source?: StatusSource;
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
content: "",
|
content: relation?.source?.text || "",
|
||||||
sensitive: false,
|
sensitive: relation?.type === "edit" ? relation.note.sensitive : false,
|
||||||
contentWarning: "",
|
contentWarning: relation?.type === "edit" ? relation.note.spoiler_text : "",
|
||||||
contentType: "text/markdown" as "text/markdown" | "text/plain",
|
contentType: "text/markdown" as "text/markdown" | "text/plain",
|
||||||
visibility: "public" as Status["visibility"],
|
visibility: (relation?.type === "edit"
|
||||||
files: [] as {
|
? 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;
|
apiId?: string;
|
||||||
file: File;
|
file: File;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
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";
|
import Composer from "./composer.vue";
|
||||||
|
|
||||||
useListen("composer:open", () => {
|
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", () => {
|
useListen("composer:close", () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
|
relation.value = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
|
const relation = ref(
|
||||||
|
null as {
|
||||||
|
type: "reply" | "quote" | "edit";
|
||||||
|
note: Status;
|
||||||
|
source?: StatusSource;
|
||||||
|
} | null,
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row w-full items-stretch justify-around text-sm *:max-w-28 *:w-full *:text-muted-foreground">
|
<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" />
|
<Reply class="size-5 text-primary" />
|
||||||
{{ numberFormat(replyCount) }}
|
{{ numberFormat(replyCount) }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -12,10 +12,10 @@
|
||||||
<Repeat class="size-5 text-primary" />
|
<Repeat class="size-5 text-primary" />
|
||||||
{{ numberFormat(reblogCount) }}
|
{{ numberFormat(reblogCount) }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost" @click="emit('quote')">
|
||||||
<Quote class="size-5 text-primary" />
|
<Quote class="size-5 text-primary" />
|
||||||
</Button>
|
</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">
|
<Button variant="ghost">
|
||||||
<Ellipsis class="size-5 text-primary" />
|
<Ellipsis class="size-5 text-primary" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -39,6 +39,12 @@ defineProps<{
|
||||||
authorId: string;
|
authorId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
edit: [];
|
||||||
|
reply: [];
|
||||||
|
quote: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
const numberFormat = (number = 0) =>
|
const numberFormat = (number = 0) =>
|
||||||
number !== 0
|
number !== 0
|
||||||
? new Intl.NumberFormat(undefined, {
|
? new Intl.NumberFormat(undefined, {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
Ban,
|
Ban,
|
||||||
Check,
|
|
||||||
Code,
|
Code,
|
||||||
Delete,
|
Delete,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
|
@ -22,7 +21,7 @@ import {
|
||||||
} from "lucide-vue-next";
|
} from "lucide-vue-next";
|
||||||
import { toast } from "vue-sonner";
|
import { toast } from "vue-sonner";
|
||||||
|
|
||||||
defineProps<{
|
const { authorId } = defineProps<{
|
||||||
apiNoteString: string;
|
apiNoteString: string;
|
||||||
isRemote: boolean;
|
isRemote: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
|
|
@ -30,7 +29,13 @@ defineProps<{
|
||||||
authorId: string;
|
authorId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
edit: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
const { copy } = useClipboard();
|
const { copy } = useClipboard();
|
||||||
|
const loggedIn = !!identity.value;
|
||||||
|
const authorIsMe = loggedIn && authorId === identity.value?.account.id;
|
||||||
|
|
||||||
const copyText = (text: string) => {
|
const copyText = (text: string) => {
|
||||||
copy(text);
|
copy(text);
|
||||||
|
|
@ -53,7 +58,7 @@ const blockUser = async (id: string) => {
|
||||||
<DropdownMenuLabel>Note Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Note Actions</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem as="button">
|
<DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')">
|
||||||
<Pencil class="mr-2 size-4" />
|
<Pencil class="mr-2 size-4" />
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
<DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
|
<DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
|
||||||
|
|
@ -79,8 +84,8 @@ const blockUser = async (id: string) => {
|
||||||
<DropdownMenuShortcut>⌘F</DropdownMenuShortcut>
|
<DropdownMenuShortcut>⌘F</DropdownMenuShortcut>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator v-if="authorIsMe" />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup v-if="authorIsMe">
|
||||||
<DropdownMenuItem as="button">
|
<DropdownMenuItem as="button">
|
||||||
<Delete class="mr-2 size-4" />
|
<Delete class="mr-2 size-4" />
|
||||||
<span>Delete and redraft</span>
|
<span>Delete and redraft</span>
|
||||||
|
|
@ -91,8 +96,8 @@ const blockUser = async (id: string) => {
|
||||||
<DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
|
<DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator v-if="loggedIn && !authorIsMe" />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup v-if="loggedIn && !authorIsMe">
|
||||||
<DropdownMenuItem as="button" :disabled="true">
|
<DropdownMenuItem as="button" :disabled="true">
|
||||||
<MessageSquare class="mr-2 size-4" />
|
<MessageSquare class="mr-2 size-4" />
|
||||||
<span>Report</span>
|
<span>Report</span>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter v-if="!hideActions">
|
<CardFooter v-if="!hideActions">
|
||||||
<Actions :reply-count="noteToUse.replies_count" :like-count="noteToUse.favourites_count" :url="url"
|
<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>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import type { Notification } from "@versia/client/types";
|
||||||
import Avatar from "~/components/avatars/avatar.vue";
|
import Avatar from "~/components/avatars/avatar.vue";
|
||||||
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
||||||
import Button from "~/packages/ui/components/buttons/button.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";
|
import SmallCard from "../users/SmallCard.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue