♻️ Migrate the exploring page

This commit is contained in:
2025-09-19 01:04:05 +08:00
parent 773cc220e0
commit 7904ce9ca7
13 changed files with 498 additions and 9 deletions

View File

@@ -0,0 +1,16 @@
<template>
<img v-if="itemType == 'image'" :src="remoteSource" class="rounded-md">
<audio v-else-if="itemType == 'audio'" :src="remoteSource" controls />
<video v-else-if="itemType == 'video'" :src="remoteSource" controls />
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{ item: any }>()
const itemType = computed(() => props.item.mime_type.split('/')[0] ?? 'unknown')
const apiBase = useSolarNetworkUrl();
const remoteSource = computed(() => `${apiBase}/drive/files/${props.item.id}?original=true`)
</script>

View File

@@ -0,0 +1,131 @@
<template>
<div class="flex flex-col gap-2">
<pub-select v-model:value="publisher" />
<v-textarea
v-model="content"
placeholder="What's happended?!"
@keydown.meta.enter.exact="submit"
@keydown.ctrl.enter.exact="submit"
/>
<div v-if="fileList.length > 0" class="d-flex gap-2 flex-wrap">
<v-img
v-for="file in fileList"
:key="file.name"
:src="file.url"
width="100"
height="100"
class="rounded"
/>
</div>
<div class="flex justify-between">
<div class="flex gap-2">
<v-file-input
v-model="selectedFiles"
multiple
accept="image/*,video/*,audio/*"
label="Upload files"
prepend-icon="mdi-upload"
hide-details
density="compact"
@change="handleFileSelect"
/>
</div>
<v-btn type="primary" :loading="submitting" @click="submit">
Post
<template #append>
<v-icon>mdi-send</v-icon>
</template>
</v-btn>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import * as tus from 'tus-js-client'
import { useSolarNetwork } from '~/composables/useSolarNetwork'
import PubSelect from './PubSelect.vue'
const emits = defineEmits(['posted'])
const publisher = ref<string | undefined>()
const content = ref('')
const selectedFiles = ref<File[]>([])
const fileList = ref<any[]>([])
const submitting = ref(false)
async function submit() {
submitting.value = true
const api = useSolarNetwork()
await api(`/posts?pub=${publisher.value}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
content: content.value,
attachments: fileList.value
.filter((e) => e.url != null)
.map((e) => e.url!.split('/').reverse()[0]),
}),
})
submitting.value = false
content.value = ''
fileList.value = []
emits('posted')
}
function handleFileSelect() {
selectedFiles.value.forEach(file => {
uploadFile(file)
})
selectedFiles.value = []
}
function uploadFile(file: File) {
const upload = new tus.Upload(file, {
endpoint: '/cgi/drive/tus',
retryDelays: [0, 3000, 5000, 10000, 20000],
removeFingerprintOnSuccess: false,
uploadDataDuringCreation: false,
metadata: {
filename: file.name,
'content-type': file.type ?? 'application/octet-stream',
},
headers: {
'X-DirectUpload': 'true',
},
onShouldRetry: () => false,
onError: function (error) {
console.error('[DRIVE] Upload failed:', error)
},
onProgress: function (bytesUploaded, bytesTotal) {
// Could show progress
},
onSuccess: function (payload) {
const rawInfo = payload.lastResponse.getHeader('x-fileinfo')
const jsonInfo = JSON.parse(rawInfo as string)
console.log('[DRIVE] Upload successful: ', jsonInfo)
fileList.value.push({
name: file.name,
url: `/cgi/drive/files/${jsonInfo.id}`,
type: jsonInfo.mime_type,
})
},
onBeforeRequest: function (req) {
const xhr = req.getUnderlyingObject()
xhr.withCredentials = true
},
})
upload.findPreviousUploads().then(function (previousUploads) {
if (previousUploads.length) {
upload.resumeFromPreviousUpload(previousUploads[0])
}
upload.start()
})
}
</script>

View File

@@ -0,0 +1,28 @@
<template>
<div class="flex gap-3 items-center">
<v-avatar :image="publisherAvatar" size="40" />
<div class="flex-grow-1 flex flex-col">
<p class="flex gap-1 items-baseline">
<span class="font-bold">{{ props.item.publisher.nick }}</span>
<span class="text-xs">@{{ props.item.publisher.name }}</span>
</p>
<p class="text-xs flex gap-1">
<span>{{ DateTime.fromISO(props.item.created_at).toRelative() }}</span>
<span class="font-bold">·</span>
<span>{{ DateTime.fromISO(props.item.created_at).toLocaleString() }}</span>
</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { DateTime } from 'luxon'
const props = defineProps<{ item: any }>()
const apiBase = useSolarNetworkUrl();
const publisherAvatar = computed(() =>
props.item.publisher.picture ? `${apiBase}/drive/files/${props.item.publisher.picture.id}` : undefined,
)
</script>

View File

@@ -0,0 +1,50 @@
<template>
<v-card>
<v-card-text>
<div class="flex flex-col gap-3">
<post-header :item="props.item" />
<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">
<div v-html="htmlContent"/>
</article>
<div v-if="props.item.attachments" class="d-flex gap-2 flex-wrap">
<attachment-item
v-for="attachment in props.item.attachments"
:key="attachment.id"
:item="attachment"
/>
</div>
</div>
</v-card-text>
</v-card>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { Marked } from 'marked'
import PostHeader from './PostHeader.vue'
import AttachmentItem from './AttachmentItem.vue'
const props = defineProps<{ item: any }>()
const marked = new Marked()
const htmlContent = ref<string>('')
watch(
props.item,
async (value) => {
if (value.content) htmlContent.value = await marked.parse(value.content)
},
{ immediate: true, deep: true },
)
</script>

View File

@@ -0,0 +1,55 @@
<template>
<v-select
:items="pubStore.publishers"
item-title="nick"
item-value="name"
:model-value="props.value"
@update:model-value="(v) => emits('update:value', v)"
>
<template #item="{ props: itemProps, item }">
<v-list-item v-bind="itemProps">
<template #prepend>
<v-avatar
:image="item.raw.picture ? `${apiBase}/api/drive/files/${item.raw.picture.id}` : undefined"
size="small"
rounded
/>
</template>
<v-list-item-title>{{ item.raw?.nick }}</v-list-item-title>
<v-list-item-subtitle>@{{ item.raw?.name }}</v-list-item-subtitle>
</v-list-item>
</template>
<template #selection="{ item }">
<div class="d-flex align-center">
<v-avatar
:image="item.raw.picture ? `${apiBase}/api/drive/files/${item.raw.picture.id}` : undefined"
size="24"
rounded
class="me-2"
/>
{{ item.raw?.nick }}
</div>
</template>
</v-select>
</template>
<script setup lang="ts">
import { usePubStore } from '~/stores/pub'
import { watch } from 'vue'
const pubStore = usePubStore()
const apiBase = useSolarNetworkUrl()
const props = defineProps<{ value: string | undefined }>()
const emits = defineEmits(['update:value'])
watch(
pubStore,
(value) => {
if (!props.value && value.publishers) {
emits('update:value', pubStore.publishers[0]?.name)
}
},
{ deep: true, immediate: true },
)
</script>

View File

@@ -12,7 +12,7 @@ export function useCustomTheme(): {
valueLight: "light",
initialValue: "light",
onChanged: (dark: boolean) => {
$vuetify.theme.global.name.value = dark ? "dark" : "light"
$vuetify.theme.change(dark ? "dark" : "light")
}
})

View File

@@ -0,0 +1,10 @@
// Solar Network aka the api client
export const useSolarNetwork = () => {
const apiBase = useSolarNetworkUrl();
return $fetch.create({ baseURL: apiBase, credentials: 'include' })
}
export const useSolarNetworkUrl = () => {
const config = useRuntimeConfig()
return config.public.apiBase
}

View File

@@ -1,5 +1,88 @@
<template>
<v-container>
<h1>Welcome!</h1>
<v-row>
<v-col cols="12" md="8" :order="$vuetify.display.lgAndUp ? 1 : 2">
<v-infinite-scroll style="height: calc(100vh - 57px)" :distance="10" @load="fetchActivites">
<div v-for="activity in activites" :key="activity.id" class="mt-4">
<post-item
v-if="activity.type.startsWith('posts')"
:item="activity.data"
@click="router.push('/posts/' + activity.id)"
/>
</div>
</v-infinite-scroll>
</v-col>
<v-col cols="12" md="4" :order="$vuetify.display.lgAndUp ? 2 : 1">
<v-card v-if="!userStore.user" class="w-full mt-4" title="About">
<v-card-text>
<p>Welcome to the <b>Solar Network</b></p>
<p>The open social network. Friendly to everyone.</p>
<p class="mt-4 opacity-75 text-xs">
<span v-if="version == null">Loading...</span>
<span v-else>
v{{ version.version }} @
{{ version.commit.substring(0, 6) }}
{{ version.updatedAt }}
</span>
</p>
</v-card-text>
</v-card>
<v-card class="mt-4 w-full">
<v-card-text>
<post-editor @posted="refreshActivities" />
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useUserStore } from '~/stores/user'
import { useSolarNetwork } from '~/composables/useSolarNetwork'
import PostEditor from '~/components/PostEditor.vue'
import PostItem from '~/components/PostItem.vue'
const router = useRouter()
const userStore = useUserStore()
const version = ref<any>(null)
async function fetchVersion() {
const api = useSolarNetwork()
const resp = await api('/sphere/version')
version.value = resp
}
onMounted(() => fetchVersion())
const loading = ref(false)
const activites = ref<any[]>([])
const activitesLast = computed(() => activites.value[Math.max(activites.value.length - 1, 0)])
const activitesHasMore = ref(true)
async function fetchActivites() {
if (loading.value) return
if (!activitesHasMore.value) return
loading.value = true
const api = useSolarNetwork()
const resp = await api(
activitesLast.value == null
? '/sphere/activities'
: `/sphere/activities?cursor=${new Date(activitesLast.value.created_at).toISOString()}`,
)
const data = resp
activites.value = [...activites.value, ...data]
activitesHasMore.value = data[0]?.type != 'empty'
loading.value = false
}
onMounted(() => fetchActivites())
async function refreshActivities() {
activites.value = []
fetchActivites()
}
</script>

15
app/stores/pub.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useSolarNetwork } from '~/composables/useSolarNetwork'
export const usePubStore = defineStore('pub', () => {
const publishers = ref<any[]>([])
async function fetchPublishers() {
const api = useSolarNetwork()
const resp = await api('/publishers')
publishers.value = resp as any[]
}
return { publishers, fetchPublishers }
})

45
app/stores/user.ts Normal file
View File

@@ -0,0 +1,45 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useSolarNetwork } from '~/composables/useSolarNetwork'
export const useUserStore = defineStore('user', () => {
// State
const user = ref<any>(null)
const isLoading = ref(false)
const error = ref<string | null>(null)
// Getters
const isAuthenticated = computed(() => !!user.value)
// Actions
async function fetchUser(reload = true) {
if (!reload && user.value) return // Skip fetching if already loaded and not forced to
isLoading.value = true
error.value = null
const api = useSolarNetwork()
try {
const response = await api('/id/accounts/me')
user.value = response
} catch (e: any) {
error.value = e.message
user.value = null // Clear user data on error
} finally {
isLoading.value = false
}
}
function logout() {
user.value = null
localStorage.removeItem('authToken')
}
return {
user,
isLoading,
error,
isAuthenticated,
fetchUser,
logout,
}
})

View File

@@ -19,12 +19,15 @@
"marked": "^16.3.0",
"nuxt": "^4.1.2",
"pinia": "^3.0.3",
"tus-js-client": "^4.3.1",
"vue": "^3.5.21",
"vue-router": "^4.5.1",
"vuetify-nuxt-module": "0.18.7",
},
"devDependencies": {
"@mdi/font": "^7.4.47",
"@types/luxon": "^3.7.1",
"@types/node": "^24.5.2",
},
},
},
@@ -497,6 +500,10 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="],
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
"@types/parse-path": ["@types/parse-path@7.1.0", "", { "dependencies": { "parse-path": "*" } }, "sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
@@ -769,6 +776,8 @@
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
"combine-errors": ["combine-errors@3.0.3", "", { "dependencies": { "custom-error-instance": "2.1.1", "lodash.uniqby": "4.5.0" } }, "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q=="],
"commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
"comment-parser": ["comment-parser@1.4.1", "", {}, "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg=="],
@@ -835,6 +844,8 @@
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"custom-error-instance": ["custom-error-instance@2.1.1", "", {}, "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg=="],
"db0": ["db0@0.3.2", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw=="],
"de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="],
@@ -1163,7 +1174,7 @@
"is-ssh": ["is-ssh@1.4.1", "", { "dependencies": { "protocols": "^2.0.1" } }, "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg=="],
"is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
@@ -1181,6 +1192,8 @@
"jiti-v1": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
@@ -1243,6 +1256,18 @@
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash._baseiteratee": ["lodash._baseiteratee@4.7.0", "", { "dependencies": { "lodash._stringtopath": "~4.8.0" } }, "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ=="],
"lodash._basetostring": ["lodash._basetostring@4.12.0", "", {}, "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw=="],
"lodash._baseuniq": ["lodash._baseuniq@4.6.0", "", { "dependencies": { "lodash._createset": "~4.0.0", "lodash._root": "~3.0.0" } }, "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A=="],
"lodash._createset": ["lodash._createset@4.0.3", "", {}, "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA=="],
"lodash._root": ["lodash._root@3.0.1", "", {}, "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ=="],
"lodash._stringtopath": ["lodash._stringtopath@4.8.0", "", { "dependencies": { "lodash._basetostring": "~4.12.0" } }, "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ=="],
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
@@ -1251,8 +1276,12 @@
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
"lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
"lodash.uniqby": ["lodash.uniqby@4.5.0", "", { "dependencies": { "lodash._baseiteratee": "~4.7.0", "lodash._baseuniq": "~4.6.0" } }, "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
@@ -1531,6 +1560,8 @@
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
"protocols": ["protocols@2.0.2", "", {}, "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
@@ -1539,6 +1570,8 @@
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
"querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
@@ -1575,6 +1608,8 @@
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -1583,6 +1618,8 @@
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
@@ -1625,7 +1662,7 @@
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
@@ -1747,6 +1784,8 @@
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"tus-js-client": ["tus-js-client@4.3.1", "", { "dependencies": { "buffer-from": "^1.1.2", "combine-errors": "^3.0.3", "is-stream": "^2.0.0", "js-base64": "^3.7.2", "lodash.throttle": "^4.1.1", "proper-lockfile": "^4.1.2", "url-parse": "^1.5.7" } }, "sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -1767,6 +1806,8 @@
"unctx": ["unctx@2.4.1", "", { "dependencies": { "acorn": "^8.14.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.17", "unplugin": "^2.1.0" } }, "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="],
"unhead": ["unhead@2.0.17", "", { "dependencies": { "hookable": "^5.5.3" } }, "sha512-xX3PCtxaE80khRZobyWCVxeFF88/Tg9eJDcJWY9us727nsTC7C449B8BUfVBmiF2+3LjPcmqeoB2iuMs0U4oJQ=="],
@@ -1801,6 +1842,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
@@ -1983,8 +2026,6 @@
"archiver-utils/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
@@ -1995,8 +2036,6 @@
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -2007,10 +2046,16 @@
"eslint-plugin-vue/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"execa/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"fast-glob/@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"globby/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"globby/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],

View File

@@ -12,5 +12,13 @@ export default defineNuxtConfig({
],
features: {
inlineStyles: false
},
runtimeConfig: {
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || "https://api.solian.app"
}
},
image: {
domains: ["api.solian.app"]
}
})

View File

@@ -25,11 +25,14 @@
"marked": "^16.3.0",
"nuxt": "^4.1.2",
"pinia": "^3.0.3",
"tus-js-client": "^4.3.1",
"vue": "^3.5.21",
"vue-router": "^4.5.1",
"vuetify-nuxt-module": "0.18.7"
},
"devDependencies": {
"@mdi/font": "^7.4.47"
"@mdi/font": "^7.4.47",
"@types/luxon": "^3.7.1",
"@types/node": "^24.5.2"
}
}