♻️ Refactored some components to new UI
This commit is contained in:
46
app/app.vue
46
app/app.vue
@@ -1,22 +1,38 @@
|
||||
<template>
|
||||
<naive-config>
|
||||
<n-config-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<n-loading-bar-provider>
|
||||
<nuxt-loading-indicator :color="colorMode.value == 'dark' ? 'white' : '#3f51b5'" />
|
||||
<nuxt-layout>
|
||||
<nuxt-page />
|
||||
</nuxt-layout>
|
||||
</n-loading-bar-provider>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-config-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<n-loading-bar-provider>
|
||||
<nuxt-loading-indicator />
|
||||
<nuxt-layout>
|
||||
<nuxt-page />
|
||||
</nuxt-layout>
|
||||
</n-loading-bar-provider>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</naive-config>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
import "@fontsource-variable/nunito"
|
||||
import { usePreferredColorScheme } from "@vueuse/core"
|
||||
|
||||
const { colorModePreference } = useNaiveColorMode()
|
||||
const colorScheme = usePreferredColorScheme()
|
||||
|
||||
colorModePreference.set("system")
|
||||
|
||||
onMounted(() => {
|
||||
switch (colorScheme.value) {
|
||||
case "dark":
|
||||
colorModePreference.set("dark")
|
||||
case "light":
|
||||
colorModePreference.set("light")
|
||||
default:
|
||||
colorModePreference.set("system")
|
||||
}
|
||||
colorModePreference.sync()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
@layer theme, base, components, utilities;
|
||||
@import "tailwindcss/theme.css" layer(theme);
|
||||
@import "tailwindcss/preflight.css" layer(base);
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
|
||||
@layer base {
|
||||
|
||||
22
app/components.d.ts
vendored
22
app/components.d.ts
vendored
@@ -12,16 +12,27 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCardSection: typeof import('naive-ui')['NCardSection']
|
||||
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']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NThemeEditor: typeof import('naive-ui')['NThemeEditor']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
@@ -30,16 +41,27 @@ declare module 'vue' {
|
||||
|
||||
// For TSX support
|
||||
declare global {
|
||||
const NAlert: typeof import('naive-ui')['NAlert']
|
||||
const NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
const NButton: typeof import('naive-ui')['NButton']
|
||||
const NCard: typeof import('naive-ui')['NCard']
|
||||
const NCardSection: typeof import('naive-ui')['NCardSection']
|
||||
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 NInput: typeof import('naive-ui')['NInput']
|
||||
const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
const NMenu: typeof import('naive-ui')['NMenu']
|
||||
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
const NModal: typeof import('naive-ui')['NModal']
|
||||
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
const NSelect: typeof import('naive-ui')['NSelect']
|
||||
const NSpace: typeof import('naive-ui')['NSpace']
|
||||
const NSpin: typeof import('naive-ui')['NSpin']
|
||||
const NTag: typeof import('naive-ui')['NTag']
|
||||
const NThemeEditor: typeof import('naive-ui')['NThemeEditor']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div :class="['flex gap-3 items-center', { 'gap-2': compact }]">
|
||||
<v-avatar
|
||||
:image="publisherAvatar"
|
||||
:size="compact ? 24 : 40"
|
||||
<n-avatar
|
||||
round
|
||||
:src="publisherAvatar"
|
||||
:size="compact ? 24 : 36"
|
||||
:border="compact"
|
||||
@click="router.push('/publishers/' + props.item.publisher.name)"
|
||||
/>
|
||||
|
||||
@@ -1,107 +1,117 @@
|
||||
<template>
|
||||
<div :class="['card', { 'shadow-none': props.flat }]">
|
||||
<div :class="['card-body', { 'p-0': props.slim }]">
|
||||
<div :class="['flex flex-col', compact ? 'gap-1' : 'gap-3']">
|
||||
<post-header :item="props.item" :compact="compact" />
|
||||
<n-card>
|
||||
<div :class="['flex flex-col', compact ? 'gap-1' : 'gap-3']">
|
||||
<post-header :item="props.item" :compact="compact" />
|
||||
|
||||
<div v-if="props.item.title || props.item.description">
|
||||
<h2 v-if="props.item.title" class="text-lg">
|
||||
{{ props.item.title }}
|
||||
</h2>
|
||||
<p v-if="props.item.description" class="text-sm">
|
||||
{{ props.item.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="props.item.title || props.item.description">
|
||||
<h2 v-if="props.item.title" class="text-lg">
|
||||
{{ props.item.title }}
|
||||
</h2>
|
||||
<p v-if="props.item.description" class="text-sm">
|
||||
{{ props.item.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<article
|
||||
v-if="htmlContent"
|
||||
class="prose prose-sm dark:prose-invert prose-slate prose-p:m-0 max-w-none"
|
||||
>
|
||||
<div v-html="htmlContent" />
|
||||
</article>
|
||||
<article
|
||||
v-if="htmlContent"
|
||||
class="prose prose-sm dark:prose-invert prose-slate prose-p:m-0 max-w-none"
|
||||
>
|
||||
<div v-html="htmlContent" />
|
||||
</article>
|
||||
|
||||
<template v-if="showReferenced">
|
||||
<div v-if="props.item.repliedPost || props.item.repliedGone" class="border rounded-md">
|
||||
<div class="p-2 flex items-center gap-2">
|
||||
<span class="mdi mdi-reply"></span>
|
||||
<span class="font-bold">Replying to</span>
|
||||
</div>
|
||||
<div v-if="props.item.repliedGone" class="px-4 pb-3 text-sm opacity-60">
|
||||
Post unavailable
|
||||
</div>
|
||||
<post-item
|
||||
v-else-if="props.item.repliedPost"
|
||||
class="px-4 pb-3"
|
||||
:item="props.item.repliedPost"
|
||||
slim
|
||||
compact
|
||||
flat
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="props.item.forwardedPost || props.item.forwardedGone" class="border rounded-md">
|
||||
<div class="p-2 flex items-center gap-2">
|
||||
<span class="mdi mdi-forward"></span>
|
||||
<span class="font-bold">Forwarded</span>
|
||||
</div>
|
||||
<div v-if="props.item.forwardedGone" class="px-4 pb-3 text-sm opacity-60">
|
||||
Post unavailable
|
||||
</div>
|
||||
<post-item
|
||||
v-else-if="props.item.forwardedPost"
|
||||
class="px-4 pb-3"
|
||||
:item="props.item.forwardedPost"
|
||||
slim
|
||||
compact
|
||||
flat
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<attachment-list
|
||||
v-if="!compact"
|
||||
:attachments="props.item.attachments"
|
||||
:max-height="640"
|
||||
/>
|
||||
|
||||
<div ref="repliesTarget">
|
||||
<replies-compact-list
|
||||
v-if="props.item.repliesCount && !compact && repliesVisible"
|
||||
:params="{ postId: props.item.id }"
|
||||
:hide-quick-reply="true"
|
||||
@react="handleReplyReaction"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="showReferenced">
|
||||
<div
|
||||
v-if="props.item.isTruncated"
|
||||
class="flex gap-2 text-xs opacity-80 items-center"
|
||||
v-if="props.item.repliedPost || props.item.repliedGone"
|
||||
class="border rounded-md"
|
||||
>
|
||||
<span class="mdi mdi-dots-horizontal"></span>
|
||||
<p>Post truncated, tap to see details...</p>
|
||||
</div>
|
||||
|
||||
<!-- Post Reactions -->
|
||||
<div v-if="!compact" @click.stop>
|
||||
<post-reaction-list
|
||||
:parent-id="props.item.id"
|
||||
:reactions="props.item.reactionsCount"
|
||||
:reactions-made="props.item.reactionsMade"
|
||||
:can-react="true"
|
||||
<div class="p-2 flex items-center gap-2">
|
||||
<span class="mdi mdi-reply"></span>
|
||||
<span class="font-bold">Replying to</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.item.repliedGone"
|
||||
class="px-4 pb-3 text-sm opacity-60"
|
||||
>
|
||||
Post unavailable
|
||||
</div>
|
||||
<post-item
|
||||
v-else-if="props.item.repliedPost"
|
||||
class="px-4 pb-3"
|
||||
:item="props.item.repliedPost"
|
||||
slim
|
||||
compact
|
||||
flat
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="props.item.forwardedPost || props.item.forwardedGone"
|
||||
class="border rounded-md"
|
||||
>
|
||||
<div class="p-2 flex items-center gap-2">
|
||||
<span class="mdi mdi-forward"></span>
|
||||
<span class="font-bold">Forwarded</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.item.forwardedGone"
|
||||
class="px-4 pb-3 text-sm opacity-60"
|
||||
>
|
||||
Post unavailable
|
||||
</div>
|
||||
<post-item
|
||||
v-else-if="props.item.forwardedPost"
|
||||
class="px-4 pb-3"
|
||||
:item="props.item.forwardedPost"
|
||||
slim
|
||||
compact
|
||||
flat
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<attachment-list
|
||||
v-if="!compact"
|
||||
:attachments="props.item.attachments"
|
||||
:max-height="640"
|
||||
/>
|
||||
|
||||
<div ref="repliesTarget">
|
||||
<replies-compact-list
|
||||
v-if="props.item.repliesCount && !compact && repliesVisible"
|
||||
:params="{ postId: props.item.id }"
|
||||
:hide-quick-reply="true"
|
||||
@react="handleReplyReaction"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.item.isTruncated"
|
||||
class="flex gap-2 text-xs opacity-80 items-center"
|
||||
>
|
||||
<span class="mdi mdi-dots-horizontal"></span>
|
||||
<p>Post truncated, tap to see details...</p>
|
||||
</div>
|
||||
|
||||
<!-- Post Reactions -->
|
||||
<div v-if="!compact" @click.stop>
|
||||
<post-reaction-list
|
||||
:parent-id="props.item.id"
|
||||
:reactions="props.item.reactionsCount"
|
||||
:reactions-made="props.item.reactionsMade"
|
||||
:can-react="true"
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue"
|
||||
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
|
||||
import type { SnPost } from "~/types/api"
|
||||
import { useIntersectionObserver } from '@vueuse/core'
|
||||
import { useIntersectionObserver } from "@vueuse/core"
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -152,8 +162,9 @@ const repliesVisible = ref(false)
|
||||
|
||||
useIntersectionObserver(
|
||||
repliesTarget,
|
||||
([{ isIntersecting }]) => {
|
||||
if (isIntersecting) {
|
||||
(entries) => {
|
||||
const entry = entries[0]
|
||||
if (entry?.isIntersecting) {
|
||||
repliesVisible.value = true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,140 +1,57 @@
|
||||
<template>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<!-- Add Reaction Button -->
|
||||
<v-chip
|
||||
<n-tag
|
||||
v-if="canReact"
|
||||
rounded
|
||||
clickable
|
||||
style="cursor: pointer"
|
||||
type="primary"
|
||||
:disabled="submitting"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="showReactionDialog"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="HeartPlus" />
|
||||
</template>
|
||||
React
|
||||
</v-chip>
|
||||
</n-tag>
|
||||
|
||||
<!-- Existing Reactions -->
|
||||
<v-chip
|
||||
v-for="(count, symbol) in reactions"
|
||||
:key="symbol"
|
||||
rounded
|
||||
:color="getReactionColor(symbol)"
|
||||
:disabled="submitting"
|
||||
@click="reactToPost(symbol)"
|
||||
>
|
||||
<span class="reaction-emoji">{{ getReactionEmoji(symbol) }}</span>
|
||||
<span class="reaction-symbol">{{ symbol }}</span>
|
||||
<v-chip size="x-small" variant="flat" class="reaction-count ms-1">
|
||||
{{ count }}
|
||||
</v-chip>
|
||||
</v-chip>
|
||||
<n-space>
|
||||
<n-tag
|
||||
v-for="(count, symbol) in reactions"
|
||||
:key="symbol"
|
||||
:type="getReactionColor(symbol)"
|
||||
:disabled="submitting"
|
||||
@click="reactToPost(symbol)"
|
||||
style="cursor: pointer"
|
||||
class="reaction-tag"
|
||||
>
|
||||
<span class="reaction-emoji">{{ getReactionEmoji(symbol) }}</span>
|
||||
<span class="reaction-symbol ms-2">{{ symbol }}</span>
|
||||
<code class="text-xs ms-1.5">x{{ count }}</code>
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<!-- Reaction Selection Dialog -->
|
||||
<v-dialog v-model="reactionDialog" max-width="500" height="600">
|
||||
<v-card prepend-icon="mdi-emoticon-outline" title="React Post">
|
||||
<!-- Dialog Content -->
|
||||
<n-modal v-model:show="reactionDialog">
|
||||
<n-card class="max-w-[540px]">
|
||||
<template #header>
|
||||
<span class="font-bold">React Post</span>
|
||||
</template>
|
||||
<div class="dialog-content">
|
||||
<!-- Positive Reactions -->
|
||||
<div class="reaction-section">
|
||||
<div class="section-header d-flex align-center px-6 py-3">
|
||||
<v-icon class="me-2">mdi-emoticon-happy</v-icon>
|
||||
<span class="font-bold">Positive</span>
|
||||
</div>
|
||||
<div class="reaction-grid">
|
||||
<v-card
|
||||
v-for="reaction in getReactionsByAttitude(0)"
|
||||
:key="reaction.symbol"
|
||||
class="reaction-card mx-2"
|
||||
:class="{ selected: isReactionMade(reaction.symbol) }"
|
||||
:disabled="submitting"
|
||||
@click="selectReaction(reaction.symbol)"
|
||||
>
|
||||
<div class="d-flex flex-column align-center justify-center pa-3">
|
||||
<span class="text-h4 mb-1">{{ reaction.emoji }}</span>
|
||||
<span class="text-xs text-center mb-1">{{
|
||||
reaction.symbol
|
||||
}}</span>
|
||||
<span
|
||||
v-if="getReactionCount(reaction.symbol) > 0"
|
||||
class="text-xs"
|
||||
>
|
||||
x{{ getReactionCount(reaction.symbol) }}
|
||||
</span>
|
||||
<div v-else class="spacer"></div>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Neutral Reactions -->
|
||||
<div class="reaction-section">
|
||||
<div class="section-header d-flex align-center px-6 py-3">
|
||||
<v-icon class="me-2">mdi-emoticon-neutral</v-icon>
|
||||
<span class="font-bold">Neutral</span>
|
||||
</div>
|
||||
<div class="reaction-grid">
|
||||
<v-card
|
||||
v-for="reaction in getReactionsByAttitude(1)"
|
||||
:key="reaction.symbol"
|
||||
class="reaction-card mx-2"
|
||||
:class="{ selected: isReactionMade(reaction.symbol) }"
|
||||
:disabled="submitting"
|
||||
@click="selectReaction(reaction.symbol)"
|
||||
>
|
||||
<div class="d-flex flex-column align-center justify-center pa-3">
|
||||
<span class="text-h4 mb-1">{{ reaction.emoji }}</span>
|
||||
<span class="text-xs text-center mb-1">{{
|
||||
reaction.symbol
|
||||
}}</span>
|
||||
<span
|
||||
v-if="getReactionCount(reaction.symbol) > 0"
|
||||
class="text-xs"
|
||||
>
|
||||
x{{ getReactionCount(reaction.symbol) }}
|
||||
</span>
|
||||
<div v-else class="spacer"></div>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Negative Reactions -->
|
||||
<div class="reaction-section">
|
||||
<div class="section-header d-flex align-center px-6 py-3">
|
||||
<v-icon class="me-2">mdi-emoticon-sad</v-icon>
|
||||
<span class="font-bold">Negative</span>
|
||||
</div>
|
||||
<div class="reaction-grid">
|
||||
<v-card
|
||||
v-for="reaction in getReactionsByAttitude(2)"
|
||||
:key="reaction.symbol"
|
||||
class="reaction-card mx-2"
|
||||
:class="{ selected: isReactionMade(reaction.symbol) }"
|
||||
:disabled="submitting"
|
||||
@click="selectReaction(reaction.symbol)"
|
||||
>
|
||||
<div class="d-flex flex-column align-center justify-center pa-3">
|
||||
<span class="text-h4 mb-1">{{ reaction.emoji }}</span>
|
||||
<span class="text-xs text-center mb-1">{{
|
||||
reaction.symbol
|
||||
}}</span>
|
||||
<span
|
||||
v-if="getReactionCount(reaction.symbol) > 0"
|
||||
class="text-xs"
|
||||
>
|
||||
x{{ getReactionCount(reaction.symbol) }}
|
||||
</span>
|
||||
<div v-else class="spacer"></div>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
<n-alert type="info" title="Reaction not available">
|
||||
Due to various of reasons, we stop providing the react creation on the
|
||||
FloatingIsland. To react post, head to web.solian.app
|
||||
</n-alert>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { Smile, Meh, Frown, HeartPlus } from "lucide-vue-next"
|
||||
|
||||
interface Props {
|
||||
parentId: string
|
||||
@@ -191,12 +108,21 @@ function getReactionEmoji(symbol: string): string {
|
||||
return reaction?.emoji || "❓"
|
||||
}
|
||||
|
||||
function getReactionColor(symbol: string): string {
|
||||
const attitude =
|
||||
availableReactions.find((r) => r.symbol === symbol)?.attitude || 1
|
||||
function getReactionColor(
|
||||
symbol: string
|
||||
):
|
||||
| "success"
|
||||
| "error"
|
||||
| "primary"
|
||||
| "default"
|
||||
| "info"
|
||||
| "warning"
|
||||
| undefined {
|
||||
const attitude = availableReactions.find((r) => r.symbol === symbol)?.attitude
|
||||
if (attitude === 0) return "success"
|
||||
if (attitude === 2) return "error"
|
||||
return "primary"
|
||||
// neutral or unspecified attitudes use default
|
||||
return "default"
|
||||
}
|
||||
|
||||
async function reactToPost(symbol: string) {
|
||||
@@ -255,64 +181,3 @@ function getReactionCount(symbol: string): number {
|
||||
return (props.reactions || {})[symbol] || 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.reaction-emoji {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.reaction-symbol {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.reaction-count {
|
||||
font-size: 10px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.reaction-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.5);
|
||||
border-bottom: 1px solid rgb(var(--v-theme-outline-variant));
|
||||
}
|
||||
|
||||
.reaction-grid {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.reaction-grid::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reaction-card {
|
||||
min-width: 80px;
|
||||
height: 100px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.reaction-card:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.reaction-card.selected {
|
||||
border-color: rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary-container));
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
<template>
|
||||
<v-card
|
||||
<n-card
|
||||
class="replies-compact-list"
|
||||
flat
|
||||
border
|
||||
embedded
|
||||
title="Replies"
|
||||
prepend-icon="mdi-comment-text-multiple"
|
||||
density="compact"
|
||||
size="small"
|
||||
>
|
||||
<!-- Error State -->
|
||||
<v-alert
|
||||
v-if="hasError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
closable
|
||||
@click:close="refresh"
|
||||
>
|
||||
<n-alert v-if="hasError" type="error" closable @click:close="refresh">
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
</n-alert>
|
||||
|
||||
<!-- Replies List -->
|
||||
<div class="flex flex-col gap-2 pb-2.5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<template v-for="item in replies" :key="item.id">
|
||||
<v-sheet class="px-4" @click="router.push('/posts/' + item.id)">
|
||||
<div @click="router.push('/posts/' + item.id)">
|
||||
<div class="flex gap-3">
|
||||
<v-avatar :image="getPublisherAvatar(item)" size="24" border />
|
||||
<n-avatar :src="getPublisherAvatar(item)" :size="24" round />
|
||||
<article
|
||||
v-if="getHtmlContent(item)"
|
||||
class="prose prose-sm dark:prose-invert prose-slate prose-p:m-0 max-w-none flex-1"
|
||||
@@ -31,24 +24,22 @@
|
||||
<div v-html="getHtmlContent(item)" />
|
||||
</article>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!replies || replies.length === 0" class="text-center py-8 text-muted-foreground">
|
||||
<v-icon
|
||||
icon="mdi-comment-outline"
|
||||
size="48"
|
||||
class="mb-2 opacity-50"
|
||||
/>
|
||||
<div
|
||||
v-if="!replies || replies.length === 0"
|
||||
class="text-center py-8 text-muted-foreground"
|
||||
>
|
||||
<v-icon icon="mdi-comment-outline" size="48" class="mb-2 opacity-50" />
|
||||
<p>No replies yet</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { useRepliesList } from "~/composables/useRepliesList"
|
||||
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
|
||||
import type { RepliesListParams } from "~/composables/useRepliesList"
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
<post-quick-reply v-if="!props.hideQuickReply" class="mb-4" />
|
||||
|
||||
<!-- Error State -->
|
||||
<v-alert
|
||||
<n-alert
|
||||
v-if="hasError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
closable
|
||||
@click:close="refresh"
|
||||
:closable="true"
|
||||
@close="refresh"
|
||||
>
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
</n-alert>
|
||||
|
||||
<!-- Replies List -->
|
||||
<v-infinite-scroll
|
||||
@@ -36,18 +36,16 @@
|
||||
<!-- Loading State -->
|
||||
<template #loading>
|
||||
<div class="flex justify-center py-4">
|
||||
<v-progress-circular indeterminate size="32" />
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<template #empty>
|
||||
<div v-if="!replies" class="text-center py-8 text-muted-foreground">
|
||||
<v-icon
|
||||
icon="mdi-comment-outline"
|
||||
size="48"
|
||||
class="mb-2 opacity-50"
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -1,31 +1,43 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-screen" :data-theme="colorMode.preference">
|
||||
<header class="navbar bg-base-100 shadow-lg">
|
||||
<div class="container mx-auto flex items-center justify-center">
|
||||
<img :src="colorMode.value == 'dark' ? IconDark : IconLight" width="32" height="32" class="mr-4"
|
||||
alt="The Solar Network" />
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<header
|
||||
class="navbar bg-transparent shadow-lg fixed top-0 left-0 right-0 backdrop-blur-2xl z-1000 h-[64px]"
|
||||
>
|
||||
<div class="container mx-auto flex items-center">
|
||||
<img
|
||||
:src="IconLight"
|
||||
width="32"
|
||||
height="32"
|
||||
class="mr-4"
|
||||
alt="The Solar Network"
|
||||
/>
|
||||
|
||||
<n-button v-for="link in links" :key="link.title" text @click="() => router.push(link.href)">
|
||||
<template #icon>
|
||||
<span :class="`mdi ${link.icon}`"></span>
|
||||
</template>
|
||||
{{ link.title }}
|
||||
</n-button>
|
||||
<n-menu
|
||||
v-model:value="activeKey"
|
||||
mode="horizontal"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
|
||||
<div class="grow" />
|
||||
|
||||
<n-dropdown :options="dropdownOptions" @select="handleDropdownSelect">
|
||||
<n-avatar round class="mr-4 cursor-pointer" :size="32" :src="user?.profile.picture
|
||||
? `${apiBase}/drive/files/${user?.profile.picture?.id}`
|
||||
: undefined
|
||||
">
|
||||
<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-avatar>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="grow container mx-auto py-4">
|
||||
<main class="grow container mx-auto py-4 mt-[64px]">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
@@ -33,23 +45,36 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import IconLight from "~/assets/images/cloudy-lamb.png"
|
||||
import IconDark from "~/assets/images/cloudy-lamb@dark.png"
|
||||
|
||||
import type { NavLink } from "~/types/navlink"
|
||||
import type { MenuOption } from "naive-ui"
|
||||
import { computed, h } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import { CompassIcon } from "lucide-vue-next"
|
||||
|
||||
const apiBase = useSolarNetworkUrl()
|
||||
const colorMode = useColorMode()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const { user } = useUserStore()
|
||||
|
||||
const links: NavLink[] = [
|
||||
const activeKey = computed(() => {
|
||||
// Map route paths to menu keys
|
||||
if (route.path === "/") return "explore"
|
||||
return null
|
||||
})
|
||||
|
||||
function renderIcon(icon: any) {
|
||||
return () => h(NIcon, null, { default: () => icon })
|
||||
}
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
{
|
||||
title: "Explore",
|
||||
href: "/",
|
||||
icon: "mdi-compass"
|
||||
label: "Explore",
|
||||
key: "explore",
|
||||
icon: renderIcon(h(CompassIcon)),
|
||||
props: {
|
||||
onClick: () => router.push("/")
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -59,26 +84,26 @@ const dropdownOptions = computed(() => {
|
||||
{
|
||||
label: "Dashboard",
|
||||
key: "/accounts/me",
|
||||
icon: () => h('span', { class: 'mdi mdi-view-dashboard' })
|
||||
icon: () => h("span", { class: "mdi mdi-view-dashboard" })
|
||||
}
|
||||
];
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
label: "Login",
|
||||
key: "/auth/login",
|
||||
icon: () => h('span', { class: 'mdi mdi-login' })
|
||||
icon: () => h("span", { class: "mdi mdi-login" })
|
||||
},
|
||||
{
|
||||
label: "Create Account",
|
||||
key: "/auth/create-account",
|
||||
icon: () => h('span', { class: 'mdi mdi-account-plus' })
|
||||
icon: () => h("span", { class: "mdi mdi-account-plus" })
|
||||
}
|
||||
];
|
||||
]
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function handleDropdownSelect(key: string) {
|
||||
router.push(key);
|
||||
router.push(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-screen" :data-theme="colorMode.preference">
|
||||
<div class="min-h-screen">
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
@@ -25,6 +25,4 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from "~/assets/images/cloudy-lamb.png"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
</script>
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
</div>
|
||||
<div class="pa-8">
|
||||
<div class="mb-4">
|
||||
<img
|
||||
:src="colorMode.value == 'dark' ? IconDark : IconLight"
|
||||
alt="CloudyLamb"
|
||||
height="60"
|
||||
width="60"
|
||||
/>
|
||||
<img :src="IconLight" alt="CloudyLamb" height="60" width="60" />
|
||||
</div>
|
||||
<v-row>
|
||||
<v-col cols="12" lg="6" class="d-flex align-start justify-start">
|
||||
@@ -74,7 +69,7 @@
|
||||
<v-btn
|
||||
color="primary"
|
||||
:loading="isAuthorizing"
|
||||
class="flex-grow-1"
|
||||
class="grow"
|
||||
size="large"
|
||||
@click="handleAuthorize"
|
||||
>
|
||||
@@ -83,7 +78,7 @@
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
:disabled="isAuthorizing"
|
||||
class="flex-grow-1"
|
||||
class="grow"
|
||||
size="large"
|
||||
@click="handleDeny"
|
||||
>
|
||||
@@ -106,9 +101,6 @@ import { useRoute } from "vue-router"
|
||||
import { useSolarNetwork } from "~/composables/useSolarNetwork"
|
||||
|
||||
import IconLight from "~/assets/images/cloudy-lamb.png"
|
||||
import IconDark from "~/assets/images/cloudy-lamb@dark.png"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const route = useRoute()
|
||||
const api = useSolarNetwork()
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
</div>
|
||||
<div class="pa-8">
|
||||
<div class="mb-4">
|
||||
<img
|
||||
:src="colorMode.value == 'dark' ? IconDark : IconLight"
|
||||
alt="CloudyLamb"
|
||||
height="60"
|
||||
width="60"
|
||||
/>
|
||||
<img :src="IconLight" alt="CloudyLamb" height="60" width="60" />
|
||||
</div>
|
||||
<v-row>
|
||||
<v-col cols="12" lg="6" class="d-flex align-start justify-start">
|
||||
@@ -148,13 +143,10 @@ import { useSolarNetwork } from "~/composables/useSolarNetwork"
|
||||
import CaptchaWidget from "~/components/CaptchaWidget.vue"
|
||||
|
||||
import IconLight from "~/assets/images/cloudy-lamb.png"
|
||||
import IconDark from "~/assets/images/cloudy-lamb@dark.png"
|
||||
|
||||
const router = useRouter()
|
||||
const api = useSolarNetwork()
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
useHead({
|
||||
title: "Create Account"
|
||||
})
|
||||
|
||||
@@ -242,8 +242,6 @@ function getFactorName(factorType: number) {
|
||||
return "Unknown Factor"
|
||||
}
|
||||
}
|
||||
|
||||
const colorMode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -254,12 +252,7 @@ const colorMode = useColorMode()
|
||||
</div>
|
||||
<div class="pa-8">
|
||||
<div class="mb-4">
|
||||
<img
|
||||
:src="colorMode.value == 'dark' ? IconDark : IconLight"
|
||||
alt="CloudyLamb"
|
||||
height="60"
|
||||
width="60"
|
||||
/>
|
||||
<img :src="IconLight" alt="CloudyLamb" height="60" width="60" />
|
||||
</div>
|
||||
<v-row>
|
||||
<v-col cols="12" lg="6" class="d-flex align-start justify-start">
|
||||
@@ -370,14 +363,14 @@ const colorMode = useColorMode()
|
||||
factor.type === 0
|
||||
? "mdi-lock"
|
||||
: factor.type === 1
|
||||
? "mdi-email"
|
||||
: factor.type === 2
|
||||
? "mdi-cellphone"
|
||||
: factor.type === 3
|
||||
? "mdi-clock"
|
||||
: factor.type === 4
|
||||
? "mdi-numeric"
|
||||
: "mdi-shield-key"
|
||||
? "mdi-email"
|
||||
: factor.type === 2
|
||||
? "mdi-cellphone"
|
||||
: factor.type === 3
|
||||
? "mdi-clock"
|
||||
: factor.type === 4
|
||||
? "mdi-numeric"
|
||||
: "mdi-shield-key"
|
||||
}}</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar flex flex-col gap-3">
|
||||
<div v-if="!userStore.isAuthenticated" class="card w-full bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div
|
||||
v-if="!userStore.isAuthenticated"
|
||||
class="card w-full bg-base-100 shadow-xl"
|
||||
>
|
||||
<n-card>
|
||||
<h2 class="card-title">About</h2>
|
||||
<p>Welcome to the <b>Solar Network</b></p>
|
||||
<p>The open social network. Friendly to everyone.</p>
|
||||
@@ -25,7 +28,7 @@
|
||||
{{ version.updatedAt }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
<div v-else class="card w-full bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
@@ -70,7 +73,7 @@ const userStore = useUserStore()
|
||||
const version = ref<SnVersion | null>(null)
|
||||
async function fetchVersion() {
|
||||
const api = useSolarNetwork()
|
||||
const resp = await api("/sphere/version")
|
||||
const resp = await api("/version")
|
||||
version.value = resp as SnVersion
|
||||
}
|
||||
onMounted(() => fetchVersion())
|
||||
@@ -118,6 +121,7 @@ async function refreshActivities() {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
<template>
|
||||
<v-container class="py-6">
|
||||
<div class="py-6">
|
||||
<div v-if="pending" class="text-center py-12">
|
||||
<v-progress-circular indeterminate size="64" color="primary" />
|
||||
<n-spin size="large" />
|
||||
<p class="mt-4">Loading post...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-center py-12">
|
||||
<v-alert type="error" class="mb-4" prominent>
|
||||
<v-alert-title>Error Loading Post</v-alert-title>
|
||||
<n-alert
|
||||
type="error"
|
||||
title="Error Loading Post"
|
||||
class="mb-4"
|
||||
:closable="false"
|
||||
>
|
||||
{{ error?.statusMessage || "Failed to load post" }}
|
||||
</v-alert>
|
||||
</n-alert>
|
||||
</div>
|
||||
|
||||
<div v-else-if="post" class="max-w-7xl mx-auto">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
||||
<!-- Main Content Column -->
|
||||
<div class="lg:col-span-8 flex flex-col gap-4">
|
||||
<v-card class="pa-6">
|
||||
<n-card class="pa-6">
|
||||
<post-header :item="post" class="mb-4" />
|
||||
|
||||
<!-- Post Title and Description -->
|
||||
@@ -38,27 +42,27 @@
|
||||
<!-- Post Metadata -->
|
||||
<div class="flex items-center gap-4 text-sm text-medium-emphasis">
|
||||
<div class="flex items-center gap-1">
|
||||
<v-icon size="16">mdi-calendar</v-icon>
|
||||
<n-icon size="16" name="mdi-calendar" />
|
||||
<span>{{ formatDate(post.createdAt) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="post.updatedAt && post.updatedAt !== post.createdAt"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<v-icon size="16">mdi-pencil</v-icon>
|
||||
<n-icon size="16" name="mdi-pencil" />
|
||||
<span>Updated {{ formatDate(post.updatedAt) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<v-icon size="16">mdi-eye</v-icon>
|
||||
<n-icon size="16" name="mdi-eye" />
|
||||
<span>
|
||||
{{ post.viewsTotal }} / {{ post.viewsUnique }}
|
||||
views
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</n-card>
|
||||
|
||||
<v-card class="pa-6">
|
||||
<n-card class="pa-6">
|
||||
<article
|
||||
v-if="htmlContent"
|
||||
class="prose dark:prose-invert prose-slate max-w-none mb-8"
|
||||
@@ -72,70 +76,54 @@
|
||||
v-if="post.type != 1"
|
||||
:attachments="post.attachments || []"
|
||||
/>
|
||||
</v-card>
|
||||
</n-card>
|
||||
|
||||
<v-card
|
||||
title="Replies"
|
||||
prepend-icon="mdi-comment-text-multiple"
|
||||
color="transparent"
|
||||
flat
|
||||
>
|
||||
<n-card bordered>
|
||||
<template #header> Replies </template>
|
||||
<replies-list :params="{ postId: post.id }" />
|
||||
</v-card>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Column -->
|
||||
<div class="lg:col-span-4 flex flex-col gap-4">
|
||||
<!-- Tags Section -->
|
||||
<v-card
|
||||
v-if="post.tags && post.tags.length > 0"
|
||||
rounded="lg"
|
||||
prepend-icon="mdi-tag-multiple"
|
||||
title="Tags & Categories"
|
||||
>
|
||||
<v-card-text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<v-chip
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
prepend-icon="mdi-shape"
|
||||
rounded
|
||||
>
|
||||
{{ category.slug }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-for="tag in post.tags"
|
||||
:key="tag.id"
|
||||
prepend-icon="mdi-tag"
|
||||
rounded
|
||||
>
|
||||
{{ tag.slug }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<n-card v-if="post.tags && post.tags.length > 0" bordered>
|
||||
<template #header> Tags & Categories </template>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<n-tag
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
type="info"
|
||||
round
|
||||
>
|
||||
{{ category.slug }}
|
||||
</n-tag>
|
||||
<n-tag
|
||||
v-for="tag in post.tags"
|
||||
:key="tag.id"
|
||||
type="primary"
|
||||
round
|
||||
>
|
||||
{{ tag.slug }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- Post Reactions -->
|
||||
<v-card
|
||||
class="elevation-1"
|
||||
rounded="lg"
|
||||
title="Reactions"
|
||||
prepend-icon="mdi-thumb-up"
|
||||
>
|
||||
<v-card-text>
|
||||
<post-reaction-list
|
||||
can-react
|
||||
:parent-id="id"
|
||||
:reactions="(post as any).reactions || {}"
|
||||
:reactions-made="(post as any).reactionsMade || {}"
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<n-card class="elevation-1" bordered>
|
||||
<template #header> Reactions </template>
|
||||
<post-reaction-list
|
||||
can-react
|
||||
:parent-id="id"
|
||||
:reactions="(post as any).reactions || {}"
|
||||
:reactions-made="(post as any).reactionsMade || {}"
|
||||
@react="handleReaction"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -235,20 +223,11 @@ const userBackground = computed(() => {
|
||||
return firstImageAttachment
|
||||
? `${apiBase}/drive/files/${firstImageAttachment.id}`
|
||||
: post.value?.publisher.background
|
||||
? `${apiBase}/drive/files/${post.value?.publisher.background.id}`
|
||||
: undefined
|
||||
? `${apiBase}/drive/files/${post.value?.publisher.background.id}`
|
||||
: undefined
|
||||
})
|
||||
|
||||
defineOgImage({
|
||||
component: "ImageCard",
|
||||
title: computed(() => post.value?.title || "Post"),
|
||||
description: computed(
|
||||
() =>
|
||||
post.value?.description || post.value?.content?.substring(0, 150) || ""
|
||||
),
|
||||
avatarUrl: computed(() => userPicture.value),
|
||||
backgroundImage: computed(() => userBackground.value)
|
||||
})
|
||||
// defineOgImage block removed due to type incompatibility
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleDateString("en-US", {
|
||||
|
||||
@@ -6,26 +6,7 @@
|
||||
// @ts-ignore
|
||||
import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist"
|
||||
import "swagger-ui-dist/swagger-ui.css"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
onMounted(() => {
|
||||
// Load theme once on page load
|
||||
loadTheme(colorMode.value)
|
||||
|
||||
// Reactively switch if user toggles mode
|
||||
watch(colorMode, (newVal) => {
|
||||
loadTheme(newVal.value)
|
||||
})
|
||||
})
|
||||
|
||||
function loadTheme(mode: string) {
|
||||
if (mode === "dark") {
|
||||
import("swagger-themes/themes/one-dark.css")
|
||||
} else {
|
||||
import("swagger-themes/themes/material.css")
|
||||
}
|
||||
}
|
||||
import "swagger-themes/themes/one-dark.css"
|
||||
|
||||
const apiBase = useSolarNetworkUrl()
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { VNode } from "vue"
|
||||
|
||||
export interface NavLink {
|
||||
title: string
|
||||
href: string
|
||||
icon: string
|
||||
}
|
||||
title: string
|
||||
href: string
|
||||
icon: VNode
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import AutoImport from "unplugin-auto-import/vite"
|
||||
import Components from "unplugin-vue-components/vite"
|
||||
import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
|
||||
import { generateTailwindColorThemes } from "@bg-dev/nuxt-naiveui/utils"
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
@@ -12,13 +13,10 @@ export default defineNuxtConfig({
|
||||
"@nuxt/eslint",
|
||||
"@pinia/nuxt",
|
||||
"@nuxtjs/i18n",
|
||||
"@nuxtjs/color-mode",
|
||||
"nuxt-og-image",
|
||||
"@bg-dev/nuxt-naiveui",
|
||||
],
|
||||
css: [
|
||||
"~/assets/css/main.css",
|
||||
"@bg-dev/nuxt-naiveui"
|
||||
],
|
||||
css: ["~/assets/css/main.css"],
|
||||
app: {
|
||||
pageTransition: { name: "page", mode: "out-in" },
|
||||
head: {
|
||||
@@ -44,10 +42,6 @@ export default defineNuxtConfig({
|
||||
"Nunito:400"
|
||||
]
|
||||
},
|
||||
colorMode: {
|
||||
preference: "system",
|
||||
fallback: "light"
|
||||
},
|
||||
features: {
|
||||
inlineStyles: false
|
||||
},
|
||||
@@ -73,18 +67,37 @@ export default defineNuxtConfig({
|
||||
AutoImport({
|
||||
imports: [
|
||||
{
|
||||
'naive-ui': [
|
||||
'useDialog',
|
||||
'useMessage',
|
||||
'useNotification',
|
||||
'useLoadingBar'
|
||||
"naive-ui": [
|
||||
"useDialog",
|
||||
"useMessage",
|
||||
"useNotification",
|
||||
"useLoadingBar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}),
|
||||
Components({
|
||||
resolvers: [NaiveUiResolver()],
|
||||
resolvers: [NaiveUiResolver()]
|
||||
})
|
||||
]
|
||||
},
|
||||
naiveui: {
|
||||
themeConfig: {
|
||||
...generateTailwindColorThemes(),
|
||||
shared: {
|
||||
common: {
|
||||
fontFamily:
|
||||
"Nunito Variable, v-sans, ui-system, -apple-system, sans-serif",
|
||||
primaryColor: "#3F51B5FF",
|
||||
primaryColorHover: "#5767C1FF",
|
||||
primaryColorPressed: "#3546A4FF",
|
||||
primaryColorSuppl: "#4C5EC5FF",
|
||||
borderRadius: "16px",
|
||||
borderRadiusSmall: "8px"
|
||||
}
|
||||
},
|
||||
light: {},
|
||||
dark: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||
"@nuxt/eslint": "1.9.0",
|
||||
"@nuxt/image": "1.11.0",
|
||||
"@nuxtjs/color-mode": "3.5.2",
|
||||
"@nuxtjs/i18n": "10.1.0",
|
||||
"@pinia/nuxt": "0.11.2",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
@@ -26,6 +25,7 @@
|
||||
"eslint": "^9.39.1",
|
||||
"highlightjs": "^9.16.2",
|
||||
"katex": "^0.16.25",
|
||||
"lucide-vue-next": "^0.555.0",
|
||||
"luxon": "^3.7.2",
|
||||
"markdown-exit": "^1.0.0-beta.6",
|
||||
"markdown-it-highlightjs": "^4.2.0",
|
||||
@@ -36,9 +36,9 @@
|
||||
"pinia": "^3.0.4",
|
||||
"sharp": "^0.34.5",
|
||||
"swagger-themes": "^1.4.3",
|
||||
"swagger-ui-dist": "^5.30.2",
|
||||
"swagger-ui-dist": "^5.30.3",
|
||||
"tus-js-client": "^4.3.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue": "^3.5.25",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -46,7 +46,7 @@
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"daisyui": "^5.5.5",
|
||||
"naive-ui": "^2.43.2",
|
||||
"tailwindcss": "^4.1.17"
|
||||
|
||||
Reference in New Issue
Block a user