♻️ Continued to migrate components

This commit is contained in:
2025-11-29 02:12:12 +08:00
parent 040e19025e
commit 54bf8cf915
10 changed files with 102 additions and 97 deletions

View File

@@ -27,3 +27,11 @@
opacity: 0;
filter: blur(1rem);
}
.n-image-preview-toolbar .n-base-icon {
padding: 0;
}
.n-image-preview-toolbar {
gap: 1rem;
}

14
app/components.d.ts vendored
View File

@@ -14,15 +14,21 @@ declare module 'vue' {
export interface GlobalComponents {
NAlert: typeof import('naive-ui')['NAlert']
NAvatar: typeof import('naive-ui')['NAvatar']
NBtn: typeof import('naive-ui')['NBtn']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCardSection: typeof import('naive-ui')['NCardSection']
NCarousel: typeof import('naive-ui')['NCarousel']
NCarouselItem: typeof import('naive-ui')['NCarouselItem']
NChip: typeof import('naive-ui')['NChip']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialog: typeof import('naive-ui')['NDialog']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDropdown: typeof import('naive-ui')['NDropdown']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NImg: typeof import('naive-ui')['NImg']
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
NInput: typeof import('naive-ui')['NInput']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu']
@@ -33,6 +39,7 @@ declare module 'vue' {
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NTag: typeof import('naive-ui')['NTag']
NTextarea: typeof import('naive-ui')['NTextarea']
NThemeEditor: typeof import('naive-ui')['NThemeEditor']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
@@ -43,15 +50,21 @@ declare module 'vue' {
declare global {
const NAlert: typeof import('naive-ui')['NAlert']
const NAvatar: typeof import('naive-ui')['NAvatar']
const NBtn: typeof import('naive-ui')['NBtn']
const NButton: typeof import('naive-ui')['NButton']
const NCard: typeof import('naive-ui')['NCard']
const NCardSection: typeof import('naive-ui')['NCardSection']
const NCarousel: typeof import('naive-ui')['NCarousel']
const NCarouselItem: typeof import('naive-ui')['NCarouselItem']
const NChip: typeof import('naive-ui')['NChip']
const NConfigProvider: typeof import('naive-ui')['NConfigProvider']
const NDialog: typeof import('naive-ui')['NDialog']
const NDialogProvider: typeof import('naive-ui')['NDialogProvider']
const NDropdown: typeof import('naive-ui')['NDropdown']
const NIcon: typeof import('naive-ui')['NIcon']
const NImage: typeof import('naive-ui')['NImage']
const NImg: typeof import('naive-ui')['NImg']
const NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
const NInput: typeof import('naive-ui')['NInput']
const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
const NMenu: typeof import('naive-ui')['NMenu']
@@ -62,6 +75,7 @@ declare global {
const NSpace: typeof import('naive-ui')['NSpace']
const NSpin: typeof import('naive-ui')['NSpin']
const NTag: typeof import('naive-ui')['NTag']
const NTextarea: typeof import('naive-ui')['NTextarea']
const NThemeEditor: typeof import('naive-ui')['NThemeEditor']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']

View File

@@ -15,13 +15,12 @@
/>
</div>
<!-- Main image -->
<img
<n-image
:src="remoteSource"
class="w-full h-auto rounded-md transition-opacity duration-500 object-cover cursor-pointer"
:class="{ 'opacity-0': !imageLoaded && blurhash }"
@load="imageLoaded = true"
@error="imageLoaded = true"
@click="openExternally"
/>
</template>
@@ -98,7 +97,9 @@ const remoteSource = computed(
const blurhashContainerStyle = computed(() => {
return {
"padding-bottom": `${aspectRatio.value == null ? 0 : aspectRatio.value * 100}%`
"padding-bottom": `${
aspectRatio.value == null ? 0 : aspectRatio.value * 100
}%`
}
})

View File

@@ -14,28 +14,16 @@
class="carousel-container rounded-lg overflow-hidden"
:style="carouselStyle"
>
<v-card width="100%" height="100%" class="transition-all duration-300" border>
<v-carousel
height="100%"
hide-delimiter-background
show-arrows="hover"
hide-delimiters
progress="primary"
>
<v-carousel-item
<n-carousel height="100%" show-arrow>
<n-carousel-item
v-for="attachment in attachments"
:key="attachment.id"
height="100%"
cover
>
<attachment-item
original
class="h-full"
:item="attachment"
/>
</v-carousel-item>
</v-carousel>
</v-card>
<attachment-item original class="h-full" :item="attachment" />
</n-carousel-item>
</n-carousel>
</div>
<!-- Mixed content: vertical scrollable -->
@@ -60,7 +48,6 @@ const props = defineProps<{
maxHeight?: number
}>()
const isAllImages = computed(
() =>
props.attachments.length > 0 &&

View File

@@ -1,5 +1,5 @@
<template>
<n-card>
<n-card :size="compact ? 'small' : 'medium'">
<div :class="['flex flex-col', compact ? 'gap-1' : 'gap-3']">
<post-header :item="props.item" :compact="compact" />
@@ -22,10 +22,10 @@
<template v-if="showReferenced">
<div
v-if="props.item.repliedPost || props.item.repliedGone"
class="border rounded-md"
class="border rounded-xl"
>
<div class="p-2 flex items-center gap-2">
<span class="mdi mdi-reply"></span>
<n-icon :component="ReplyIcon" class="ms-2" />
<span class="font-bold">Replying to</span>
</div>
<div
@@ -47,10 +47,10 @@
<div
v-if="props.item.forwardedPost || props.item.forwardedGone"
class="border rounded-md"
class="border rounded-xl"
>
<div class="p-2 flex items-center gap-2">
<span class="mdi mdi-forward"></span>
<n-icon :component="ForwardIcon" class="ms-2" />
<span class="font-bold">Forwarded</span>
</div>
<div
@@ -112,6 +112,7 @@ import { ref, watch } from "vue"
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
import type { SnPost } from "~/types/api"
import { useIntersectionObserver } from "@vueuse/core"
import { ForwardIcon, ReplyIcon } from "lucide-vue-next"
const props = withDefaults(
defineProps<{

View File

@@ -1,22 +1,14 @@
<template>
<v-card
title="Post your reply"
prepend-icon="mdi-post-lamp"
flat
border
density="comfortable"
>
<v-card-text>
<v-textarea
<n-card title="Post your reply" size="small" embedded>
<n-input
type="textarea"
placeholder="Talk about this post for a bit."
disabled
max-rows="5"
size="large"
:rows="5"
auto-grow
:hide-details="true"
></v-textarea>
></n-input>
<div class="flex justify-end mt-4">
<v-btn append-icon="mdi-send">Send</v-btn>
<n-button append-icon="mdi-send" size="small">Send</n-button>
</div>
</v-card-text>
</v-card>
</n-card>
</template>

View File

@@ -14,17 +14,16 @@
</n-alert>
<!-- Replies List -->
<v-infinite-scroll
<n-infinite-scroll
class="flex flex-col gap-4 mt-0"
height="auto"
side="end"
manual
:distance="10"
@load="loadMore"
>
<template v-for="item in replies" :key="item.id">
<post-item
:show-referenced="false"
:item="item"
class="mb-4"
@click="router.push('/posts/' + item.id)"
@react="
(symbol, attitude, delta) =>
@@ -33,23 +32,17 @@
/>
</template>
<!-- Loading State -->
<template #loading>
<div class="flex justify-center py-4">
<n-spin size="large" />
</div>
</template>
<!-- Empty State -->
<template #empty>
<div v-if="!replies" class="text-center py-8 text-muted-foreground">
<div
v-if="!replies || replies.length === 0"
class="text-center py-8 text-muted-foreground"
>
<n-icon size="48" class="mb-2 opacity-50">
<i class="mdi mdi-comment-outline"></i>
</n-icon>
<p>No replies yet</p>
</div>
</template>
</v-infinite-scroll>
</n-infinite-scroll>
</div>
</template>
@@ -74,7 +67,5 @@ const { replies, hasError, error, loadMore, refresh } = useRepliesList(
</script>
<style>
.replies-list .v-infinite-scroll .v-infinite-scroll__side:first-child {
display: none;
}
/* Removed Vuetify-specific styles */
</style>

View File

@@ -24,14 +24,13 @@
<n-avatar
round
class="mr-4 cursor-pointer"
:size="32"
:src="
user?.profile.picture
? `${apiBase}/drive/files/${user?.profile.picture?.id}`
: undefined
"
>
<span v-if="!user" class="mdi mdi-account-circle text-3xl"></span>
<n-icon :component="UserCircleIcon" />
</n-avatar>
</n-dropdown>
</div>
@@ -49,7 +48,7 @@ import IconLight from "~/assets/images/cloudy-lamb.png"
import type { MenuOption } from "naive-ui"
import { computed, h } from "vue"
import { useRouter, useRoute } from "vue-router"
import { CompassIcon } from "lucide-vue-next"
import { CompassIcon, UserCircleIcon } from "lucide-vue-next"
const apiBase = useSolarNetworkUrl()
const router = useRouter()

View File

@@ -1,7 +1,12 @@
<template>
<div class="container mx-auto">
<div class="container mx-auto px-5">
<div class="layout">
<div class="main">
<n-infinite-scroll
style="overflow: auto"
:distance="0"
@load="fetchActivites"
>
<div v-for="activity in activites" :key="activity.id" class="mb-4">
<post-item
v-if="activity.type.startsWith('posts')"
@@ -9,12 +14,24 @@
@click="router.push('/posts/' + activity.id)"
/>
</div>
<div v-if="loading" class="flex justify-center py-4">
<n-spin size="large" />
</div>
<div
v-if="!loading && activites.length === 0"
class="text-center py-8 text-muted-foreground"
>
<n-icon size="48" class="mb-2 opacity-50">
<i class="mdi mdi-post-outline"></i>
</n-icon>
<p>No posts yet</p>
</div>
</n-infinite-scroll>
</div>
<div class="sidebar flex flex-col gap-3">
<div
v-if="!userStore.isAuthenticated"
class="card w-full bg-base-100 shadow-xl"
>
<div v-if="!userStore.isAuthenticated">
<n-card>
<h2 class="card-title">About</h2>
<p>Welcome to the <b>Solar Network</b></p>
@@ -43,7 +60,6 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue"
import { useInfiniteScroll } from "@vueuse/core"
import { useUserStore } from "~/stores/user"
import { useSolarNetwork } from "~/composables/useSolarNetwork"
import type { SnVersion, SnActivity } from "~/types/api"
@@ -105,11 +121,6 @@ async function fetchActivites() {
}
onMounted(() => fetchActivites())
useInfiniteScroll(window, fetchActivites, {
canLoadMore: () => !loading.value && activitesHasMore.value,
distance: 10
})
async function refreshActivities() {
activites.value = []
fetchActivites()

View File

@@ -42,18 +42,18 @@
<!-- Post Metadata -->
<div class="flex items-center gap-4 text-sm text-medium-emphasis">
<div class="flex items-center gap-1">
<n-icon size="16" name="mdi-calendar" />
<n-icon :component="CalendarIcon" />
<span>{{ formatDate(post.createdAt) }}</span>
</div>
<div
v-if="post.updatedAt && post.updatedAt !== post.createdAt"
class="flex items-center gap-1"
>
<n-icon size="16" name="mdi-pencil" />
<n-icon :component="PencilIcon" />
<span>Updated {{ formatDate(post.updatedAt) }}</span>
</div>
<div class="flex items-center gap-1">
<n-icon size="16" name="mdi-eye" />
<n-icon :component="EyeIcon" />
<span>
{{ post.viewsTotal }} / {{ post.viewsUnique }}
views
@@ -62,7 +62,7 @@
</div>
</n-card>
<n-card class="pa-6">
<n-card class="px-6 py-0">
<article
v-if="htmlContent"
class="prose dark:prose-invert prose-slate max-w-none mb-8"
@@ -127,6 +127,7 @@
</template>
<script setup lang="ts">
import { CalendarIcon, EyeIcon, PencilIcon } from "lucide-vue-next"
import { computed, ref } from "vue" // Added ref
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
import type { SnPost } from "~/types/api"