refactor: ♻️ Disable Nuxt component auto-importing (obscures code flow)

This commit is contained in:
Jesse Wierzbinski 2024-06-20 16:09:09 -10:00
parent 32d1acb4c1
commit e309c56a86
No known key found for this signature in database
58 changed files with 440 additions and 292 deletions

View file

@ -1,3 +1,4 @@
{
"conventionalCommits.scopes": ["build"]
"conventionalCommits.scopes": ["build"],
"typescript.tsdk": "node_modules/typescript/lib"
}

View file

@ -13,6 +13,8 @@
import "~/styles/theme.css";
import { convert } from "html-to-text";
import "iconify-icon";
import Loading from "./components/loading.vue";
import NotificationsRenderer from "./components/notifications/notifications-renderer.vue";
// Use SSR-safe IDs for Headless UI
provideHeadlessUseId(() => useId());

View file

@ -8,6 +8,7 @@
<script lang="ts" setup>
import type { HTMLAttributes } from "vue";
import Skeleton from "../skeleton/Skeleton.vue";
interface Props extends /* @vue-ignore */ HTMLAttributes {
src?: string;

View file

@ -1,12 +1,13 @@
<template>
<ButtonsBase class="enabled:hover:bg-white/20 !rounded-sm !text-left flex flex-row gap-x-3 !ring-0 !p-4 sm:!p-2">
<ButtonBase class="enabled:hover:bg-white/20 !rounded-sm !text-left flex flex-row gap-x-3 !ring-0 !p-4 sm:!p-2">
<iconify-icon :icon="icon" width="none" class="text-gray-200 size-5" aria-hidden="true" />
<slot />
</ButtonsBase>
</ButtonBase>
</template>
<script lang="ts" setup>
import type { ButtonHTMLAttributes } from "vue";
import ButtonBase from "./button-base.vue";
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}

View file

@ -1,12 +1,14 @@
<template>
<ButtonsBase
<ButtonBase
class="hover:bg-white/5 text-xs max-w-full w-full gap-1 h-full !px-0 flex flex-col items-center justify-center">
<iconify-icon :icon="icon" class="size-6" width="none" />
<span class="text-xs hidden md:inline">{{ text }}</span>
</ButtonsBase>
</ButtonBase>
</template>
<script lang="ts" setup>
import ButtonBase from "./button-base.vue";
defineProps<{
icon: string;
text: string;

View file

@ -1,13 +1,14 @@
<template>
<ButtonsBase :loading="loading"
<ButtonBase :loading="loading"
class="[--btn-border:theme(colors.primary.950/90%)] [--btn-bg:theme(colors.primary.600)] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.primary.400)] active:[--btn-icon:theme(colors.primary.300)] hover:[--btn-icon:theme(colors.primary.300)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5"
v-bind="$props">
<slot />
</ButtonsBase>
</ButtonBase>
</template>
<script lang="ts" setup>
import type { ButtonHTMLAttributes } from "vue";
import ButtonBase from "./button-base.vue";
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}

View file

@ -1,13 +1,14 @@
<template>
<ButtonsBase
<ButtonBase
class="[--btn-border:theme(colors.zinc.950/90%)] [--btn-bg:theme(colors.zinc.800)] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.400)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5"
v-bind="$props" :loading="loading">
<slot />
</ButtonsBase>
</ButtonBase>
</template>
<script lang="ts" setup>
import type { ButtonHTMLAttributes } from "vue";
import ButtonBase from "./button-base.vue";
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}

View file

@ -1,12 +1,11 @@
<template>
<div v-if="respondingTo" class="mb-4" role="region" aria-label="Responding to">
<OverlayScrollbarsComponent :defer="true" class="max-h-72 overflow-y-auto">
<LazySocialElementsNotesNote :note="respondingTo" :small="true" :disabled="true"
class="!rounded-none !bg-primary-500/10" />
<Note :note="respondingTo" :small="true" :disabled="true" class="!rounded-none !bg-primary-500/10" />
</OverlayScrollbarsComponent>
</div>
<div class="px-6 pb-4 pt-5">
<InputsRichTextbox v-model:model-content="content" @paste="handlePaste" :disabled="loading"
<RichTextboxInput v-model:model-content="content" @paste="handlePaste" :disabled="loading"
:placeholder="chosenSplash" :max-characters="characterLimit" class="focus:!ring-0 max-h-[70dvh]" />
<!-- Content warning textbox -->
<div v-if="cw" class="mb-4">
@ -14,33 +13,33 @@
class="w-full p-2 mt-1 text-sm prose prose-invert bg-dark-900 rounded focus:!ring-0 !ring-none !border-none !outline-none placeholder:text-zinc-500 appearance-none focus:!border-none focus:!outline-none"
aria-label="Content warning" />
</div>
<ComposerFileUploader v-model:files="files" ref="uploader" />
<FileUploader v-model:files="files" ref="uploader" />
<div class="flex flex-row gap-1 border-white/20">
<ComposerButton title="Mention someone">
<Button title="Mention someone">
<iconify-icon height="1.5rem" width="1.5rem" icon="tabler:at" aria-hidden="true" />
</ComposerButton>
<ComposerButton title="Toggle Markdown" @click="markdown = !markdown" :toggled="markdown">
</Button>
<Button title="Toggle Markdown" @click="markdown = !markdown" :toggled="markdown">
<iconify-icon width="1.25rem" height="1.25rem"
:icon="markdown ? 'tabler:markdown' : 'tabler:markdown-off'" aria-hidden="true" />
</ComposerButton>
<ComposerButton title="Use a custom emoji">
</Button>
<Button title="Use a custom emoji">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:mood-smile" aria-hidden="true" />
</ComposerButton>
<ComposerButton title="Add media" @click="openFilePicker">
</Button>
<Button title="Add media" @click="openFilePicker">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:photo-up" aria-hidden="true" />
</ComposerButton>
<ComposerButton title="Add a file" @click="openFilePicker">
</Button>
<Button title="Add a file" @click="openFilePicker">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:file-upload" aria-hidden="true" />
</ComposerButton>
<ComposerButton title="Add content warning" @click="cw = !cw" :toggled="cw">
</Button>
<Button title="Add content warning" @click="cw = !cw" :toggled="cw">
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:rating-18-plus" aria-hidden="true" />
</ComposerButton>
<ButtonsPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
</Button>
<ButtonPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
:disabled="!canSubmit || loading">
<span>{{
respondingType === "edit" ? "Edit!" : "Send!"
}}</span>
</ButtonsPrimary>
</ButtonPrimary>
</div>
</div>
</template>
@ -49,7 +48,12 @@
import type { Instance, Status } from "@lysand-org/client/types";
import { nanoid } from "nanoid";
import { OverlayScrollbarsComponent } from "#imports";
import type FileUploader from "./file-uploader.vue";
import ButtonPrimary from "../buttons/button-primary.vue";
import RichTextboxInput from "../inputs/rich-textbox-input.vue";
import Note from "../social-elements/notes/note.vue";
import Button from "./button.vue";
// biome-ignore lint/style/useImportType: Biome doesn't see the Vue code
import FileUploader from "./file-uploader.vue";
const uploader = ref<InstanceType<typeof FileUploader> | undefined>(undefined);
const { Control_Enter, Command_Enter, Control_Alt } = useMagicKeys();

View file

@ -1,17 +1,18 @@
<template>
<ComposerSuggestbox class="max-h-40 overflow-auto !w-auto !flex-row">
<Suggestbox class="max-h-40 overflow-auto !w-auto !flex-row">
<div v-for="(emoji, index) in topEmojis" :key="emoji.shortcode" @click="emit('autocomplete', emoji.shortcode)"
:ref="el => { if (el) emojiRefs[index] = el as Element }" :title="emoji.shortcode"
:class="['flex', 'justify-center', 'shrink-0', 'items-center', 'p-2', 'size-12', 'hover:bg-dark-900/70', { 'bg-primary-500': index === selectedEmojiIndex }]">
<img :src="emoji.url" class="w-full h-full object-contain"
:alt="`Emoji with shortcode ${emoji.shortcode}`" />
</div>
</ComposerSuggestbox>
</Suggestbox>
</template>
<script lang="ts" setup>
import type { Emoji } from "@lysand-org/client/types";
import { distance } from "fastest-levenshtein";
import Suggestbox from "./suggestbox.vue";
const props = defineProps<{
currentlyTypingEmoji: string | null;
textarea: HTMLTextAreaElement | undefined;

View file

@ -42,10 +42,10 @@
<textarea :disabled="data.progress < 1.0" @keydown.enter.stop v-model="data.alt_text"
placeholder="Add alt text"
class="w-full p-2 text-sm prose prose-invert bg-dark-900 rounded focus:!ring-0 !ring-none !border-none !outline-none placeholder:text-zinc-500 appearance-none focus:!border-none focus:!outline-none" />
<ButtonsSecondary @click="updateAltText(data.id, data.alt_text)" class="w-full"
<ButtonSecondary @click="updateAltText(data.id, data.alt_text)" class="w-full"
:loading="data.progress < 1.0">
<span>Edit</span>
</ButtonsSecondary>
</ButtonSecondary>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
@ -57,6 +57,7 @@
<script lang="ts" setup>
import { Popover } from "@ark-ui/vue";
import { nanoid } from "nanoid";
import ButtonSecondary from "../buttons/button-secondary.vue";
const files = defineModel<
{

View file

@ -1,16 +1,17 @@
<template>
<ComposerSuggestbox class="max-h-40 overflow-auto !w-auto !flex-row">
<Suggestbox class="max-h-40 overflow-auto !w-auto !flex-row">
<div v-for="(user, index) in topUsers" :key="user.username" @click="emit('autocomplete', user.acct)"
:ref="el => { if (el) userRefs[index] = el as Element }" :title="user.acct"
:class="['flex', 'justify-center', 'shrink-0', 'items-center', 'p-2', 'size-12', 'hover:bg-dark-900/70', { 'bg-primary-500': index === selectedUserIndex }]">
<img :src="user.avatar" class="w-full h-full object-contain" :alt="`User ${user.acct}`" />
</div>
</ComposerSuggestbox>
</Suggestbox>
</template>
<script lang="ts" setup>
import type { Account } from "@lysand-org/client/types";
import { distance } from "fastest-levenshtein";
import Suggestbox from "./suggestbox.vue";
const props = defineProps<{
currentlyTypingMention: string | null;
textarea: HTMLTextAreaElement | undefined;

View file

@ -31,6 +31,7 @@
<script lang="ts" setup>
import { Dialog } from "@ark-ui/vue";
import Composer from "./composer.vue";
const open = ref(false);
const identity = useCurrentIdentity();

View file

@ -7,9 +7,9 @@
</h1>
<p class="mt-6 text-base leading-7 text-gray-400" v-html="error.message"></p>
<div class="mt-10 grid grid-cols-2 gap-x-6 mx-auto max-w-md">
<ButtonsPrimary class="w-full" @click="back">Go back</ButtonsPrimary>
<ButtonPrimary class="w-full" @click="back">Go back</ButtonPrimary>
<a href="https://github.com/lysand-org/lysand-fe/issues" target="_blank">
<ButtonsSecondary class="w-full">Report an issue</ButtonsSecondary>
<ButtonSecondary class="w-full">Report an issue</ButtonSecondary>
</a>
</div>
</div>
@ -19,6 +19,9 @@
</template>
<script lang="ts" setup>
import ButtonPrimary from "../buttons/button-primary.vue";
import ButtonSecondary from "../buttons/button-secondary.vue";
const error = ref<{
title: string;
message: string;

View file

@ -1,6 +1,10 @@
<script setup lang="ts">
import SquarePattern from "../graphics/square-pattern.vue";
</script>
<template>
<div class="grid min-h-screen place-items-center px-6 py-24 sm:py-32 lg:px-8 fixed inset-0 z-[1000000] bg-dark-900">
<GraphicsSquarePattern />
<SquarePattern />
<div class="prose prose-invert max-w-lg">
<h1 class="mt-4 text-3xl font-bold tracking-tight text-gray-100 sm:text-5xl">JavaScript is disabled
</h1>

View file

@ -2,7 +2,7 @@
<div v-if="identity" class="bg-dark-800 z-0 p-6 my-5 relative overflow-hidden rounded ring-1 ring-white/5">
<div class="sm:flex sm:items-center sm:justify-between gap-3">
<div class="sm:flex sm:space-x-5 grow">
<AvatarsCentered :src="identity.account.avatar"
<Avatar :src="identity.account.avatar"
class="mx-auto shrink-0 size-20 rounded overflow-hidden ring-1 ring-white/10"
:alt="'Your avatar'" />
<div
@ -16,6 +16,8 @@
</template>
<script lang="ts" setup>
import Avatar from "../avatars/avatar.vue";
const identity = useCurrentIdentity();
const settings = useSettings();
const { display_name } = useParsedAccount(

View file

@ -1,10 +1,11 @@
<template>
<InputsText type="checkbox" v-bind="$attrs, $props"
<TextInput type="checkbox" v-bind="$attrs, $props"
class="rounded disabled:hover:cursor-wait text-primary-700 !size-5" />
</template>
<script lang="ts" setup>
import type { InputHTMLAttributes } from "vue";
import TextInput from "./text-input.vue";
interface Props extends /* @vue-ignore */ InputHTMLAttributes {}

View file

@ -1,5 +1,5 @@
<template>
<InputsText @input="e => content = (e.target as HTMLInputElement).value" v-bind="$attrs, $props" v-model="content"
<TextInput @input="e => content = (e.target as HTMLInputElement).value" v-bind="$attrs, $props" v-model="content"
:type="showPassword ? 'text' : 'password'" :spellcheck="false" />
<Progress.Root class="flex flex-row items-center gap-x-2" v-if="showStrength">
<Progress.Label class="text-xs text-gray-300 font-semibold w-12">
@ -27,6 +27,7 @@ const showPassword = ref(false);
const content = ref("");
import type { InputHTMLAttributes } from "vue";
import TextInput from "./text-input.vue";
interface Props extends /* @vue-ignore */ InputHTMLAttributes {
isInvalid?: boolean;
@ -66,6 +67,7 @@ const color = computed(() => {
});
onMounted(() => {
// Workaround to make sure the teleport is rendered after the parent component
teleport.value = true;
});
</script>

View file

@ -8,15 +8,17 @@
aria-live="polite">
{{ remainingCharacters }}
</div>
<ComposerEmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
<EmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
<ComposerMentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
<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/emoji-suggestbox.vue";
import MentionSuggestbox from "../composer/mention-suggestbox.vue";
defineOptions({
inheritAttrs: false,

View file

@ -9,17 +9,17 @@
<div v-for="provider of ssoConfig?.providers" :key="provider.id"
class="flex items-center justify-between p-4 bg-dark-700 rounded">
<div class="flex items-center gap-4">
<AvatarsCentered :src="provider.icon" :alt="provider.name" class="h-8 w-8" />
<Avatar :src="provider.icon" :alt="provider.name" class="h-8 w-8" />
<span class="font-semibold text-gray-300">{{ provider.name }}</span>
</div>
<div>
<ButtonsPrimary :loading="loading" v-if="!linkedProviders?.find(p => p.id === provider.id)"
<ButtonPrimary :loading="loading" v-if="!linkedProviders?.find(p => p.id === provider.id)"
@click="link(provider.id)">
<span>Link</span>
</ButtonsPrimary>
<ButtonsSecondary :loading="loading" v-else @click="unlink(provider.id)">
</ButtonPrimary>
<ButtonSecondary :loading="loading" v-else @click="unlink(provider.id)">
<span>Unlink</span>
</ButtonsSecondary>
</ButtonSecondary>
</div>
</div>
</div>
@ -29,6 +29,9 @@
<script lang="ts" setup>
import type { ResponseError } from "@lysand-org/client";
import Avatar from "../avatars/avatar.vue";
import ButtonPrimary from "../buttons/button-primary.vue";
import ButtonSecondary from "../buttons/button-secondary.vue";
const client = useClient();
const ssoConfig = useSSOConfig();

View file

@ -3,20 +3,20 @@
class="w-full md:px-8 px-4 py-4 bg-dark-700 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">
<AvatarsCentered :src="account?.header" :alt="`${account?.acct}'s header image'`"
<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">
<AvatarsCentered :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
<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">
<InputsText @input="displayName = ($event.target as HTMLInputElement).value" :value="displayName"
<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" />
<InputsText @input="acct = ($event.target as HTMLInputElement).value" :value="acct"
<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">
@ -25,27 +25,33 @@
</div>
<div class="mt-3 px-4">
<InputsRichTextbox v-model:model-content="note" :max-characters="bio" :disabled="loading"
<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">
<ButtonsPrimary class="w-full" type="submit" :loading="loading">
<ButtonPrimary class="w-full" type="submit" :loading="loading">
<span>Save</span>
</ButtonsPrimary>
<ButtonsSecondary class="w-full" @click="revert" type="button" :loading="loading">
</ButtonPrimary>
<ButtonSecondary class="w-full" @click="revert" type="button" :loading="loading">
<span>Revert</span>
</ButtonsSecondary>
</ButtonSecondary>
</div>
</form>
<div>
<SettingsOidc />
<Oidc />
</div>
</div>
</template>
<script lang="ts" setup>
import type { ResponseError } from "@lysand-org/client";
import Avatar from "../avatars/avatar.vue";
import ButtonPrimary from "../buttons/button-primary.vue";
import ButtonSecondary from "../buttons/button-secondary.vue";
import RichTextboxInput from "../inputs/rich-textbox-input.vue";
import TextInput from "../inputs/text-input.vue";
import Oidc from "./oidc.vue";
const identity = useCurrentIdentity();
const account = computed(() => identity.value?.account);

View file

@ -1,12 +1,12 @@
<template>
<DropdownsAdaptiveDropdown>
<AdaptiveDropdown>
<template #button>
<slot>
<div class="rounded text-left flex flex-row gap-x-2 hover:scale-[95%] duration-100"
v-if="currentIdentity">
<div class="shrink-0">
<AvatarsCentered class="size-12 rounded ring-1 ring-white/5"
:src="currentIdentity.account.avatar" :alt="`${currentIdentity.account.acct}'s avatar'`" />
<Avatar class="size-12 rounded ring-1 ring-white/5" :src="currentIdentity.account.avatar"
:alt="`${currentIdentity.account.acct}'s avatar'`" />
</div>
<div class="flex flex-col items-start p-1 justify-around grow overflow-hidden">
<div class="flex flex-row items-center justify-between w-full">
@ -20,11 +20,11 @@
</span>
</div>
</div>
<ButtonsBase v-else class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10
<ButtonBase v-else class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10
overflow-hidden h-12 w-full duration-200">
<iconify-icon icon="tabler:login" class="shrink-0 text-2xl" />
<span class="pr-28 line-clamp-1">Sign In</span>
</ButtonsBase>
</ButtonBase>
</slot>
</template>
<template #items>
@ -36,8 +36,8 @@
<Menu.Item value="" v-for="identity of identities" class="hover:scale-[95%] duration-100">
<div class="flex flex-row gap-x-4">
<div class="shrink-0" data-part="item" @click="useEvent('identity:change', identity)">
<AvatarsCentered class="h-12 w-12 rounded ring-1 ring-white/5"
:src="identity.account.avatar" :alt="`${identity.account.acct}'s avatar'`" />
<Avatar class="h-12 w-12 rounded ring-1 ring-white/5" :src="identity.account.avatar"
:alt="`${identity.account.acct}'s avatar'`" />
</div>
<div data-part="item" class="flex flex-col items-start justify-around grow overflow-hidden"
@click="useEvent('identity:change', identity)">
@ -104,11 +104,14 @@
</Menu.Item>
</div>
</template>
</DropdownsAdaptiveDropdown>
</AdaptiveDropdown>
</template>
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
import Avatar from "../avatars/avatar.vue";
import ButtonBase from "../buttons/button-base.vue";
import AdaptiveDropdown from "../dropdowns/AdaptiveDropdown.vue";
const identities = useIdentities();
const currentIdentity = useCurrentIdentity();

View file

@ -12,11 +12,11 @@
Timelines</h3>
<NuxtLink v-for="timeline in visibleTimelines" :key="timeline.href" :to="timeline.href">
<ButtonsBase
<ButtonBase
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
<iconify-icon :icon="timeline.icon" class="shrink-0 text-2xl" />
<span class="pr-28 line-clamp-1">{{ timeline.name }}</span>
</ButtonsBase>
</ButtonBase>
</NuxtLink>
</div>
@ -25,14 +25,14 @@
<h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
Account</h3>
<SidebarsAccountPicker @sign-in="signIn().finally(() => loadingAuth = false)"
<AccountPicker @sign-in="signIn().finally(() => loadingAuth = false)"
@sign-out="id => signOut(id).finally(() => loadingAuth = false)" />
<NuxtLink href="/register" v-if="!identity">
<ButtonsBase
<ButtonBase
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
<iconify-icon icon="tabler:certificate" class="shrink-0 text-2xl" />
<span class="pr-28 line-clamp-1">Register</span>
</ButtonsBase>
</ButtonBase>
</NuxtLink>
<NuxtLink href="/settings" v-if="identity">
<button @click="$emit('signIn')" class="w-full overflow-hidden">
@ -51,7 +51,7 @@
<h3 v-if="identity"
class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
Posts</h3>
<ButtonsBase v-if="identity" @click="compose" title="Open composer (shortcut: n)"
<ButtonBase v-if="identity" @click="compose" title="Open composer (shortcut: n)"
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 bg-gradient-to-tr from-primary-300 via-purple-300 to-indigo-400 overflow-hidden h-12 w-full duration-200">
<iconify-icon icon="tabler:writing" class="shrink-0 text-2xl" />
<span class="pr-28 line-clamp-1">Compose</span>
@ -60,12 +60,12 @@
<iconify-icon icon="tabler:letter-n-small" height="1rem" width="1rem" class="inline -mr-1"
aria-hidden="true" />
</kbd>
</ButtonsBase>
<ButtonsBase v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker()" title="Update service worker"
</ButtonBase>
<ButtonBase v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker()" title="Update service worker"
class="flex flex-row text-left items-center justify-start gap-3 text-lg ring-2 ring-primary-600 overflow-hidden h-12 w-full duration-200">
<iconify-icon icon="tabler:refresh" class="shrink-0 text-2xl" />
<span class="pr-28 line-clamp-1">Update</span>
</ButtonsBase>
</ButtonBase>
</div>
</aside>
@ -73,30 +73,30 @@
<nav
:class="['fixed bottom-0 left-0 right-0 z-20 h-16 md:hidden grid gap-3 p-2 *:shadow-xl bg-dark-900 ring-1 ring-white/10 text-gray-200', !!identity ? 'grid-cols-4' : 'grid-cols-3']">
<DropdownsAdaptiveDropdown>
<AdaptiveDropdown>
<template #button>
<ButtonsMobileNavbarButton icon="tabler:home" text="Timelines" />
<ButtonMobileNavbar icon="tabler:home" text="Timelines" />
</template>
<template #items>
<Menu.Item value="" v-for="timeline in visibleTimelines" :key="timeline.href">
<NuxtLink :href="timeline.href">
<ButtonsDropdownElement :icon="timeline.icon" class="w-full">
<ButtonDropdown :icon="timeline.icon" class="w-full">
{{ timeline.name }}
</ButtonsDropdownElement>
</ButtonDropdown>
</NuxtLink>
</Menu.Item>
</template>
</DropdownsAdaptiveDropdown>
</AdaptiveDropdown>
<NuxtLink href="/notifications" class="w-full">
<ButtonsMobileNavbarButton icon="tabler:bell" text="Notifications" />
<ButtonMobileNavbar icon="tabler:bell" text="Notifications" />
</NuxtLink>
<ButtonsMobileNavbarButton v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker(true)"
icon="tabler:refresh" text="Update" />
<SidebarsAccountPicker v-else @sign-in="signIn().finally(() => loadingAuth = false)"
<ButtonMobileNavbar v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker(true)" icon="tabler:refresh"
text="Update" />
<AccountPicker v-else @sign-in="signIn().finally(() => loadingAuth = false)"
@sign-out="id => signOut(id).finally(() => loadingAuth = false)">
<ButtonsMobileNavbarButton icon="tabler:user" text="Account" />
</SidebarsAccountPicker>
<ButtonMobileNavbar icon="tabler:user" text="Account" />
</AccountPicker>
<button @click="compose" v-if="identity"
class="flex flex-col items-center justify-center p-2 rounded bg-gradient-to-tr from-[theme(colors.primary.300/70%)] via-purple-300/70 to-indigo-400/70">
<iconify-icon icon="tabler:writing" class="text-2xl" />
@ -107,6 +107,11 @@
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
import ButtonBase from "../buttons/button-base.vue";
import ButtonDropdown from "../buttons/button-dropdown.vue";
import ButtonMobileNavbar from "../buttons/button-mobile-navbar.vue";
import AdaptiveDropdown from "../dropdowns/AdaptiveDropdown.vue";
import AccountPicker from "./account-picker.vue";
const { $pwa } = useNuxtApp();
const timelines = ref([
{

View file

@ -2,9 +2,9 @@
<Tabs.Root v-model="tab">
<Tabs.List class="flex flex-row p-4 gap-4 bg-dark-800 relative ring-1 ring-white/5 overflow-x-auto">
<Tabs.Trigger :value="page" v-for="page of SettingPages" :as-child="true">
<ButtonsBase class="capitalize hover:bg-white/5">
<ButtonBase class="capitalize hover:bg-white/5">
{{ page }}
</ButtonsBase>
</ButtonBase>
</Tabs.Trigger>
<Tabs.Indicator class="h-1 bg-gray-300 w-[--width] top-0 rounded-b" />
</Tabs.List>
@ -17,6 +17,7 @@
<script lang="ts" setup>
import { Tabs } from "@ark-ui/vue";
import { SettingPages } from "~/settings";
import ButtonBase from "../buttons/button-base.vue";
const tab = ref<SettingPages>(
(window.location.hash.slice(1) as SettingPages) || SettingPages.Account,

View file

@ -13,12 +13,14 @@
<div v-if="instance?.contact.account" class="flex flex-col gap-2 mt-auto">
<h2 class="text-gray-200 font-semibold uppercase text-xs">Administrator</h2>
<SocialElementsUsersSmallCard :account="instance.contact.account" />
<SmallCard :account="instance.contact.account" />
</div>
</div>
</template>
<script lang="ts" setup>
import SmallCard from "../users/SmallCard.vue";
const client = useClient();
const instance = useInstance();
const description = useExtendedDescription(client);

View file

@ -1,7 +1,7 @@
<template>
<div v-if="small" class="flex flex-row">
<NuxtLink :href="accountUrl" class="shrink-0">
<AvatarsCentered :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
<Avatar :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
class="size-6 rounded ring-1 ring-white/5" />
<span class="sr-only">Account profile</span>
</NuxtLink>
@ -23,7 +23,7 @@
</div>
<div v-else class="flex flex-row">
<NuxtLink :href="accountUrl" class="shrink-0">
<AvatarsCentered :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
<Avatar :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
class="h-12 w-12 rounded ring-1 ring-white/5" />
<span class="sr-only">Account profile</span>
</NuxtLink>
@ -67,6 +67,8 @@
<script lang="ts" setup>
import type { Status } from "@lysand-org/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
const props = defineProps<{
note?: Status;

View file

@ -10,7 +10,7 @@
<script lang="ts" setup>
import type { Account } from "@lysand-org/client/types";
const props = defineProps<{
defineProps<{
account: Account;
}>();
</script>

View file

@ -9,11 +9,9 @@
</Skeleton>
<div v-if="note && note.media_attachments.length > 0"
class="[&:not(:first-child)]:mt-6 grid grid-cols-2 gap-4 [&>*]:aspect-square [&:has(>:last-child:nth-child(1))>*]:aspect-video [&:has(>:last-child:nth-child(1))]:block">
<SocialElementsNotesAttachment v-for="attachment of note.media_attachments" :key="attachment.id"
:attachment="attachment" />
<Attachment v-for="attachment of note.media_attachments" :key="attachment.id" :attachment="attachment" />
</div>
<LazySocialElementsNotesNote v-if="isQuote && note?.quote" :note="note?.quote" :small="true"
class="mt-4 !rounded" />
<Note v-if="isQuote && note?.quote" :note="note?.quote" :small="true" class="mt-4 !rounded" />
</div>
<div v-else
class="rounded text-center ring-1 !max-w-full ring-white/10 h-52 mt-6 prose prose-invert p-4 flex flex-col justify-center items-center">
@ -22,12 +20,16 @@
<!-- Spoiler text is it's specified -->
<span v-if="note?.spoiler_text" class="mt-2 break-all">{{ note.spoiler_text
}}</span>
<ButtonsSecondary @click="collapsed = false" class="mt-4">Show content</ButtonsSecondary>
<ButtonSecondary @click="collapsed = false" class="mt-4">Show content</ButtonSecondary>
</div>
</template>
<script lang="ts" setup>
import type { Status } from "@lysand-org/client/types";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import Attachment from "./attachment.vue";
import Note from "./note.vue";
const props = defineProps<{
content: string | null;

View file

@ -7,15 +7,15 @@
<div v-if="reblog" class="mb-4 flex flex-row gap-2 items-center text-primary-400">
<Skeleton :enabled="!loaded" shape="rect" class="!h-6" :min-width="40" :max-width="100" width-unit="%">
<iconify-icon width="1.5rem" height="1.5rem" icon="tabler:repeat" class="size-6" aria-hidden="true" />
<AvatarsCentered v-if="reblog.avatar" :src="reblog.avatar" :alt="`${reblog.acct}'s avatar'`"
<Avatar v-if="reblog.avatar" :src="reblog.avatar" :alt="`${reblog.acct}'s avatar'`"
class="size-6 rounded shrink-0 ring-1 ring-white/10" />
<span><strong v-html="reblogDisplayName"></strong> reblogged</span>
</Skeleton>
</div>
<SocialElementsNotesReplyHeader v-if="isReply" :account_id="outputtedNote?.in_reply_to_account_id ?? null" />
<SocialElementsNotesHeader :note="outputtedNote" :small="small" />
<LazySocialElementsNotesNoteContent :note="outputtedNote" :loaded="loaded" :url="url" :content="content"
:is-quote="isQuote" :should-hide="shouldHide" />
<ReplyHeader v-if="isReply" :account_id="outputtedNote?.in_reply_to_account_id ?? null" />
<Header :note="outputtedNote" :small="small" />
<NoteContent :note="outputtedNote" :loaded="loaded" :url="url" :content="content" :is-quote="isQuote"
:should-hide="shouldHide" />
<Skeleton class="!h-10 w-full mt-6" :enabled="!props.note || !loaded" v-if="!small || !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">
@ -45,7 +45,7 @@
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>
</button>
<DropdownsAdaptiveDropdown>
<AdaptiveDropdown>
<template #button>
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
aria-hidden="true" />
@ -55,93 +55,91 @@
<template #items>
<Menu.ItemGroup>
<Menu.Item value="" v-if="isMyAccount">
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:edit', outputtedNote)"
<ButtonDropdown @click="outputtedNote && useEvent('note:edit', outputtedNote)"
icon="tabler:pencil" class="w-full">
Edit
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(JSON.stringify(props.note, null, 4))"
icon="tabler:code" class="w-full">
<ButtonDropdown @click="copy(JSON.stringify(props.note, null, 4))" icon="tabler:code"
class="w-full">
Copy API
Response
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="copy(url)" icon="tabler:link" class="w-full">
<ButtonDropdown @click="copy(url)" icon="tabler:link" class="w-full">
Copy Link
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
<ButtonsDropdownElement @click="copy(outputtedNote.url)" icon="tabler:link"
class="w-full">
<ButtonDropdown @click="copy(outputtedNote.url)" icon="tabler:link" class="w-full">
Copy Link (Origin)
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
<ButtonsDropdownElement @click="openBlank(outputtedNote.url)"
icon="tabler:external-link" class="w-full">
<ButtonDropdown @click="openBlank(outputtedNote.url)" icon="tabler:external-link"
class="w-full">
View on Remote
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="" v-if="isMyAccount">
<ButtonsDropdownElement @click="remove" icon="tabler:backspace" :disabled="!identity"
<ButtonDropdown @click="remove" icon="tabler:backspace" :disabled="!identity"
class="w-full border-r-2 border-red-500">
Delete
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
</Menu.ItemGroup>
<hr class="border-white/10 rounded" v-if="identity" />
<Menu.ItemGroup v-if="identity">
<Menu.Item value="">
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:reply', outputtedNote)"
<ButtonDropdown @click="outputtedNote && useEvent('note:reply', outputtedNote)"
icon="tabler:arrow-back-up" class="w-full">
Reply
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="likeFn" icon="tabler:heart" class="w-full"
<ButtonDropdown @click="likeFn" icon="tabler:heart" class="w-full"
v-if="!outputtedNote?.favourited">
Like
</ButtonsDropdownElement>
<ButtonsDropdownElement @click="likeFn" icon="tabler:heart-filled" class="w-full"
v-else>
</ButtonDropdown>
<ButtonDropdown @click="likeFn" icon="tabler:heart-filled" class="w-full" v-else>
Unlike
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="reblogFn" icon="tabler:repeat" class="w-full"
<ButtonDropdown @click="reblogFn" icon="tabler:repeat" class="w-full"
v-if="!outputtedNote?.reblogged">
Reblog
</ButtonsDropdownElement>
<ButtonsDropdownElement @click="reblogFn" icon="tabler:repeat" class="w-full" v-else>
</ButtonDropdown>
<ButtonDropdown @click="reblogFn" icon="tabler:repeat" class="w-full" v-else>
Unreblog
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="">
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:quote', outputtedNote)"
<ButtonDropdown @click="outputtedNote && useEvent('note:quote', outputtedNote)"
icon="tabler:quote" class="w-full">
Quote
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
</Menu.ItemGroup>
<hr class="border-white/10 rounded" v-if="identity" />
<Menu.ItemGroup v-if="identity">
<Menu.Item value="">
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:report', outputtedNote)"
<ButtonDropdown @click="outputtedNote && useEvent('note:report', outputtedNote)"
icon="tabler:flag" class="w-full"
:disabled="!permissions.includes(RolePermission.ManageOwnReports)">
Report
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
<Menu.Item value="" v-if="permissions.includes(RolePermission.ManageAccounts)">
<ButtonsDropdownElement icon="tabler:shield-bolt" class="w-full">
<ButtonDropdown icon="tabler:shield-bolt" class="w-full">
Open Moderation Panel
</ButtonsDropdownElement>
</ButtonDropdown>
</Menu.Item>
</Menu.ItemGroup>
</template>
</DropdownsAdaptiveDropdown>
</AdaptiveDropdown>
</div>
</Skeleton>
</article>
@ -150,7 +148,13 @@
<script lang="ts" setup>
import { Menu } from "@ark-ui/vue";
import { RolePermission, type Status } from "@lysand-org/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import ButtonDropdown from "~/components/buttons/button-dropdown.vue";
import AdaptiveDropdown from "~/components/dropdowns/AdaptiveDropdown.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import Header from "./header.vue";
import NoteContent from "./note-content.vue";
import ReplyHeader from "./reply-header.vue";
const props = withDefaults(
defineProps<{

View file

@ -3,7 +3,7 @@
<Skeleton :enabled="!account" shape="rect" class="!h-6" :min-width="40" :max-width="100" width-unit="%">
<iconify-icon icon="tabler:arrow-back-up" width="1.5rem" height="1.5rem" aria-hidden="true" />
<span class="shrink-0">Replying to</span>
<AvatarsCentered v-if="account?.avatar" :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
<Avatar v-if="account?.avatar" :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
class="size-5 shrink-0 rounded ring-1 ring-white/10" />
<strong class="line-clamp-1">{{ account?.display_name || account?.acct }}</strong>
</Skeleton>
@ -11,6 +11,9 @@
</template>
<script lang="ts" setup>
import Avatar from "~/components/avatars/avatar.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
const props = defineProps<{
account_id: string | null;
}>();

View file

@ -4,7 +4,7 @@
<Skeleton :enabled="!notification" shape="rect" class="!h-6" :min-width="40" :max-width="100"
width-unit="%">
<iconify-icon :icon="icon" width="1.5rem" height="1.5rem" class="text-gray-200" aria-hidden="true" />
<AvatarsCentered v-if="notification?.account?.avatar" :src="notification?.account.avatar"
<Avatar v-if="notification?.account?.avatar" :src="notification?.account.avatar"
:alt="`${notification?.account.acct}'s avatar'`"
class="h-6 w-6 shrink-0 rounded ring-1 ring-white/10" />
<span class="text-gray-200 line-clamp-1"><strong v-html="display_name"></strong> {{ text
@ -12,24 +12,29 @@
</Skeleton>
</div>
<div>
<LazySocialElementsNotesNote v-if="notification?.status || !notification" :note="notification?.status"
:small="true" />
<Note v-if="notification?.status || !notification" :note="notification?.status" :small="true" />
<div v-else-if="notification.account" class="p-6 ring-1 ring-white/5 bg-dark-800">
<SocialElementsUsersSmallCard :account="notification.account" />
<SmallCard :account="notification.account" />
</div>
<div v-if="notification?.type === 'follow_request' && relationship?.requested_by"
class="w-full grid grid-cols-2 gap-4 p-2 ">
<ButtonsPrimary :loading="isWorkingOnFollowRequest" @click="acceptFollowRequest"><span>Accept</span>
</ButtonsPrimary>
<ButtonsSecondary :loading="isWorkingOnFollowRequest" @click="rejectFollowRequest"><span>Reject</span>
</ButtonsSecondary>
<ButtonPrimary :loading="isWorkingOnFollowRequest" @click="acceptFollowRequest"><span>Accept</span>
</ButtonPrimary>
<ButtonSecondary :loading="isWorkingOnFollowRequest" @click="rejectFollowRequest"><span>Reject</span>
</ButtonSecondary>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { Notification, Relationship } from "@lysand-org/client/types";
import type { Notification } from "@lysand-org/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import ButtonPrimary from "~/components/buttons/button-primary.vue";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import Note from "../notes/note.vue";
import SmallCard from "../users/SmallCard.vue";
const props = defineProps<{
notification?: Notification;

View file

@ -1,26 +1,26 @@
<template>
<div class="w-full ring-1 ring-inset ring-white/5 pb-10 bg-dark-800">
<AvatarsCentered :src="account?.header" :alt="`${account?.acct}'s header image'`"
<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">
<AvatarsCentered :src="account?.avatar" :alt="`${account?.acct}'s avatar'`"
<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" />
<ButtonsSecondary v-if="account && account?.id === identity?.account?.id">Edit Profile
</ButtonsSecondary>
<ButtonsSecondary :loading="isLoading" @click="follow()"
<ButtonSecondary v-if="account && account?.id === identity?.account?.id">Edit Profile
</ButtonSecondary>
<ButtonSecondary :loading="isLoading" @click="follow()"
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && !relationship.requested">
<span>Follow</span>
</ButtonsSecondary>
<ButtonsSecondary :loading="isLoading" @click="unfollow()"
</ButtonSecondary>
<ButtonSecondary :loading="isLoading" @click="unfollow()"
v-if="account && account?.id !== identity?.account?.id && relationship && relationship.following">
<span>Unfollow</span>
</ButtonsSecondary>
<ButtonsSecondary :loading="isLoading" :disabled="true"
</ButtonSecondary>
<ButtonSecondary :loading="isLoading" :disabled="true"
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && relationship.requested">
<span>Requested</span>
</ButtonsSecondary>
</ButtonSecondary>
</div>
<div class="mt-2 px-4">
@ -37,10 +37,10 @@
<Skeleton :enabled="skeleton" :min-width="130" :max-width="250">@{{ account?.acct }}</Skeleton>
</span>
<div class="flex flex-row flex-wrap gap-4 mt-4" v-if="isDeveloper || visibleRoles.length > 0">
<SocialElementsUsersBadge v-for="role of visibleRoles" :key="role.id" :name="role.name"
:description="role.description" :img="role.icon" />
<SocialElementsUsersBadge v-if="isDeveloper" name="Lysand Developer"
description="This user is a Lysand developer." :verified="true" />
<Badge v-for="role of visibleRoles" :key="role.id" :name="role.name" :description="role.description"
:img="role.icon" />
<Badge v-if="isDeveloper" name="Lysand Developer" description="This user is a Lysand developer."
:verified="true" />
</div>
</div>
@ -99,6 +99,10 @@
<script lang="ts" setup>
import type { Account } from "@lysand-org/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import Badge from "./Badge.vue";
const props = defineProps<{
account?: Account;

View file

@ -2,7 +2,7 @@
<component :is="disableLink ? 'div' : NuxtLink" :href="accountUrl" class="flex flex-row">
<Skeleton :enabled="!account" shape="rect" class="!h-12 w-12">
<div class="shrink-0">
<AvatarsCentered class="h-12 w-12 rounded ring-1 ring-white/5" :src="account?.avatar"
<Avatar class="h-12 w-12 rounded ring-1 ring-white/5" :src="account?.avatar"
:alt="`${account?.acct}'s avatar'`" />
</div>
</Skeleton>
@ -28,6 +28,8 @@
<script lang="ts" setup>
import type { Account } from "@lysand-org/client/types";
import Avatar from "~/components/avatars/avatar.vue";
import Skeleton from "~/components/skeleton/Skeleton.vue";
import { NuxtLink } from "#components";
const props = defineProps<{

View file

@ -1,8 +1,10 @@
<template>
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
</template>
<script lang="ts" setup>
import Timeline from "./timeline.vue";
const props = defineProps<{
id?: string;
}>();

View file

@ -1,8 +1,10 @@
<template>
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
</template>
<script lang="ts" setup>
import Timeline from "./timeline.vue";
const client = useClient();
const timelineParameters = ref({});
const { timeline, loadNext, loadPrev } = useHomeTimeline(

View file

@ -1,8 +1,10 @@
<template>
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
</template>
<script lang="ts" setup>
import Timeline from "./timeline.vue";
const client = useClient();
const timelineParameters = ref({});
const { timeline, loadNext, loadPrev } = useLocalTimeline(

View file

@ -1,7 +1,7 @@
<template>
<SocialElementsNotificationsNotif v-for="notif of timeline" :key="notif.id" :notification="notif" />
<Notif v-for="notif of timeline" :key="notif.id" :notification="notif" />
<span ref="skeleton"></span>
<SocialElementsNotificationsNotif v-for="index of 5" v-if="!hasReachedEnd" :skeleton="true" />
<Notif v-for="index of 5" v-if="!hasReachedEnd" :skeleton="true" />
<div v-if="hasReachedEnd" class="text-center flex flex-row justify-center items-center py-10 text-gray-400 gap-3">
<iconify-icon name="tabler:message-off" width="1.5rem" height="1.5rem" />
@ -10,6 +10,8 @@
</template>
<script lang="ts" setup>
import Notif from "../social-elements/notifications/notif.vue";
const client = useClient();
const isLoading = ref(true);

View file

@ -1,8 +1,10 @@
<template>
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
</template>
<script lang="ts" setup>
import Timeline from "./timeline.vue";
const client = useClient();
const timelineParameters = ref({});
const { timeline, loadNext, loadPrev } = usePublicTimeline(

View file

@ -1,10 +1,10 @@
<template>
<TransitionGroup leave-active-class="ease-in duration-200" leave-from-class="scale-100 opacity-100"
leave-to-class="opacity-0 scale-90">
<SocialElementsNotesNote v-for="note of timeline" :key="note.id" :note="note" />
<Note v-for="note of timeline" :key="note.id" :note="note" />
</TransitionGroup>
<span ref="skeleton"></span>
<LazySocialElementsNotesNote v-for="index of 5" v-if="!hasReachedEnd" :skeleton="true" />
<Note v-for="_ of 5" v-if="!hasReachedEnd" :skeleton="true" />
<div v-if="hasReachedEnd" class="text-center flex flex-row justify-center items-center py-10 text-gray-400 gap-3">
<iconify-icon icon="tabler:message-off" width="1.5rem" height="1.5rem" />
@ -14,6 +14,7 @@
<script lang="ts" setup>
import type { Status } from "@lysand-org/client/types";
import Note from "../social-elements/notes/note.vue";
const props = defineProps<{
timeline: Status[];

View file

@ -1,25 +1,32 @@
<template>
<div class="from-dark-600 to-dark-900 bg-gradient-to-tl relative min-h-dvh">
<GraphicsSquarePattern />
<LazySidebarsNavigation />
<SquarePattern />
<Navigation />
<div class="relative md:pl-20 min-h-dvh flex flex-row overflow-hidden justify-center xl:justify-between">
<OverlayScrollbarsComponent :defer="true" class="w-full max-h-dvh overflow-y-auto" :element="'main'">
<slot />
</OverlayScrollbarsComponent>
<LazySidebarsCollapsibleAside v-if="width > 1280 && identity" direction="right"
<CollapsibleAside v-if="width > 1280 && identity" direction="right"
class="max-w-md max-h-dvh overflow-y-auto w-full hidden absolute inset-y-0 xl:flex">
<LazyTimelinesTimelineScroller>
<LazyTimelinesNotifications />
</LazyTimelinesTimelineScroller>
</LazySidebarsCollapsibleAside>
<TimelineScroller>
<Notifications />
</TimelineScroller>
</CollapsibleAside>
</div>
</div>
<LazyComposerModal />
<LazySocialElementsNotesAttachmentDialog />
<ComposerModal />
<AttachmentDialog />
</template>
<script setup lang="ts">
import ComposerModal from "~/components/composer/modal.client.vue";
import SquarePattern from "~/components/graphics/square-pattern.vue";
import CollapsibleAside from "~/components/sidebars/collapsible-aside.vue";
import Navigation from "~/components/sidebars/navigation.vue";
import AttachmentDialog from "~/components/social-elements/notes/attachment-dialog.vue";
import Notifications from "~/components/timelines/notifications.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import { OverlayScrollbarsComponent } from "#imports";
const { width } = useWindowSize();

View file

@ -1,10 +1,10 @@
<template>
<main class="from-dark-600 to-dark-900 bg-gradient-to-tl min-h-dvh pb-20 md:pb-0">
<LazySidebarsNavigation />
<Navigation />
<slot />
</main>
</template>
<script lang="ts" setup>
import Navigation from "~/components/sidebars/navigation.vue";
</script>

View file

@ -15,6 +15,9 @@ export default defineNuxtConfig({
isCustomElement: (tag) => tag === "iconify-icon",
},
},
components: {
dirs: [],
},
future: {
compatibilityVersion: 4,
},

View file

@ -1,17 +1,19 @@
<template>
<div v-if="loaded" :defer="true" class="mx-auto max-w-2xl w-full pb-72">
<LazySocialElementsNotesNote v-for="note of context?.ancestors" :note="note" />
<Note v-for="note of context?.ancestors" :note="note" />
<div ref="element" class="first:rounded-t last:rounded-b overflow-hidden">
<LazySocialElementsNotesNote class="!rounded-none border-2 border-primary-500" v-if="note" :note="note" />
<Note class="!rounded-none border-2 border-primary-500" v-if="note" :note="note" />
</div>
<LazySocialElementsNotesNote v-for="note of context?.descendants" :note="note" />
<Note v-for="note of context?.descendants" :note="note" />
</div>
<div v-else class="mx-auto max-w-2xl w-full overflow-y-auto">
<LazySocialElementsNotesNote v-for="_ of 5" :skeleton="true" />
<Note v-for="_ of 5" :skeleton="true" />
</div>
</template>
<script setup lang="ts">
import Note from "~/components/social-elements/notes/note.vue";
definePageMeta({
layout: "app",
});

View file

@ -1,16 +1,20 @@
<template>
<ErrorsErrorBoundary>
<ErrorBoundary>
<div class="mx-auto max-w-2xl w-full">
<LazyTimelinesTimelineScroller>
<LazySocialElementsUsersAccount :account="account ?? undefined" />
<LazyTimelinesAccount :id="accountId" :key="accountId" />
</LazyTimelinesTimelineScroller>
<TimelineScroller>
<AccountProfile :account="account ?? undefined" />
<AccountTimeline :id="accountId" :key="accountId" />
</TimelineScroller>
</div>
</ErrorsErrorBoundary>
</ErrorBoundary>
</template>
<script setup lang="ts">
import type { Account } from "@lysand-org/client/types";
import ErrorBoundary from "~/components/errors/ErrorBoundary.vue";
import AccountProfile from "~/components/timelines/account.vue";
import AccountTimeline from "~/components/timelines/account.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",

View file

@ -1,13 +1,17 @@
<template>
<div class="mx-auto max-w-2xl w-full">
<LazyTimelinesTimelineScroller>
<LazyHeadersGreeting />
<LazyTimelinesHome />
</LazyTimelinesTimelineScroller>
<TimelineScroller>
<Greeting />
<Home />
</TimelineScroller>
</div>
</template>
<script setup lang="ts">
import Greeting from "~/components/headers/greeting.vue";
import Home from "~/components/timelines/home.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",
});

View file

@ -1,14 +1,18 @@
<template>
<div class="mx-auto max-w-2xl w-full">
<LazyTimelinesTimelineScroller>
<LazyHeadersGreeting />
<LazyTimelinesPublic />
</LazyTimelinesTimelineScroller>
<TimelineScroller>
<Greeting />
<Public />
</TimelineScroller>
</div>
</template>
<script setup lang="ts">
import Greeting from "~/components/headers/greeting.vue";
import Public from "~/components/timelines/public.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",
});

View file

@ -1,14 +1,18 @@
<template>
<div class="mx-auto max-w-2xl w-full">
<LazyTimelinesTimelineScroller>
<LazyHeadersGreeting />
<LazyTimelinesLocal />
</LazyTimelinesTimelineScroller>
<TimelineScroller>
<Greeting />
<Local />
</TimelineScroller>
</div>
</template>
<script lang="ts" setup>
import Greeting from "~/components/headers/greeting.vue";
import Local from "~/components/timelines/local.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",
});

View file

@ -11,16 +11,20 @@
sign in</span>
</button>
</div>
<LazyTimelinesTimelineScroller v-else>
<LazyHeadersGreeting />
<TimelineScroller v-else>
<Greeting />
<div class="rounded overflow-hidden ring-1 ring-white/10">
<LazyTimelinesNotifications />
<Notifications />
</div>
</LazyTimelinesTimelineScroller>
</TimelineScroller>
</div>
</template>
<script lang="ts" setup>
import Greeting from "~/components/headers/greeting.vue";
import Notifications from "~/components/timelines/notifications.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",
});

View file

@ -13,25 +13,25 @@
</div>
<VeeField name="identifier" as="div" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="identifier">Username or Email</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsText id="identifier" placeholder="joemama" autocomplete="email username" required
<Field>
<LabelAndError>
<Label for="identifier">Username or Email</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<TextInput id="identifier" placeholder="joemama" autocomplete="email username" required
v-bind="field" :is-invalid="!!errorMessage" />
</InputsField>
</Field>
</VeeField>
<VeeField name="password" as="div" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="password">Password</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsPassword id="password" placeholder="hunter2" autocomplete="current-password" required
<Field>
<LabelAndError>
<Label for="password">Password</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<PasswordInput id="password" placeholder="hunter2" autocomplete="current-password" required
v-bind="field" :is-invalid="!!errorMessage" />
</InputsField>
</Field>
</VeeField>
<div v-if="ssoConfig && ssoConfig.providers.length > 0" class="w-full space-y-3">
@ -42,13 +42,13 @@
<div class="grid md:grid-cols-2 md:[&:has(>:last-child:nth-child(1))]:grid-cols-1 gap-4 w-full">
<a v-for="provider of ssoConfig.providers" :key="provider.id"
:href="`/oauth/sso?issuer=${provider.id}&redirect_uri=${params.redirect_uri}&response_type=${params.response_type}&client_id=${params.client_id}&scope=${params.scope}`">
<ButtonsSecondary class="flex flex-row w-full items-center justify-center gap-3">
<ButtonSecondary class="flex flex-row w-full items-center justify-center gap-3">
<img crossorigin="anonymous" :src="provider.icon" :alt="`${provider.name}'s logo'`"
class="w-6 h-6" />
<div class="flex flex-col gap-0 justify-center">
<h3 class="font-bold">{{ provider.name }}</h3>
</div>
</ButtonsSecondary>
</ButtonSecondary>
</a>
</div>
</div>
@ -58,7 +58,7 @@
here, please close this page.
</p>
<ButtonsPrimary type="submit" class="w-full">Sign in</ButtonsPrimary>
<ButtonPrimary type="submit" class="w-full">Sign in</ButtonPrimary>
</VeeForm>
</div>
<div v-else class="mx-auto max-w-md">
@ -100,6 +100,14 @@
import { LysandClient } from "@lysand-org/client";
import { toTypedSchema } from "@vee-validate/zod";
import { z } from "zod";
import ButtonPrimary from "~/components/buttons/button-primary.vue";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
import FieldError from "~/components/inputs/field-error.vue";
import Field from "~/components/inputs/field.vue";
import LabelAndError from "~/components/inputs/label-and-error.vue";
import Label from "~/components/inputs/label.vue";
import PasswordInput from "~/components/inputs/password-input.vue";
import TextInput from "~/components/inputs/text-input.vue";
const schema = toTypedSchema(
z.object({

View file

@ -40,9 +40,9 @@
</div>
<div class="flex flex-col gap-3">
<ButtonsPrimary type="submit">Authorize</ButtonsPrimary>
<ButtonPrimary type="submit">Authorize</ButtonPrimary>
<NuxtLink href="/" class="w-full">
<ButtonsSecondary class="w-full">Cancel</ButtonsSecondary>
<ButtonSecondary class="w-full">Cancel</ButtonSecondary>
</NuxtLink>
</div>
</form>
@ -83,6 +83,9 @@
</template>
<script setup lang="ts">
import ButtonPrimary from "~/components/buttons/button-primary.vue";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
const url = useRequestURL();
const params = useUrlSearchParams();

View file

@ -14,32 +14,32 @@
</div>
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="password">New password</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsPassword id="password" placeholder="hunter2" autocomplete="new-password" required
<Field>
<LabelAndError>
<Label for="password">New password</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<PasswordInput id="password" placeholder="hunter2" autocomplete="new-password" required
v-bind="field" :is-invalid="!!errorMessage" :show-strength="true" />
</InputsField>
</Field>
</VeeField>
<VeeField name="password-confirm" as="div" v-slot="{ errors, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="password-confirm">Confirm password</InputsLabel>
<InputsError v-if="errors.length > 0">{{ errors[0] }}</InputsError>
</InputsLabelAndError>
<InputsPassword id="password-confirm" placeholder="hunter2" autocomplete="new-password" required
<Field>
<LabelAndError>
<Label for="password-confirm">Confirm password</Label>
<FieldError v-if="errors.length > 0">{{ errors[0] }}</FieldError>
</LabelAndError>
<PasswordInput id="password-confirm" placeholder="hunter2" autocomplete="new-password" required
v-bind="field" :is-invalid="errors.length > 0" />
</InputsField>
</Field>
</VeeField>
<p class="text-xs font-semibold text-red-300">This will reset your
password. Make sure to put it in a password manager.
</p>
<ButtonsPrimary type="submit" class="w-full">Reset</ButtonsPrimary>
<ButtonPrimary type="submit" class="w-full">Reset</ButtonPrimary>
</VeeForm>
</div>
<div v-else-if="params.success">
@ -69,6 +69,12 @@
<script setup lang="ts">
import { toTypedSchema } from "@vee-validate/zod";
import { z } from "zod";
import ButtonPrimary from "~/components/buttons/button-primary.vue";
import FieldError from "~/components/inputs/field-error.vue";
import Field from "~/components/inputs/field.vue";
import LabelAndError from "~/components/inputs/label-and-error.vue";
import Label from "~/components/inputs/label.vue";
import PasswordInput from "~/components/inputs/password-input.vue";
const identity = useCurrentIdentity();
identity.value = null;

View file

@ -1,13 +1,17 @@
<template>
<div class="mx-auto max-w-2xl w-full">
<LazyTimelinesTimelineScroller>
<LazyHeadersGreeting />
<TimelinesPublic />
</LazyTimelinesTimelineScroller>
<TimelineScroller>
<Greeting />
<Public />
</TimelineScroller>
</div>
</template>
<script setup lang="ts">
import Greeting from "~/components/headers/greeting.vue";
import Public from "~/components/timelines/public.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
definePageMeta({
layout: "app",
});

View file

@ -12,66 +12,65 @@
<h1 class="font-bold text-2xl text-gray-50 text-center tracking-tight">Account details</h1>
<VeeField name="username" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="username">Username</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsText id="username" type="text" placeholder="thespeedy" required v-bind="field"
<Field>
<LabelAndError>
<Label for="username">Username</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<TextInput id="username" type="text" placeholder="thespeedy" required v-bind="field"
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="username"
:spellcheck="false" />
</InputsField>
</Field>
</VeeField>
<VeeField name="email" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="email">Email address</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsText id="email" type="email" placeholder="joseph.jones@gmail.com" required v-bind="field"
<Field>
<LabelAndError>
<Label for="email">Email address</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<TextInput id="email" type="email" placeholder="joseph.jones@gmail.com" required v-bind="field"
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="email" />
</InputsField>
</Field>
</VeeField>
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="password">Password</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsPassword id="password" placeholder="hunter2" required v-bind="field"
:disabled="isLoading" :is-invalid="!!errorMessage" :show-strength="true"
autocomplete="new-password" />
</InputsField>
<Field>
<LabelAndError>
<Label for="password">Password</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<PasswordInput id="password" placeholder="hunter2" required v-bind="field" :disabled="isLoading"
:is-invalid="!!errorMessage" :show-strength="true" autocomplete="new-password" />
</Field>
</VeeField>
<VeeField name="password-confirm" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<InputsLabelAndError>
<InputsLabel for="password-confirm">Confirm password</InputsLabel>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsLabelAndError>
<InputsPassword id="password-confirm" placeholder="hunter2" required v-bind="field"
<Field>
<LabelAndError>
<Label for="password-confirm">Confirm password</Label>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</LabelAndError>
<PasswordInput id="password-confirm" placeholder="hunter2" required v-bind="field"
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="new-password" />
</InputsField>
</Field>
</VeeField>
<VeeField name="tos" v-slot="{ errorMessage, field }" validate-on-change>
<InputsField>
<Field>
<div class="flex flex-row gap-x-2 items-center">
<InputsCheckbox :checked="true" id="tos" required :disabled="true" v-bind="field" />
<InputsLabel for="tos" class="!text-gray-200">
<CheckboxInput :checked="true" id="tos" required :disabled="true" v-bind="field" />
<Label for="tos" class="!text-gray-200">
I agree to the Terms of Service
</InputsLabel>
</Label>
</div>
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
</InputsField>
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
</Field>
</VeeField>
<Collapsible.Root>
<Collapsible.Trigger class="w-full">
<ButtonsSecondary type="button" class="w-full">View Terms of Service</ButtonsSecondary>
<ButtonSecondary type="button" class="w-full">View Terms of Service</ButtonSecondary>
</Collapsible.Trigger>
<Collapsible.Content
class="prose prose-invert prose-sm p-4 ring-1 ring-white/10 bg-dark-700 rounded mt-3">
@ -85,8 +84,8 @@
cannot see your password.
</p>
<ButtonsPrimary type="submit" class="w-full" :disabled="isLoading">{{ isLoading ? "Registering..." :
"Register" }}</ButtonsPrimary>
<ButtonPrimary type="submit" class="w-full" :disabled="isLoading">{{ isLoading ? "Registering..." :
"Register" }}</ButtonPrimary>
</VeeForm>
</div>
<div v-else>
@ -105,6 +104,15 @@ import { Collapsible } from "@ark-ui/vue";
import type { ResponseError } from "@lysand-org/client";
import { toTypedSchema } from "@vee-validate/zod";
import { z } from "zod";
import ButtonPrimary from "~/components/buttons/button-primary.vue";
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
import CheckboxInput from "~/components/inputs/checkbox-input.vue";
import FieldError from "~/components/inputs/field-error.vue";
import Field from "~/components/inputs/field.vue";
import LabelAndError from "~/components/inputs/label-and-error.vue";
import Label from "~/components/inputs/label.vue";
import PasswordInput from "~/components/inputs/password-input.vue";
import TextInput from "~/components/inputs/text-input.vue";
const schema = toTypedSchema(
z

View file

@ -1,30 +1,33 @@
<template>
<SidebarsSettings>
<SettingsSidebar>
<template #behaviour>
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
SettingPages.Behaviour,
)" :key="setting.id" />
</template>
<template #appearance>
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
SettingPages.Appearance,
)" :key="setting.id" />
</template>
<template #advanced>
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
SettingPages.Advanced,
)" :key="setting.id" />
</template>
<template #account>
<SettingsProfileEditor />
<ProfileEditor />
</template>
</SidebarsSettings>
</SettingsSidebar>
</template>
<script setup lang="ts">
import ProfileEditor from "~/components/settings/profile-editor.vue";
import Renderer from "~/components/settings/renderer.vue";
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
import { SettingPages, getSettingsForPath } from "~/settings";
definePageMeta({