⬆️ Upgrade tailwindcss to v4

This commit is contained in:
2025-09-20 16:42:11 +08:00
parent 534e0f6080
commit ae0990a6cc
11 changed files with 274 additions and 342 deletions

View File

@@ -5,10 +5,6 @@
</template>
<script lang="ts">
import "@mdi/font/css/materialdesignicons.css"
import "~/assets/css/tailwind.css"
onMounted(() => {
const userStore = useUserStore()
userStore.fetchUser()

22
app/assets/css/main.css Normal file
View File

@@ -0,0 +1,22 @@
@import "tailwindcss";
@layer base {
:root {
--font-family: "Nunito Variable", "Helvatica", sans-serif;
}
}
@plugin "@tailwindcss/typography";
@import "@fontsource-variable/nunito";
@import "@mdi/font/css/materialdesignicons.css";
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
html,
body {
--font-family: "Nunito Variable", sans-serif;
font-family: var(--font-family);
}

View File

@@ -1,10 +0,0 @@
@import "@fontsource-variable/nunito";
html, body {
--font-family: 'Nunito Variable', sans-serif;
font-family: var(--font-family);
}
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,9 +1,13 @@
<template>
<div v-if="itemType == 'image'" class="relative rounded-md overflow-hidden" :style="containerStyle">
<div
v-if="itemType == 'image'"
class="relative rounded-md overflow-hidden"
:style="`width: 100%; max-height: 800px; aspect-ratio: ${aspectRatio}`"
>
<!-- Blurhash placeholder -->
<div
v-if="blurhash"
class="absolute inset-0"
class="absolute inset-0 z-[-1]"
:style="blurhashContainerStyle"
>
<canvas
@@ -16,7 +20,7 @@
<!-- Main image -->
<img
:src="remoteSource"
class="w-full h-auto rounded-md"
class="w-full h-auto rounded-md transition-opacity duration-500"
:class="{ 'opacity-0': !imageLoaded && blurhash }"
@load="imageLoaded = true"
@error="imageLoaded = true"
@@ -37,54 +41,23 @@ const itemType = computed(() => props.item.mimeType.split("/")[0] ?? "unknown")
const blurhash = computed(() => props.item.fileMeta?.blur)
const imageWidth = computed(() => props.item.fileMeta?.width)
const imageHeight = computed(() => props.item.fileMeta?.height)
const aspectRatio = computed(() => props.item.fileMeta?.ratio ?? (imageWidth.value && imageHeight.value ? imageHeight.value / imageWidth.value : 1))
const aspectRatio = computed(
() =>
props.item.fileMeta?.ratio ??
(imageWidth.value && imageHeight.value
? imageHeight.value / imageWidth.value
: 1)
)
const imageLoaded = ref(false)
const blurCanvas = ref<HTMLCanvasElement | null>(null)
const apiBase = useSolarNetworkUrl()
const remoteSource = computed(
() => `${apiBase}/drive/files/${props.item.id}?original=true`
)
const containerStyle = computed(() => {
if (imageWidth.value && imageHeight.value) {
const maxWidth = 640 // Cap maximum width
const maxHeight = 800 // Cap maximum height
let width = imageWidth.value
let height = imageHeight.value
// Scale down if width exceeds max
if (width > maxWidth) {
const ratio = maxWidth / width
width = maxWidth
height = height * ratio
}
// Scale down if height exceeds max
if (height > maxHeight) {
const ratio = maxHeight / height
height = maxHeight
width = width * ratio
}
return {
width: `${width}px`,
height: `${height}px`,
'max-width': '100%',
'max-height': '100%'
}
}
return {
'max-width': '800px',
'max-height': '600px'
}
})
const remoteSource = computed(() => `${apiBase}/drive/files/${props.item.id}`)
const blurhashContainerStyle = computed(() => {
return {
'padding-bottom': `${aspectRatio.value * 100}%`
"padding-bottom": `${aspectRatio.value * 100}%`
}
})

View File

@@ -324,7 +324,7 @@ function getFactorName(factorType: number) {
variant="text"
class="text-capitalize"
color="primary"
>Forgot email?</v-btn
>Forgot password?</v-btn
>
<div class="d-flex justify-end">

96
app/pages/posts/[id].vue Normal file
View File

@@ -0,0 +1,96 @@
<template>
<v-container>
<div v-if="loading" class="text-center py-8">
<v-progress-circular indeterminate size="64" />
<p class="mt-4">Loading post...</p>
</div>
<div v-else-if="error" class="text-center py-8">
<v-alert type="error" class="mb-4">
{{ error }}
</v-alert>
</div>
<div v-else-if="post">
<v-card class="mb-4">
<v-card-text>
<div class="flex flex-col gap-3">
<post-header :item="post" />
<div v-if="post.title || post.description">
<h1 v-if="post.title" class="text-2xl font-bold">
{{ post.title }}
</h1>
<p v-if="post.description" class="text-sm text-gray-600 dark:text-gray-400">
{{ post.description }}
</p>
</div>
<article
v-if="htmlContent"
class="prose prose-lg dark:prose-invert prose-slate prose-p:m-0 max-w-none"
>
<div v-html="htmlContent" />
</article>
<div v-if="post.attachments && post.attachments.length > 0" class="d-flex gap-2 flex-wrap mt-4">
<attachment-item
v-for="attachment in post.attachments"
:key="attachment.id"
:item="attachment"
/>
</div>
<div v-if="post.tags && post.tags.length > 0" class="mt-4">
<v-chip
v-for="tag in post.tags"
:key="tag"
size="small"
variant="outlined"
class="mr-2 mb-2"
>
{{ tag }}
</v-chip>
</div>
</div>
</v-card-text>
</v-card>
</div>
</v-container>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue"
import { Marked } from "marked"
import type { SnPost } from "~/types/api"
import PostHeader from "~/components/PostHeader.vue"
import AttachmentItem from "~/components/AttachmentItem.vue"
const route = useRoute()
const id = route.params.id as string
const post = ref<SnPost | null>(null)
const loading = ref(true)
const error = ref("")
const htmlContent = ref("")
const marked = new Marked()
async function fetchPost() {
try {
const api = useSolarNetwork()
const resp = await api(`/sphere/posts/${id}`)
post.value = resp as SnPost
if (post.value.content) {
htmlContent.value = await marked.parse(post.value.content, { breaks: true })
}
} catch (e) {
error.value = e instanceof Error ? e.message : "Failed to load post"
} finally {
loading.value = false
}
}
onMounted(() => {
fetchPost()
})
</script>