mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
refactor: 🔥 Remove more old code
This commit is contained in:
parent
48196026ee
commit
9098720b49
|
|
@ -1,39 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-bind="$attrs" class="bg-dark-700 overflow-hidden flex items-center justify-center">
|
|
||||||
<Skeleton :enabled="!imageLoaded" class="!h-full !w-full !rounded-none">
|
|
||||||
<img class="cursor-pointer ring-1 w-full h-full object-cover" :src="src" :alt="alt" />
|
|
||||||
</Skeleton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import Skeleton from "../skeleton/Skeleton.vue";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
inheritAttrs: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
src?: string;
|
|
||||||
alt?: string;
|
|
||||||
}>();
|
|
||||||
const imageLoaded = ref(false);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.src,
|
|
||||||
(src) => {
|
|
||||||
if (!src) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load in background
|
|
||||||
const img = new Image();
|
|
||||||
img.src = src;
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
imageLoaded.value = true;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative">
|
|
||||||
<textarea v-bind="$attrs" ref="textarea" v-model="content"
|
|
||||||
class="resize-none min-h-48 prose prose-invert w-full p-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"
|
|
||||||
aria-label="Compose your message" :autofocus="true"></textarea>
|
|
||||||
<div v-if="maxCharacters"
|
|
||||||
:class="['absolute bottom-0 right-0 p-2 text-gray-300 font-semibold text-xs', remainingCharacters < 0 && 'text-red-500']"
|
|
||||||
aria-live="polite">
|
|
||||||
{{ remainingCharacters }}
|
|
||||||
</div>
|
|
||||||
<EmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
|
|
||||||
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
|
|
||||||
<MentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
|
|
||||||
:currently-typing-mention="currentlyBeingTypedMention" @autocomplete="autocompleteMention" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { char, createRegExp, exactly } from "magic-regexp";
|
|
||||||
import EmojiSuggestbox from "../composer-old/emoji-suggestbox.vue";
|
|
||||||
import MentionSuggestbox from "../composer-old/mention-suggestbox.vue";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
inheritAttrs: false,
|
|
||||||
});
|
|
||||||
const props = defineProps<{
|
|
||||||
maxCharacters?: number;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const modelContent = defineModel<string>("modelContent", {
|
|
||||||
required: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const textarea = ref<HTMLTextAreaElement | undefined>(undefined);
|
|
||||||
const { input: content } = useTextareaAutosize({
|
|
||||||
element: textarea,
|
|
||||||
input: modelContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
const remainingCharacters = computed(
|
|
||||||
() =>
|
|
||||||
(props.maxCharacters ?? Number.POSITIVE_INFINITY) -
|
|
||||||
(content.value?.length ?? 0),
|
|
||||||
);
|
|
||||||
const currentlyBeingTypedEmoji = computed(() => {
|
|
||||||
const match = content.value?.match(partiallyTypedEmojiValidator);
|
|
||||||
return match ? (match.at(-1)?.replace(":", "") ?? "") : null;
|
|
||||||
});
|
|
||||||
const currentlyBeingTypedMention = computed(() => {
|
|
||||||
const match = content.value?.match(partiallyTypedMentionValidator);
|
|
||||||
return match ? (match.at(-1)?.replace("@", "") ?? "") : null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const autocompleteEmoji = (emoji: string) => {
|
|
||||||
// Replace the end of the string with the emoji
|
|
||||||
content.value = content.value?.replace(
|
|
||||||
createRegExp(
|
|
||||||
exactly(":"),
|
|
||||||
exactly(currentlyBeingTypedEmoji.value ?? "").notBefore(char),
|
|
||||||
),
|
|
||||||
`:${emoji}:`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const autocompleteMention = (mention: string) => {
|
|
||||||
// Replace the end of the string with the mention
|
|
||||||
content.value = content.value?.replace(
|
|
||||||
createRegExp(
|
|
||||||
exactly("@"),
|
|
||||||
exactly(currentlyBeingTypedMention.value ?? "").notBefore(char),
|
|
||||||
),
|
|
||||||
`@${mention} `,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<template>
|
|
||||||
<input :class="['block disabled:opacity-70 disabled:hover:cursor-wait w-full bg-dark-500 rounded-md border-0 py-1.5 text-gray-50 shadow-sm ring-1 ring-inset ring-white/10 placeholder:text-gray-500 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6',
|
|
||||||
isInvalid && '!ring-red-600 ring-2']" v-model="value">
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { InputHTMLAttributes } from "vue";
|
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ InputHTMLAttributes {
|
|
||||||
isInvalid?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = defineModel<string>("value");
|
|
||||||
|
|
||||||
defineProps<Props>();
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="w-full md:px-8 px-4 py-4 grid justify-center lg:grid-cols-[minmax(auto,_36rem)_1fr] grid-cols-1 gap-4">
|
|
||||||
<form class="w-full ring-1 ring-inset ring-white/5 pb-5 bg-dark-800 rounded overflow-hidden"
|
|
||||||
@submit.prevent="save">
|
|
||||||
<Avatar :src="account?.header" :alt="`${account?.acct}'s header image'`"
|
|
||||||
class="w-full aspect-[8/3] border-b border-white/10 bg-dark-700 !rounded-none" />
|
|
||||||
|
|
||||||
<div class="flex items-start justify-between px-4 py-3">
|
|
||||||
<Avatar :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
|
|
||||||
class="h-32 w-32 -mt-[4.5rem] z-10 shrink-0 rounded ring-2 ring-dark-800" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2 px-4">
|
|
||||||
<TextInput @input="displayName = ($event.target as HTMLInputElement).value" :value="displayName"
|
|
||||||
aria-label="Display name" :disabled="loading" />
|
|
||||||
<div class="mt-2 grid grid-cols-[auto_1fr] items-center gap-x-2">
|
|
||||||
<iconify-icon icon="tabler:at" width="none" class="size-6" aria-hidden="true" />
|
|
||||||
<TextInput @input="acct = ($event.target as HTMLInputElement).value" :value="acct"
|
|
||||||
aria-label="Username" :disabled="loading" />
|
|
||||||
</div>
|
|
||||||
<p class="text-gray-300 text-xs mt-2">
|
|
||||||
Changing your username will break all links to your profile.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 px-4">
|
|
||||||
<RichTextboxInput v-model:model-content="note" :max-characters="bio" :disabled="loading"
|
|
||||||
class="rounded ring-white/10 ring-2 focus:ring-primary-600 px-4 py-2 max-h-[40dvh] max-w-full" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-4 mt-4 grid grid-cols-2 gap-2">
|
|
||||||
<Button theme="primary" class="w-full" type="submit" :loading="loading">
|
|
||||||
<span>Save</span>
|
|
||||||
</Button>
|
|
||||||
<Button theme="secondary" class="w-full" @click="revert" type="button" :loading="loading">
|
|
||||||
<span>Revert</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<Oidc />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { ResponseError } from "@versia/client";
|
|
||||||
import Button from "~/packages/ui/components/buttons/button.vue";
|
|
||||||
import Avatar from "../avatars/avatar.vue";
|
|
||||||
import RichTextboxInput from "../inputs/rich-textbox-input.vue";
|
|
||||||
import TextInput from "../inputs/text-input.vue";
|
|
||||||
import Oidc from "./oidc.vue";
|
|
||||||
|
|
||||||
const account = computed(() => identity.value?.account);
|
|
||||||
const note = ref(account.value?.source?.note ?? "");
|
|
||||||
const displayName = ref(account.value?.display_name ?? "");
|
|
||||||
const acct = ref(account.value?.acct ?? "");
|
|
||||||
const bio = computed(
|
|
||||||
() => identity.value?.instance.configuration.statuses.max_characters ?? 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const revert = () => {
|
|
||||||
useEvent("notification:new", {
|
|
||||||
title: "Reverted to current bio",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
|
|
||||||
note.value = account.value?.source?.note ?? "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
const changedData = {
|
|
||||||
display_name:
|
|
||||||
displayName.value === account.value?.display_name
|
|
||||||
? undefined
|
|
||||||
: displayName.value,
|
|
||||||
username: acct.value === account.value?.acct ? undefined : acct.value,
|
|
||||||
note:
|
|
||||||
note.value === account.value?.source?.note ? undefined : note.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
Object.values(changedData).filter((v) => v !== undefined).length === 0
|
|
||||||
) {
|
|
||||||
useEvent("notification:new", {
|
|
||||||
title: "No changes",
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await client.value.updateCredentials(
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(changedData).filter(([, v]) => v !== undefined),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEvent("notification:new", {
|
|
||||||
title: "Profile updated",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (identity.value) {
|
|
||||||
identity.value.account = data;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const error = e as ResponseError<{ error: string }>;
|
|
||||||
|
|
||||||
useEvent("notification:new", {
|
|
||||||
title: "Failed to update profile",
|
|
||||||
description: error.response.data.error,
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-for="index of lines" :class="[
|
|
||||||
'duration-200 animate-pulse bg-dark-100 [&:not(:first-child)]:mt-2',
|
|
||||||
shape === 'circle' ? 'rounded-full' : 'rounded',
|
|
||||||
['text', 'content'].includes(type) && 'h-[1em]',
|
|
||||||
props.class
|
|
||||||
]" v-if="enabled" :style="{
|
|
||||||
width: getWidth(index, lines),
|
|
||||||
}">
|
|
||||||
</div>
|
|
||||||
<slot v-else />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
enabled: boolean;
|
|
||||||
shape?: "circle" | "rect";
|
|
||||||
type?: "text" | "content";
|
|
||||||
minWidth?: number;
|
|
||||||
maxWidth?: number;
|
|
||||||
widthUnit?: "px" | "%";
|
|
||||||
class?: string;
|
|
||||||
lines?: number;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
shape: "rect",
|
|
||||||
type: "text",
|
|
||||||
widthUnit: "px",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const isContent = computed(() => props.type === "content");
|
|
||||||
const isText = computed(() => props.type === "text");
|
|
||||||
const isWidthSpecified = computed(() => props.minWidth && props.maxWidth);
|
|
||||||
const calculatedWidth = computed(
|
|
||||||
() =>
|
|
||||||
Math.random() * ((props.maxWidth ?? 0) - (props.minWidth ?? 0)) +
|
|
||||||
(props.minWidth ?? 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
const getWidth = (index: number, lines: number) => {
|
|
||||||
if (isWidthSpecified.value) {
|
|
||||||
if (isContent.value) {
|
|
||||||
return index === lines
|
|
||||||
? `${calculatedWidth.value}${props.widthUnit}`
|
|
||||||
: "100%";
|
|
||||||
}
|
|
||||||
return `${calculatedWidth.value}${props.widthUnit}`;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const lines = isContent.value
|
|
||||||
? (props.lines ?? Math.ceil(Math.random() * 5))
|
|
||||||
: 1;
|
|
||||||
</script>
|
|
||||||
Loading…
Reference in a new issue