Compare commits
18 Commits
refactor/n
...
master
Author | SHA1 | Date | |
---|---|---|---|
4fab2183ce | |||
c6ccdd83a8 | |||
d544eeccb6 | |||
51fd602cd0 | |||
65b46c0195 | |||
abc3156149 | |||
991acbfb7b | |||
63bcd3e58e | |||
266b14f169 | |||
2306ec893b | |||
4e0ce9118d | |||
0276272b42 | |||
41b887faf6 | |||
b12d1deece | |||
f97310c01d | |||
95e0d3fb29 | |||
234043fece | |||
1739cd92b7 |
@ -22,8 +22,8 @@ const { t } = useI18n()
|
||||
const projects: { [id: string]: [string, string] } = {
|
||||
"solar-network": ["Solar Network", "https://solsynth.dev/products/solar-network"],
|
||||
"capital": ["Capital", "https://git.solsynth.dev/Goatworks/Capital"],
|
||||
"passport": ["Hydrogen.Passport", "https://git.solsynth.dev/Hydrogen/Passport"],
|
||||
"paperclip": ["Hydrogen.Paperclip", "https://git.solsynth.dev/Hydrogen/Paperclip"],
|
||||
"passport": ["HyperNet.Passport", "https://git.solsynth.dev/HyperNet/Passport"],
|
||||
"paperclip": ["HyperNet.Paperclip", "https://git.solsynth.dev/HyperNet/Paperclip"],
|
||||
"roadsign": ["RoadSign", "https://git.solsynth.dev/Goatworks/RoadSign"],
|
||||
}
|
||||
</script>
|
||||
|
@ -35,8 +35,9 @@ As used herein, account number, account, and Solarpass refer to the User's accou
|
||||
- Partial deactivation: Partial disabling of the user's rights, e.g. uploading of files, publishing of posts, etc.
|
||||
- Disablement: The user's entire account and all rights of Solsynth LLC to use other services are disabled. We also reserve the right to delete the relevant data.
|
||||
5. A natural person can register and own only one Solarpass account, and we reserve the right to take action against other sub-accounts of the same User for deletion of data.
|
||||
6. If a user opens a sub-account in any way during the penalty period in an attempt to evade the penalty, the sub-account shall be subject to deletion of data and the penalty shall be escalated or the time limit extended, as the case may be.
|
||||
7. Bot accounts opened through the Developer Portal are not considered sub-accounts. *For more information on the use of bot accounts, please refer to the Developer Rules (/terms/developer-rules).
|
||||
6. The transfer and sale of Solarpass accounts are strictly prohibited. If such behavior is discovered, measures will be taken to delete the relevant data immediately.
|
||||
7. If a user opens a sub-account in any way during the penalty period in an attempt to evade the penalty, the sub-account shall be subject to deletion of data and the penalty shall be escalated or the time limit extended, as the case may be.
|
||||
8. Bot accounts opened through the Developer Portal are not considered sub-accounts. *For more information on the use of bot accounts, please refer to the Developer Rules (/terms/developer-rules).
|
||||
|
||||
## 4. User Generated Content
|
||||
|
||||
|
@ -35,8 +35,9 @@ date: 2025-03-19T16:12:21.897Z
|
||||
- 部份停权:禁用用户的部份权利,例如上传文件、发布帖子等。
|
||||
- 禁用:禁用用户的整个帐号和所有 Solsynth LLC 使用其他服务的权利。同时我们保留删除相关数据的权利。
|
||||
5. 一个自然人只能注册、拥有一个 Solarpass 帐号,我们有权对其他同用户的子帐号采取删除数据的措施。
|
||||
6. 若用户在处罚期间采取任何方式开设子帐号试图逃避处罚,应当对子帐号采取删除数据的措施,并且视情况升级处罚或延长时限。
|
||||
7. 通过「开发者门户」开设的机器人帐号不属于子帐号范畴。*关于「机器人帐号」的使用规定,详见 [开发者守则](/terms/developer-rules)*
|
||||
6. 关于 Solarpass 帐号的转让、出售是绝对禁止的行为,关于发现相关行为将立即采取删除相关数据的措施。
|
||||
7. 若用户在处罚期间采取任何方式开设子帐号试图逃避处罚,应当对子帐号采取删除数据的措施,并且视情况升级处罚或延长时限。
|
||||
8. 通过「开发者门户」开设的机器人帐号不属于子帐号范畴。*关于「机器人帐号」的使用规定,详见 [开发者守则](/terms/developer-rules)*
|
||||
|
||||
## 4. 用户生成内容
|
||||
|
||||
|
@ -1,24 +1,35 @@
|
||||
<template>
|
||||
<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" />
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center pr-8 relative">
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" class="z-10" />
|
||||
|
||||
<nuxt-link to="/" exact>
|
||||
<h2>Solsynth LLC</h2>
|
||||
<nuxt-link to="/" exact class="z-10">
|
||||
<h2 v-if="isLargeScreen">Solsynth LLC</h2>
|
||||
<v-icon v-else icon="mdi-home" />
|
||||
</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 class="absolute left-0 right-0 flex justify-center gap-2 w-screen">
|
||||
<v-btn v-if="isLargeScreen" v-for="item in navItems" :to="item.to" exact :prepend-icon="item.icon">{{
|
||||
t(item.title)
|
||||
}}</v-btn>
|
||||
<v-menu location="bottom center" v-else>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-dots-horizontal-circle" slim size="small" />
|
||||
</template>
|
||||
<v-list nav slim class="w-[280px]">
|
||||
<v-list-item v-for="item in navItems" :to="item.to" :prepend-icon="item.icon">
|
||||
<v-list-item-title>{{ t(item.title) }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<locale-select />
|
||||
<user-menu />
|
||||
<locale-select class="z-10" />
|
||||
<user-menu class="z-10" />
|
||||
</v-container>
|
||||
</v-app-bar>
|
||||
|
||||
@ -47,9 +58,38 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBreakpoints, breakpointsVuetifyV3 } from "@vueuse/core"
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const openDrawer = ref(false)
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsVuetifyV3)
|
||||
const isLargeScreen = computed(() => breakpoints.isGreaterOrEqual("md").valueOf())
|
||||
|
||||
interface NavItem {
|
||||
icon: string
|
||||
title: string
|
||||
to: string
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
icon: "mdi-shape",
|
||||
title: "navProducts",
|
||||
to: "/products",
|
||||
},
|
||||
{
|
||||
icon: "mdi-note-text",
|
||||
title: "navPosts",
|
||||
to: "/posts",
|
||||
},
|
||||
{
|
||||
icon: "mdi-image-multiple",
|
||||
title: "navGallery",
|
||||
to: "/gallery",
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
6
middleware/redirectLocale.global.ts
Normal file
6
middleware/redirectLocale.global.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
// No further supported path prefix localization
|
||||
if (to.path.startsWith("/zh-CN")) {
|
||||
return navigateTo(to.fullPath.replace("/zh-CN", ""))
|
||||
}
|
||||
})
|
@ -53,8 +53,8 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
"/.well-known/openid-configuration": {
|
||||
proxy: "/api/well-known/openid-configuration",
|
||||
"/.well-known/**": {
|
||||
proxy: "/api/well-known/**",
|
||||
},
|
||||
},
|
||||
|
||||
@ -143,6 +143,12 @@ export default defineNuxtConfig({
|
||||
transpile: ["vuetify"],
|
||||
},
|
||||
|
||||
umami: {
|
||||
id: "eef151fb-07e2-461b-8b7f-2547aab735d4",
|
||||
host: "https://us.umami.is",
|
||||
autoTrack: true,
|
||||
},
|
||||
|
||||
modules: [
|
||||
"@unocss/nuxt",
|
||||
"@nuxt/content",
|
||||
@ -152,6 +158,7 @@ export default defineNuxtConfig({
|
||||
"@nuxtjs/i18n",
|
||||
"nuxt-schema-org",
|
||||
"@vueuse/motion/nuxt",
|
||||
"nuxt-umami",
|
||||
(_options, nuxt) => {
|
||||
nuxt.hooks.hook("vite:extendConfig", (config) => {
|
||||
// @ts-expect-error
|
||||
@ -160,10 +167,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
],
|
||||
|
||||
gtag: {
|
||||
id: "G-ZFJ7RX0JXF",
|
||||
},
|
||||
|
||||
vite: {
|
||||
vue: {
|
||||
template: {
|
||||
|
@ -18,11 +18,13 @@
|
||||
"@nuxtjs/sitemap": "^6.1.5",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@pinia/nuxt": "^0.5.5",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"feed": "^4.2.2",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt-gtag": "^2.1.0",
|
||||
"nuxt-schema-org": "^3.5.0",
|
||||
"nuxt-umami": "3.2.0",
|
||||
"pinia": "^2.3.1",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
|
@ -46,8 +46,8 @@
|
||||
<span>Solar Network Attachment Web Preview</span>
|
||||
<span
|
||||
>Powered by
|
||||
<a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip"
|
||||
>Hydrogen.Paperclip</a
|
||||
<a class="underline" target="_blank" href="https://git.solsynth.dev/HyperNet/Paperclip"
|
||||
>HyperNet.Paperclip</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8 }
|
||||
transition: { duration: 0.8 },
|
||||
},
|
||||
}"
|
||||
:src="Logo"
|
||||
@ -107,11 +107,13 @@ const { data: products } = await useAsyncData("products", () => {
|
||||
const canvasRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
const isDarkMode = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
|
||||
const canvas: HTMLCanvasElement = canvasRef.value!
|
||||
const ctx = canvas.getContext("2d")!
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = window.innerWidth * dpr;
|
||||
canvas.height = window.innerHeight * dpr;
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
canvas.width = window.innerWidth * dpr
|
||||
canvas.height = window.innerHeight * dpr
|
||||
|
||||
let particles: Particle[] = []
|
||||
const numParticles = 100
|
||||
@ -139,10 +141,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x * dpr, this.y * dpr, this.size * dpr, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
||||
ctx.fill();
|
||||
ctx.beginPath()
|
||||
ctx.arc(this.x * dpr, this.y * dpr, this.size * dpr, 0, Math.PI * 2)
|
||||
ctx.fillStyle = isDarkMode ? "rgba(255, 255, 255, 0.8)" : "rgba(0, 0, 0, 0.8)"
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,17 +158,17 @@ onMounted(() => {
|
||||
function drawLines() {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
let dx = particles[i].x - particles[j].x;
|
||||
let dy = particles[i].y - particles[j].y;
|
||||
let distance = Math.sqrt(dx * dx + dy * dy);
|
||||
let dx = particles[i].x - particles[j].x
|
||||
let dy = particles[i].y - particles[j].y
|
||||
let distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x * dpr, particles[i].y * dpr);
|
||||
ctx.lineTo(particles[j].x * dpr, particles[j].y * dpr);
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
||||
ctx.lineWidth = 0.5 * dpr;
|
||||
ctx.stroke();
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(particles[i].x * dpr, particles[i].y * dpr)
|
||||
ctx.lineTo(particles[j].x * dpr, particles[j].y * dpr)
|
||||
ctx.strokeStyle = isDarkMode ? "rgba(255, 255, 255, 0.2)" : "rgba(0, 0, 0, 0.2)"
|
||||
ctx.lineWidth = 0.5 * dpr
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,25 +6,34 @@
|
||||
<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>
|
||||
<span
|
||||
>{{ account?.nick }} <span class="text-xs">@{{ account?.name }}</span></span
|
||||
>
|
||||
<p>
|
||||
{{ accountStatus.status ? accountStatus.status.label : accountStatus["is_online"] ? "Online" : "Offline" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-7">
|
||||
|
||||
</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 cols="12" lg="8">
|
||||
<v-card>
|
||||
<v-card-text v-if="accountPageStatus.valueOf() === 'success'">
|
||||
<div class="prose prose-sm" style="max-width: unset">
|
||||
<m-d-c :value="accountPage.content" />
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text v-else>
|
||||
<p class="font-italic">The user has no account page.</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col row="12" lg="4" order="first" order-lg="last">
|
||||
<v-col cols="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>{{ account?.profile.description }}</p>
|
||||
<p class="mt-3"><b>Joined At</b></p>
|
||||
<p>{{ new Date(account.created_at).toLocaleString() }}</p>
|
||||
</v-card-text>
|
||||
@ -41,8 +50,6 @@ const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const tab = ref(1)
|
||||
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}`)
|
||||
|
||||
if (account.value == null) {
|
||||
@ -52,6 +59,17 @@ if (account.value == null) {
|
||||
})
|
||||
}
|
||||
|
||||
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 { data: accountPage, status: accountPageStatus } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}/page`,
|
||||
)
|
||||
const { data: accountStatus, status: accountStatusStatus } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}/status`,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
</script>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<v-avatar :image="urlOfAvatar" />
|
||||
<div class="flex flex-col">
|
||||
<span>{{ auth.userinfo?.nick }} <span class="text-xs">@{{ auth.userinfo?.name }}</span></span>
|
||||
<span class="text-sm">{{ auth.userinfo?.description }}</span>
|
||||
<span class="text-sm">{{ auth.userinfo?.profile?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card class="w-28 aspect-square" to="/docs">
|
||||
<v-card class="w-28 aspect-square" href="https://kb.solsynth.dev" target="_blank">
|
||||
<v-card-text class="flex flex-col justify-center items-center text-center h-full">
|
||||
<v-icon icon="mdi-library" size="32" />
|
||||
<span class="text-sm mt-1.75">Knowledge Base</span>
|
||||
|
BIN
public/bento/programs/developer-banner.webp
Executable file
BIN
public/bento/programs/developer-banner.webp
Executable file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
public/bento/programs/moderator-banner.webp
Executable file
BIN
public/bento/programs/moderator-banner.webp
Executable file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
public/bento/programs/stellar-banner.webp
Executable file
BIN
public/bento/programs/stellar-banner.webp
Executable file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -5,7 +5,7 @@ export default defineSitemapEventHandler(async () => {
|
||||
const result = await res.json()
|
||||
|
||||
return result.data.map((item: any) => asSitemapUrl({
|
||||
loc: item.alias ? `/posts/${item.area_alias}/${item.alias}` : `/posts/${item.id}`,
|
||||
loc: item.alias ? `/posts/${item.alias_prefix}/${item.alias}` : `/posts/${item.id}`,
|
||||
lastmod: item.edited_at ?? item.published_at,
|
||||
priority: 0.7,
|
||||
_sitemap: "posts",
|
||||
|
9
server/api/well-known/jwks.ts
Normal file
9
server/api/well-known/jwks.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineEventHandler } from 'h3'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const resp = await fetch(`${config.public.solarNetworkApi}/cgi/id/well-known/jwks`)
|
||||
|
||||
return await resp.json()
|
||||
})
|
@ -1,31 +1,20 @@
|
||||
export default defineEventHandler((event) => {
|
||||
const config = useRuntimeConfig()
|
||||
import { defineEventHandler } from 'h3'
|
||||
|
||||
return {
|
||||
"authorization_endpoint": `${config.public.siteUrl}/auth/authorize`,
|
||||
"grant_types_supported": [
|
||||
"authorization_code",
|
||||
"implicit",
|
||||
"refresh_token",
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"HS512",
|
||||
],
|
||||
"issuer": config.public.siteUrl,
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"token",
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public",
|
||||
],
|
||||
"token_endpoint": `${config.public.solarNetworkApi}/cgi/id/auth/token`,
|
||||
"token_endpoint_auth_methods_supported": [
|
||||
"client_secret_post",
|
||||
],
|
||||
"token_endpoint_auth_signing_alg_values_supported": [
|
||||
"HS512",
|
||||
],
|
||||
"userinfo_endpoint": `${config.public.solarNetworkApi}/cgi/id/users/me`,
|
||||
export default defineEventHandler(async () => {
|
||||
const config = useRuntimeConfig();
|
||||
const siteUrl = config.public.siteUrl
|
||||
|
||||
const resp = await fetch(`${config.public.solarNetworkApi}/cgi/id/well-known/openid-configuration`)
|
||||
const out: Record<string, any> = await resp.json()
|
||||
|
||||
out['authorization_endpoint'] = `${siteUrl}/auth/authorize`
|
||||
out['jwks_uri'] = `${siteUrl}/.well-known/jwks`
|
||||
|
||||
for (const [k, v] of Object.entries(out)) {
|
||||
if (typeof v === 'string' && v.startsWith('https://id.solsynth.dev/api')) {
|
||||
out[k] = v.replace('https://id.solsynth.dev/api', `${config.public.solarNetworkApi}/cgi/id`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return out
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user