mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 16:38:20 +01:00
feat: ✨ Add note editing capabilities
This commit is contained in:
parent
5a8e4e5d0f
commit
29d98c9f2c
|
|
@ -47,7 +47,9 @@
|
||||||
</ComposerButton>
|
</ComposerButton>
|
||||||
<ButtonsPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
|
<ButtonsPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
|
||||||
:disabled="!canSubmit || loading">
|
:disabled="!canSubmit || loading">
|
||||||
<span>Send!</span>
|
<span>{{
|
||||||
|
respondingType === "edit" ? "Edit!" : "Send!"
|
||||||
|
}}</span>
|
||||||
</ButtonsPrimary>
|
</ButtonsPrimary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,7 +70,7 @@ const { input: content } = useTextareaAutosize({
|
||||||
});
|
});
|
||||||
const { Control_Enter, Command_Enter, Control_Alt } = useMagicKeys();
|
const { Control_Enter, Command_Enter, Control_Alt } = useMagicKeys();
|
||||||
const respondingTo = ref<Status | null>(null);
|
const respondingTo = ref<Status | null>(null);
|
||||||
const respondingType = ref<"reply" | "quote" | null>(null);
|
const respondingType = ref<"reply" | "quote" | "edit" | null>(null);
|
||||||
const me = useMe();
|
const me = useMe();
|
||||||
const cw = ref(false);
|
const cw = ref(false);
|
||||||
const cwContent = ref("");
|
const cwContent = ref("");
|
||||||
|
|
@ -161,6 +163,30 @@ onMounted(() => {
|
||||||
content.value = `@${note.account.acct} `;
|
content.value = `@${note.account.acct} `;
|
||||||
textarea.value?.focus();
|
textarea.value?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useListen("composer:edit", async (note: Status) => {
|
||||||
|
loading.value = true;
|
||||||
|
files.value = note.media_attachments.map((file) => ({
|
||||||
|
id: nanoid(),
|
||||||
|
file: new File([], file.url),
|
||||||
|
progress: 1,
|
||||||
|
api_id: file.id,
|
||||||
|
alt_text: file.description ?? undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Fetch source
|
||||||
|
const source = await client.value?.getStatusSource(note.id);
|
||||||
|
|
||||||
|
if (source?.data) {
|
||||||
|
respondingTo.value = note;
|
||||||
|
respondingType.value = "edit";
|
||||||
|
content.value = source.data.text;
|
||||||
|
cwContent.value = source.data.spoiler_text;
|
||||||
|
textarea.value?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
|
@ -185,6 +211,46 @@ const client = useMegalodon(tokenData);
|
||||||
const send = async () => {
|
const send = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
|
if (respondingType.value === "edit") {
|
||||||
|
fetch(
|
||||||
|
new URL(
|
||||||
|
`/api/v1/statuses/${respondingTo.value?.id}`,
|
||||||
|
client.value?.baseUrl ?? "",
|
||||||
|
).toString(),
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${tokenData.value?.access_token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: content.value?.trim() ?? "",
|
||||||
|
content_type: markdown.value
|
||||||
|
? "text/markdown"
|
||||||
|
: "text/plain",
|
||||||
|
spoiler_text: cw.value ? cwContent.value.trim() : undefined,
|
||||||
|
sensitive: cw.value,
|
||||||
|
media_ids: files.value
|
||||||
|
.filter((file) => !!file.api_id)
|
||||||
|
.map((file) => file.api_id),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Failed to edit status");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.value = "";
|
||||||
|
loading.value = false;
|
||||||
|
useEvent("composer:send-edit", await res.json());
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
useEvent("composer:close");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetch(new URL("/api/v1/statuses", client.value?.baseUrl ?? "").toString(), {
|
fetch(new URL("/api/v1/statuses", client.value?.baseUrl ?? "").toString(), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Dialog.Positioner
|
<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">
|
class="flex items-start z-50 justify-center p-4 text-center sm:items-center sm:p-0 fixed inset-0 w-screen h-screen overflow-y-hidden">
|
||||||
<HeadlessTransitionChild as="template" enter="ease-out duration-200" enter-from="opacity-0"
|
<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"
|
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100"
|
||||||
leave-to="opacity-0">
|
leave-to="opacity-0">
|
||||||
|
|
@ -16,9 +16,11 @@
|
||||||
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
|
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-from="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||||
<Dialog.Content
|
<Dialog.Content class="overflow-y-auto w-full max-h-full md:py-16">
|
||||||
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">
|
<div
|
||||||
<Composer v-if="instance" :instance="instance" />
|
class="relative overflow-hidden max-w-xl mx-auto rounded-lg bg-dark-700 ring-1 ring-dark-800 text-left shadow-xl transition-all w-full">
|
||||||
|
<Composer v-if="instance" :instance="instance" />
|
||||||
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</HeadlessTransitionChild>
|
</HeadlessTransitionChild>
|
||||||
</Dialog.Positioner>
|
</Dialog.Positioner>
|
||||||
|
|
@ -40,6 +42,11 @@ useListen("note:quote", async (note) => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
useEvent("composer:quote", note);
|
useEvent("composer:quote", note);
|
||||||
});
|
});
|
||||||
|
useListen("note:edit", async (note) => {
|
||||||
|
open.value = true;
|
||||||
|
await nextTick();
|
||||||
|
useEvent("composer:edit", note);
|
||||||
|
});
|
||||||
useListen("composer:open", () => {
|
useListen("composer:open", () => {
|
||||||
if (tokenData.value) open.value = true;
|
if (tokenData.value) open.value = true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
</video>
|
</video>
|
||||||
<a v-else class="bg-dark-800 w-full h-full rounded flex items-center justify-center" :href="attachment.url"
|
<a v-else class="bg-dark-800 w-full h-full rounded flex items-center justify-center" :href="attachment.url"
|
||||||
target="_blank" download>
|
target="_blank" download>
|
||||||
<div class="flex flex-col items-center gap-2 max-w-56 overflow-hidden text-ellipsis">
|
<div class="flex flex-col items-center gap-2 text-center max-w-56 overflow-hidden text-ellipsis">
|
||||||
<iconify-icon icon="tabler:file" width="none" class="size-10 text-gray-300" />
|
<iconify-icon icon="tabler:file" width="none" class="size-10 text-gray-300" />
|
||||||
<p class="text-gray-300 text-sm font-mono">{{ getFilename(attachment.url) }}</p>
|
<p class="text-gray-300 text-sm font-mono">{{ getFilename(attachment.url) }}</p>
|
||||||
<p class="text-gray-300 text-xs" v-if="attachment.meta?.length">{{
|
<p class="text-gray-300 text-xs" v-if="attachment.meta?.length">{{
|
||||||
|
|
@ -60,7 +60,9 @@ const getFilename = (url: string) => {
|
||||||
if (url.includes("/media/proxy")) {
|
if (url.includes("/media/proxy")) {
|
||||||
// Decode last part of URL as base64url, which is the real URL
|
// Decode last part of URL as base64url, which is the real URL
|
||||||
const realUrl = atob(url.split("/").pop() ?? "");
|
const realUrl = atob(url.split("/").pop() ?? "");
|
||||||
return realUrl.substring(realUrl.lastIndexOf("/") + 1);
|
return decodeURIComponent(
|
||||||
|
realUrl.substring(realUrl.lastIndexOf("/") + 1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const path = new URL(url).pathname;
|
const path = new URL(url).pathname;
|
||||||
return path.substring(path.lastIndexOf("/") + 1);
|
return path.substring(path.lastIndexOf("/") + 1);
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,35 @@
|
||||||
<span><strong v-html="reblogDisplayName"></strong> reblogged</span>
|
<span><strong v-html="reblogDisplayName"></strong> reblogged</span>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
<SocialElementsNotesReplyHeader v-if="isReply" :account_id="note?.in_reply_to_account_id ?? null" />
|
<SocialElementsNotesReplyHeader v-if="isReply" :account_id="outputtedNote?.in_reply_to_account_id ?? null" />
|
||||||
<SocialElementsNotesHeader :note="note" :small="small" />
|
<SocialElementsNotesHeader :note="outputtedNote" :small="small" />
|
||||||
<LazySocialElementsNotesNoteContent :note="note" :loaded="loaded" :url="url" :content="content"
|
<LazySocialElementsNotesNoteContent :note="outputtedNote" :loaded="loaded" :url="url" :content="content"
|
||||||
:is-quote="isQuote" :should-hide="shouldHide" />
|
:is-quote="isQuote" :should-hide="shouldHide" />
|
||||||
<Skeleton class="!h-10 w-full mt-6" :enabled="!props.note || !loaded" v-if="!small || !showInteractions">
|
<Skeleton class="!h-10 w-full mt-6" :enabled="!props.note || !loaded" v-if="!small || !showInteractions">
|
||||||
<div v-if="showInteractions"
|
<div v-if="showInteractions"
|
||||||
class="mt-6 flex flex-row items-stretch disabled:*:opacity-70 [&>button]:max-w-28 disabled:*:cursor-not-allowed relative justify-around text-sm h-10 hover:enabled:[&>button]:bg-dark-800 [&>button]:duration-200 [&>button]:rounded [&>button]:flex [&>button]:flex-1 [&>button]:flex-row [&>button]:items-center [&>button]:justify-center">
|
class="mt-6 flex flex-row items-stretch disabled:*:opacity-70 [&>button]:max-w-28 disabled:*:cursor-not-allowed relative justify-around text-sm h-10 hover:enabled:[&>button]:bg-dark-800 [&>button]:duration-200 [&>button]:rounded [&>button]:flex [&>button]:flex-1 [&>button]:flex-row [&>button]:items-center [&>button]:justify-center">
|
||||||
<button class="group" @click="note && useEvent('note:reply', note)" :disabled="!isSignedIn">
|
<button class="group" @click="outputtedNote && useEvent('note:reply', outputtedNote)"
|
||||||
|
:disabled="!isSignedIn">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:arrow-back-up"
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:arrow-back-up"
|
||||||
class="text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
|
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>
|
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(outputtedNote?.replies_count) }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="group" @click="likeFn" :disabled="!isSignedIn">
|
<button class="group" @click="likeFn" :disabled="!isSignedIn">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart" v-if="!note?.favourited"
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:heart" v-if="!outputtedNote?.favourited"
|
||||||
class="size-5 text-gray-200 group-hover:group-enabled:text-pink-600" aria-hidden="true" />
|
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
|
<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" />
|
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>
|
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(outputtedNote?.favourites_count) }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="group" @click="reblogFn" :disabled="!isSignedIn">
|
<button class="group" @click="reblogFn" :disabled="!isSignedIn">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-if="!note?.reblogged"
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:repeat" v-if="!outputtedNote?.reblogged"
|
||||||
class="size-5 text-gray-200 group-hover:group-enabled:text-green-600" aria-hidden="true" />
|
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" 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" />
|
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>
|
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(outputtedNote?.reblogs_count) }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="group" @click="note && useEvent('note:quote', note)" :disabled="!isSignedIn">
|
<button class="group" @click="outputtedNote && useEvent('note:quote', outputtedNote)"
|
||||||
|
:disabled="!isSignedIn">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:quote"
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:quote"
|
||||||
class="size-5 text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
|
class="size-5 text-gray-200 group-hover:group-enabled:text-blue-600" aria-hidden="true" />
|
||||||
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(0) }}</span>
|
<span class="text-gray-400 mt-0.5 ml-2">{{ numberFormat(0) }}</span>
|
||||||
|
|
@ -51,9 +53,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #items>
|
<template #items>
|
||||||
|
<Menu.Item value="" v-if="isSignedIn && outputtedNote?.account.id === me?.id">
|
||||||
|
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:edit', outputtedNote)"
|
||||||
|
icon="tabler:pencil" class="w-full">
|
||||||
|
Edit
|
||||||
|
</ButtonsDropdownElement>
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<Menu.Item value="">
|
||||||
<ButtonsDropdownElement @click="copy(JSON.stringify(note, null, 4))" icon="tabler:code"
|
<ButtonsDropdownElement @click="copy(JSON.stringify(outputtedNote, null, 4))"
|
||||||
class="w-full">
|
icon="tabler:code" class="w-full">
|
||||||
Copy API
|
Copy API
|
||||||
Response
|
Response
|
||||||
</ButtonsDropdownElement>
|
</ButtonsDropdownElement>
|
||||||
|
|
@ -95,12 +103,19 @@ const props = withDefaults(
|
||||||
|
|
||||||
const noteRef = ref(props.note);
|
const noteRef = ref(props.note);
|
||||||
|
|
||||||
|
useListen("composer:send-edit", (note) => {
|
||||||
|
if (note.id === noteRef.value?.id) {
|
||||||
|
noteRef.value = note;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const tokenData = useTokenData();
|
const tokenData = useTokenData();
|
||||||
const isSignedIn = useSignedIn();
|
const isSignedIn = useSignedIn();
|
||||||
|
const me = useMe();
|
||||||
const client = useMegalodon(tokenData);
|
const client = useMegalodon(tokenData);
|
||||||
const {
|
const {
|
||||||
loaded,
|
loaded,
|
||||||
note,
|
note: outputtedNote,
|
||||||
remove,
|
remove,
|
||||||
content,
|
content,
|
||||||
shouldHide,
|
shouldHide,
|
||||||
|
|
@ -120,15 +135,19 @@ const numberFormat = (number = 0) =>
|
||||||
}).format(number);
|
}).format(number);
|
||||||
|
|
||||||
const likeFn = async () => {
|
const likeFn = async () => {
|
||||||
if (!note.value) return;
|
if (!outputtedNote.value) return;
|
||||||
if (note.value.favourited) {
|
if (outputtedNote.value.favourited) {
|
||||||
const output = await client.value?.unfavouriteStatus(note.value.id);
|
const output = await client.value?.unfavouriteStatus(
|
||||||
|
outputtedNote.value.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (output?.data) {
|
if (output?.data) {
|
||||||
noteRef.value = output.data;
|
noteRef.value = output.data;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const output = await client.value?.favouriteStatus(note.value.id);
|
const output = await client.value?.favouriteStatus(
|
||||||
|
outputtedNote.value.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (output?.data) {
|
if (output?.data) {
|
||||||
noteRef.value = output.data;
|
noteRef.value = output.data;
|
||||||
|
|
@ -137,15 +156,17 @@ const likeFn = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const reblogFn = async () => {
|
const reblogFn = async () => {
|
||||||
if (!note.value) return;
|
if (!outputtedNote.value) return;
|
||||||
if (note.value?.reblogged) {
|
if (outputtedNote.value?.reblogged) {
|
||||||
const output = await client.value?.unreblogStatus(note.value.id);
|
const output = await client.value?.unreblogStatus(
|
||||||
|
outputtedNote.value.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (output?.data) {
|
if (output?.data) {
|
||||||
noteRef.value = output.data;
|
noteRef.value = output.data;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const output = await client.value?.reblogStatus(note.value.id);
|
const output = await client.value?.reblogStatus(outputtedNote.value.id);
|
||||||
|
|
||||||
if (output?.data.reblog) {
|
if (output?.data.reblog) {
|
||||||
noteRef.value = output.data.reblog;
|
noteRef.value = output.data.reblog;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ type ApplicationEvents = {
|
||||||
"composer:open": undefined;
|
"composer:open": undefined;
|
||||||
"composer:reply": Status;
|
"composer:reply": Status;
|
||||||
"composer:quote": Status;
|
"composer:quote": Status;
|
||||||
|
"composer:edit": Status;
|
||||||
"composer:send": Status;
|
"composer:send": Status;
|
||||||
|
"composer:send-edit": Status;
|
||||||
"composer:close": undefined;
|
"composer:close": undefined;
|
||||||
"notification:new": NotificationEvent;
|
"notification:new": NotificationEvent;
|
||||||
"attachment:view": Attachment;
|
"attachment:view": Attachment;
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ export const useNoteData = (
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const mentions = useResolveMentions(
|
const mentions = useResolveMentions(
|
||||||
renderedNote.value?.mentions ?? [],
|
computed(() => renderedNote.value?.mentions ?? []),
|
||||||
client.value,
|
client.value,
|
||||||
);
|
);
|
||||||
const content = useParsedContent(
|
const content = useParsedContent(
|
||||||
renderedNote.value?.content ?? "",
|
computed(() => renderedNote.value?.content ?? ""),
|
||||||
renderedNote.value?.emojis ?? [],
|
computed(() => renderedNote.value?.emojis ?? []),
|
||||||
mentions,
|
mentions,
|
||||||
);
|
);
|
||||||
const loaded = computed(() => content.value !== null);
|
const loaded = computed(() => content.value !== null);
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,21 @@ import MentionComponent from "../components/social-elements/notes/mention.vue";
|
||||||
* @returns Reactive object with the parsed content
|
* @returns Reactive object with the parsed content
|
||||||
*/
|
*/
|
||||||
export const useParsedContent = (
|
export const useParsedContent = (
|
||||||
content: string,
|
content: MaybeRef<string>,
|
||||||
emojis: Emoji[],
|
emojis: MaybeRef<Emoji[]>,
|
||||||
mentions: MaybeRef<Account[]> = ref([]),
|
mentions: MaybeRef<Account[]> = ref([]),
|
||||||
): Ref<string | null> => {
|
): Ref<string | null> => {
|
||||||
const result = ref(null as string | null);
|
const result = ref(null as string | null);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
mentions,
|
isRef(content)
|
||||||
|
? isRef(emojis)
|
||||||
|
? [content, mentions, emojis]
|
||||||
|
: [content, mentions]
|
||||||
|
: mentions,
|
||||||
async () => {
|
async () => {
|
||||||
const contentHtml = document.createElement("div");
|
const contentHtml = document.createElement("div");
|
||||||
contentHtml.innerHTML = content;
|
contentHtml.innerHTML = toValue(content);
|
||||||
|
|
||||||
// Replace emoji shortcodes with images
|
// Replace emoji shortcodes with images
|
||||||
const paragraphs = contentHtml.querySelectorAll("p");
|
const paragraphs = contentHtml.querySelectorAll("p");
|
||||||
|
|
@ -29,7 +33,7 @@ export const useParsedContent = (
|
||||||
paragraph.innerHTML = paragraph.innerHTML.replace(
|
paragraph.innerHTML = paragraph.innerHTML.replace(
|
||||||
/:([a-z0-9_-]+):/g,
|
/:([a-z0-9_-]+):/g,
|
||||||
(match, emoji) => {
|
(match, emoji) => {
|
||||||
const emojiData = emojis.find(
|
const emojiData = toValue(emojis).find(
|
||||||
(e) => e.shortcode === emoji,
|
(e) => e.shortcode === emoji,
|
||||||
);
|
);
|
||||||
if (!emojiData) {
|
if (!emojiData) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Account } from "~/types/mastodon/account";
|
||||||
import type { Mention } from "~/types/mastodon/mention";
|
import type { Mention } from "~/types/mastodon/mention";
|
||||||
|
|
||||||
export const useResolveMentions = (
|
export const useResolveMentions = (
|
||||||
mentions: Mention[],
|
mentions: Ref<Mention[]>,
|
||||||
client: Mastodon | null,
|
client: Mastodon | null,
|
||||||
): Ref<Account[]> => {
|
): Ref<Account[]> => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
|
@ -12,14 +12,14 @@ export const useResolveMentions = (
|
||||||
|
|
||||||
const output = ref<Account[]>([]);
|
const output = ref<Account[]>([]);
|
||||||
|
|
||||||
(async () => {
|
watch(mentions, async () => {
|
||||||
output.value = await Promise.all(
|
output.value = await Promise.all(
|
||||||
mentions.map(async (mention) => {
|
toValue(mentions).map(async (mention) => {
|
||||||
const response = await client.getAccount(mention.id);
|
const response = await client.getAccount(mention.id);
|
||||||
return response.data;
|
return response.data;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
})();
|
});
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue