mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
refactor: ♻️ Disable Nuxt component auto-importing (obscures code flow)
This commit is contained in:
parent
32d1acb4c1
commit
e309c56a86
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"conventionalCommits.scopes": ["build"]
|
"conventionalCommits.scopes": ["build"],
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
app.vue
2
app.vue
|
|
@ -13,6 +13,8 @@
|
||||||
import "~/styles/theme.css";
|
import "~/styles/theme.css";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
import "iconify-icon";
|
import "iconify-icon";
|
||||||
|
import Loading from "./components/loading.vue";
|
||||||
|
import NotificationsRenderer from "./components/notifications/notifications-renderer.vue";
|
||||||
// Use SSR-safe IDs for Headless UI
|
// Use SSR-safe IDs for Headless UI
|
||||||
provideHeadlessUseId(() => useId());
|
provideHeadlessUseId(() => useId());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { HTMLAttributes } from "vue";
|
import type { HTMLAttributes } from "vue";
|
||||||
|
import Skeleton from "../skeleton/Skeleton.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ HTMLAttributes {
|
interface Props extends /* @vue-ignore */ HTMLAttributes {
|
||||||
src?: string;
|
src?: string;
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
<template>
|
<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" />
|
<iconify-icon :icon="icon" width="none" class="text-gray-200 size-5" aria-hidden="true" />
|
||||||
<slot />
|
<slot />
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ButtonHTMLAttributes } from "vue";
|
import type { ButtonHTMLAttributes } from "vue";
|
||||||
|
import ButtonBase from "./button-base.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
||||||
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
<template>
|
<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">
|
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" />
|
<iconify-icon :icon="icon" class="size-6" width="none" />
|
||||||
<span class="text-xs hidden md:inline">{{ text }}</span>
|
<span class="text-xs hidden md:inline">{{ text }}</span>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import ButtonBase from "./button-base.vue";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
icon: string;
|
icon: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
<template>
|
<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"
|
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">
|
v-bind="$props">
|
||||||
<slot />
|
<slot />
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ButtonHTMLAttributes } from "vue";
|
import type { ButtonHTMLAttributes } from "vue";
|
||||||
|
import ButtonBase from "./button-base.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
||||||
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
<template>
|
<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"
|
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">
|
v-bind="$props" :loading="loading">
|
||||||
<slot />
|
<slot />
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ButtonHTMLAttributes } from "vue";
|
import type { ButtonHTMLAttributes } from "vue";
|
||||||
|
import ButtonBase from "./button-base.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
||||||
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="respondingTo" class="mb-4" role="region" aria-label="Responding to">
|
<div v-if="respondingTo" class="mb-4" role="region" aria-label="Responding to">
|
||||||
<OverlayScrollbarsComponent :defer="true" class="max-h-72 overflow-y-auto">
|
<OverlayScrollbarsComponent :defer="true" class="max-h-72 overflow-y-auto">
|
||||||
<LazySocialElementsNotesNote :note="respondingTo" :small="true" :disabled="true"
|
<Note :note="respondingTo" :small="true" :disabled="true" class="!rounded-none !bg-primary-500/10" />
|
||||||
class="!rounded-none !bg-primary-500/10" />
|
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6 pb-4 pt-5">
|
<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]" />
|
:placeholder="chosenSplash" :max-characters="characterLimit" class="focus:!ring-0 max-h-[70dvh]" />
|
||||||
<!-- Content warning textbox -->
|
<!-- Content warning textbox -->
|
||||||
<div v-if="cw" class="mb-4">
|
<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"
|
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" />
|
aria-label="Content warning" />
|
||||||
</div>
|
</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">
|
<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" />
|
<iconify-icon height="1.5rem" width="1.5rem" icon="tabler:at" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ComposerButton title="Toggle Markdown" @click="markdown = !markdown" :toggled="markdown">
|
<Button title="Toggle Markdown" @click="markdown = !markdown" :toggled="markdown">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem"
|
<iconify-icon width="1.25rem" height="1.25rem"
|
||||||
:icon="markdown ? 'tabler:markdown' : 'tabler:markdown-off'" aria-hidden="true" />
|
:icon="markdown ? 'tabler:markdown' : 'tabler:markdown-off'" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ComposerButton title="Use a custom emoji">
|
<Button title="Use a custom emoji">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:mood-smile" aria-hidden="true" />
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:mood-smile" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ComposerButton title="Add media" @click="openFilePicker">
|
<Button title="Add media" @click="openFilePicker">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:photo-up" aria-hidden="true" />
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:photo-up" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ComposerButton title="Add a file" @click="openFilePicker">
|
<Button title="Add a file" @click="openFilePicker">
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:file-upload" aria-hidden="true" />
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:file-upload" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ComposerButton title="Add content warning" @click="cw = !cw" :toggled="cw">
|
<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" />
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:rating-18-plus" aria-hidden="true" />
|
||||||
</ComposerButton>
|
</Button>
|
||||||
<ButtonsPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
|
<ButtonPrimary :loading="loading" @click="send" class="ml-auto rounded-full"
|
||||||
:disabled="!canSubmit || loading">
|
:disabled="!canSubmit || loading">
|
||||||
<span>{{
|
<span>{{
|
||||||
respondingType === "edit" ? "Edit!" : "Send!"
|
respondingType === "edit" ? "Edit!" : "Send!"
|
||||||
}}</span>
|
}}</span>
|
||||||
</ButtonsPrimary>
|
</ButtonPrimary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -49,7 +48,12 @@
|
||||||
import type { Instance, Status } from "@lysand-org/client/types";
|
import type { Instance, Status } from "@lysand-org/client/types";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { OverlayScrollbarsComponent } from "#imports";
|
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 uploader = ref<InstanceType<typeof FileUploader> | undefined>(undefined);
|
||||||
const { Control_Enter, Command_Enter, Control_Alt } = useMagicKeys();
|
const { Control_Enter, Command_Enter, Control_Alt } = useMagicKeys();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
<template>
|
<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)"
|
<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"
|
: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 }]">
|
: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"
|
<img :src="emoji.url" class="w-full h-full object-contain"
|
||||||
:alt="`Emoji with shortcode ${emoji.shortcode}`" />
|
:alt="`Emoji with shortcode ${emoji.shortcode}`" />
|
||||||
</div>
|
</div>
|
||||||
</ComposerSuggestbox>
|
</Suggestbox>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Emoji } from "@lysand-org/client/types";
|
import type { Emoji } from "@lysand-org/client/types";
|
||||||
import { distance } from "fastest-levenshtein";
|
import { distance } from "fastest-levenshtein";
|
||||||
|
import Suggestbox from "./suggestbox.vue";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
currentlyTypingEmoji: string | null;
|
currentlyTypingEmoji: string | null;
|
||||||
textarea: HTMLTextAreaElement | undefined;
|
textarea: HTMLTextAreaElement | undefined;
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
</button>
|
</button>
|
||||||
<!-- Alt text editor -->
|
<!-- Alt text editor -->
|
||||||
<Popover.Root :positioning="{
|
<Popover.Root :positioning="{
|
||||||
strategy: 'fixed',
|
strategy: 'fixed',
|
||||||
}" v-if="data.api_id" @update:open="o => !o && updateAltText(data.id, data.alt_text)">
|
}" v-if="data.api_id" @update:open="o => !o && updateAltText(data.id, data.alt_text)">
|
||||||
<Popover.Trigger aria-hidden="true"
|
<Popover.Trigger aria-hidden="true"
|
||||||
class="absolute top-1 left-1 p-1 bg-dark-800 ring-1 ring-white/5 text-white text-xs rounded size-6">
|
class="absolute top-1 left-1 p-1 bg-dark-800 ring-1 ring-white/5 text-white text-xs rounded size-6">
|
||||||
<iconify-icon icon="tabler:alt" width="none" class="size-4" />
|
<iconify-icon icon="tabler:alt" width="none" class="size-4" />
|
||||||
|
|
@ -42,10 +42,10 @@
|
||||||
<textarea :disabled="data.progress < 1.0" @keydown.enter.stop v-model="data.alt_text"
|
<textarea :disabled="data.progress < 1.0" @keydown.enter.stop v-model="data.alt_text"
|
||||||
placeholder="Add 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" />
|
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">
|
:loading="data.progress < 1.0">
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</Popover.Positioner>
|
</Popover.Positioner>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Popover } from "@ark-ui/vue";
|
import { Popover } from "@ark-ui/vue";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import ButtonSecondary from "../buttons/button-secondary.vue";
|
||||||
|
|
||||||
const files = defineModel<
|
const files = defineModel<
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
<template>
|
<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)"
|
<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"
|
: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 }]">
|
: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}`" />
|
<img :src="user.avatar" class="w-full h-full object-contain" :alt="`User ${user.acct}`" />
|
||||||
</div>
|
</div>
|
||||||
</ComposerSuggestbox>
|
</Suggestbox>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Account } from "@lysand-org/client/types";
|
import type { Account } from "@lysand-org/client/types";
|
||||||
import { distance } from "fastest-levenshtein";
|
import { distance } from "fastest-levenshtein";
|
||||||
|
import Suggestbox from "./suggestbox.vue";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
currentlyTypingMention: string | null;
|
currentlyTypingMention: string | null;
|
||||||
textarea: HTMLTextAreaElement | undefined;
|
textarea: HTMLTextAreaElement | undefined;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Dialog } from "@ark-ui/vue";
|
import { Dialog } from "@ark-ui/vue";
|
||||||
|
import Composer from "./composer.vue";
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
|
|
||||||
const identity = useCurrentIdentity();
|
const identity = useCurrentIdentity();
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-6 text-base leading-7 text-gray-400" v-html="error.message"></p>
|
<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">
|
<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">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import ButtonPrimary from "../buttons/button-primary.vue";
|
||||||
|
import ButtonSecondary from "../buttons/button-secondary.vue";
|
||||||
|
|
||||||
const error = ref<{
|
const error = ref<{
|
||||||
title: string;
|
title: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SquarePattern from "../graphics/square-pattern.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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">
|
<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 class="mt-4 text-3xl font-bold tracking-tight text-gray-100 sm:text-5xl">JavaScript is disabled
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -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 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:items-center sm:justify-between gap-3">
|
||||||
<div class="sm:flex sm:space-x-5 grow">
|
<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"
|
class="mx-auto shrink-0 size-20 rounded overflow-hidden ring-1 ring-white/10"
|
||||||
:alt="'Your avatar'" />
|
:alt="'Your avatar'" />
|
||||||
<div
|
<div
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Avatar from "../avatars/avatar.vue";
|
||||||
|
|
||||||
const identity = useCurrentIdentity();
|
const identity = useCurrentIdentity();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { display_name } = useParsedAccount(
|
const { display_name } = useParsedAccount(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<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" />
|
class="rounded disabled:hover:cursor-wait text-primary-700 !size-5" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { InputHTMLAttributes } from "vue";
|
import type { InputHTMLAttributes } from "vue";
|
||||||
|
import TextInput from "./text-input.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ InputHTMLAttributes {}
|
interface Props extends /* @vue-ignore */ InputHTMLAttributes {}
|
||||||
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<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" />
|
:type="showPassword ? 'text' : 'password'" :spellcheck="false" />
|
||||||
<Progress.Root class="flex flex-row items-center gap-x-2" v-if="showStrength">
|
<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">
|
<Progress.Label class="text-xs text-gray-300 font-semibold w-12">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</Progress.Label>
|
</Progress.Label>
|
||||||
<Progress.Track class="rounded-sm w-full h-2 duration-300" :style="{
|
<Progress.Track class="rounded-sm w-full h-2 duration-300" :style="{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}">
|
}">
|
||||||
<Progress.Range />
|
<Progress.Range />
|
||||||
</Progress.Track>
|
</Progress.Track>
|
||||||
</Progress.Root>
|
</Progress.Root>
|
||||||
|
|
@ -27,6 +27,7 @@ const showPassword = ref(false);
|
||||||
const content = ref("");
|
const content = ref("");
|
||||||
|
|
||||||
import type { InputHTMLAttributes } from "vue";
|
import type { InputHTMLAttributes } from "vue";
|
||||||
|
import TextInput from "./text-input.vue";
|
||||||
|
|
||||||
interface Props extends /* @vue-ignore */ InputHTMLAttributes {
|
interface Props extends /* @vue-ignore */ InputHTMLAttributes {
|
||||||
isInvalid?: boolean;
|
isInvalid?: boolean;
|
||||||
|
|
@ -66,6 +67,7 @@ const color = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// Workaround to make sure the teleport is rendered after the parent component
|
||||||
teleport.value = true;
|
teleport.value = true;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -8,15 +8,17 @@
|
||||||
aria-live="polite">
|
aria-live="polite">
|
||||||
{{ remainingCharacters }}
|
{{ remainingCharacters }}
|
||||||
</div>
|
</div>
|
||||||
<ComposerEmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
|
<EmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
|
||||||
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
|
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
|
||||||
<ComposerMentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
|
<MentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
|
||||||
:currently-typing-mention="currentlyBeingTypedMention" @autocomplete="autocompleteMention" />
|
:currently-typing-mention="currentlyBeingTypedMention" @autocomplete="autocompleteMention" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { char, createRegExp, exactly } from "magic-regexp";
|
import { char, createRegExp, exactly } from "magic-regexp";
|
||||||
|
import EmojiSuggestbox from "../composer/emoji-suggestbox.vue";
|
||||||
|
import MentionSuggestbox from "../composer/mention-suggestbox.vue";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|
@ -9,17 +9,17 @@
|
||||||
<div v-for="provider of ssoConfig?.providers" :key="provider.id"
|
<div v-for="provider of ssoConfig?.providers" :key="provider.id"
|
||||||
class="flex items-center justify-between p-4 bg-dark-700 rounded">
|
class="flex items-center justify-between p-4 bg-dark-700 rounded">
|
||||||
<div class="flex items-center gap-4">
|
<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>
|
<span class="font-semibold text-gray-300">{{ provider.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<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)">
|
@click="link(provider.id)">
|
||||||
<span>Link</span>
|
<span>Link</span>
|
||||||
</ButtonsPrimary>
|
</ButtonPrimary>
|
||||||
<ButtonsSecondary :loading="loading" v-else @click="unlink(provider.id)">
|
<ButtonSecondary :loading="loading" v-else @click="unlink(provider.id)">
|
||||||
<span>Unlink</span>
|
<span>Unlink</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -29,6 +29,9 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ResponseError } from "@lysand-org/client";
|
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 client = useClient();
|
||||||
const ssoConfig = useSSOConfig();
|
const ssoConfig = useSSOConfig();
|
||||||
|
|
|
||||||
|
|
@ -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">
|
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"
|
<form class="w-full ring-1 ring-inset ring-white/5 pb-5 bg-dark-800 rounded overflow-hidden"
|
||||||
@submit.prevent="save">
|
@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" />
|
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">
|
<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" />
|
class="h-32 w-32 -mt-[4.5rem] z-10 shrink-0 rounded ring-2 ring-dark-800" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 px-4">
|
<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" />
|
aria-label="Display name" :disabled="loading" />
|
||||||
<div class="mt-2 grid grid-cols-[auto_1fr] items-center gap-x-2">
|
<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" />
|
<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" />
|
aria-label="Username" :disabled="loading" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-300 text-xs mt-2">
|
<p class="text-gray-300 text-xs mt-2">
|
||||||
|
|
@ -25,27 +25,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 px-4">
|
<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" />
|
class="rounded ring-white/10 ring-2 focus:ring-primary-600 px-4 py-2 max-h-[40dvh] max-w-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 mt-4 grid grid-cols-2 gap-2">
|
<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>
|
<span>Save</span>
|
||||||
</ButtonsPrimary>
|
</ButtonPrimary>
|
||||||
<ButtonsSecondary class="w-full" @click="revert" type="button" :loading="loading">
|
<ButtonSecondary class="w-full" @click="revert" type="button" :loading="loading">
|
||||||
<span>Revert</span>
|
<span>Revert</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
<SettingsOidc />
|
<Oidc />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ResponseError } from "@lysand-org/client";
|
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 identity = useCurrentIdentity();
|
||||||
const account = computed(() => identity.value?.account);
|
const account = computed(() => identity.value?.account);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<DropdownsAdaptiveDropdown>
|
<AdaptiveDropdown>
|
||||||
<template #button>
|
<template #button>
|
||||||
<slot>
|
<slot>
|
||||||
<div class="rounded text-left flex flex-row gap-x-2 hover:scale-[95%] duration-100"
|
<div class="rounded text-left flex flex-row gap-x-2 hover:scale-[95%] duration-100"
|
||||||
v-if="currentIdentity">
|
v-if="currentIdentity">
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<AvatarsCentered class="size-12 rounded ring-1 ring-white/5"
|
<Avatar class="size-12 rounded ring-1 ring-white/5" :src="currentIdentity.account.avatar"
|
||||||
:src="currentIdentity.account.avatar" :alt="`${currentIdentity.account.acct}'s avatar'`" />
|
:alt="`${currentIdentity.account.acct}'s avatar'`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start p-1 justify-around grow overflow-hidden">
|
<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">
|
<div class="flex flex-row items-center justify-between w-full">
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
overflow-hidden h-12 w-full duration-200">
|
||||||
<iconify-icon icon="tabler:login" class="shrink-0 text-2xl" />
|
<iconify-icon icon="tabler:login" class="shrink-0 text-2xl" />
|
||||||
<span class="pr-28 line-clamp-1">Sign In</span>
|
<span class="pr-28 line-clamp-1">Sign In</span>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #items>
|
<template #items>
|
||||||
|
|
@ -36,8 +36,8 @@
|
||||||
<Menu.Item value="" v-for="identity of identities" class="hover:scale-[95%] duration-100">
|
<Menu.Item value="" v-for="identity of identities" class="hover:scale-[95%] duration-100">
|
||||||
<div class="flex flex-row gap-x-4">
|
<div class="flex flex-row gap-x-4">
|
||||||
<div class="shrink-0" data-part="item" @click="useEvent('identity:change', identity)">
|
<div class="shrink-0" data-part="item" @click="useEvent('identity:change', identity)">
|
||||||
<AvatarsCentered class="h-12 w-12 rounded ring-1 ring-white/5"
|
<Avatar class="h-12 w-12 rounded ring-1 ring-white/5" :src="identity.account.avatar"
|
||||||
:src="identity.account.avatar" :alt="`${identity.account.acct}'s avatar'`" />
|
:alt="`${identity.account.acct}'s avatar'`" />
|
||||||
</div>
|
</div>
|
||||||
<div data-part="item" class="flex flex-col items-start justify-around grow overflow-hidden"
|
<div data-part="item" class="flex flex-col items-start justify-around grow overflow-hidden"
|
||||||
@click="useEvent('identity:change', identity)">
|
@click="useEvent('identity:change', identity)">
|
||||||
|
|
@ -104,11 +104,14 @@
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</DropdownsAdaptiveDropdown>
|
</AdaptiveDropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Menu } from "@ark-ui/vue";
|
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 identities = useIdentities();
|
||||||
|
|
||||||
const currentIdentity = useCurrentIdentity();
|
const currentIdentity = useCurrentIdentity();
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@
|
||||||
Timelines</h3>
|
Timelines</h3>
|
||||||
|
|
||||||
<NuxtLink v-for="timeline in visibleTimelines" :key="timeline.href" :to="timeline.href">
|
<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">
|
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" />
|
<iconify-icon :icon="timeline.icon" class="shrink-0 text-2xl" />
|
||||||
<span class="pr-28 line-clamp-1">{{ timeline.name }}</span>
|
<span class="pr-28 line-clamp-1">{{ timeline.name }}</span>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -25,14 +25,14 @@
|
||||||
<h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
|
<h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
|
||||||
Account</h3>
|
Account</h3>
|
||||||
|
|
||||||
<SidebarsAccountPicker @sign-in="signIn().finally(() => loadingAuth = false)"
|
<AccountPicker @sign-in="signIn().finally(() => loadingAuth = false)"
|
||||||
@sign-out="id => signOut(id).finally(() => loadingAuth = false)" />
|
@sign-out="id => signOut(id).finally(() => loadingAuth = false)" />
|
||||||
<NuxtLink href="/register" v-if="!identity">
|
<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">
|
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" />
|
<iconify-icon icon="tabler:certificate" class="shrink-0 text-2xl" />
|
||||||
<span class="pr-28 line-clamp-1">Register</span>
|
<span class="pr-28 line-clamp-1">Register</span>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink href="/settings" v-if="identity">
|
<NuxtLink href="/settings" v-if="identity">
|
||||||
<button @click="$emit('signIn')" class="w-full overflow-hidden">
|
<button @click="$emit('signIn')" class="w-full overflow-hidden">
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
<h3 v-if="identity"
|
<h3 v-if="identity"
|
||||||
class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
|
class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
|
||||||
Posts</h3>
|
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">
|
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" />
|
<iconify-icon icon="tabler:writing" class="shrink-0 text-2xl" />
|
||||||
<span class="pr-28 line-clamp-1">Compose</span>
|
<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"
|
<iconify-icon icon="tabler:letter-n-small" height="1rem" width="1rem" class="inline -mr-1"
|
||||||
aria-hidden="true" />
|
aria-hidden="true" />
|
||||||
</kbd>
|
</kbd>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
<ButtonsBase v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker()" title="Update service worker"
|
<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">
|
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" />
|
<iconify-icon icon="tabler:refresh" class="shrink-0 text-2xl" />
|
||||||
<span class="pr-28 line-clamp-1">Update</span>
|
<span class="pr-28 line-clamp-1">Update</span>
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
@ -73,30 +73,30 @@
|
||||||
<nav
|
<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']">
|
: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>
|
<template #button>
|
||||||
<ButtonsMobileNavbarButton icon="tabler:home" text="Timelines" />
|
<ButtonMobileNavbar icon="tabler:home" text="Timelines" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #items>
|
<template #items>
|
||||||
<Menu.Item value="" v-for="timeline in visibleTimelines" :key="timeline.href">
|
<Menu.Item value="" v-for="timeline in visibleTimelines" :key="timeline.href">
|
||||||
<NuxtLink :href="timeline.href">
|
<NuxtLink :href="timeline.href">
|
||||||
<ButtonsDropdownElement :icon="timeline.icon" class="w-full">
|
<ButtonDropdown :icon="timeline.icon" class="w-full">
|
||||||
{{ timeline.name }}
|
{{ timeline.name }}
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</template>
|
</template>
|
||||||
</DropdownsAdaptiveDropdown>
|
</AdaptiveDropdown>
|
||||||
<NuxtLink href="/notifications" class="w-full">
|
<NuxtLink href="/notifications" class="w-full">
|
||||||
<ButtonsMobileNavbarButton icon="tabler:bell" text="Notifications" />
|
<ButtonMobileNavbar icon="tabler:bell" text="Notifications" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<ButtonsMobileNavbarButton v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker(true)"
|
<ButtonMobileNavbar v-if="$pwa?.needRefresh" @click="$pwa?.updateServiceWorker(true)" icon="tabler:refresh"
|
||||||
icon="tabler:refresh" text="Update" />
|
text="Update" />
|
||||||
<SidebarsAccountPicker v-else @sign-in="signIn().finally(() => loadingAuth = false)"
|
<AccountPicker v-else @sign-in="signIn().finally(() => loadingAuth = false)"
|
||||||
@sign-out="id => signOut(id).finally(() => loadingAuth = false)">
|
@sign-out="id => signOut(id).finally(() => loadingAuth = false)">
|
||||||
<ButtonsMobileNavbarButton icon="tabler:user" text="Account" />
|
<ButtonMobileNavbar icon="tabler:user" text="Account" />
|
||||||
</SidebarsAccountPicker>
|
</AccountPicker>
|
||||||
<button @click="compose" v-if="identity"
|
<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">
|
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" />
|
<iconify-icon icon="tabler:writing" class="text-2xl" />
|
||||||
|
|
@ -107,6 +107,11 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Menu } from "@ark-ui/vue";
|
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 { $pwa } = useNuxtApp();
|
||||||
const timelines = ref([
|
const timelines = ref([
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
<Tabs.Root v-model="tab">
|
<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.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">
|
<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 }}
|
{{ page }}
|
||||||
</ButtonsBase>
|
</ButtonBase>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Indicator class="h-1 bg-gray-300 w-[--width] top-0 rounded-b" />
|
<Tabs.Indicator class="h-1 bg-gray-300 w-[--width] top-0 rounded-b" />
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Tabs } from "@ark-ui/vue";
|
import { Tabs } from "@ark-ui/vue";
|
||||||
import { SettingPages } from "~/settings";
|
import { SettingPages } from "~/settings";
|
||||||
|
import ButtonBase from "../buttons/button-base.vue";
|
||||||
|
|
||||||
const tab = ref<SettingPages>(
|
const tab = ref<SettingPages>(
|
||||||
(window.location.hash.slice(1) as SettingPages) || SettingPages.Account,
|
(window.location.hash.slice(1) as SettingPages) || SettingPages.Account,
|
||||||
|
|
@ -13,12 +13,14 @@
|
||||||
|
|
||||||
<div v-if="instance?.contact.account" class="flex flex-col gap-2 mt-auto">
|
<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>
|
<h2 class="text-gray-200 font-semibold uppercase text-xs">Administrator</h2>
|
||||||
<SocialElementsUsersSmallCard :account="instance.contact.account" />
|
<SmallCard :account="instance.contact.account" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import SmallCard from "../users/SmallCard.vue";
|
||||||
|
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const description = useExtendedDescription(client);
|
const description = useExtendedDescription(client);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="small" class="flex flex-row">
|
<div v-if="small" class="flex flex-row">
|
||||||
<NuxtLink :href="accountUrl" class="shrink-0">
|
<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" />
|
class="size-6 rounded ring-1 ring-white/5" />
|
||||||
<span class="sr-only">Account profile</span>
|
<span class="sr-only">Account profile</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-row">
|
<div v-else class="flex flex-row">
|
||||||
<NuxtLink :href="accountUrl" class="shrink-0">
|
<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" />
|
class="h-12 w-12 rounded ring-1 ring-white/5" />
|
||||||
<span class="sr-only">Account profile</span>
|
<span class="sr-only">Account profile</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -67,6 +67,8 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Status } from "@lysand-org/client/types";
|
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<{
|
const props = defineProps<{
|
||||||
note?: Status;
|
note?: Status;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Account } from "@lysand-org/client/types";
|
import type { Account } from "@lysand-org/client/types";
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
account: Account;
|
account: Account;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -9,11 +9,9 @@
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<div v-if="note && note.media_attachments.length > 0"
|
<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">
|
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 v-for="attachment of note.media_attachments" :key="attachment.id" :attachment="attachment" />
|
||||||
:attachment="attachment" />
|
|
||||||
</div>
|
</div>
|
||||||
<LazySocialElementsNotesNote v-if="isQuote && note?.quote" :note="note?.quote" :small="true"
|
<Note v-if="isQuote && note?.quote" :note="note?.quote" :small="true" class="mt-4 !rounded" />
|
||||||
class="mt-4 !rounded" />
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else
|
<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">
|
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 -->
|
<!-- Spoiler text is it's specified -->
|
||||||
<span v-if="note?.spoiler_text" class="mt-2 break-all">{{ note.spoiler_text
|
<span v-if="note?.spoiler_text" class="mt-2 break-all">{{ note.spoiler_text
|
||||||
}}</span>
|
}}</span>
|
||||||
<ButtonsSecondary @click="collapsed = false" class="mt-4">Show content</ButtonsSecondary>
|
<ButtonSecondary @click="collapsed = false" class="mt-4">Show content</ButtonSecondary>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Status } from "@lysand-org/client/types";
|
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<{
|
const props = defineProps<{
|
||||||
content: string | null;
|
content: string | null;
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,15 @@
|
||||||
<div v-if="reblog" class="mb-4 flex flex-row gap-2 items-center text-primary-400">
|
<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="%">
|
<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" />
|
<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" />
|
class="size-6 rounded shrink-0 ring-1 ring-white/10" />
|
||||||
<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="outputtedNote?.in_reply_to_account_id ?? null" />
|
<ReplyHeader v-if="isReply" :account_id="outputtedNote?.in_reply_to_account_id ?? null" />
|
||||||
<SocialElementsNotesHeader :note="outputtedNote" :small="small" />
|
<Header :note="outputtedNote" :small="small" />
|
||||||
<LazySocialElementsNotesNoteContent :note="outputtedNote" :loaded="loaded" :url="url" :content="content"
|
<NoteContent :note="outputtedNote" :loaded="loaded" :url="url" :content="content" :is-quote="isQuote"
|
||||||
:is-quote="isQuote" :should-hide="shouldHide" />
|
: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">
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
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>
|
||||||
</button>
|
</button>
|
||||||
<DropdownsAdaptiveDropdown>
|
<AdaptiveDropdown>
|
||||||
<template #button>
|
<template #button>
|
||||||
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
|
<iconify-icon width="1.25rem" height="1.25rem" icon="tabler:dots" class="size-5 text-gray-200"
|
||||||
aria-hidden="true" />
|
aria-hidden="true" />
|
||||||
|
|
@ -55,93 +55,91 @@
|
||||||
<template #items>
|
<template #items>
|
||||||
<Menu.ItemGroup>
|
<Menu.ItemGroup>
|
||||||
<Menu.Item value="" v-if="isMyAccount">
|
<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">
|
icon="tabler:pencil" class="w-full">
|
||||||
Edit
|
Edit
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<Menu.Item value="">
|
||||||
<ButtonsDropdownElement @click="copy(JSON.stringify(props.note, null, 4))"
|
<ButtonDropdown @click="copy(JSON.stringify(props.note, null, 4))" icon="tabler:code"
|
||||||
icon="tabler:code" class="w-full">
|
class="w-full">
|
||||||
Copy API
|
Copy API
|
||||||
Response
|
Response
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<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
|
Copy Link
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
|
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
|
||||||
<ButtonsDropdownElement @click="copy(outputtedNote.url)" icon="tabler:link"
|
<ButtonDropdown @click="copy(outputtedNote.url)" icon="tabler:link" class="w-full">
|
||||||
class="w-full">
|
|
||||||
Copy Link (Origin)
|
Copy Link (Origin)
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
|
<Menu.Item value="" v-if="outputtedNote?.url && isRemote">
|
||||||
<ButtonsDropdownElement @click="openBlank(outputtedNote.url)"
|
<ButtonDropdown @click="openBlank(outputtedNote.url)" icon="tabler:external-link"
|
||||||
icon="tabler:external-link" class="w-full">
|
class="w-full">
|
||||||
View on Remote
|
View on Remote
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="" v-if="isMyAccount">
|
<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">
|
class="w-full border-r-2 border-red-500">
|
||||||
Delete
|
Delete
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
<hr class="border-white/10 rounded" v-if="identity" />
|
<hr class="border-white/10 rounded" v-if="identity" />
|
||||||
<Menu.ItemGroup v-if="identity">
|
<Menu.ItemGroup v-if="identity">
|
||||||
<Menu.Item value="">
|
<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">
|
icon="tabler:arrow-back-up" class="w-full">
|
||||||
Reply
|
Reply
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<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">
|
v-if="!outputtedNote?.favourited">
|
||||||
Like
|
Like
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
<ButtonsDropdownElement @click="likeFn" icon="tabler:heart-filled" class="w-full"
|
<ButtonDropdown @click="likeFn" icon="tabler:heart-filled" class="w-full" v-else>
|
||||||
v-else>
|
|
||||||
Unlike
|
Unlike
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<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">
|
v-if="!outputtedNote?.reblogged">
|
||||||
Reblog
|
Reblog
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
<ButtonsDropdownElement @click="reblogFn" icon="tabler:repeat" class="w-full" v-else>
|
<ButtonDropdown @click="reblogFn" icon="tabler:repeat" class="w-full" v-else>
|
||||||
Unreblog
|
Unreblog
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="">
|
<Menu.Item value="">
|
||||||
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:quote', outputtedNote)"
|
<ButtonDropdown @click="outputtedNote && useEvent('note:quote', outputtedNote)"
|
||||||
icon="tabler:quote" class="w-full">
|
icon="tabler:quote" class="w-full">
|
||||||
Quote
|
Quote
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
<hr class="border-white/10 rounded" v-if="identity" />
|
<hr class="border-white/10 rounded" v-if="identity" />
|
||||||
<Menu.ItemGroup v-if="identity">
|
<Menu.ItemGroup v-if="identity">
|
||||||
<Menu.Item value="">
|
<Menu.Item value="">
|
||||||
<ButtonsDropdownElement @click="outputtedNote && useEvent('note:report', outputtedNote)"
|
<ButtonDropdown @click="outputtedNote && useEvent('note:report', outputtedNote)"
|
||||||
icon="tabler:flag" class="w-full"
|
icon="tabler:flag" class="w-full"
|
||||||
:disabled="!permissions.includes(RolePermission.ManageOwnReports)">
|
:disabled="!permissions.includes(RolePermission.ManageOwnReports)">
|
||||||
Report
|
Report
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item value="" v-if="permissions.includes(RolePermission.ManageAccounts)">
|
<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
|
Open Moderation Panel
|
||||||
</ButtonsDropdownElement>
|
</ButtonDropdown>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
</template>
|
</template>
|
||||||
</DropdownsAdaptiveDropdown>
|
</AdaptiveDropdown>
|
||||||
</div>
|
</div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</article>
|
</article>
|
||||||
|
|
@ -150,7 +148,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Menu } from "@ark-ui/vue";
|
import { Menu } from "@ark-ui/vue";
|
||||||
import { RolePermission, type Status } from "@lysand-org/client/types";
|
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 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(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<Skeleton :enabled="!account" shape="rect" class="!h-6" :min-width="40" :max-width="100" width-unit="%">
|
<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" />
|
<iconify-icon icon="tabler:arrow-back-up" width="1.5rem" height="1.5rem" aria-hidden="true" />
|
||||||
<span class="shrink-0">Replying to</span>
|
<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" />
|
class="size-5 shrink-0 rounded ring-1 ring-white/10" />
|
||||||
<strong class="line-clamp-1">{{ account?.display_name || account?.acct }}</strong>
|
<strong class="line-clamp-1">{{ account?.display_name || account?.acct }}</strong>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
@ -11,6 +11,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Avatar from "~/components/avatars/avatar.vue";
|
||||||
|
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
account_id: string | null;
|
account_id: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<Skeleton :enabled="!notification" shape="rect" class="!h-6" :min-width="40" :max-width="100"
|
<Skeleton :enabled="!notification" shape="rect" class="!h-6" :min-width="40" :max-width="100"
|
||||||
width-unit="%">
|
width-unit="%">
|
||||||
<iconify-icon :icon="icon" width="1.5rem" height="1.5rem" class="text-gray-200" aria-hidden="true" />
|
<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'`"
|
:alt="`${notification?.account.acct}'s avatar'`"
|
||||||
class="h-6 w-6 shrink-0 rounded ring-1 ring-white/10" />
|
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
|
<span class="text-gray-200 line-clamp-1"><strong v-html="display_name"></strong> {{ text
|
||||||
|
|
@ -12,24 +12,29 @@
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LazySocialElementsNotesNote v-if="notification?.status || !notification" :note="notification?.status"
|
<Note v-if="notification?.status || !notification" :note="notification?.status" :small="true" />
|
||||||
:small="true" />
|
|
||||||
<div v-else-if="notification.account" class="p-6 ring-1 ring-white/5 bg-dark-800">
|
<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>
|
||||||
<div v-if="notification?.type === 'follow_request' && relationship?.requested_by"
|
<div v-if="notification?.type === 'follow_request' && relationship?.requested_by"
|
||||||
class="w-full grid grid-cols-2 gap-4 p-2 ">
|
class="w-full grid grid-cols-2 gap-4 p-2 ">
|
||||||
<ButtonsPrimary :loading="isWorkingOnFollowRequest" @click="acceptFollowRequest"><span>Accept</span>
|
<ButtonPrimary :loading="isWorkingOnFollowRequest" @click="acceptFollowRequest"><span>Accept</span>
|
||||||
</ButtonsPrimary>
|
</ButtonPrimary>
|
||||||
<ButtonsSecondary :loading="isWorkingOnFollowRequest" @click="rejectFollowRequest"><span>Reject</span>
|
<ButtonSecondary :loading="isWorkingOnFollowRequest" @click="rejectFollowRequest"><span>Reject</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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<{
|
const props = defineProps<{
|
||||||
notification?: Notification;
|
notification?: Notification;
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full ring-1 ring-inset ring-white/5 pb-10 bg-dark-800">
|
<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" />
|
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">
|
<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" />
|
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
|
<ButtonSecondary v-if="account && account?.id === identity?.account?.id">Edit Profile
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
<ButtonsSecondary :loading="isLoading" @click="follow()"
|
<ButtonSecondary :loading="isLoading" @click="follow()"
|
||||||
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && !relationship.requested">
|
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && !relationship.requested">
|
||||||
<span>Follow</span>
|
<span>Follow</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
<ButtonsSecondary :loading="isLoading" @click="unfollow()"
|
<ButtonSecondary :loading="isLoading" @click="unfollow()"
|
||||||
v-if="account && account?.id !== identity?.account?.id && relationship && relationship.following">
|
v-if="account && account?.id !== identity?.account?.id && relationship && relationship.following">
|
||||||
<span>Unfollow</span>
|
<span>Unfollow</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
<ButtonsSecondary :loading="isLoading" :disabled="true"
|
<ButtonSecondary :loading="isLoading" :disabled="true"
|
||||||
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && relationship.requested">
|
v-if="account && account?.id !== identity?.account?.id && relationship && !relationship.following && relationship.requested">
|
||||||
<span>Requested</span>
|
<span>Requested</span>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 px-4">
|
<div class="mt-2 px-4">
|
||||||
|
|
@ -37,10 +37,10 @@
|
||||||
<Skeleton :enabled="skeleton" :min-width="130" :max-width="250">@{{ account?.acct }}</Skeleton>
|
<Skeleton :enabled="skeleton" :min-width="130" :max-width="250">@{{ account?.acct }}</Skeleton>
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-row flex-wrap gap-4 mt-4" v-if="isDeveloper || visibleRoles.length > 0">
|
<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"
|
<Badge v-for="role of visibleRoles" :key="role.id" :name="role.name" :description="role.description"
|
||||||
:description="role.description" :img="role.icon" />
|
:img="role.icon" />
|
||||||
<SocialElementsUsersBadge v-if="isDeveloper" name="Lysand Developer"
|
<Badge v-if="isDeveloper" name="Lysand Developer" description="This user is a Lysand developer."
|
||||||
description="This user is a Lysand developer." :verified="true" />
|
:verified="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -99,6 +99,10 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Account } from "@lysand-org/client/types";
|
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<{
|
const props = defineProps<{
|
||||||
account?: Account;
|
account?: Account;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<component :is="disableLink ? 'div' : NuxtLink" :href="accountUrl" class="flex flex-row">
|
<component :is="disableLink ? 'div' : NuxtLink" :href="accountUrl" class="flex flex-row">
|
||||||
<Skeleton :enabled="!account" shape="rect" class="!h-12 w-12">
|
<Skeleton :enabled="!account" shape="rect" class="!h-12 w-12">
|
||||||
<div class="shrink-0">
|
<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'`" />
|
:alt="`${account?.acct}'s avatar'`" />
|
||||||
</div>
|
</div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Account } from "@lysand-org/client/types";
|
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";
|
import { NuxtLink } from "#components";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Timeline from "./timeline.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: string;
|
id?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Timeline from "./timeline.vue";
|
||||||
|
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const timelineParameters = ref({});
|
const timelineParameters = ref({});
|
||||||
const { timeline, loadNext, loadPrev } = useHomeTimeline(
|
const { timeline, loadNext, loadPrev } = useHomeTimeline(
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Timeline from "./timeline.vue";
|
||||||
|
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const timelineParameters = ref({});
|
const timelineParameters = ref({});
|
||||||
const { timeline, loadNext, loadPrev } = useLocalTimeline(
|
const { timeline, loadNext, loadPrev } = useLocalTimeline(
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<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>
|
<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">
|
<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" />
|
<iconify-icon name="tabler:message-off" width="1.5rem" height="1.5rem" />
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Notif from "../social-elements/notifications/notif.vue";
|
||||||
|
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
|
|
||||||
const isLoading = ref(true);
|
const isLoading = ref(true);
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<TimelinesTimeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
<Timeline :timeline="timeline" :load-next="loadNext" :load-prev="loadPrev" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Timeline from "./timeline.vue";
|
||||||
|
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const timelineParameters = ref({});
|
const timelineParameters = ref({});
|
||||||
const { timeline, loadNext, loadPrev } = usePublicTimeline(
|
const { timeline, loadNext, loadPrev } = usePublicTimeline(
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<TransitionGroup leave-active-class="ease-in duration-200" leave-from-class="scale-100 opacity-100"
|
<TransitionGroup leave-active-class="ease-in duration-200" leave-from-class="scale-100 opacity-100"
|
||||||
leave-to-class="opacity-0 scale-90">
|
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>
|
</TransitionGroup>
|
||||||
<span ref="skeleton"></span>
|
<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">
|
<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" />
|
<iconify-icon icon="tabler:message-off" width="1.5rem" height="1.5rem" />
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Status } from "@lysand-org/client/types";
|
import type { Status } from "@lysand-org/client/types";
|
||||||
|
import Note from "../social-elements/notes/note.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
timeline: Status[];
|
timeline: Status[];
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="from-dark-600 to-dark-900 bg-gradient-to-tl relative min-h-dvh">
|
<div class="from-dark-600 to-dark-900 bg-gradient-to-tl relative min-h-dvh">
|
||||||
<GraphicsSquarePattern />
|
<SquarePattern />
|
||||||
<LazySidebarsNavigation />
|
<Navigation />
|
||||||
|
|
||||||
<div class="relative md:pl-20 min-h-dvh flex flex-row overflow-hidden justify-center xl:justify-between">
|
<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'">
|
<OverlayScrollbarsComponent :defer="true" class="w-full max-h-dvh overflow-y-auto" :element="'main'">
|
||||||
<slot />
|
<slot />
|
||||||
</OverlayScrollbarsComponent>
|
</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">
|
class="max-w-md max-h-dvh overflow-y-auto w-full hidden absolute inset-y-0 xl:flex">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazyTimelinesNotifications />
|
<Notifications />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</LazySidebarsCollapsibleAside>
|
</CollapsibleAside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LazyComposerModal />
|
<ComposerModal />
|
||||||
<LazySocialElementsNotesAttachmentDialog />
|
<AttachmentDialog />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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";
|
import { OverlayScrollbarsComponent } from "#imports";
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<main class="from-dark-600 to-dark-900 bg-gradient-to-tl min-h-dvh pb-20 md:pb-0">
|
<main class="from-dark-600 to-dark-900 bg-gradient-to-tl min-h-dvh pb-20 md:pb-0">
|
||||||
<LazySidebarsNavigation />
|
<Navigation />
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Navigation from "~/components/sidebars/navigation.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -15,6 +15,9 @@ export default defineNuxtConfig({
|
||||||
isCustomElement: (tag) => tag === "iconify-icon",
|
isCustomElement: (tag) => tag === "iconify-icon",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
dirs: [],
|
||||||
|
},
|
||||||
future: {
|
future: {
|
||||||
compatibilityVersion: 4,
|
compatibilityVersion: 4,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="loaded" :defer="true" class="mx-auto max-w-2xl w-full pb-72">
|
<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">
|
<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>
|
</div>
|
||||||
<LazySocialElementsNotesNote v-for="note of context?.descendants" :note="note" />
|
<Note v-for="note of context?.descendants" :note="note" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mx-auto max-w-2xl w-full overflow-y-auto">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Note from "~/components/social-elements/notes/note.vue";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<ErrorsErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div class="mx-auto max-w-2xl w-full">
|
<div class="mx-auto max-w-2xl w-full">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazySocialElementsUsersAccount :account="account ?? undefined" />
|
<AccountProfile :account="account ?? undefined" />
|
||||||
<LazyTimelinesAccount :id="accountId" :key="accountId" />
|
<AccountTimeline :id="accountId" :key="accountId" />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</ErrorsErrorBoundary>
|
</ErrorBoundary>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Account } from "@lysand-org/client/types";
|
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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-2xl w-full">
|
<div class="mx-auto max-w-2xl w-full">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazyHeadersGreeting />
|
<Greeting />
|
||||||
<LazyTimelinesHome />
|
<Home />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-2xl w-full">
|
<div class="mx-auto max-w-2xl w-full">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazyHeadersGreeting />
|
<Greeting />
|
||||||
<LazyTimelinesPublic />
|
<Public />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-2xl w-full">
|
<div class="mx-auto max-w-2xl w-full">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazyHeadersGreeting />
|
<Greeting />
|
||||||
<LazyTimelinesLocal />
|
<Local />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,20 @@
|
||||||
sign in</span>
|
sign in</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<LazyTimelinesTimelineScroller v-else>
|
<TimelineScroller v-else>
|
||||||
<LazyHeadersGreeting />
|
<Greeting />
|
||||||
<div class="rounded overflow-hidden ring-1 ring-white/10">
|
<div class="rounded overflow-hidden ring-1 ring-white/10">
|
||||||
<LazyTimelinesNotifications />
|
<Notifications />
|
||||||
</div>
|
</div>
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,25 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VeeField name="identifier" as="div" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="identifier" as="div" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="identifier">Username or Email</InputsLabel>
|
<Label for="identifier">Username or Email</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsText id="identifier" placeholder="joemama" autocomplete="email username" required
|
<TextInput id="identifier" placeholder="joemama" autocomplete="email username" required
|
||||||
v-bind="field" :is-invalid="!!errorMessage" />
|
v-bind="field" :is-invalid="!!errorMessage" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="password" as="div" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="password" as="div" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="password">Password</InputsLabel>
|
<Label for="password">Password</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsPassword id="password" placeholder="hunter2" autocomplete="current-password" required
|
<PasswordInput id="password" placeholder="hunter2" autocomplete="current-password" required
|
||||||
v-bind="field" :is-invalid="!!errorMessage" />
|
v-bind="field" :is-invalid="!!errorMessage" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<div v-if="ssoConfig && ssoConfig.providers.length > 0" class="w-full space-y-3">
|
<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">
|
<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"
|
<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}`">
|
: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'`"
|
<img crossorigin="anonymous" :src="provider.icon" :alt="`${provider.name}'s logo'`"
|
||||||
class="w-6 h-6" />
|
class="w-6 h-6" />
|
||||||
<div class="flex flex-col gap-0 justify-center">
|
<div class="flex flex-col gap-0 justify-center">
|
||||||
<h3 class="font-bold">{{ provider.name }}</h3>
|
<h3 class="font-bold">{{ provider.name }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</ButtonsSecondary>
|
</ButtonSecondary>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
here, please close this page.
|
here, please close this page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ButtonsPrimary type="submit" class="w-full">Sign in</ButtonsPrimary>
|
<ButtonPrimary type="submit" class="w-full">Sign in</ButtonPrimary>
|
||||||
</VeeForm>
|
</VeeForm>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mx-auto max-w-md">
|
<div v-else class="mx-auto max-w-md">
|
||||||
|
|
@ -100,6 +100,14 @@
|
||||||
import { LysandClient } from "@lysand-org/client";
|
import { LysandClient } from "@lysand-org/client";
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import { z } from "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(
|
const schema = toTypedSchema(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<ButtonsPrimary type="submit">Authorize</ButtonsPrimary>
|
<ButtonPrimary type="submit">Authorize</ButtonPrimary>
|
||||||
<NuxtLink href="/" class="w-full">
|
<NuxtLink href="/" class="w-full">
|
||||||
<ButtonsSecondary class="w-full">Cancel</ButtonsSecondary>
|
<ButtonSecondary class="w-full">Cancel</ButtonSecondary>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -83,6 +83,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ButtonPrimary from "~/components/buttons/button-primary.vue";
|
||||||
|
import ButtonSecondary from "~/components/buttons/button-secondary.vue";
|
||||||
|
|
||||||
const url = useRequestURL();
|
const url = useRequestURL();
|
||||||
const params = useUrlSearchParams();
|
const params = useUrlSearchParams();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,32 +14,32 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="password">New password</InputsLabel>
|
<Label for="password">New password</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsPassword id="password" placeholder="hunter2" autocomplete="new-password" required
|
<PasswordInput id="password" placeholder="hunter2" autocomplete="new-password" required
|
||||||
v-bind="field" :is-invalid="!!errorMessage" :show-strength="true" />
|
v-bind="field" :is-invalid="!!errorMessage" :show-strength="true" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="password-confirm" as="div" v-slot="{ errors, field }" validate-on-change>
|
<VeeField name="password-confirm" as="div" v-slot="{ errors, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="password-confirm">Confirm password</InputsLabel>
|
<Label for="password-confirm">Confirm password</Label>
|
||||||
<InputsError v-if="errors.length > 0">{{ errors[0] }}</InputsError>
|
<FieldError v-if="errors.length > 0">{{ errors[0] }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsPassword id="password-confirm" placeholder="hunter2" autocomplete="new-password" required
|
<PasswordInput id="password-confirm" placeholder="hunter2" autocomplete="new-password" required
|
||||||
v-bind="field" :is-invalid="errors.length > 0" />
|
v-bind="field" :is-invalid="errors.length > 0" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<p class="text-xs font-semibold text-red-300">This will reset your
|
<p class="text-xs font-semibold text-red-300">This will reset your
|
||||||
password. Make sure to put it in a password manager.
|
password. Make sure to put it in a password manager.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ButtonsPrimary type="submit" class="w-full">Reset</ButtonsPrimary>
|
<ButtonPrimary type="submit" class="w-full">Reset</ButtonPrimary>
|
||||||
</VeeForm>
|
</VeeForm>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="params.success">
|
<div v-else-if="params.success">
|
||||||
|
|
@ -69,6 +69,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import { z } from "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();
|
const identity = useCurrentIdentity();
|
||||||
identity.value = null;
|
identity.value = null;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-2xl w-full">
|
<div class="mx-auto max-w-2xl w-full">
|
||||||
<LazyTimelinesTimelineScroller>
|
<TimelineScroller>
|
||||||
<LazyHeadersGreeting />
|
<Greeting />
|
||||||
<TimelinesPublic />
|
<Public />
|
||||||
</LazyTimelinesTimelineScroller>
|
</TimelineScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,66 +12,65 @@
|
||||||
<h1 class="font-bold text-2xl text-gray-50 text-center tracking-tight">Account details</h1>
|
<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>
|
<VeeField name="username" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="username">Username</InputsLabel>
|
<Label for="username">Username</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsText id="username" type="text" placeholder="thespeedy" required v-bind="field"
|
<TextInput id="username" type="text" placeholder="thespeedy" required v-bind="field"
|
||||||
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="username"
|
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="username"
|
||||||
:spellcheck="false" />
|
:spellcheck="false" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="email" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="email" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="email">Email address</InputsLabel>
|
<Label for="email">Email address</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsText id="email" type="email" placeholder="joseph.jones@gmail.com" required v-bind="field"
|
<TextInput id="email" type="email" placeholder="joseph.jones@gmail.com" required v-bind="field"
|
||||||
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="email" />
|
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="email" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="password" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="password">Password</InputsLabel>
|
<Label for="password">Password</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsPassword id="password" placeholder="hunter2" required v-bind="field"
|
<PasswordInput id="password" placeholder="hunter2" required v-bind="field" :disabled="isLoading"
|
||||||
:disabled="isLoading" :is-invalid="!!errorMessage" :show-strength="true"
|
:is-invalid="!!errorMessage" :show-strength="true" autocomplete="new-password" />
|
||||||
autocomplete="new-password" />
|
</Field>
|
||||||
</InputsField>
|
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="password-confirm" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="password-confirm" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<InputsLabelAndError>
|
<LabelAndError>
|
||||||
<InputsLabel for="password-confirm">Confirm password</InputsLabel>
|
<Label for="password-confirm">Confirm password</Label>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsLabelAndError>
|
</LabelAndError>
|
||||||
<InputsPassword id="password-confirm" placeholder="hunter2" required v-bind="field"
|
<PasswordInput id="password-confirm" placeholder="hunter2" required v-bind="field"
|
||||||
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="new-password" />
|
:disabled="isLoading" :is-invalid="!!errorMessage" autocomplete="new-password" />
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<VeeField name="tos" v-slot="{ errorMessage, field }" validate-on-change>
|
<VeeField name="tos" v-slot="{ errorMessage, field }" validate-on-change>
|
||||||
<InputsField>
|
<Field>
|
||||||
<div class="flex flex-row gap-x-2 items-center">
|
<div class="flex flex-row gap-x-2 items-center">
|
||||||
<InputsCheckbox :checked="true" id="tos" required :disabled="true" v-bind="field" />
|
<CheckboxInput :checked="true" id="tos" required :disabled="true" v-bind="field" />
|
||||||
<InputsLabel for="tos" class="!text-gray-200">
|
<Label for="tos" class="!text-gray-200">
|
||||||
I agree to the Terms of Service
|
I agree to the Terms of Service
|
||||||
</InputsLabel>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<InputsError v-if="errorMessage">{{ errorMessage }}</InputsError>
|
<FieldError v-if="errorMessage">{{ errorMessage }}</FieldError>
|
||||||
</InputsField>
|
</Field>
|
||||||
</VeeField>
|
</VeeField>
|
||||||
|
|
||||||
<Collapsible.Root>
|
<Collapsible.Root>
|
||||||
<Collapsible.Trigger class="w-full">
|
<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.Trigger>
|
||||||
<Collapsible.Content
|
<Collapsible.Content
|
||||||
class="prose prose-invert prose-sm p-4 ring-1 ring-white/10 bg-dark-700 rounded mt-3">
|
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.
|
cannot see your password.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ButtonsPrimary type="submit" class="w-full" :disabled="isLoading">{{ isLoading ? "Registering..." :
|
<ButtonPrimary type="submit" class="w-full" :disabled="isLoading">{{ isLoading ? "Registering..." :
|
||||||
"Register" }}</ButtonsPrimary>
|
"Register" }}</ButtonPrimary>
|
||||||
</VeeForm>
|
</VeeForm>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
@ -105,6 +104,15 @@ import { Collapsible } from "@ark-ui/vue";
|
||||||
import type { ResponseError } from "@lysand-org/client";
|
import type { ResponseError } from "@lysand-org/client";
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import { z } from "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(
|
const schema = toTypedSchema(
|
||||||
z
|
z
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<SidebarsSettings>
|
<SettingsSidebar>
|
||||||
<template #behaviour>
|
<template #behaviour>
|
||||||
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
||||||
settings,
|
settings,
|
||||||
SettingPages.Behaviour,
|
SettingPages.Behaviour,
|
||||||
)" :key="setting.id" />
|
)" :key="setting.id" />
|
||||||
</template>
|
</template>
|
||||||
<template #appearance>
|
<template #appearance>
|
||||||
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
||||||
settings,
|
settings,
|
||||||
SettingPages.Appearance,
|
SettingPages.Appearance,
|
||||||
)" :key="setting.id" />
|
)" :key="setting.id" />
|
||||||
</template>
|
</template>
|
||||||
<template #advanced>
|
<template #advanced>
|
||||||
<SettingsRenderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
||||||
settings,
|
settings,
|
||||||
SettingPages.Advanced,
|
SettingPages.Advanced,
|
||||||
)" :key="setting.id" />
|
)" :key="setting.id" />
|
||||||
</template>
|
</template>
|
||||||
<template #account>
|
<template #account>
|
||||||
<SettingsProfileEditor />
|
<ProfileEditor />
|
||||||
</template>
|
</template>
|
||||||
</SidebarsSettings>
|
</SettingsSidebar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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";
|
import { SettingPages, getSettingsForPath } from "~/settings";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue