chore: ⬆️ Upgrade to Nuxt 4
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 1s
Deploy to GitHub Pages / build (push) Failing after 1s
Deploy to GitHub Pages / deploy (push) Has been skipped
Docker / build (push) Failing after 1s
Mirror to Codeberg / Mirror (push) Failing after 1s

This commit is contained in:
Jesse Wierzbinski 2025-07-16 07:48:39 +02:00
parent 8debe97f63
commit 7f7cf20311
386 changed files with 2376 additions and 2332 deletions

View file

@ -0,0 +1,91 @@
<template>
<Dialog>
<DialogTrigger as-child>
<slot />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Accounts</DialogTitle>
<DialogDescription class="sr-only">
Manage your accounts and settings.
</DialogDescription>
</DialogHeader>
<div v-if="identities.length > 0" class="grid gap-4 py-2">
<div v-for="identity of identities" :key="identity.account.id"
class="grid grid-cols-[1fr_auto] has-[>[data-switch]]:grid-cols-[1fr_auto_auto] gap-2">
<TinyCard :account="identity.account" :domain="identity.instance.domain" naked />
<Button data-switch v-if="currentIdentity?.id !== identity.id"
@click="switchAccount(identity.account.id)" variant="outline">
Switch
</Button>
<Button @click="signOut(appData, identity)" variant="outline" size="icon"
:title="m.sharp_big_mallard_reap()">
<LogOut />
</Button>
</div>
</div>
<div v-else>
<p class="text-sm text-muted-foreground">
Log in to or register an account on your favourite instance.
</p>
</div>
<DialogFooter>
<Button :as="NuxtLink" href="/register" variant="outline">
<UserPlus />
{{ m.honest_few_baboon_pop() }}
</Button>
<Button @click="signInAction">
<LogIn />
{{ m.sunny_pink_hyena_walk() }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
<script lang="ts" setup>
import { LogIn, LogOut, UserPlus } from "lucide-vue-next";
import { toast } from "vue-sonner";
import { NuxtLink } from "#components";
import { identity as currentIdentity } from "#imports";
import TinyCard from "~/components/profiles/tiny-card.vue";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import * as m from "~~/paraglide/messages.js";
const appData = useAppData();
const signInAction = async () => signIn(appData, await askForInstance());
const switchAccount = async (userId: string) => {
if (userId === currentIdentity.value?.account.id) {
return await navigateTo(`/@${currentIdentity.value.account.username}`);
}
const id = toast.loading("Switching account...");
const identityToSwitch = identities.value.find(
(i) => i.account.id === userId,
);
if (!identityToSwitch) {
toast.dismiss(id);
toast.error("No identity to switch to");
return;
}
currentIdentity.value = identityToSwitch;
toast.dismiss(id);
toast.success("Switched account");
window.location.href = "/";
};
</script>

View file

@ -0,0 +1,61 @@
<script setup lang="ts">
import {
ChevronsUpDown,
Cog,
DownloadCloud,
Pen,
UserPlus,
} from "lucide-vue-next";
import TinyCard from "~/components/profiles/tiny-card.vue";
import { Button } from "~/components/ui/button";
import {
SidebarFooter,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "~/components/ui/sidebar";
import * as m from "~~/paraglide/messages.js";
import AccountManager from "../account/account-manager.vue";
const { $pwa } = useNuxtApp();
</script>
<template>
<SidebarFooter>
<SidebarMenu class="gap-3">
<SidebarMenuItem>
<AccountManager>
<SidebarMenuButton v-if="identity" size="lg">
<TinyCard :account="identity.account" :domain="identity.instance.domain" naked />
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
<SidebarMenuButton v-else>
<UserPlus />
{{ m.sunny_pink_hyena_walk() }}
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</AccountManager>
</SidebarMenuItem>
<SidebarMenuItem class="flex flex-col gap-2">
<Button v-if="identity" variant="default" size="lg" class="w-full group-data-[collapsible=icon]:px-4"
@click="useEvent('composer:open')">
<Pen />
<span class="group-data-[collapsible=icon]:hidden">
{{ m.salty_aloof_turkey_nudge() }}
</span>
</Button>
<Button v-if="identity" size="lg" variant="secondary" @click="useEvent('preferences:open')">
<Cog />
Preferences
</Button>
<Button v-if="$pwa?.needRefresh" variant="destructive" size="lg"
class="w-full group-data-[collapsible=icon]:px-4" @click="$pwa?.updateServiceWorker(true)">
<DownloadCloud />
<span class="group-data-[collapsible=icon]:hidden">
{{ m.quaint_low_felix_pave() }}
</span>
</Button>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</template>

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
import InstanceSmallCard from "~/components/instance/small-card.vue";
import {
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
} from "~/components/ui/sidebar";
const instance = useInstance();
</script>
<template>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<NuxtLink href="/">
<InstanceSmallCard v-if="instance" :instance="instance" />
</NuxtLink>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
</template>

View file

@ -0,0 +1,36 @@
<template>
<Sidebar collapsible="offcanvas">
<InstanceHeader />
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>{{
m.trite_real_sawfish_drum()
}}</SidebarGroupLabel>
<NavItems
:items="
sidebarConfig.other.filter((i) =>
i.requiresLogin ? !!identity : true
)
"
/>
</SidebarGroup>
</SidebarContent>
<FooterActions />
<SidebarRail />
</Sidebar>
</template>
<script lang="ts" setup>
import { sidebarConfig } from "~/components/sidebars/sidebar";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarRail,
} from "~/components/ui/sidebar";
import * as m from "~~/paraglide/messages.js";
import FooterActions from "./footer/footer-actions.vue";
import InstanceHeader from "./instance/instance-header.vue";
import NavItems from "./navigation/nav-items.vue";
</script>

View file

@ -0,0 +1,59 @@
<script setup lang="ts">
import { ChevronRight } from "lucide-vue-next";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "~/components/ui/collapsible";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "~/components/ui/sidebar";
import type { SidebarNavMainItem } from "~/types/sidebar";
defineProps<{
items: SidebarNavMainItem[];
}>();
</script>
<template>
<SidebarMenu>
<Collapsible
v-for="item in items"
:key="item.title"
as-child
default-open
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<component :is="item.icon" />
{{ item.title }}
<ChevronRight
class="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem
v-for="subItem in item.items"
:key="subItem.title"
>
<SidebarMenuSubButton as-child>
<NuxtLink :href="subItem.url">
<span>{{ subItem.title }}</span>
</NuxtLink>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</template>

View file

@ -0,0 +1,25 @@
<script setup lang="ts">
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "~/components/ui/sidebar";
import type { SidebarNavItem } from "~/types/sidebar";
defineProps<{
items: SidebarNavItem[];
}>();
</script>
<template>
<SidebarMenu>
<SidebarMenuItem v-for="item in items" :key="item.title">
<SidebarMenuButton as-child>
<NuxtLink :href="item.url">
<component :is="item.icon" />
<span>{{ item.title }}</span>
</NuxtLink>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</template>

View file

@ -0,0 +1,17 @@
<template>
<Sidebar
side="right"
collapsible="none"
class="hidden md:flex"
style="--sidebar-width: 24rem; --sidebar-width-mobile: 18rem"
>
<SidebarContent class="overflow-y-auto *:p-2 *:gap-2">
<NotificationsTimeline />
</SidebarContent>
</Sidebar>
</template>
<script setup lang="ts">
import NotificationsTimeline from "~/components/timelines/notifications.vue";
import { Sidebar, SidebarContent } from "~/components/ui/sidebar";
</script>

View file

@ -0,0 +1,39 @@
import { BedSingle, Bell, Globe, House, MapIcon } from "lucide-vue-next";
import type { SidebarConfig } from "~/types/sidebar";
import * as m from "~~/paraglide/messages.js";
export const sidebarConfig: SidebarConfig = {
navMain: [],
other: [
{
title: m.bland_chunky_sparrow_propel(),
url: "/home",
icon: House,
requiresLogin: true,
},
{
title: m.lost_trick_dog_grace(),
url: "/public",
icon: MapIcon,
requiresLogin: false,
},
{
title: m.crazy_game_parrot_pave(),
url: "/local",
icon: BedSingle,
requiresLogin: false,
},
{
title: m.real_tame_moose_greet(),
url: "/global",
icon: Globe,
requiresLogin: false,
},
{
title: m.that_patchy_mare_snip(),
url: "/notifications",
icon: Bell,
requiresLogin: true,
},
],
};

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import Timelines from "~/components/navigation/timelines.vue";
import LeftSidebar from "./left-sidebar.vue";
import RightSidebar from "./right-sidebar.vue";
const route = useRoute();
const isMd = useMediaQuery("(max-width: 768px)");
const showTimelines = computed(
() =>
["/", "/home", "/local", "/public", "/global"].includes(route.path) &&
isMd.value,
);
</script>
<template>
<LeftSidebar />
<main class="grow h-dvh overflow-y-auto">
<header
v-if="showTimelines"
class="flex h-16 items-center bg-background/80 backdrop-blur-2xl sticky top-0 inset-x-0 z-10 p-4"
>
<Timelines />
</header>
<slot />
</main>
<RightSidebar v-if="identity" v-show="preferences.display_notifications_sidebar" />
</template>