Compare commits

...

18 Commits

Author SHA1 Message Date
4fab2183ce Rollback changes about Google AdSense 2025-03-25 21:33:08 +08:00
c6ccdd83a8 Google AdSense 2025-03-24 23:17:41 +08:00
d544eeccb6 🍱 Add two extra programs banner 2025-03-23 22:58:24 +08:00
51fd602cd0 🐛 Fix z index issue 2025-03-23 22:21:55 +08:00
65b46c0195 🐛 Trying to fix bug 2025-03-23 22:12:32 +08:00
abc3156149 🍱 Add developer program banner 2025-03-23 21:17:05 +08:00
991acbfb7b 🐛 Fix jwks request did not forward 2025-03-23 00:30:46 +08:00
63bcd3e58e 🐛 Fix compile error 2025-03-22 23:55:44 +08:00
266b14f169 🐛 Fix missing baseURL 2025-03-22 23:52:57 +08:00
2306ec893b 🐛 Yeah, bug fix again... 2025-03-22 23:49:56 +08:00
4e0ce9118d 🐛 Fix well known error 2025-03-22 23:43:15 +08:00
0276272b42 🐛 Fix OIDC bugs 2025-03-22 23:18:34 +08:00
41b887faf6 Account page support 2025-03-21 01:00:13 +08:00
b12d1deece 📝 Update Basic Law 2025-03-20 23:53:28 +08:00
f97310c01d 📱 Halfway to responsive 2025-03-20 22:07:26 +08:00
95e0d3fb29 💄 Home page background add supports for light mode 2025-03-20 21:42:39 +08:00
234043fece 📈 Add umami analyze 2025-03-20 21:38:49 +08:00
1739cd92b7 🐛 Add backward compatibility 2025-03-20 21:32:52 +08:00
18 changed files with 158 additions and 87 deletions

View File

@ -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>

View File

@ -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

View File

@ -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. 用户生成内容

View File

@ -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>

View 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", ""))
}
})

View File

@ -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: {

View File

@ -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",

View File

@ -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>

View File

@ -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()
}
}
}

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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",

View 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()
})

View File

@ -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
})