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