Compare commits
4 Commits
archive/nu
...
e4111dc06e
Author | SHA1 | Date | |
---|---|---|---|
|
e4111dc06e | ||
|
3e7f259834 | ||
|
97449bdc1e | ||
|
975766302a |
@@ -1,22 +1,28 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&family=Noto+Sans+JP:wght@100..900&family=Noto+Sans+SC:wght@100..900&family=Noto+Sans+TC:wght@100..900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
|
||||
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&family=Noto+Sans+SC:wght@100..900&family=Noto+Sans+TC:wght@100..900&family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap");
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body, #app, .v-application {
|
||||
overflow: auto !important;
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.v-application {
|
||||
overflow: auto !important;
|
||||
|
||||
font-family: "Comfortaa", "Noto Sans SC", "Noto Sans TC", "Noto Sans JP", sans-serif !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-family: "Nunito", "Noto Sans SC", "Noto Sans TC", "Noto Sans JP", sans-serif !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.font-mono, code, pre {
|
||||
font-family: "Roboto Mono", monospace !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
.font-mono,
|
||||
code,
|
||||
pre {
|
||||
font-family: "Roboto Mono", monospace !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@@ -3,10 +3,12 @@
|
||||
<v-card-text>
|
||||
<div class="mb-3 flex flex-row gap-4">
|
||||
<nuxt-link :to="`/users/${post.publisher?.name}`">
|
||||
<v-avatar :image="post.publisher?.avatar" />
|
||||
<v-avatar :image="getAttachmentUrl(post.publisher?.avatar)" icon="mdi-account-circle" />
|
||||
</nuxt-link>
|
||||
<div class="flex flex-col">
|
||||
<span>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span>
|
||||
<span
|
||||
>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span
|
||||
>
|
||||
<span v-if="post.body?.title" class="text-md">{{ post.body?.title }}</span>
|
||||
<span v-if="post.body?.description" class="text-sm">{{ post.body?.description }}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">
|
||||
@@ -29,7 +31,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<article v-if="post.type == 'story' || props.forceShowContent" class="text-base prose max-w-none">
|
||||
<article v-if="(post.type == 'story' || props.forceShowContent) && post.body?.content" class="text-base prose max-w-none">
|
||||
<m-d-c :value="post.body?.content"></m-d-c>
|
||||
</article>
|
||||
|
||||
@@ -41,24 +43,19 @@
|
||||
</v-card>
|
||||
|
||||
<div class="text-sm flex flex-col">
|
||||
<span class="flex flex-row gap-1">
|
||||
<span>
|
||||
{{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }},
|
||||
</span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="flex flex-row gap-1">
|
||||
<span> {{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }}, </span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
{{ new Date(post.published_at).toLocaleString() }}
|
||||
</span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
{{ new Date(post.published_at).toLocaleString() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="post.tags?.length > 0"
|
||||
class="text-xs text-grey flex flex-row gap-1 mt-3"
|
||||
>
|
||||
<div v-if="post.tags?.length > 0" class="text-xs text-grey flex flex-row gap-1 mt-3">
|
||||
<nuxt-link
|
||||
v-for="tag in post.tags"
|
||||
:to="`/posts/tags/${tag.alias}`"
|
||||
@@ -73,10 +70,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ post: any, forceShowContent?: boolean, noClickableAttachment?: boolean }>()
|
||||
const props = defineProps<{ post: any; forceShowContent?: boolean; noClickableAttachment?: boolean }>()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const url = computed(() => props.post.alias ? `/posts/${props.post.area_alias}/${props.post.alias}` : `/posts/${props.post.id}`)
|
||||
const url = computed(() =>
|
||||
props.post.alias ? `/posts/${props.post.area_alias}/${props.post.alias}` : `/posts/${props.post.id}`,
|
||||
)
|
||||
</script>
|
||||
|
@@ -5,13 +5,11 @@
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-data-table-server
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="dataDefinitions.stickers"
|
||||
:items="stickers"
|
||||
:items-length="pagination.stickers.total"
|
||||
:loading="reverting.stickers"
|
||||
v-model:items-per-page="pagination.stickers.pageSize"
|
||||
@update:options="readStickers"
|
||||
item-value="id"
|
||||
>
|
||||
@@ -74,23 +72,24 @@
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card :title="`Delete sticker #${item.id}?`">
|
||||
<v-card-text>
|
||||
This action will delete this sticker, all content used it will no longer show your sticker.
|
||||
But the attachment will still exists.
|
||||
This action will delete this sticker, all content used it will no longer show your sticker. But the
|
||||
attachment will still exists.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn
|
||||
text="Cancel"
|
||||
color="grey"
|
||||
@click="isActive.value = false"
|
||||
></v-btn>
|
||||
<v-btn text="Cancel" color="grey" @click="isActive.value = false"></v-btn>
|
||||
|
||||
<v-btn
|
||||
text="Delete"
|
||||
color="error"
|
||||
@click="() => { deleteSticker(item); isActive.value = false }"
|
||||
@click="
|
||||
() => {
|
||||
deleteSticker(item)
|
||||
isActive.value = false
|
||||
}
|
||||
"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -99,7 +98,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-data-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -108,7 +107,7 @@ import { solarFetch } from "~/utils/request"
|
||||
const config = useRuntimeConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{ packId: number, packPrefix?: string }>()
|
||||
const props = defineProps<{ packId: number; packPrefix?: string }>()
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
@@ -125,34 +124,20 @@ const dataDefinitions: { [id: string]: any[] } = {
|
||||
const stickers = ref<any>([])
|
||||
|
||||
const reverting = reactive({ stickers: false })
|
||||
const pagination = reactive({
|
||||
stickers: { page: 1, pageSize: 5, total: 0 },
|
||||
})
|
||||
|
||||
async function readStickers({ page, itemsPerPage }: { page?: number; itemsPerPage?: number }) {
|
||||
if (itemsPerPage) pagination.stickers.pageSize = itemsPerPage
|
||||
if (page) pagination.stickers.page = page
|
||||
|
||||
async function readStickers() {
|
||||
reverting.stickers = true
|
||||
const res = await solarFetch(
|
||||
"/cgi/uc/stickers?" +
|
||||
new URLSearchParams({
|
||||
pack: props.packId.toString(),
|
||||
take: pagination.stickers.pageSize.toString(),
|
||||
offset: ((pagination.stickers.page - 1) * pagination.stickers.pageSize).toString(),
|
||||
}),
|
||||
)
|
||||
const res = await solarFetch("/cgi/uc/stickers/packs/" + props.packId)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
stickers.value = data["data"]
|
||||
pagination.stickers.total = data["count"]
|
||||
stickers.value = data["stickers"]
|
||||
}
|
||||
reverting.stickers = false
|
||||
}
|
||||
|
||||
onMounted(() => readStickers({}))
|
||||
onMounted(() => readStickers())
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
@@ -165,7 +150,7 @@ async function deleteSticker(item: any) {
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
await readStickers({})
|
||||
await readStickers()
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
|
@@ -1,20 +0,0 @@
|
||||
import fs from "fs"
|
||||
|
||||
const tones = ["↑", "→", "↓", "↗", "↘"]
|
||||
|
||||
const raw = fs.readFileSync("../lang/zh-CN.json", "utf-8")
|
||||
|
||||
const original: { [id: string]: string } = JSON.parse(raw)
|
||||
|
||||
const result: { [id: string]: string } = {}
|
||||
|
||||
for (const key in original) {
|
||||
let str = ""
|
||||
for (const char of original[key]) {
|
||||
const tone = tones[Math.floor(Math.random() * tones.length)]
|
||||
str += "咩" + tone
|
||||
}
|
||||
result[key] = str
|
||||
}
|
||||
|
||||
fs.writeFileSync("../lang/ml-SG.json", JSON.stringify(result))
|
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"brandName": "咩→咩↗咩↘咩↑",
|
||||
"brandNameFormal": "咩↓咩↓咩→咩↘咩↗咩↗咩↘咩↗咩↓咩↘",
|
||||
"navProducts": "咩↑咩→",
|
||||
"navActivity": "咩→咩↑",
|
||||
"navActivityCaption": "咩→咩↑咩↑咩↑咩↗咩↑咩↗咩↘咩↗咩↑咩→咩↘",
|
||||
"navGallery": "咩→咩↑",
|
||||
"navGalleryCaption": "咩↓咩↘咩↓咩↘咩↘咩↓咩↑咩↘咩→咩↗咩↗咩↑咩↗咩↗咩↓咩↗咩→咩↑咩↘咩↑咩↓咩→咩↑咩↗",
|
||||
"indexIntroduce": "咩↓咩↗咩↓咩↑咩↘咩↓咩↑咩↑咩↗咩↗咩↗咩↓咩↘咩↗咩→咩→咩↓咩↓咩↘咩→咩↓咩↓咩↑咩↑咩→",
|
||||
"indexProductListHint": "咩↗咩↓咩↘咩↑咩↑咩↘咩↓咩↑咩↘咩↓咩↗咩↘",
|
||||
"indexActivities": "咩↘咩↗",
|
||||
"indexActivitiesCaption": "咩↑咩↘咩→咩↗咩↑咩↗咩↓咩→咩↗咩↑咩↓咩→咩↑咩↓咩↓咩↑咩→咩→咩↑咩↗咩→咩→咩↓咩→",
|
||||
"indexActivitiesHint": "咩↑咩↓咩↘咩→咩→咩↓咩↘咩→咩↘咩↗咩→咩↓",
|
||||
"userMenuDashboard": "咩↓咩↗咩↗",
|
||||
"userMenuSignOut": "咩↑咩→",
|
||||
"userMenuSignIn": "咩→咩→",
|
||||
"userMenuSignUp": "咩↑咩↘咩→咩↗",
|
||||
"next": "咩↑咩→咩↗",
|
||||
"errorOccurred": "咩→咩↑咩↘咩→咩→咩↓咩↓咩↑咩↘咩↘",
|
||||
"username": "咩→咩↓咩↑",
|
||||
"nickname": "咩↘咩→咩↘",
|
||||
"email": "咩↓咩↓咩↓咩↓",
|
||||
"password": "咩↘咩↓",
|
||||
"copyright": "咩↑咩↗咩↓咩↗",
|
||||
"signUpTitle": "咩↘咩→咩↗咩↗",
|
||||
"signUpCaption": "咩↑咩↓咩↘咩↓咩↑咩→咩↗咩↓咩↘咩↘咩↘咩↓咩↓咩↑咩↑咩↘咩↗咩↘咩↑咩↓咩↘咩↓咩↘咩→咩↗咩↗咩→咩↘咩↘咩↗咩↘咩→咩↑咩→咩↓",
|
||||
"signUpCompleted": "咩↓咩→咩↓咩↓咩↗咩↗咩→咩↗咩↑咩↗咩→咩→咩↘咩→咩→咩↗咩↑咩↘咩↓咩↓咩↓咩↑咩↗咩↓咩↓咩↑咩↗咩↘咩→咩↓咩↘咩↓咩↗咩↘咩↓咩↗咩↗咩↓咩↑",
|
||||
"signUpCompletedAction": "咩↘咩↗",
|
||||
"signInTitle": "咩↑咩↘",
|
||||
"signInCaption": "咩↗咩↘咩↑咩↑咩↘咩↑咩→咩↘咩→咩→咩↑咩↑咩↑咩↓咩↘咩↓咩↗咩→咩↘咩↓咩↓咩↓咩↑咩→咩↗咩↘咩↘咩→",
|
||||
"multiFactorCaption": "咩→咩→咩↓咩↓咩↓咩↘咩→咩↗咩↓咩↓咩↑咩↘咩↑咩↓咩↓咩↗咩→咩↗咩↓咩↑",
|
||||
"multiFactorHint": "咩↓咩→咩↓咩↑咩↑咩↑咩↑",
|
||||
"multiFactorTypeEmail": "咩↘咩→咩↑咩↓咩↑咩↘咩↓咩↗咩↑",
|
||||
"signInCompleted": "咩→咩↓",
|
||||
"signInCompletedCaption": "咩↑咩↘咩↗咩↗咩↑咩→咩↗咩↑咩↓咩↑咩↘咩↑咩↓咩↘咩↓咩↓咩↘咩↘咩↗咩↑咩↗咩→咩↘咩↗咩↑咩→咩↓",
|
||||
"transferredToSolianHint": "咩↑咩→咩↑咩↑咩→咩→咩↘咩→咩↓咩→咩↑咩↘咩↘咩↑咩↗咩↗咩↓咩→咩↑咩↓咩↓咩↗咩↗咩↑咩→咩↑咩↗咩↓咩→咩↑咩→咩↑咩↘咩↘咩↗咩↘咩↑咩↘咩↘咩↓咩↘咩↑咩↑咩↗咩↑咩↘咩→咩→咩↓咩↘咩↗咩↓咩↑咩↑咩↘",
|
||||
"personalize": "咩↓咩↑咩↑",
|
||||
"personalizeCaption": "咩↑咩↓咩↘咩↑咩↓咩↑咩→咩↑咩↘咩↓咩→咩↘咩↗咩→咩↑咩↗咩↑咩→咩↓咩→咩↗咩↓咩↘",
|
||||
"security": "咩↗咩↓",
|
||||
"securityCaption": "咩↓咩↗咩→咩→咩↘咩↑咩↘咩↗咩↓咩→咩↘咩→咩→咩↘咩→咩↓咩↗咩↗咩↑咩→咩↓咩↘",
|
||||
"userActivity": "咩↗咩↗",
|
||||
"userActivityCaption": "咩↑咩↑咩↗咩↓咩↑咩↗咩↗咩↗咩↓",
|
||||
"productArchived": "咩↘咩↓咩→",
|
||||
"callbackHint": "咩↑咩↘咩↓咩↓咩↓咩↓咩↗咩↑咩↘咩↗咩↗咩→咩↗咩↑咩↑咩↓咩↗咩↘咩↑咩↗咩↗咩↓咩↓咩↘咩↑咩↗咩↗咩→咩↓",
|
||||
"authorizeTitle": "咩↑咩↑咩→咩↑咩↘咩↗",
|
||||
"authorizeCaption": "咩↓咩↘咩↑咩↓咩↗咩↘咩↓咩↘咩↗咩↘咩↑咩↘咩↓咩→咩↓咩↑咩↗咩↘咩↘咩↗咩↗咩↘咩↓咩↑咩↘咩→咩↑咩↘",
|
||||
"authorizeErrorHint": "咩↑咩↓咩↘咩→咩↓咩↗咩↗咩↑咩↘咩↘咩↓咩↑咩→咩↘咩↘咩↗咩↓咩↘咩↗咩↓咩↓咩↗咩↗咩↑咩→咩→咩↓咩↘咩↑咩→",
|
||||
"authorizeRedirectHint": "咩↓咩↓咩→咩↑咩↓咩→咩→咩↑咩↓咩↗咩↘咩↗咩↑咩↗咩→咩↗咩↑",
|
||||
"authorizeCompleted": "咩↗咩→咩↘",
|
||||
"authorizeCompletedCaption": "咩↓咩↓咩↗咩↗咩↑咩↘咩↘咩↑咩↗咩↘咩↑咩→咩↓咩↑咩↘咩↗咩↗咩↑咩↘咩↗咩↘咩↘咩↑",
|
||||
"authorizeCompletedRedirect": "咩→咩↘咩↑咩↓咩↗咩↓咩→咩→咩↗咩↘咩→咩↑咩↘咩→咩↘咩↘咩↓咩↘咩→咩→咩↗咩↘咩↑咩↗咩→咩↗",
|
||||
"authorizeCompletedRedirectHint": "咩↑咩↑咩↑咩↘咩→咩↗",
|
||||
"decline": "咩↗咩↘",
|
||||
"approve": "咩→咩↓"
|
||||
}
|
@@ -1,17 +1,10 @@
|
||||
<template>
|
||||
<v-app-bar flat color="primary" scroll-behavior="hide" scroll-threshold="800">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center px-8">
|
||||
<v-tooltip>
|
||||
<template #activator="{ props }">
|
||||
<div @click="openDrawer = !openDrawer" v-bind="props" class="cursor-pointer">
|
||||
<v-img class="me-4 ms-1" width="32" height="32" alt="Logo" :src="Logo" />
|
||||
</div>
|
||||
</template>
|
||||
Open / close drawer
|
||||
</v-tooltip>
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" />
|
||||
|
||||
<nuxt-link to="/dev" exact>
|
||||
<h2 class="mt-1">Creator Hub</h2>
|
||||
<nuxt-link to="/creator" exact>
|
||||
<h2>Creator Hub</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
@@ -45,12 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const openDrawer = ref(false)
|
||||
|
||||
useHead({
|
||||
titleTemplate: "%s | Solsynth Creator Hub"
|
||||
titleTemplate: "%s | Solsynth Creator Hub",
|
||||
})
|
||||
</script>
|
||||
|
@@ -1,41 +1,38 @@
|
||||
<template>
|
||||
<v-app-bar flat color="primary">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center px-8">
|
||||
<v-tooltip>
|
||||
<template #activator="{ props }">
|
||||
<div @click="openDrawer = !openDrawer" v-bind="props" class="cursor-pointer">
|
||||
<v-img class="me-4 ms-1" width="32" height="32" alt="Logo" :src="Logo" />
|
||||
</div>
|
||||
</template>
|
||||
Open / close drawer
|
||||
</v-tooltip>
|
||||
|
||||
<v-app-bar app flat color="surface" class="app-bar-blur">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center pr-8">
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" />
|
||||
|
||||
<nuxt-link to="/" exact>
|
||||
<h2 class="mt-1">Solsynth LLC</h2>
|
||||
<h2>Solsynth LLC</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<v-btn to="/products" exact prepend-icon="mdi-shape">{{ t("navProducts") }}</v-btn>
|
||||
<v-btn to="/posts" exact prepend-icon="mdi-note-text">{{ t("navPosts") }}</v-btn>
|
||||
<v-btn to="/gallery" exact prepend-icon="mdi-image-multiple">{{ t("navGallery") }}</v-btn>
|
||||
</div>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<locale-select />
|
||||
<user-menu />
|
||||
</v-container>
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer v-model="openDrawer" location="left" width="300" floating>
|
||||
<v-navigation-drawer v-model="openDrawer" location="left" width="300" temporary order="-1">
|
||||
<v-list density="compact" nav color="primary">
|
||||
<v-list-item :title="t('navProducts')" prepend-icon="mdi-shape" to="/products" exact />
|
||||
<v-list-item :title="t('navPosts')" prepend-icon="mdi-note-text" to="/posts" exact />
|
||||
<v-list-item :title="t('navActivity')" prepend-icon="mdi-newspaper-variant-multiple-outline" to="/activity" exact />
|
||||
<v-list-item :title="t('navGallery')" prepend-icon="mdi-image-multiple" to="/gallery" exact />
|
||||
<v-list-item title="Knowledge Base" prepend-icon="mdi-library" to="/docs" exact />
|
||||
<v-list-item title="Developer Portal" prepend-icon="mdi-code-tags" to="/dev" exact />
|
||||
<v-list-item title="Creator Hub" prepend-icon="mdi-pencil" to="/creator" exact />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 my-1" />
|
||||
|
||||
<v-list density="compact" nav color="primary">
|
||||
<v-list-item title="Knowledge Base" prepend-icon="mdi-library" to="/docs" exact />
|
||||
<v-list-item title="Developer Portal" prepend-icon="mdi-code-tags" to="/dev" exact />
|
||||
<v-list-item title="Creator Hub" prepend-icon="mdi-pencil" to="/creator" exact />
|
||||
<v-list-item title="Code Repository" prepend-icon="mdi-git" href="https://git.solsynth.dev" target="_blank" />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 mb-4 mt-0.5" />
|
||||
@@ -51,9 +48,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n()
|
||||
|
||||
const openDrawer = ref(false)
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.app-bar-blur {
|
||||
-webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 40%, rgba(0, 0, 0, 0.5) 65%, rgba(0, 0, 0, 0) 100%);
|
||||
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 40%, rgba(0, 0, 0, 0.5) 65%, rgba(0, 0, 0, 0) 100%);
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,17 +1,10 @@
|
||||
<template>
|
||||
<v-app-bar flat color="primary" scroll-behavior="hide" scroll-threshold="800">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center px-8">
|
||||
<v-tooltip>
|
||||
<template #activator="{ props }">
|
||||
<div @click="openDrawer = !openDrawer" v-bind="props" class="cursor-pointer">
|
||||
<v-img class="me-4 ms-1" width="32" height="32" alt="Logo" :src="Logo" />
|
||||
</div>
|
||||
</template>
|
||||
Open / close drawer
|
||||
</v-tooltip>
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center pr-8">
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" />
|
||||
|
||||
<nuxt-link to="/dev" exact>
|
||||
<h2 class="mt-1">Developer Portal</h2>
|
||||
<h2>Developer Portal</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
@@ -51,6 +44,6 @@ const { t } = useI18n()
|
||||
const openDrawer = ref(false)
|
||||
|
||||
useHead({
|
||||
titleTemplate: "%s | Solsynth Dev Portal"
|
||||
titleTemplate: "%s | Solsynth Dev Portal",
|
||||
})
|
||||
</script>
|
||||
|
@@ -19,9 +19,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
posts: {
|
||||
includeAppSources: false,
|
||||
sources: [
|
||||
"/api/sitemap/posts",
|
||||
],
|
||||
sources: ["/api/sitemap/posts"],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -37,7 +35,6 @@ export default defineNuxtConfig({
|
||||
locales: [
|
||||
{ code: "en", name: "English", file: "en-US.json" },
|
||||
{ code: "zh-CN", name: "简体中文", file: "zh-CN.json" },
|
||||
{ code: "tb-SG", name: "音调羊文", file: "tb-SG.json" },
|
||||
],
|
||||
lazy: true,
|
||||
langDir: "lang",
|
||||
@@ -67,9 +64,7 @@ export default defineNuxtConfig({
|
||||
title: "Solsynth LLC",
|
||||
titleTemplate: "%s | Solsynth",
|
||||
meta: [],
|
||||
link: [
|
||||
{ rel: "icon", type: "image/png", href: "/icon.png" },
|
||||
],
|
||||
link: [{ rel: "icon", type: "image/png", href: "/icon.png" }],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -79,11 +74,62 @@ export default defineNuxtConfig({
|
||||
},
|
||||
highlight: {
|
||||
theme: { default: "github-light", dark: "github-dark" },
|
||||
langs: ["json", "yaml", "toml", "java", "javascript", "astro", "css", "scss", "dart", "go", "typescript", "c", "csharp",
|
||||
"cpp", "bat", "bash", "sh", "dockerfile", "erlang", "fsharp", "markdown", "log",
|
||||
"lua", "objc", "swift", "regex", "ruby", "rust", "postcss", "blade", "asciidoc", "cmake", "cobol", "pascal",
|
||||
"nginx", "angular-html", "angular-ts", "gdscript", "gdshader", "gdresource", "groovy", "gql", "python",
|
||||
"crystal", "sql", "plsql", "kotlin", "html", "vue", "gleam", "julia", "lisp", "xml", "csv"],
|
||||
langs: [
|
||||
"json",
|
||||
"yaml",
|
||||
"toml",
|
||||
"java",
|
||||
"javascript",
|
||||
"astro",
|
||||
"css",
|
||||
"scss",
|
||||
"dart",
|
||||
"go",
|
||||
"typescript",
|
||||
"c",
|
||||
"csharp",
|
||||
"cpp",
|
||||
"bat",
|
||||
"bash",
|
||||
"sh",
|
||||
"dockerfile",
|
||||
"erlang",
|
||||
"fsharp",
|
||||
"markdown",
|
||||
"log",
|
||||
"lua",
|
||||
"objc",
|
||||
"swift",
|
||||
"regex",
|
||||
"ruby",
|
||||
"rust",
|
||||
"postcss",
|
||||
"blade",
|
||||
"asciidoc",
|
||||
"cmake",
|
||||
"cobol",
|
||||
"pascal",
|
||||
"nginx",
|
||||
"angular-html",
|
||||
"angular-ts",
|
||||
"gdscript",
|
||||
"gdshader",
|
||||
"gdresource",
|
||||
"groovy",
|
||||
"gql",
|
||||
"python",
|
||||
"crystal",
|
||||
"sql",
|
||||
"plsql",
|
||||
"kotlin",
|
||||
"html",
|
||||
"vue",
|
||||
"gleam",
|
||||
"julia",
|
||||
"lisp",
|
||||
"xml",
|
||||
"csv",
|
||||
],
|
||||
},
|
||||
locales: ["en", "zh-CN"],
|
||||
defaultLocale: "en",
|
||||
@@ -105,7 +151,7 @@ export default defineNuxtConfig({
|
||||
"@pinia/nuxt",
|
||||
"@nuxtjs/i18n",
|
||||
"nuxt-schema-org",
|
||||
"nuxt-gtag",
|
||||
"@vueuse/motion/nuxt",
|
||||
(_options, nuxt) => {
|
||||
nuxt.hooks.hook("vite:extendConfig", (config) => {
|
||||
// @ts-expect-error
|
||||
|
25
package.json
25
package.json
@@ -11,29 +11,32 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@nuxt/content": "^2.13.2",
|
||||
"@nuxt/image": "^1.8.0",
|
||||
"@nuxtjs/i18n": "^8.5.5",
|
||||
"@nuxtjs/sitemap": "^6.1.1",
|
||||
"@pinia/nuxt": "^0.5.4",
|
||||
"@nuxt/content": "^2.13.4",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/kit": "^3.16.0",
|
||||
"@nuxtjs/i18n": "^8.5.6",
|
||||
"@nuxtjs/sitemap": "^6.1.5",
|
||||
"@pinia/nuxt": "^0.5.5",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"feed": "^4.2.2",
|
||||
"nuxt": "^3.13.2",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt-gtag": "^2.1.0",
|
||||
"nuxt-schema-org": "^3.4.0",
|
||||
"pinia": "^2.2.2",
|
||||
"nuxt-schema-org": "^3.5.0",
|
||||
"pinia": "^2.3.1",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"unhead": "1.9.0",
|
||||
"unified": "^11.0.5",
|
||||
"vue": "latest"
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/nuxt": "^0.61.9",
|
||||
"@unocss/preset-typography": "^0.61.9",
|
||||
"@unocss/reset": "^0.61.9",
|
||||
"vite-plugin-vuetify": "^2.0.4",
|
||||
"vuetify": "^3.7.2"
|
||||
"vite-plugin-vuetify": "^2.1.0",
|
||||
"vuetify": "^3.7.16"
|
||||
}
|
||||
}
|
||||
|
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<v-container class="content-container mx-auto">
|
||||
<div class="my-3 mx-[1.5ch]">
|
||||
<div class="flex gap-1">
|
||||
<h1 class="text-2xl">{{ t("navActivity") }}</h1>
|
||||
<v-btn size="x-small" variant="text" icon="mdi-rss" slim to="/activity/feed" />
|
||||
</div>
|
||||
<span>{{ t("navActivityCaption") }}</span>
|
||||
</div>
|
||||
|
||||
<post-list class="mx-[-2.5ch]" :realm="config.public.solarRealm" />
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
useHead({
|
||||
title: t("navActivity"),
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: t("navActivity"),
|
||||
ogTitle: t("navActivity"),
|
||||
description: t("navActivityCaption"),
|
||||
ogDescription: t("navActivityCaption"),
|
||||
ogType: "website",
|
||||
})
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-container {
|
||||
max-width: 70ch !important;
|
||||
}
|
||||
</style>
|
@@ -6,13 +6,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<v-btn
|
||||
color="primary"
|
||||
text="New"
|
||||
append-icon="mdi-plus"
|
||||
variant="tonal"
|
||||
to="/creator/stickers/new"
|
||||
/>
|
||||
<v-btn color="primary" text="New" append-icon="mdi-plus" variant="tonal" to="/creator/stickers/new" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,10 +18,7 @@
|
||||
|
||||
<div class="mt-5">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
v-for="item in data"
|
||||
:key="'sticker-pack#'+item.id"
|
||||
>
|
||||
<v-expansion-panel v-for="item in data" :key="'sticker-pack#' + item.id">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<p>{{ item.name }}</p>
|
||||
@@ -87,16 +78,17 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn
|
||||
text="Cancel"
|
||||
color="grey"
|
||||
@click="isActive.value = false"
|
||||
></v-btn>
|
||||
<v-btn text="Cancel" color="grey" @click="isActive.value = false"></v-btn>
|
||||
|
||||
<v-btn
|
||||
text="Delete"
|
||||
color="error"
|
||||
@click="() => { deletePack(item); isActive.value = false }"
|
||||
@click="
|
||||
() => {
|
||||
deletePack(item)
|
||||
isActive.value = false
|
||||
}
|
||||
"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -131,6 +123,7 @@ useHead({
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const ua = useUserinfo()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<null | string>(null)
|
||||
@@ -140,7 +133,7 @@ const data = ref<any[]>([])
|
||||
async function readPacks() {
|
||||
loading.value = true
|
||||
|
||||
const res = await solarFetch(`/cgi/uc/stickers/packs?take=10&offset=${data.value.length}`)
|
||||
const res = await solarFetch(`/cgi/uc/stickers/packs?take=10&author=${ua.userinfo?.id}&offset=${data.value.length}`)
|
||||
if (res.status != 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
|
@@ -1,39 +1,71 @@
|
||||
<template>
|
||||
<v-container class="flex flex-col my-2 px-12 gap-[4rem]">
|
||||
<v-row class="content-section">
|
||||
<v-col cols="12" md="4" class="flex justify-start">
|
||||
<div class="flex flex-col items-start">
|
||||
<h1 class="text-4xl font-bold">{{ t("brandName") }}</h1>
|
||||
<p class="text-lg mt-3 max-w-2/3">
|
||||
{{ t("indexIntroduce") }}
|
||||
</p>
|
||||
<p class="text-grey mt-2">
|
||||
{{ t("indexProductListHint") }}
|
||||
<v-icon icon="mdi-arrow-right" size="16" class="mb-0.5" />
|
||||
</p>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8">
|
||||
<v-card>
|
||||
<section class="content-section flex flex-col items-center justify-center text-center px-4">
|
||||
<img
|
||||
v-motion="{
|
||||
initial: {
|
||||
y: 100,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
}"
|
||||
:src="Logo"
|
||||
alt="Company Logo"
|
||||
class="w-32 h-32 mb-4"
|
||||
/>
|
||||
<h1 class="text-4xl font-bold">Welcome to {{ t("brandName") }}</h1>
|
||||
<p class="mt-2 text-lg">Building cool, open-source, and elegant apps for human.</p>
|
||||
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#products">{{ t("learnMore") }}</v-btn>
|
||||
</section>
|
||||
|
||||
<section class="content-section py-16" id="products">
|
||||
<div class="container mx-auto text-center">
|
||||
<h2 class="text-3xl font-bold">Our Projects</h2>
|
||||
<p>Take a peek of our works.</p>
|
||||
<v-card class="mt-12">
|
||||
<product-carousel class="carousel-section" :products="products as any[]" />
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<v-row class="content-section">
|
||||
<v-col cols="12" md="8">
|
||||
<v-card class="h-[500px]">
|
||||
<activity-list class="carousel-section" />
|
||||
<v-col cols="12" md="6">
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
title="GitHub"
|
||||
subtitle="The place hosts most of our public projects' code"
|
||||
prepend-icon="mdi-github"
|
||||
href="https://github.com/Solsynth"
|
||||
target="_blank"
|
||||
/>
|
||||
<v-list-item
|
||||
lines="two"
|
||||
title="Solsynth Code Repository"
|
||||
subtitle="Our self-hosted git server, may contains some unpublished projects' code"
|
||||
prepend-icon="mdi-git"
|
||||
href="https://git.solsynth.dev/explore"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" class="flex justify-end" order="first" order-md="last">
|
||||
<v-col cols="12" md="6" class="flex justify-end" order="first" order-md="last">
|
||||
<div class="text-right flex flex-col items-end">
|
||||
<h2 class="text-4xl font-bold">{{ t("indexActivities") }}</h2>
|
||||
<p class="text-lg mt-3 max-w-2/3">
|
||||
{{ t("indexActivitiesCaption") }}
|
||||
<h2 class="text-4xl font-bold">
|
||||
We<br />
|
||||
❤️ Open-source
|
||||
</h2>
|
||||
<p class="text-md mt-3 max-w-2/3">
|
||||
No software can run without the support of open source software, and our software is no exception.
|
||||
Therefore, we feel it is important to contribute to open source as well.
|
||||
</p>
|
||||
<p class="text-grey mt-2">
|
||||
<v-icon icon="mdi-arrow-left" size="16" class="mb-0.5" />
|
||||
{{ t("indexActivitiesHint") }}
|
||||
Check out our GitHub
|
||||
</p>
|
||||
</div>
|
||||
</v-col>
|
||||
@@ -42,6 +74,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
import { getLocale } from "~/utils/locale"
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -61,13 +95,16 @@ useSeoMeta({
|
||||
})
|
||||
|
||||
const { data: products } = await useAsyncData("products", () => {
|
||||
return queryContent("/products").where({ _locale: getLocale(), archived: { $ne: true } }).limit(5).find()
|
||||
return queryContent("/products")
|
||||
.where({ _locale: getLocale(), archived: { $ne: true } })
|
||||
.limit(5)
|
||||
.find()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.carousel-section {
|
||||
height: 96rem;
|
||||
height: 120rem;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
@@ -76,3 +113,10 @@ const { data: products } = await useAsyncData("products", () => {
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<v-container class="content-container mx-auto">
|
||||
<div class="my-3 flex flex-row gap-4">
|
||||
<nuxt-link :to="`/users/${post.publisher?.name}`">
|
||||
<v-avatar :image="post.publisher?.avatar" />
|
||||
<nuxt-link :to="`/publishers/${post.publisher?.name}`">
|
||||
<v-avatar :image="getAttachmentUrl(post.publisher?.avatar)" />
|
||||
</nuxt-link>
|
||||
<div class="flex flex-col">
|
||||
<span>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span>
|
||||
<span>
|
||||
{{ post.publisher?.nick }}
|
||||
<span class="text-xs">@{{ post.publisher?.name }}</span>
|
||||
</span>
|
||||
<span v-if="post.body?.title" class="text-md">{{ post.body?.title }}</span>
|
||||
<span v-if="post.body?.description" class="text-sm">{{ post.body?.description }}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">{{ post.publisher?.description }}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">{{
|
||||
post.publisher?.description
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,22 +25,18 @@
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
<article class="text-base prose xl:text-lg mx-auto">
|
||||
<article v-if="post.body?.content" class="text-base prose xl:text-lg mx-auto">
|
||||
<m-d-c :value="post.body?.content"></m-d-c>
|
||||
</article>
|
||||
|
||||
<v-card v-if="post.body?.attachments?.length > 0" class="mb-5">
|
||||
<attachment-carousel :attachments="post.body?.attachments" @update:metadata="args => attachments = args" />
|
||||
<attachment-carousel :attachments="post.body?.attachments" @update:metadata="(args) => (attachments = args)" />
|
||||
</v-card>
|
||||
|
||||
<div class="mb-3 text-sm flex flex-col">
|
||||
<span class="flex flex-row gap-1">
|
||||
<span>
|
||||
{{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }},
|
||||
</span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
<span> {{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }}, </span>
|
||||
<span> {{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }} </span>
|
||||
</span>
|
||||
<span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
@@ -43,10 +44,7 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="post.tags?.length > 0"
|
||||
class="text-xs text-grey flex flex-row gap-1 mb-3"
|
||||
>
|
||||
<div v-if="post.tags?.length > 0" class="text-xs text-grey flex flex-row gap-1 mb-3">
|
||||
<nuxt-link
|
||||
v-for="tag in post.tags"
|
||||
:to="`/posts/tags/${tag.alias}`"
|
||||
@@ -108,21 +106,29 @@ if (!post.value) {
|
||||
navigateTo(`/posts/${post.value.area_alias}/${post.value.alias}`)
|
||||
}
|
||||
|
||||
const title = computed(() => post.value.body?.title ? `${post.value.body?.title} by @${post.value.publisher.name}` : `Post by @${post.value.publisher.name}`)
|
||||
const title = computed(() =>
|
||||
post.value.body?.title
|
||||
? `${post.value.body?.title} by @${post.value.publisher.name}`
|
||||
: `Post by @${post.value.publisher.name}`,
|
||||
)
|
||||
const description = computed(() => post.value.body?.description ?? post.value.body?.content.substring(0, 280).trim())
|
||||
|
||||
watch(attachments, (value) => {
|
||||
if (post.value.body?.thumbnail) {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${post.value.body?.thumbnail}`
|
||||
}
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
watch(
|
||||
attachments,
|
||||
(value) => {
|
||||
if (post.value.body?.thumbnail) {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${post.value.body?.thumbnail}`
|
||||
}
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
useHead({
|
||||
title: title.value,
|
||||
|
73
pages/publishers/[name].vue
Normal file
73
pages/publishers/[name].vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<v-container class="mx-auto">
|
||||
<v-img v-if="urlOfBanner" :src="urlOfBanner" :aspect-ratio="16 / 5" class="rounded-md mb-3" cover />
|
||||
|
||||
<div class="mx-[2.5ch]">
|
||||
<div class="my-5 mx-4 flex flex-row gap-4">
|
||||
<v-avatar :image="urlOfAvatar" />
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
>{{ account?.nick }} <span class="text-xs">@{{ account?.name }}</span></span
|
||||
>
|
||||
<span class="text-sm">{{ account?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-7">
|
||||
<v-card rounded="xl" class="mx-[-5px]">
|
||||
<v-tabs v-model="tab" align-tabs="start" color="primary" hide-slider>
|
||||
<v-tab :value="1">{{ t("userActivity") }}</v-tab>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<v-row>
|
||||
<v-col row="12" lg="8">
|
||||
<post-list class="mx-[-2.5ch] mt-[-16px]" v-if="account" :author="account.name" />
|
||||
</v-col>
|
||||
<v-col row="12" lg="4" order="first" order-lg="last">
|
||||
<div class="sticky top-0 h-fit">
|
||||
<v-card prepend-icon="mdi-identifier" title="About">
|
||||
<v-card-text>
|
||||
<p><b>Description</b></p>
|
||||
<p>{{ account.description }}</p>
|
||||
<p class="mt-3"><b>Joined At</b></p>
|
||||
<p>{{ new Date(account.created_at).toLocaleString() }}</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
alias: ["/@:name(.*)*"],
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const tab = ref(1)
|
||||
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publisher/${route.params.name}`)
|
||||
|
||||
if (account.value == null) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "User Not Found",
|
||||
})
|
||||
}
|
||||
|
||||
const urlOfAvatar = computed(() =>
|
||||
account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0,
|
||||
)
|
||||
const urlOfBanner = computed(() =>
|
||||
account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0,
|
||||
)
|
||||
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/accounts/view/${route.params.name}`)
|
||||
</script>
|
@@ -3,11 +3,11 @@ import { ref } from "vue"
|
||||
import { solarFetch } from "~/utils/request"
|
||||
|
||||
export function useAtk() {
|
||||
return useCookie("__hydrogen_atk", { path: "/", maxAge: 31556952000 })
|
||||
return useCookie("solar_network_atk", { path: "/", maxAge: 31556952000 })
|
||||
}
|
||||
|
||||
export function useRtk() {
|
||||
return useCookie("__hydrogen_rtk", { path: "/", maxAge: 31556952000 })
|
||||
return useCookie("solar_network_rtk", { path: "/", maxAge: 31556952000 })
|
||||
}
|
||||
|
||||
export function useLoggedInState() {
|
||||
@@ -22,18 +22,23 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
let fetchCompleter: Completer<boolean> | null = null
|
||||
let refreshCompleter: Completer<string> | null = null
|
||||
|
||||
const lastRefreshedAt = ref<Date | null>(null)
|
||||
|
||||
function setTokenSet(atk: string, rtk: string) {
|
||||
lastRefreshedAt.value = new Date()
|
||||
useAtk().value = atk
|
||||
useRtk().value = rtk
|
||||
}
|
||||
|
||||
async function getAtk() {
|
||||
if (!useLoggedInState().value) return useAtk().value
|
||||
if (lastRefreshedAt.value != null && Math.floor(Math.abs(Date.now() - lastRefreshedAt.value.getTime()) / 60000) < 3) {
|
||||
return useAtk().value
|
||||
const atk = useAtk()
|
||||
if (!useLoggedInState().value) return atk.value
|
||||
|
||||
const parts = atk.value?.split(".") ?? []
|
||||
if (parts.length != 3) return atk.value
|
||||
|
||||
const payload = JSON.parse(atob(parts[1]))
|
||||
const exp: number = payload["exp"]
|
||||
|
||||
if (exp > Date.now() / 1000) {
|
||||
return atk.value
|
||||
}
|
||||
|
||||
if (refreshCompleter != null) {
|
||||
@@ -57,7 +62,7 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
throw new Error(err)
|
||||
} else {
|
||||
const out = await res.json()
|
||||
console.log("[PASSPORT] Access token has been refreshed now.")
|
||||
console.log("[Passport] Access token has been refreshed now.")
|
||||
setTokenSet(out["access_token"], out["refresh_token"])
|
||||
refreshCompleter.complete(out["access_token"])
|
||||
return out["access_token"]
|
||||
@@ -97,12 +102,12 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
fetchCompleter = null
|
||||
}
|
||||
|
||||
return { userinfo, lastRefreshedAt, isLoggedIn, isReady, fetchCompleter, setTokenSet, getAtk, readProfiles }
|
||||
return { userinfo, isLoggedIn, isReady, fetchCompleter, setTokenSet, getAtk, readProfiles }
|
||||
})
|
||||
|
||||
export class Completer<T> {
|
||||
public readonly promise: Promise<T>
|
||||
public complete: (value: (PromiseLike<T> | T)) => void
|
||||
public complete: (value: PromiseLike<T> | T) => void
|
||||
private reject: (reason?: any) => void
|
||||
|
||||
public constructor() {
|
||||
|
@@ -14,3 +14,13 @@ export async function solarFetch(input: string, init?: RequestInit) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getAttachmentUrl(identifier: string | undefined): string | undefined {
|
||||
if (identifier == null || identifier.length == 0) {
|
||||
return undefined
|
||||
}
|
||||
if (identifier.startsWith("http")) {
|
||||
return identifier
|
||||
}
|
||||
return `${useRuntimeConfig().public.solarNetworkApi}/cgi/uc/attachments/${identifier}`
|
||||
}
|
||||
|
Reference in New Issue
Block a user