Compare commits
No commits in common. "master" and "archive/nextjs" have entirely different histories.
master
...
archive/ne
4
.env
4
.env
@ -1,4 +0,0 @@
|
||||
NUXT_PUBLIC_SOLAR_REALM=solar-network
|
||||
NUXT_PUBLIC_SITE_URL=https://solsynth.dev
|
||||
NUXT_PUBLIC_SOLAR_NETWORK_API=https://api.sn.solsynth.dev
|
||||
NUXT_PUBLIC_SOLIAN_URL=https://sn.solsynth.dev
|
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
56
.gitignore
vendored
56
.gitignore
vendored
@ -1,21 +1,41 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
*.lockb
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
bun.lockb
|
||||
*.lock
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
.env
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
12
.idea/Capital.iml
generated
Normal file
12
.idea/Capital.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
57
.idea/codeStyles/Project.xml
generated
Normal file
57
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,57 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<VueCodeStyleSettings>
|
||||
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||
</VueCodeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Vue">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Capital.iml" filepath="$PROJECT_DIR$/.idea/Capital.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"printWidth": 120
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all"
|
||||
}
|
16
.roadsignrc
16
.roadsignrc
@ -1,16 +0,0 @@
|
||||
{
|
||||
"sync": {
|
||||
"region": "capital",
|
||||
"configPath": "roadsign.toml"
|
||||
},
|
||||
"deployments": [
|
||||
{
|
||||
"region": "capital",
|
||||
"site": "capital-app",
|
||||
"path": ".output",
|
||||
"postDeploy": {
|
||||
"command": "apk add nodejs npm; cd server && npm install --platform=linux --arch=x64 sharp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
36
README.md
Normal file
36
README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
48
app.vue
48
app.vue
@ -1,48 +0,0 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<nuxt-loading-indicator color="white" />
|
||||
<nuxt-layout>
|
||||
<nuxt-page />
|
||||
</nuxt-layout>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from "vuetify"
|
||||
import "@unocss/reset/tailwind.css"
|
||||
|
||||
const theme = useTheme()
|
||||
const auth = useUserinfo()
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
watch(locale, (value) => {
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang: value,
|
||||
},
|
||||
})
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
theme.global.name.value = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", event => {
|
||||
theme.global.name.value = event.matches ? "dark" : "light"
|
||||
})
|
||||
|
||||
auth.readProfiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
.page-enter-from,
|
||||
.page-leave-to {
|
||||
opacity: 0;
|
||||
filter: blur(1rem);
|
||||
}
|
||||
</style>
|
17
app/console/[[...index]]/page.tsx
Normal file
17
app/console/[[...index]]/page.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* This route is responsible for the built-in authoring environment using Sanity Studio.
|
||||
* All routes under your studio path is handled by this file using Next.js' catch-all routes:
|
||||
* https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
|
||||
*
|
||||
* You can learn more about the next-sanity package here:
|
||||
* https://github.com/sanity-io/next-sanity
|
||||
*/
|
||||
|
||||
import { NextStudio } from 'next-sanity/studio'
|
||||
import config from '../../../sanity.config'
|
||||
|
||||
export default function StudioPage() {
|
||||
return <NextStudio config={config} />
|
||||
}
|
31
app/consts.tsx
Normal file
31
app/consts.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { ReactNode } from "react";
|
||||
import GitHubIcon from "@mui/icons-material/GitHub";
|
||||
import TwitterIcon from "@mui/icons-material/Twitter";
|
||||
import CoffeeIcon from "@mui/icons-material/Coffee";
|
||||
|
||||
export interface RelatedAccount {
|
||||
icon: ReactNode;
|
||||
platform: string;
|
||||
accountName: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const SITE_NAME = "Goatshed";
|
||||
export const SITE_DESCRIPTION = "山羊寒舍,在这里最终智羊工作室的最新动态。";
|
||||
export const SITE_URL = "https://smartsheep.studio";
|
||||
|
||||
export const RELATED_ACCOUNTS: RelatedAccount[] = [
|
||||
{ icon: <GitHubIcon />, platform: "GitHub", accountName: "@smartsheep-hq", link: "https://github.com/smartsheep-hq" },
|
||||
{
|
||||
icon: <TwitterIcon />,
|
||||
platform: "Twitter",
|
||||
accountName: "@littlesheepovo",
|
||||
link: "https://twitter.com/littlesheepovo",
|
||||
},
|
||||
{
|
||||
icon: <CoffeeIcon />,
|
||||
platform: "Ko-fi",
|
||||
accountName: "@littlesheep2code",
|
||||
link: "https://ko-fi.com/littlesheep2code",
|
||||
},
|
||||
];
|
36
app/feed/route.ts
Normal file
36
app/feed/route.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Feed } from "feed";
|
||||
import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/app/consts";
|
||||
import { client } from "@/sanity/lib/client";
|
||||
|
||||
export async function GET() {
|
||||
const feed = new Feed({
|
||||
title: SITE_NAME,
|
||||
description: SITE_DESCRIPTION,
|
||||
id: SITE_URL,
|
||||
link: SITE_URL,
|
||||
favicon: `${SITE_URL}/favicon.png`,
|
||||
feedLinks: { atom: `${SITE_URL}/feed` },
|
||||
language: "zh-CN",
|
||||
copyright: `Copyright © ${new Date().getFullYear()} SmartSheep Studio`,
|
||||
});
|
||||
|
||||
const posts = await client.fetch<any[]>(`*[_type == "post"] {
|
||||
title, description, slug, publishedAt,
|
||||
}`);
|
||||
|
||||
posts.forEach((item) => {
|
||||
feed.addItem({
|
||||
id: `${SITE_URL}/p/${item.slug.current}`,
|
||||
link: `${SITE_URL}/p/${item.slug.current}`,
|
||||
title: item.title,
|
||||
description: item.description ?? "No description yet.",
|
||||
date: new Date(item.publishedAt)
|
||||
});
|
||||
});
|
||||
|
||||
return new Response(feed.rss2(), {
|
||||
headers: {
|
||||
"content-type": "application/xml"
|
||||
}
|
||||
});
|
||||
}
|
7
app/globals.css
Normal file
7
app/globals.css
Normal file
@ -0,0 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.zoomist-wrapper {
|
||||
background: transparent !important;
|
||||
}
|
21
app/icon.svg
Executable file
21
app/icon.svg
Executable file
@ -0,0 +1,21 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
||||
<title>SmartSheep Logo</title>
|
||||
<defs>
|
||||
<image width="124" height="198" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHwAAADGCAMAAAAnkRSfAAAAAXNSR0IB2cksfwAAAq9QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////J9QBRAAAAOV0Uk5TAA0WEiFfjMLMr40/BRdmtuz//vbQYAQmpfLdQAGovyURj/iOD0r07loDGJfbPDTf/bhd94QLdPrrVQLWcPmtHU31egknzehRg9Ev1xnzbgYHhfzmTMorSOSbFJloKsniReXDKG/7kQ6m8WMw3ue7I4cKNdTZOFTqsR5/Hzra0zZY7al4Ir5SPWHvnpbG4MRrlKJs4UfjvKos3Eu1DICGCBq0XjHV6YqCG7lZM89cEJJ72EQ5WztxJC1OfGl+T315dnVzkHKVbZ9no2ViwFZDNzIuKSAcFfBGy4tQd7Onk0K3PqxTmt0ZReEAAAZZSURBVHic7dzpX1RVGAfwyyKMyHYuyDqgjLKICjcIBodNhEoJFAY0wCBEJyTCBWUx0WRJU1wqlKIwTLDEUoMiyFJssWyjtBAr2/1DGvywzzPDeXGehzf8/oHv594z985znnPOlaTRWFlbW9tIMxLbOXb2KtVch3mO5LSTs4srG47s5j7fg9b29PJmY5F9fNWEtrUfmxTZfwGZvTBAw6ZEs2gxER4YNNU2XnxwyBIKO3SpqT3ML1tOgIeFgzhjykO26HiEtxmcsciHo5DxaK1ZnLGYFbqZwxmLDcPE4+It4kxJSMTDVwJP2uQkrUrGwlMemQ5n7NHHkIY+avX0OGNrUnH01Md5dK1XGooe58ajs/S16xDwjEw9l86yshH+bNUOlp/1scjrN4jXpSdy+HSmz80Tr1tvnOZdMxbvJxHqrPwChZN/qlC8rg7cxKmzos3i+XVbpn3VjsTwdLF4Pm2rgZMveQahzip1lzn5Z33F67rsMk5d2bZdPJ+8info43fsFM8nzuV84bLgCIQ/2/JdnEMv764Qr+sqqzh5pXqPeN4qM5Lz3kc+lyGe3+vCPfQhCH+2Nft4n/r9z4vXdQdqOXl9Xah4HphBm4lbPUJPpeEF3qGPOSheVx/y4X3qVaXieccXD3NevOEIQj8tr5G3zkqPQ6izPI/yPnZZ2eJ1te8xTl05ni+ezzhRwslrX7ISzzu9zDm/YN6vINRZ+et5h76pULyuPnmK96lvxqizotM5Lz7eDqGflfYqd4ntjDD0r63h1FnLPPG6+nXe2ZXyBkKd1Xo6iZPXvtkmnj/zFu/M9vBZhDqrvYNTZx014nld5TlOXUlAqLMWv83XzWJM8w5CiX2+k7vOuoAwu3o3lnd6816Y+KH3uOjKefF6L4R+1qXLvCV20vsYsyt/zqde7uoWr0sfLOO8+KAPEXRHZ84SOx5hemGcXeXy1VlJGNdunF0d53rs3J1QdKknnIPXX8TBpbaPzK8UjqUICZckm+lbmUGIuwR6C6a593IfHi6pu5ss6x8j4pK084rFVuYnqLhxYp1g4Y17Ghk3TqxVZof+U3RcWnLVXIl9DR83ltiZcJ11hQI3TqxbILyfBpfsILyeCHeA8OtE+CII/4wID4Dwz4nwLyD8SyL8MoTHEeE3IHwLEd4I4V8R4Rsh/AQRngvh2H+po/kawm8S4d9A+LdEeB2Ef0eEJ0D4fCK8GsLnEOEuEB5BhDdDOM5U0TTfQ3gIEV4E4QeI8KMQ/gMRXgDhlUS4CsJROiNAwJWBq0T4fgjHnaWOJxbCERY9wewGbDmQCIeag/JJItwHwjF6oFCgBok8QIUDHQIZYUsbmBwAVw4R4VnQbe8hwqH9fXINEQ4tfyk/EuEx0Jgj7KYC4wr92suJ8GBozH8iwqHFD307EQ7tcFFuzSCux9grD0QN4rdpcB20u8TgSYN7QH13Qy8NngFtrtA20ODJUNvZ8DMNHgVt39X+QoMXQ3g8zpEgk9hAS+vxCPs3oAyC+CAN7gThGqJj16HQsrbmEg1+HsLdWmnwRBBHOA8BZS+0iScJYacYlAZoPTud6Jz9dhAnOWotSSnQbQ8iOmOfD60ll9DY0oaZxPug236HCC+FfnC1RHg7dNup8DAIjyHCK6CtC1T48pnEhyC8ighfDj1q4UR4KlTJ3CXCbaGGkBcRLvmb2soFKvym6X1vQjhSDKfNdKnhV9zPZ0xMxNSpYixVX8KYtoDJNWTVEJ0tSbqAiae+an+jtI2pbxnh5aBmqoWlsXjY/H73lEbj1nTvIMLhdY409Pb2LpwReTazmc1sZjMbsdm8IsDPb2v/ENIhEktJi1ZphmerSvA9sqp5NIkTPhPk/QdR63MkNV2TZgx1RB3AB0nbN2W+4keI/zl1khxJtTNKkspNV+87EU4Lwrlh2h0IQjgfDSZvl4nN5NVE+G1oRXEbEV4BNQE7iPBCqCGUQ4SvhPBzRPhfEF5GhA9AeBYRDl45Fd4NNX43EeEDEN5EhPdA7c8uIrwP2jlgT4TvgdqfR4jw4r9NbQPVSRIp2vTlXkbWBUwx3eycS9eSuj51ETv4HzJbOtM5+SXndo2yfL1lP1GPDMD4dqz55P873ng+toNop8hYBs9W33lA+ywtJP5A/XAW/Nd//37j2gFreno4Ua2treOj/T+0HjP//7ac7AAAAABJRU5ErkJggg=="/>
|
||||
<image width="122" height="142" id="img2" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHoAAACOCAMAAADJhOzZAAAAAXNSR0IB2cksfwAAAppQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1tr9yQAAAN50Uk5TAAkMHUtzmZp0UaTn/6ZSIOrrnRqo/P2qC/qf9PZTG7O4UPL1VASO/psHwdHc7BXo+CIF+y0BOuNI1lfGarGEl6V7vJxi10oGMRQcKrcK7k7zAtt9u6uR02Eh6TZB7+08zbU/4kBjET4vMxBvON+yMAOtJQhr+fcPiSvhWTXVHoPD0q6BwHB+ualmTJ4oH/BJRN11vxdnIzQZ5ZCjiMTUis7eVn+TGEPI5mh85Cd6bNqVXiRa2YLgR1wTW4sWN24OmL1yMk9gDSmhlstk8cKNZbCicXaFhml5vl/JXaynVit8GwAABu9JREFUeJztm/tDk1UYx88GCbLDZcDmGIoKjImATubENAhNJW9cLDU10kQhVGhqYGYMxUzNrLRMtMRLoRheyGtWhlqZl+x+Mav/pSkou3y3Pefddvyl7+/f97Nz3rPnPOd5zstYCKRSq9WqUDxIVBGRj/TrFxUdGSEbrOofo+F3pYmNkzvy+KgEfl/axHiJ5KRkHe+TfkCSNLIhKoW7Smc0yELHpXJ3DRwkiZw2mHtqSJoc9NB0L3RGphSyKcvshebDTDLQ2cO9yTxHLQWdC9B5I6SgRwK0ZZQMdL4WoK2jZaBtCQBtHiMDzQoQeqwU9KMAzcdJQY9H6MfEn6Mg1ShE6CJRcERktHiq8ThCG8XAqv6xvalGsUiqMQGhJwqR45/o+4dqh9FTjUkIPVmEnDRA72LVJ5NTjSkInVBCJxuMVjdvShQ11XgS7FzcMpWOHpThYU6NIzqnWQBaN51MTpvh5R5MTDVmlgK0uYyMzvTMrzgvH0qzVsxCL/spKtk0DPzwLGKq8TRCz6ai1TnAPTybZp6D0HOp6BFo080lop9BS7yUGpTmoVU6n4heoAfmFGowHm0F7tJ8mjkpBZjNzxLRC3TAXWmjmbOfA2Y+gYheiF5XHtFsW4TQzxPdixFaSzSzRISuIs7ZEoROoKKXwiheTTPXoEVKHvULyK2vpZkXBoVeNhDN+HKaeQVCl1LRFXUIXU8zv4jQ6XYqeyVCF9C8q1BIyVhNRb+E0CnLSN4khG4grlHGatES5zUkb3UjsFqnUdFrkJ2/TPKqyoFVv5aKtuUidA7Ja0dhWE8/LsYidCopsbS9AqzmKWT0OvSy9a+SvE3oZ9OPiw608/Fmknc9stKPi3aUlfINpB2kBVk3ktHsNeS3rKFY4XExkY7ehPycVFt4HTk309HTUSTmWyjWrchJTmidSVIlekAp5dD3BnJqBI6L8GWbFxCccOtqFDguwh2EbyM401BCq3uTjt6ONiCeR5i3mW+h+XqbjmZVcMbfCWys2IGcOwXQ/eCMv0twotIyTxZA18JY2vheYGcUMjYJoO2oQkwqoO1CvvSZAmwjRFcFTvBWIJ/ufQH0bhjQrIHT8dYGZBRpn5j2wGHvDWgsQWVt/oEAmn0I0fsqAhphgbdOBN0Ko0pba0DjfuQj5tK9OoAecTAweiycrkMi6DiUoTUFRreiKM73iaCzUTGoPjDahHoY3EI+/NzVR97DNn9M8G2BMy7USWjXePkPUpKkTIjWEMtI92SL9hp2DMU/AtXOuDlSAM2m1nvYZx0m/eTJcNg7RIbNHEfczB1HaUXDTyCaLxVBs4j1LqG8cxexjOSAYZxXijWfjh1v6zm2WusKy6gzVjEXDztGCM1KVp0oPHDyZGFXmYFcEWGfwuIA15HP2fdlOOWUkAPn8c6VJhTJlQmevDi91BqEHLD25gxJQuFUmYoxuuF0+NHz8P+Lrww/GteXnZm8hGtkUz2bfD06I+MG29mHh1597mFNOGPn0dkpSmj7UqoKEFf2fCaDzFi+90l5xgU5aFbrWautXCeJzNjn7uzyL76UhmYXO93IEjauPi2endGT55hTq77qlkl2RrUpl5o687QbLl85Ju1mZp8MXzslH/u/Qi1Vd3e3SHkoZHL0P1scU3zlm+2ywepvL/dcgNFsO3RVKvni+L76QZ5RyoXPXn3nVlC0XiN3D4OWw6P6bOmScr3XqVFeZ5tK+h2poGTr8E5BrkuZ8qtt3mTesUQC2XYDkLn1poScbyIic/0cgZ6SQiVDMjeHH+2DzPnecKNhc+PehG+ilzyUyBaL6yROfU+7SaFUqlu+wJxfDmt+b8KFwHvShfVQU4u6hve1KIxfh5QUwb5br8oVvWnaDfb2Gz4XmFN66s1gV0WU7RwwZMi147V+w2B+M+wrPNAP4mBV5paexpTZsmOjz5al6cc8v2D+kzg5/pLrzR1r2wlEb98I22YuGk6sGbso6WfPlaM7t3eMS4pnW33sl0X+p9qpHHGyYQ6srDUWTOz69ejNm8v3158ph71Nd9WJk1lkoIkkqYPQ1fZUGu7NC0qrYMysBt3QElWlwO2LBzIthV0hMSUoGTPL/81feKJJo6xUkP87YfX6Vy75rqQHuihY9C1Fs+2UfVLAWOFX5kTiRXugi7nBkFNErth46oKvxgxF84MrL9fgaxgUbW4PisxYlsK3nfpHkGDGqrcpWeTmkOSeaqP4uEeOC00CaDoNvyfyrdT9oavYtP9ZQJ/1gddDW9K2H77t/RUWkFlbpDBw+lP3X0dwT6xPDXf+DlehRh3ZMt9Xjq9PvxPtCBO3R/ZTW1vqMnRue6m+sfN280IpBXx79drzW892xcxu+edfY1b0oN2tApvEf5wd39VVwSN7AAAAAElFTkSuQmCC"/>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: #ffffff;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }
|
||||
.s1 { fill: #4750a3;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }
|
||||
</style>
|
||||
<path id="Wool" fill-rule="evenodd" class="s0" d="m128 608.4c0 95.9 77.4 173.6 172.8 173.6h441.6c84.8 0 153.6-69.1 153.6-154.3 0-74.6-52.8-136.9-122.9-151.1 4.9-12.9 7.7-27 7.7-41.7 0-63.9-51.6-115.8-115.2-115.8-23.6 0-45.7 7.3-64 19.6-33.2-57.9-95.2-96.7-166.4-96.7-106.1 0-192 86.3-192 192.9 0 3.2 0.1 6.5 0.2 9.7-67.2 23.8-115.4 88.1-115.4 163.8z"/>
|
||||
<g id="Crystal">
|
||||
<path id="Crystal" class="s1" d="m699 224l138.6 80v160l-138.6 80-138.6-80v-160z"/>
|
||||
<use id="Highlight" href="#img1" x="688" y="255"/>
|
||||
</g>
|
||||
<g id="Horn">
|
||||
</g>
|
||||
<g id="Face">
|
||||
<use id="Slime" href="#img2" x="233" y="538"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.1 KiB |
49
app/information/[id]/community-guidelines.mdx
Normal file
49
app/information/[id]/community-guidelines.mdx
Normal file
@ -0,0 +1,49 @@
|
||||
export const metadata = {
|
||||
title: "社区准则"
|
||||
}
|
||||
|
||||
Welcome to our community! We are committed to fostering an open, friendly, and respectful environment to facilitate meaningful communication and shared experiences. Please adhere to the following community guidelines to ensure that every member can enjoy a positive and enriching community experience.
|
||||
|
||||
## 1. Respect Others
|
||||
|
||||
Ensure that your words and actions respect fellow community members. Avoid the use of insulting, discriminatory, or offensive language. Respect others' viewpoints, even if you disagree. Engage in discussions constructively to foster understanding and knowledge sharing.
|
||||
|
||||
## 2. Maintain Friendliness and Inclusivity
|
||||
|
||||
We welcome members from diverse cultures, backgrounds, and perspectives. Ensure that your interactions are friendly and inclusive. Refrain from posting offensive or discriminatory content. Help us create an environment that accommodates a variety of opinions and ideas.
|
||||
|
||||
## 3. Respect Privacy
|
||||
|
||||
Respect the privacy of other members. Avoid sharing others' personal information without their consent. If you need to share information related to others, ensure you have obtained their permission.
|
||||
|
||||
## 4. Inappropriate Content and Behavior
|
||||
|
||||
Refrain from posting any illegal, obscene, threatening, or otherwise inappropriate content. This includes but is not limited to explicit material, hate speech, false information, or any behavior that violates laws and ethical standards.
|
||||
|
||||
## 5. Provide Valuable Contributions
|
||||
|
||||
Share meaningful and valuable content within the community. Avoid posting unrelated or repetitive information. Maintain the quality of discussions and provide helpful insights and experiences for other members.
|
||||
|
||||
## 6. Respect Administrators and Moderators
|
||||
|
||||
Follow the guidance and rules provided by administrators and moderators. Their goal is to maintain community order and ensure a pleasant experience for every member. If you have any questions or concerns, feel free to contact them privately.
|
||||
|
||||
## 7. Respect Diverse Political Views
|
||||
|
||||
We encourage the respectful sharing of diverse political views. However, express your opinions in a way that respects others, avoiding offensive, provocative, or insulting language. Political discussions should be conducted constructively to promote understanding and information sharing.
|
||||
|
||||
## 8. Adhere to International Political Standards
|
||||
|
||||
Our community consists of members from around the world with different political beliefs. Understand and respect international political viewpoints, avoiding conflicts arising from political stances. Keep an open mind and be willing to listen and learn from diverse cultural and national perspectives.
|
||||
|
||||
## 9. Comply with Legal Regulations
|
||||
|
||||
Any form of illegal behavior is unacceptable in our community. Ensure that your words and actions comply with local and international laws, especially in matters related to politics. Prohibitions include incitement, advocating violence, or any other unlawful activities.
|
||||
|
||||
## 10. Avoid Improper Political Promotion
|
||||
|
||||
Avoid engaging in inappropriate political promotion within the community. The community is intended for constructive exchange, not as a platform for political propaganda. Respect the preferences of other members and refrain from forcefully advocating personal or group political stances.
|
||||
|
||||
We believe that by adhering to these guidelines, we can create an inclusive community rich in diverse perspectives. Violations may result in appropriate sanctions, and the specific consequences will depend on the severity and frequency of the violations.
|
||||
|
||||
Thank you for your cooperation in maintaining an open and friendly atmosphere in our community, providing an enjoyable communication experience for all members!
|
17
app/information/[id]/layout.tsx
Normal file
17
app/information/[id]/layout.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Card, CardContent, Container } from "@mui/material";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
||||
export default function LegalLayout({ children }: Readonly<{
|
||||
children: ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<Container sx={{ display: "flex", justifyContent: "center", gap: 4, py: 2 }}>
|
||||
<Card sx={{ flexGrow: 1, maxWidth: 720 }}>
|
||||
{children}
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
63
app/information/[id]/page.tsx
Normal file
63
app/information/[id]/page.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { ReactNode } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Box, CardContent, Divider, Typography } from "@mui/material";
|
||||
import UserAgreement from "@/app/information/[id]/user-agreement.mdx";
|
||||
import PrivacyPolicy from "@/app/information/[id]/privacy-policy.mdx";
|
||||
import CommunityGuidelines from "@/app/information/[id]/community-guidelines.mdx";
|
||||
|
||||
interface InfoMeta {
|
||||
title: string;
|
||||
content: ReactNode;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const INFO_DIRECTORY: { [id: string]: InfoMeta } = {
|
||||
"user-agreement": {
|
||||
title: "User Agreement",
|
||||
content: <UserAgreement />,
|
||||
updatedAt: new Date("2019-01-28 01:28")
|
||||
},
|
||||
"privacy-policy": {
|
||||
title: "Privacy Policy",
|
||||
content: <PrivacyPolicy />,
|
||||
updatedAt: new Date("2019-01-28 01:28")
|
||||
},
|
||||
"community-guidelines": {
|
||||
title: "Goatworks Community Guidelines",
|
||||
content: <CommunityGuidelines />,
|
||||
updatedAt: new Date("2019-01-28 01:28")
|
||||
}
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: { id: string } }) {
|
||||
const info = INFO_DIRECTORY[params.id];
|
||||
|
||||
return {
|
||||
title: info?.title,
|
||||
}
|
||||
}
|
||||
|
||||
export default function InfoPage({ params }: { params: { id: string } }) {
|
||||
const info = INFO_DIRECTORY[params.id];
|
||||
|
||||
if (!info) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<CardContent sx={{ paddingX: 5, paddingY: 3 }}>
|
||||
<Typography variant="h2">
|
||||
{info.title}
|
||||
</Typography>
|
||||
<Typography color="text.secondary" variant="body2" sx={{ mt: 0.25 }}>
|
||||
Last Updated At: {info.updatedAt.toLocaleString()}
|
||||
</Typography>
|
||||
|
||||
|
||||
<Divider sx={{ my: 2.5, mx: -5 }} />
|
||||
<Box className="prose max-w-none">
|
||||
{info.content}
|
||||
</Box>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
52
app/information/[id]/privacy-policy.mdx
Normal file
52
app/information/[id]/privacy-policy.mdx
Normal file
@ -0,0 +1,52 @@
|
||||
export const metadata = {
|
||||
title: "隐私协议"
|
||||
}
|
||||
|
||||
SmartSheep Studio (referred to as "we," "our," or "the Studio") respects and protects your personal privacy. This Privacy Policy is intended to explain our practices regarding the collection, use, sharing, and protection of your personal information. Please read this Privacy Policy carefully to understand how we handle your personal information.
|
||||
|
||||
## 1. Information Collection
|
||||
|
||||
We may collect various types of information, including but not limited to:
|
||||
|
||||
* Personal identification information (e.g., name, address, email address, phone number, etc.)
|
||||
* Device information (e.g., IP address, operating system, browser type, etc.)
|
||||
* Usage data (e.g., access times, browsing history, clickstream data, etc.)
|
||||
|
||||
We may collect information through various methods, including:
|
||||
|
||||
* Information you provide (e.g., account registration, form submissions, etc.)
|
||||
* Automatically collected information (e.g., through Cookies, Web Beacons, etc.)
|
||||
|
||||
## 2. Information Use
|
||||
|
||||
We may use your personal information for various purposes, including:
|
||||
|
||||
* Providing requested products or services
|
||||
* Processing transactions and payments
|
||||
* Sending relevant notifications
|
||||
* Providing customer support and services
|
||||
* Improving our products and services
|
||||
|
||||
## 3. Information Sharing
|
||||
|
||||
We may share your personal information with third parties, including but not limited to:
|
||||
|
||||
* Business partners
|
||||
* Third-party service providers
|
||||
* When required by law or government agencies
|
||||
|
||||
## 4. Information Security
|
||||
|
||||
We will take reasonable security measures to protect your personal information from unauthorized access, use, or disclosure. However, please note that no method of transmission over the internet is 100% secure.
|
||||
|
||||
## 5. Changes to Privacy Policy
|
||||
|
||||
We reserve the right to modify this Privacy Policy at any time. The updated policy will be posted on our website and will become effective upon posting. Please check our Privacy Policy regularly for updates.
|
||||
|
||||
## 6. Contact Us
|
||||
|
||||
If you have any questions or concerns about our Privacy Policy, please contact us at:
|
||||
|
||||
alphabot@smartsheep.studio
|
||||
|
||||
Thank you for reading our Privacy Policy.
|
47
app/information/[id]/user-agreement.mdx
Normal file
47
app/information/[id]/user-agreement.mdx
Normal file
@ -0,0 +1,47 @@
|
||||
export const metadata = {
|
||||
title: "用户协议"
|
||||
}
|
||||
|
||||
Please read this user agreement carefully before using our product/service. By using our product/service, you agree to comply with the following terms and conditions:
|
||||
|
||||
## 1. User Content
|
||||
|
||||
1.1 You are solely responsible for any text, images, audio, video, or other materials (collectively referred to as "User Content") you upload, publish, or share through our product/service.
|
||||
|
||||
1.2 You warrant that you are the lawful owner of all User Content you upload, or you have obtained all necessary authorizations and licenses to use and share such User Content on our product/service.
|
||||
|
||||
1.3 You agree not to upload, publish, or share any User Content that is illegal, infringing, obscene, threatening, defamatory, or otherwise violates applicable laws.
|
||||
|
||||
## 2. Community Guidelines and Service Termination
|
||||
|
||||
2.1 You must adhere to our community guidelines, which include but are not limited to [Goatworks Community Guidelines](/legal/community-guidelines).
|
||||
|
||||
2.2 Violation of the community guidelines may result in our sole discretion to delete, modify, or refuse acceptance of any User Content and may lead to termination or suspension of your access to our product/service.
|
||||
|
||||
2.3 We reserve the right to modify the community guidelines at any time, and you agree to comply with the latest community guidelines.
|
||||
|
||||
## 3. Legal Responsibility
|
||||
|
||||
3.1 We do not assume any responsibility for the legality, accuracy, completeness, or quality of User Content.
|
||||
|
||||
3.2 You agree to bear all legal responsibilities arising from your User Content, including but not limited to claims, lawsuits, losses, and expenses incurred by third parties.
|
||||
|
||||
3.3 We are not liable for any losses or damages caused by User Content, including but not limited to direct or indirect special, incidental, consequential, or punitive damages.
|
||||
|
||||
## 4. User Responsibilities
|
||||
|
||||
4.1 You agree to maintain the confidentiality of your account and password and assume full responsibility for all activities conducted through your account.
|
||||
|
||||
4.2 You agree to promptly notify us of any unauthorized use of your account or other security vulnerabilities.
|
||||
|
||||
## 5. Termination
|
||||
|
||||
We reserve the right to terminate or suspend your access to our product/service at any time without prior notice if we believe you have violated any provisions of this user agreement or community guidelines.
|
||||
|
||||
## 6. Contact Us
|
||||
|
||||
If you have any questions about this user agreement, please contact us at:
|
||||
|
||||
alphabot@smartsheep.studio
|
||||
|
||||
Thank you for reading and complying with our user agreement.
|
54
app/layout.tsx
Normal file
54
app/layout.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ReactNode } from "react";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { SITE_DESCRIPTION, SITE_NAME } from "@/app/consts";
|
||||
import { theme } from "@/app/theme";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
|
||||
import "@fontsource/roboto/300.css";
|
||||
import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/500.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
|
||||
import "./globals.css";
|
||||
|
||||
import AppShell from "@/components/AppShell";
|
||||
import NextTopLoader from "nextjs-toploader";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: SITE_NAME,
|
||||
template: `${SITE_NAME} | %s`
|
||||
},
|
||||
description: SITE_DESCRIPTION
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: Readonly<{
|
||||
children: ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="zh-CN">
|
||||
<body>
|
||||
<AppRouterCacheProvider>
|
||||
<CssBaseline />
|
||||
<SpeedInsights />
|
||||
<NextTopLoader color="#ffffff" showSpinner={false} />
|
||||
<ThemeProvider theme={theme}>
|
||||
<AppShell>{children}</AppShell>
|
||||
</ThemeProvider>
|
||||
</AppRouterCacheProvider>
|
||||
|
||||
<script
|
||||
async
|
||||
src="https://analytics.smartsheep.studio/script.js"
|
||||
data-website-id="bbe87bab-bd5b-416b-8767-b29088c75ab2"
|
||||
/>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
76
app/page.tsx
Normal file
76
app/page.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
colors,
|
||||
Container,
|
||||
Grid,
|
||||
List,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { RELATED_ACCOUNTS } from "@/app/consts";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<Container sx={{ scrollBehavior: "smooth", px: 5 }}>
|
||||
<Grid container id="introduce" alignItems="center" sx={{ height: "calc(100vh - 64px)" }}>
|
||||
<Grid item xs={12} sm={6} sx={{ textAlign: { xs: "center", sm: "initial" } }}>
|
||||
<Typography variant="h1" gutterBottom>
|
||||
你好呀 👋
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
欢迎来到 SmartSheep Studio 的官方网站!在这里了解,订阅,跟踪我们的最新消息。
|
||||
接触我们最大的官方社区,并且尝试最新产品,参与各种活动,提供反馈,让我们更好的服务您。
|
||||
</Typography>
|
||||
<Button variant="contained" href="#about-us" size="large">
|
||||
探索更多
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
sx={{ display: "flex", justifyContent: { xs: "center", sm: "end" }, order: { xs: -100, sm: 0 } }}
|
||||
>
|
||||
<Box>
|
||||
<Image src="/smartsheep.svg" alt="Logo" width={256} height={256} />
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container id="about-us" alignItems="center" sx={{ height: "calc(100vh - 64px)" }}>
|
||||
<Grid item xs={12} sm={6} sx={{ display: "flex", justifyContent: { xs: "center", sm: "end" } }}>
|
||||
<Card sx={{ flexGrow: 1, mr: { xs: 0, sm: 4, md: 8 } }}>
|
||||
<List sx={{ width: "100%", bgcolor: "background.paper" }}>
|
||||
{RELATED_ACCOUNTS.map((item, idx) => (
|
||||
<Link key={idx} href={item.link} target="_blank" passHref>
|
||||
<ListItemButton>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: colors.blueGrey[700] }}>{item.icon}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={item.platform} secondary={item.accountName} />
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} sx={{ textAlign: { xs: "center", sm: "initial" } }}>
|
||||
<Typography variant="h1" gutterBottom>
|
||||
关于我们
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
我们是一群充满活力、对开源充满热情的开发者。成立于 2019 年。自那年起我们一直在开发让人喜欢的开源软件。
|
||||
在我们这里,“取之于开源,用之于开源” 不仅是原则,更是我们信仰的座右铭。
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
67
app/posts/[id]/page.tsx
Normal file
67
app/posts/[id]/page.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Box, Card, CardContent, CardMedia, Chip, Divider, Stack, Typography } from "@mui/material";
|
||||
import { client } from "@/sanity/lib/client";
|
||||
import PostContent from "@/components/posts/PostContent";
|
||||
import Image from "next/image";
|
||||
|
||||
export async function generateMetadata({ params }: { params: { id: string } }) {
|
||||
const post = await client.fetch<any>(`*[_type == "post" && slug.current == $slug][0] {
|
||||
title, description
|
||||
}`, { slug: params.id });
|
||||
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description
|
||||
};
|
||||
}
|
||||
|
||||
export default async function PostDetailPage({ params }: { params: { id: string } }) {
|
||||
const post = await client.fetch<any>(`*[_type == "post" && slug.current == $slug][0] {
|
||||
title, description, slug, body, author, publishedAt,
|
||||
mainImage {
|
||||
asset -> {
|
||||
_id,
|
||||
url
|
||||
},
|
||||
alt
|
||||
},
|
||||
"categories": categories[]->title,
|
||||
"author_name": author->name,
|
||||
"author_image": author->image
|
||||
}`, { slug: params.id });
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{
|
||||
post.mainImage &&
|
||||
<CardMedia sx={{ height: 360, position: "relative" }} title={post.mainImage.alt}>
|
||||
<Image
|
||||
fill
|
||||
src={post.mainImage.asset.url}
|
||||
alt={post.mainImage.alt}
|
||||
style={{ objectFit: "cover" }}
|
||||
/>
|
||||
</CardMedia>
|
||||
}
|
||||
|
||||
<CardContent sx={{ paddingX: 5, paddingY: 3 }}>
|
||||
<Box>
|
||||
<Typography variant="h2">
|
||||
{post.title}
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" sx={{ mx: -0.5, mt: 1, mb: 1.2 }}>
|
||||
{post.categories.map((category: string, idx: number) => <Chip size="small" label={category} key={idx} />)}
|
||||
</Stack>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
{post.description ?? "No description yet."}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ my: 2.5, mx: -5 }} />
|
||||
<Box component="article" className="prose max-w-none" sx={{ minWidth: 0 }}>
|
||||
<PostContent content={post.body} />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
14
app/posts/layout.tsx
Normal file
14
app/posts/layout.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Box, Container } from "@mui/material";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function PostLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<Container sx={{ display: "flex", justifyContent: "center", gap: 4, py: 2 }}>
|
||||
<Box sx={{ flexGrow: 1, maxWidth: 720 }}>{children}</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
60
app/posts/page.tsx
Normal file
60
app/posts/page.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { Button, Card, CardActions, CardContent, CardMedia, Chip, Stack, Typography } from "@mui/material";
|
||||
import { client } from "@/sanity/lib/client";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata = {
|
||||
title: "博客"
|
||||
}
|
||||
|
||||
export default async function PostList() {
|
||||
const posts = await client.fetch<any[]>(`*[_type == "post"] {
|
||||
title, description, slug, author, publishedAt,
|
||||
mainImage {
|
||||
asset -> {
|
||||
_id,
|
||||
url
|
||||
},
|
||||
alt
|
||||
},
|
||||
"categories": categories[]->title,
|
||||
"author_name": author->name,
|
||||
"author_image": author->image
|
||||
}`);
|
||||
|
||||
return (
|
||||
posts.map((post) => (
|
||||
<Card key={post.slug.current} sx={{ width: "100%" }}>
|
||||
{
|
||||
post.mainImage &&
|
||||
<CardMedia sx={{ height: 160, position: "relative" }} title={post.mainImage.alt}>
|
||||
<Image
|
||||
fill
|
||||
src={post.mainImage.asset.url}
|
||||
alt={post.mainImage.alt}
|
||||
style={{ objectFit: "cover" }}
|
||||
/>
|
||||
</CardMedia>
|
||||
}
|
||||
|
||||
<CardContent sx={{ paddingX: 5, paddingY: 3 }}>
|
||||
<Typography variant="h3">
|
||||
{post.title}
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" sx={{ mx: -0.5, mt: 1, mb: 1.2 }}>
|
||||
{post.categories.map((category: string, idx: number) => <Chip size="small" label={category} key={idx} />)}
|
||||
</Stack>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{post.description ? post.description : "No description yet."}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions sx={{ paddingX: 4, paddingBottom: 2 }}>
|
||||
<Link href={`/p/${post.slug.current}`} passHref>
|
||||
<Button>Read more</Button>
|
||||
</Link>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))
|
||||
);
|
||||
}
|
31
app/sitemap.ts
Normal file
31
app/sitemap.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { MetadataRoute } from "next";
|
||||
import { SITE_URL } from "@/app/consts";
|
||||
import { client } from "@/sanity/lib/client";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const posts = await client.fetch<any[]>(`*[_type == "post"] {
|
||||
slug, publishedAt,
|
||||
}`);
|
||||
|
||||
return [
|
||||
{
|
||||
url: `${SITE_URL}/`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly",
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/posts`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 0.8,
|
||||
},
|
||||
|
||||
...posts.map((item: any) => ({
|
||||
url: `${SITE_URL}/posts/${item.slug.current}`,
|
||||
lastModified: new Date(item.publishedAt),
|
||||
changeFrequency: "daily" as any,
|
||||
priority: 0.75,
|
||||
})),
|
||||
];
|
||||
}
|
22
app/theme.ts
Normal file
22
app/theme.ts
Normal file
@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#49509e",
|
||||
},
|
||||
secondary: {
|
||||
main: "#d43630",
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
h1: { fontSize: "2.5rem" },
|
||||
h2: { fontSize: "2rem" },
|
||||
h3: { fontSize: "1.75rem" },
|
||||
h4: { fontSize: "1.5rem" },
|
||||
h5: { fontSize: "1.25rem" },
|
||||
h6: { fontSize: "1.15rem" },
|
||||
},
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
@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');
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
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-mono, code, pre {
|
||||
font-family: "Roboto Mono", monospace !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 550 KiB |
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
139
components/AppShell.tsx
Normal file
139
components/AppShell.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Slide,
|
||||
Toolbar,
|
||||
Typography,
|
||||
AppBar as MuiAppBar,
|
||||
AppBarProps as MuiAppBarProps,
|
||||
useScrollTrigger,
|
||||
IconButton,
|
||||
styled,
|
||||
Box,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { ReactElement, ReactNode, useEffect, useState } from "react";
|
||||
import { SITE_NAME } from "@/app/consts";
|
||||
import NavigationDrawer, { DRAWER_WIDTH, AppNavigationHeader, isMobileQuery } from "@/components/NavigationDrawer";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
function HideOnScroll(props: { window?: () => Window; children: ReactElement }) {
|
||||
const { children, window } = props;
|
||||
const trigger = useScrollTrigger({
|
||||
target: window ? window() : undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<Slide appear={false} direction="down" in={!trigger}>
|
||||
{children}
|
||||
</Slide>
|
||||
);
|
||||
}
|
||||
|
||||
interface AppBarProps extends MuiAppBarProps {
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
const ShellAppBar = styled(MuiAppBar, {
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
})<AppBarProps>(({ theme, open }) => {
|
||||
const isMobile = useMediaQuery(isMobileQuery);
|
||||
|
||||
return {
|
||||
transition: theme.transitions.create(["margin", "width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
...(!isMobile &&
|
||||
open && {
|
||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
||||
transition: theme.transitions.create(["margin", "width"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginRight: DRAWER_WIDTH,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const AppMain = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{
|
||||
open?: boolean;
|
||||
}>(({ theme, open }) => {
|
||||
const isMobile = useMediaQuery(isMobileQuery);
|
||||
|
||||
return {
|
||||
flexGrow: 1,
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
marginRight: -DRAWER_WIDTH,
|
||||
...(!isMobile &&
|
||||
open && {
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginRight: 0,
|
||||
}),
|
||||
position: "relative",
|
||||
};
|
||||
});
|
||||
|
||||
export default function AppShell({ children }: { children: ReactNode }) {
|
||||
let documentWindow: Window;
|
||||
|
||||
const isMobile = useMediaQuery(isMobileQuery);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
documentWindow = window;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<HideOnScroll window={() => documentWindow}>
|
||||
<ShellAppBar open={open} position="fixed">
|
||||
<Toolbar sx={{ height: 64 }}>
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ ml: isMobile ? 0.5 : 0, mr: 2 }}
|
||||
>
|
||||
<Image src="/smartsheep.svg" alt="Logo" width={32} height={32} />
|
||||
</IconButton>
|
||||
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1, fontSize: "1.2rem" }}>
|
||||
<Link href="/">{SITE_NAME}</Link>
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
onClick={() => setOpen(true)}
|
||||
sx={{ mr: 1, display: !isMobile && open ? "none" : "block" }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</ShellAppBar>
|
||||
</HideOnScroll>
|
||||
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppMain open={open}>
|
||||
<AppNavigationHeader />
|
||||
|
||||
{children}
|
||||
</AppMain>
|
||||
|
||||
<NavigationDrawer open={open} onClose={() => setOpen(false)} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<div class="text-xs text-grey" :class="props.noCentered ? 'text-left' : 'text-center'">
|
||||
<p>{{ t("copyright") }} © {{ new Date().getFullYear() }} {{ t("brandNameFormal") }}</p>
|
||||
<p v-if="services" class="flex" :class="props.noCentered ? 'justify-start' : 'justify-center'">
|
||||
<span>Powered by</span>
|
||||
<span class="flex services-list ms-1">
|
||||
<a class="service underline" v-for="item in services" :href="projects[item][1]">
|
||||
{{ projects[item][0] }}
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ service?: string | string[], noCentered?: boolean }>()
|
||||
|
||||
const services = computed<string[]>(() => props.service instanceof Array ? (props.service ?? []) : (props.service ? [props.service] : []))
|
||||
|
||||
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"],
|
||||
"roadsign": ["RoadSign", "https://git.solsynth.dev/Goatworks/RoadSign"],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.services-list .service:nth-child(n+2)::before {
|
||||
content: ", ";
|
||||
}
|
||||
</style>
|
@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<div class="text-xs text-grey sidebar-footer transition-opacity duration-500">
|
||||
<div class="flex footer-links flex-wrap">
|
||||
<nuxt-link to="/terms/privacy-policy" class="hover:underline">Privacy Policy</nuxt-link>
|
||||
<nuxt-link to="/terms/user-agreement" class="hover:underline">Term of Service</nuxt-link>
|
||||
</div>
|
||||
<div class="flex footer-links flex-wrap">
|
||||
<nuxt-link to="https://status.solsynth.dev" target="_blank" class="hover:underline">Status of Service</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.sidebar-footer {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.sidebar-footer:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.footer-links *:not(:last-child):after {
|
||||
content: "·";
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
text-decoration: none !important;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
</script>
|
@ -1,29 +0,0 @@
|
||||
<template>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
size="small"
|
||||
icon="mdi-translate"
|
||||
v-bind="props"
|
||||
/>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
class="w-48"
|
||||
density="compact"
|
||||
v-for="item in locales"
|
||||
:key="item.code"
|
||||
:value="item.code"
|
||||
:active="locale == item.code"
|
||||
@click.prevent.stop="() => { setLocale(item.code); emits('update') }"
|
||||
>
|
||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emits = defineEmits(['update'])
|
||||
const { locale, locales, setLocale } = useI18n()
|
||||
</script>
|
154
components/NavigationDrawer.tsx
Normal file
154
components/NavigationDrawer.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
"use client";
|
||||
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import {
|
||||
Box, Collapse,
|
||||
Divider,
|
||||
Drawer,
|
||||
IconButton,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
styled,
|
||||
useMediaQuery
|
||||
} from "@mui/material";
|
||||
import { theme } from "@/app/theme";
|
||||
import { Fragment, ReactNode, useState } from "react";
|
||||
import HomeIcon from "@mui/icons-material/Home";
|
||||
import ArticleIcon from "@mui/icons-material/Article";
|
||||
import FeedIcon from "@mui/icons-material/RssFeed";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import GavelIcon from "@mui/icons-material/Gavel";
|
||||
import PolicyIcon from "@mui/icons-material/Policy";
|
||||
import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import Link from "next/link";
|
||||
|
||||
export interface NavigationItem {
|
||||
icon?: ReactNode;
|
||||
title?: string;
|
||||
link?: string;
|
||||
divider?: boolean;
|
||||
children?: NavigationItem[];
|
||||
}
|
||||
|
||||
export const DRAWER_WIDTH = 320;
|
||||
export const NAVIGATION_ITEMS: NavigationItem[] = [
|
||||
{ icon: <HomeIcon />, title: "首页", link: "/" },
|
||||
{ icon: <ArticleIcon />, title: "博客", link: "/posts" },
|
||||
{
|
||||
icon: <InfoIcon />, title: "信息中心", children: [
|
||||
{ icon: <GavelIcon />, title: "用户协议", link: "/i/user-agreement" },
|
||||
{ icon: <PolicyIcon />, title: "隐私协议", link: "/i/privacy-policy" },
|
||||
{ icon: <SupervisedUserCircleIcon />, title: "社区准则", link: "/i/community-guidelines" }
|
||||
]
|
||||
},
|
||||
{ divider: true },
|
||||
{ icon: <FeedIcon />, title: "订阅源", link: "/feed" }
|
||||
];
|
||||
|
||||
export const AppNavigationHeader = styled("div")(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing(0, 1),
|
||||
justifyContent: "flex-start",
|
||||
height: 64,
|
||||
...theme.mixins.toolbar
|
||||
}));
|
||||
|
||||
export function AppNavigationSection({ items, depth }: { items: NavigationItem[], depth?: number }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return items.map((item, idx) => {
|
||||
if (item.divider) {
|
||||
return <Divider key={idx} sx={{ my: 1 }} />;
|
||||
} else if (item.children) {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<ListItemButton onClick={() => setOpen(!open)} sx={{ pl: 2 + (depth ?? 0) * 2 }}>
|
||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
||||
<ListItemText primary={item.title} />
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemButton>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
<AppNavigationSection items={item.children} depth={(depth ?? 0) + 1} />
|
||||
</List>
|
||||
</Collapse>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link key={idx} href={item.link ?? "/"} passHref>
|
||||
<ListItemButton sx={{ pl: 2 + (depth ?? 0) * 2 }}>
|
||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
||||
<ListItemText primary={item.title} />
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function AppNavigation({ showClose, onClose }: { showClose?: boolean; onClose: () => void }) {
|
||||
return (
|
||||
<>
|
||||
<AppNavigationHeader>
|
||||
{showClose && (
|
||||
<IconButton onClick={onClose}>
|
||||
{theme.direction === "rtl" ? <ChevronLeftIcon /> : <ChevronRightIcon />}
|
||||
</IconButton>
|
||||
)}
|
||||
</AppNavigationHeader>
|
||||
<Divider />
|
||||
<List>
|
||||
<AppNavigationSection items={NAVIGATION_ITEMS} />
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const isMobileQuery = theme.breakpoints.down("md");
|
||||
|
||||
export default function NavigationDrawer({ open, onClose }: { open: boolean; onClose: () => void }) {
|
||||
const isMobile = useMediaQuery(isMobileQuery);
|
||||
|
||||
return isMobile ? (
|
||||
<>
|
||||
<Box sx={{ flexShrink: 0, width: DRAWER_WIDTH }} />
|
||||
<Drawer
|
||||
keepMounted
|
||||
anchor="right"
|
||||
variant="temporary"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
sx={{
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: DRAWER_WIDTH
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppNavigation onClose={onClose} />
|
||||
</Drawer>
|
||||
</>
|
||||
) : (
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
anchor="right"
|
||||
open={open}
|
||||
sx={{
|
||||
width: DRAWER_WIDTH,
|
||||
flexShrink: 0,
|
||||
"& .MuiDrawer-paper": {
|
||||
width: DRAWER_WIDTH
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppNavigation showClose onClose={onClose} />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<v-card :to="url" class="mx-[2.5ch] mb-3">
|
||||
<v-card-text>
|
||||
<div class="mb-3 flex flex-row gap-4">
|
||||
<nuxt-link :to="`/users/${post.author?.name}`">
|
||||
<v-avatar :image="post.author?.avatar" />
|
||||
</nuxt-link>
|
||||
<div class="flex flex-col">
|
||||
<span>{{ post.author?.nick }} <span class="text-xs">@{{ post.author?.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.author?.description }}
|
||||
</span>
|
||||
|
||||
<div v-if="post.type != 'story'" class="mt-1">
|
||||
<v-btn size="x-small" variant="flat" append-icon="mdi-arrow-right" :text="t('continueReading')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="post.body?.thumbnail" class="mb-5">
|
||||
<v-img
|
||||
:src="`${config.public.solarNetworkApi}/cgi/uc/attachments/${post.body?.thumbnail}`"
|
||||
:aspect-ratio="16 / 9"
|
||||
alt="Post thumbnail"
|
||||
class="rounded-md"
|
||||
cover
|
||||
/>
|
||||
</div>
|
||||
|
||||
<article v-if="post.type == 'story' || props.forceShowContent" class="text-base prose max-w-none">
|
||||
<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
|
||||
:no-clickable-attachment="props.noClickableAttachment"
|
||||
:attachments="post.body?.attachments"
|
||||
/>
|
||||
</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>
|
||||
{{ 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"
|
||||
>
|
||||
<nuxt-link
|
||||
v-for="tag in post.tags"
|
||||
:to="`/posts/tags/${tag.alias}`"
|
||||
class="hover:underline hover:underline-dotted"
|
||||
@click.stop
|
||||
>
|
||||
#{{ tag.alias }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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}`)
|
||||
</script>
|
@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<v-infinite-scroll :items="posts" :onLoad="loadPost">
|
||||
<template v-for="item in posts" :key="item">
|
||||
<post-item :post="item" no-clickable-attachment />
|
||||
</template>
|
||||
</v-infinite-scroll>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ author?: string, tag?: string, category?: string, realm?: string }>()
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const posts = ref<any[]>([])
|
||||
|
||||
async function loadPost({ done }: any) {
|
||||
const searchQueries = new URLSearchParams({
|
||||
take: (10).toString(),
|
||||
offset: posts.value.length.toString(),
|
||||
})
|
||||
|
||||
if (props.author) {
|
||||
searchQueries.set("author", props.author)
|
||||
}
|
||||
if (props.realm) {
|
||||
searchQueries.set("realm", props.realm)
|
||||
}
|
||||
|
||||
if (props.tag) {
|
||||
searchQueries.set("tag", props.tag)
|
||||
}
|
||||
if (props.category) {
|
||||
searchQueries.set("category", props.category)
|
||||
}
|
||||
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/co/posts?` + searchQueries)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.data.length > 0) {
|
||||
posts.value.push(...result.data)
|
||||
done("ok")
|
||||
} else {
|
||||
done("empty")
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<v-infinite-scroll :items="posts" :onLoad="loadPost">
|
||||
<template v-for="item in posts" :key="item">
|
||||
<post-item :post="item" no-clickable-attachment />
|
||||
</template>
|
||||
</v-infinite-scroll>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ postId: number }>()
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const posts = ref<any[]>([])
|
||||
|
||||
async function loadPost({ done }: any) {
|
||||
const searchQueries = new URLSearchParams({
|
||||
take: (10).toString(),
|
||||
offset: posts.value.length.toString(),
|
||||
})
|
||||
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/co/posts/${props.postId}/replies?` + searchQueries)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.data.length > 0) {
|
||||
posts.value.push(...result.data)
|
||||
done("ok")
|
||||
} else {
|
||||
done("empty")
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<v-menu>
|
||||
<template #activator="{ props }">
|
||||
<v-btn flat exact v-bind="props" icon>
|
||||
<v-avatar v-if="id.isReady" color="transparent" icon="mdi-account-circle" :image="avatar" />
|
||||
<v-progress-circular v-else indeterminate size="20" width="2.5" />
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list density="compact" v-if="id.isLoggedIn">
|
||||
<v-list-item :title="nickname" :subtitle="username" />
|
||||
|
||||
<v-divider class="border-opacity-50 my-2" />
|
||||
|
||||
<v-list-item :title="t('userMenuDashboard')" prepend-icon="mdi-account-supervisor" exact to="/users/me" />
|
||||
<v-list-item :title="t('userMenuSignOut')" prepend-icon="mdi-logout" @click="signOut"></v-list-item>
|
||||
</v-list>
|
||||
<v-list density="compact" v-else>
|
||||
<v-list-item :title="t('userMenuSignIn')" prepend-icon="mdi-login-variant" to="/auth/sign-in" />
|
||||
<v-list-item :title="t('userMenuSignUp')" prepend-icon="mdi-account-plus" to="/auth/sign-up" />
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserinfo } from "@/stores/userinfo"
|
||||
import { computed } from "vue"
|
||||
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const id = useUserinfo()
|
||||
|
||||
const username = computed(() => {
|
||||
if (id.isLoggedIn) {
|
||||
return "@" + id.userinfo?.name
|
||||
} else {
|
||||
return "@visitor"
|
||||
}
|
||||
})
|
||||
const nickname = computed(() => {
|
||||
if (id.isLoggedIn) {
|
||||
return id.userinfo?.nick
|
||||
} else {
|
||||
return "Anonymous"
|
||||
}
|
||||
})
|
||||
const avatar = computed(() => {
|
||||
return id.userinfo?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${id.userinfo?.avatar}` : void 0
|
||||
})
|
||||
|
||||
function signOut() {
|
||||
useAtk().value = null
|
||||
useRtk().value = null
|
||||
id.userinfo.value = null
|
||||
reloadNuxtApp()
|
||||
}
|
||||
</script>
|
@ -1,182 +0,0 @@
|
||||
<template>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel eager title="Tickets">
|
||||
<template #text>
|
||||
<v-card :loading="reverting.tickets" variant="outlined">
|
||||
<v-data-table-server
|
||||
density="compact"
|
||||
:headers="dataDefinitions.tickets"
|
||||
:items="tickets"
|
||||
:items-length="pagination.tickets.total"
|
||||
:loading="reverting.tickets"
|
||||
v-model:items-per-page="pagination.tickets.pageSize"
|
||||
@update:options="readTickets"
|
||||
item-value="id"
|
||||
class="overflow-y-auto text-no-wrap"
|
||||
>
|
||||
<template v-slot:item="{ item }: { item: any }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.ip_address }}</td>
|
||||
<td>
|
||||
<v-tooltip :text="item.user_agent" location="top">
|
||||
<template #activator="{ props }">
|
||||
<div v-bind="props" class="text-ellipsis whitespace-nowrap overflow-hidden max-w-[280px]">
|
||||
{{ item.user_agent }}
|
||||
</div>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
<td>{{ new Date(item.created_at).toLocaleString() }}</td>
|
||||
<td>
|
||||
<v-tooltip text="Sign Out">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="error"
|
||||
icon="mdi-logout-variant"
|
||||
@click="killTicket(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
|
||||
<v-expansion-panel eager title="Events">
|
||||
<template #text>
|
||||
<v-card :loading="reverting.events" variant="outlined">
|
||||
<v-data-table-server
|
||||
density="compact"
|
||||
:headers="dataDefinitions.events"
|
||||
:items="events"
|
||||
:items-length="pagination.events.total"
|
||||
:loading="reverting.events"
|
||||
v-model:items-per-page="pagination.events.pageSize"
|
||||
@update:options="readEvents"
|
||||
item-value="id"
|
||||
class="overflow-y-auto text-no-wrap"
|
||||
>
|
||||
<template v-slot:item="{ item }: { item: any }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.target }}</td>
|
||||
<td>{{ item.ip_address }}</td>
|
||||
<td>
|
||||
<v-tooltip :text="item.user_agent" location="top">
|
||||
<template #activator="{ props }">
|
||||
<div v-bind="props" class="text-ellipsis whitespace-nowrap overflow-hidden max-w-[180px]">
|
||||
{{ item.user_agent }}
|
||||
</div>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
<td>{{ new Date(item.created_at).toLocaleString() }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { solarFetch } from "~/utils/request"
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const dataDefinitions: { [id: string]: any[] } = {
|
||||
tickets: [
|
||||
{ align: "start", key: "id", title: "ID" },
|
||||
{ align: "start", key: "ip_address", title: "IP Address" },
|
||||
{ align: "start", key: "user_agent", title: "User Agent" },
|
||||
{ align: "start", key: "created_at", title: "Issued At" },
|
||||
{ align: "start", key: "actions", title: "Actions", sortable: false },
|
||||
],
|
||||
events: [
|
||||
{ align: "start", key: "id", title: "ID" },
|
||||
{ align: "start", key: "type", title: "Type" },
|
||||
{ align: "start", key: "target", title: "Affected Object" },
|
||||
{ align: "start", key: "ip_address", title: "IP Address" },
|
||||
{ align: "start", key: "user_agent", title: "User Agent" },
|
||||
{ align: "start", key: "created_at", title: "Performed At" },
|
||||
],
|
||||
}
|
||||
|
||||
const tickets = ref<any>([])
|
||||
const events = ref<any>([])
|
||||
|
||||
const reverting = reactive({ tickets: false, events: false })
|
||||
const pagination = reactive({
|
||||
tickets: { page: 1, pageSize: 5, total: 0 },
|
||||
events: { page: 1, pageSize: 5, total: 0 },
|
||||
})
|
||||
|
||||
async function readTickets({ page, itemsPerPage }: { page?: number; itemsPerPage?: number }) {
|
||||
if (itemsPerPage) pagination.tickets.pageSize = itemsPerPage
|
||||
if (page) pagination.tickets.page = page
|
||||
|
||||
reverting.tickets = true
|
||||
const res = await solarFetch(
|
||||
"/cgi/id/users/me/tickets?" +
|
||||
new URLSearchParams({
|
||||
take: pagination.tickets.pageSize.toString(),
|
||||
offset: ((pagination.tickets.page - 1) * pagination.tickets.pageSize).toString(),
|
||||
}),
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
tickets.value = data["data"]
|
||||
pagination.tickets.total = data["count"]
|
||||
}
|
||||
reverting.tickets = false
|
||||
}
|
||||
|
||||
async function readEvents({ page, itemsPerPage }: { page?: number; itemsPerPage?: number }) {
|
||||
if (itemsPerPage) pagination.events.pageSize = itemsPerPage
|
||||
if (page) pagination.events.page = page
|
||||
|
||||
reverting.events = true
|
||||
const res = await solarFetch(
|
||||
"/cgi/id/users/me/events?" +
|
||||
new URLSearchParams({
|
||||
take: pagination.events.pageSize.toString(),
|
||||
offset: ((pagination.events.page - 1) * pagination.events.pageSize).toString(),
|
||||
}),
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
events.value = data["data"]
|
||||
pagination.events.total = data["count"]
|
||||
}
|
||||
reverting.events = false
|
||||
}
|
||||
|
||||
Promise.all([readTickets({}), readEvents({})])
|
||||
|
||||
async function killTicket(item: any) {
|
||||
reverting.tickets = true
|
||||
const res = await solarFetch(`/cgi/id/users/me/tickets/${item.id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
await readTickets({})
|
||||
error.value = null
|
||||
}
|
||||
reverting.tickets = false
|
||||
}
|
||||
</script>
|
@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<v-card v-if="!loading" density="compact" variant="outlined">
|
||||
<div class="h-[500px] overflow-y-auto no-scrollbar">
|
||||
<div v-for="item in items" class="mt-5 mb-2">
|
||||
<post-item :key="item.id" force-show-content :post="item" />
|
||||
</div>
|
||||
<div class="mt-4 mb-5 flex justify-center">
|
||||
<v-btn :text="t('seeMore')" size="small" variant="text" to="/activity" />
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
<v-card v-else density="compact" variant="outlined">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<v-progress-circular indeterminate />
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const items = ref<any[]>([])
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/co/posts?take=5&realm=${config.public.solarRealm}`)
|
||||
const result = await res.json()
|
||||
|
||||
items.value.push(...result.data)
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.no-scrollbar {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
38
components/articles/ImageViewer.tsx
Normal file
38
components/articles/ImageViewer.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
import Zoomist from "zoomist";
|
||||
|
||||
import "zoomist/css";
|
||||
|
||||
export default function ImageViewer({ src, alt }: { src: string, alt: string }) {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (container.current) {
|
||||
new Zoomist(container.current, {
|
||||
maxScale: 5,
|
||||
bounds: true,
|
||||
pinchable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={container} className="zoomist-container h-fit">
|
||||
<div className="zoomist-wrapper">
|
||||
<div className="zoomist-image h-fit">
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={0}
|
||||
height={0}
|
||||
sizes="100vw"
|
||||
style={{ width: "100%", height: "auto" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<v-carousel
|
||||
hide-delimiter-background
|
||||
hide-delimiters
|
||||
:progress="attachments.length > 1 ? 'primary' : false"
|
||||
:show-arrows="attachments.length > 1 ? 'hover' : false"
|
||||
height="auto"
|
||||
>
|
||||
<v-carousel-item v-for="item in metadata" class="fill-height">
|
||||
<nuxt-link v-if="item.mimetype.split('/')[0] == 'image' && !props.noClickableAttachment"
|
||||
:to="`/gallery/${item.rid}`">
|
||||
<attachment-renderer :item="item" />
|
||||
</nuxt-link>
|
||||
<div v-else>
|
||||
<attachment-renderer :item="item" />
|
||||
</div>
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ attachments: string[], noClickableAttachment?: boolean }>()
|
||||
const emits = defineEmits(["update:metadata"])
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const { data } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/uc/attachments?take=${props.attachments.length}&id=${props.attachments.join(",")}`)
|
||||
const metadata = computed(() => data.value.data)
|
||||
|
||||
watch(metadata, (value) => {
|
||||
emits("update:metadata", value)
|
||||
}, { deep: true, immediate: true })
|
||||
</script>
|
@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<v-sheet v-if="item.is_mature && !showMature" color="rgba(0, 0, 0, .4)" height="calc(100% + 24px)" class="p-5">
|
||||
<v-row class="fill-height" align="center" justify="center">
|
||||
<v-col class="text-center">
|
||||
<h1 class="text-xl font-bold text-white">Mature Content</h1>
|
||||
<p class="text-md text-white">This content is rated and may not suitable for everyone to view.</p>
|
||||
|
||||
<div class="flex justify-center mt-3">
|
||||
<v-btn
|
||||
variant="text"
|
||||
color="white"
|
||||
prepend-icon="mdi-eye"
|
||||
text="Reveal"
|
||||
@click="showMature = true"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
<v-img v-else-if="item.mimetype.split('/')[0] == 'image'" :src="getAttachmentUrl(item.rid)" :alt="item.alt"
|
||||
class="w-full h-full" :cover="!props.noCover" />
|
||||
<video v-else-if="item.mimetype.split('/')[0] == 'video'" :src="getAttachmentUrl(item.rid)" class="w-full h-full"
|
||||
controls @click.stop />
|
||||
<v-sheet v-else color="rgba(0, 0, 0, .4)" height="calc(100% + 24px)" class="p-5">
|
||||
<v-row class="fill-height" align="center" justify="center">
|
||||
<v-col class="text-center">
|
||||
<h1 class="text-xl font-bold text-white">
|
||||
{{ item.alt }}
|
||||
</h1>
|
||||
<p class="text-md text-white">{{ item.mimetype }}</p>
|
||||
|
||||
<p class="text-sm text-white mt-3">Unable to preview, you can open it via other ways.</p>
|
||||
|
||||
<div class="flex justify-center mt-3">
|
||||
<v-btn
|
||||
variant="text"
|
||||
color="white"
|
||||
prepend-icon="mdi-launch"
|
||||
text="Open in browser"
|
||||
:href="getAttachmentUrl(item.rid)"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const props = defineProps<{ item: any, noCover?: boolean }>()
|
||||
|
||||
const item = computed(() => props.item)
|
||||
|
||||
const showMature = ref(false)
|
||||
|
||||
function getAttachmentUrl(id: string) {
|
||||
return `${config.public.solarNetworkApi}/cgi/uc/attachments/${id}`
|
||||
}
|
||||
</script>
|
@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<v-form class="flex-grow-1" @submit.prevent="submit">
|
||||
<v-text-field
|
||||
:label="t('username')"
|
||||
variant="solo"
|
||||
density="comfortable"
|
||||
class="mb-3"
|
||||
:hide-details="true"
|
||||
:disabled="props.loading"
|
||||
v-model="probe"
|
||||
/>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<v-btn type="button" variant="plain" color="grey-darken-3" to="/auth/sign-up">{{ t("userMenuSignUp") }}</v-btn>
|
||||
|
||||
<v-btn
|
||||
type="submit"
|
||||
variant="text"
|
||||
color="primary"
|
||||
class="justify-self-end"
|
||||
append-icon="mdi-arrow-right"
|
||||
:disabled="props.loading"
|
||||
>
|
||||
{{ t("next") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const probe = ref("")
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const props = defineProps<{ loading?: boolean }>()
|
||||
const emits = defineEmits(["swap", "update:loading", "update:ticket"])
|
||||
|
||||
async function submit() {
|
||||
if (!probe.value) return
|
||||
|
||||
emits("update:loading", true)
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/id/auth`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: probe.value }),
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
emits("update:ticket", data["ticket"])
|
||||
if (data.is_finished) emits("swap", "completed")
|
||||
else emits("swap", "mfa")
|
||||
error.value = null
|
||||
}
|
||||
emits("update:loading", false)
|
||||
}
|
||||
</script>
|
@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-icon icon="mdi-lan-check" size="32" color="grey-darken-3" class="mb-3" />
|
||||
|
||||
<h1 class="font-bold text-xl">{{ t("signInCompleted") }}</h1>
|
||||
<p>{{ t("signInCompletedCaption") }}</p>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useUserinfo()
|
||||
|
||||
const props = defineProps<{ loading?: boolean; currentFactor?: any; ticket?: any }>()
|
||||
const emits = defineEmits(["update:loading"])
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function load() {
|
||||
emits("update:loading", true)
|
||||
await getToken(props.ticket.grant_token)
|
||||
await auth.readProfiles()
|
||||
setTimeout(() => callback(), 1850)
|
||||
}
|
||||
|
||||
onMounted(() => load())
|
||||
|
||||
async function getToken(tk: string) {
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/id/auth/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
code: tk,
|
||||
grant_type: "grant_token",
|
||||
}),
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
const err = await res.text()
|
||||
error.value = err
|
||||
throw new Error(err)
|
||||
} else {
|
||||
const out = await res.json()
|
||||
auth.setTokenSet(out["access_token"], out["refresh_token"])
|
||||
error.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function callback() {
|
||||
if (route.query["close"]) {
|
||||
window.close()
|
||||
} else if (route.query["redirect_uri"]) {
|
||||
window.open((route.query["redirect_uri"] as string) ?? "/", "_self")
|
||||
} else {
|
||||
router.push("/users/me")
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full max-w-[720px]">
|
||||
<v-expand-transition>
|
||||
<v-alert v-show="route.query['redirect_uri']" variant="tonal" type="info" class="text-xs">
|
||||
{{ t("callbackHint") }} <br />
|
||||
<span class="font-mono">{{ route.query["redirect_uri"] }}</span>
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
</script>
|
@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<v-form class="flex-grow-1" @submit.prevent="submit">
|
||||
<div v-if="inputType === 'one-time-password'" class="text-center">
|
||||
<p class="text-xs opacity-90">{{ t("multiFactorHint") }}</p>
|
||||
<v-otp-input
|
||||
class="pt-0"
|
||||
variant="solo"
|
||||
density="compact"
|
||||
type="text"
|
||||
:length="6"
|
||||
v-model="password"
|
||||
:loading="loading"
|
||||
/>
|
||||
</div>
|
||||
<v-text-field
|
||||
v-else
|
||||
:label="t('password')"
|
||||
type="password"
|
||||
variant="solo"
|
||||
density="comfortable"
|
||||
:disabled="loading"
|
||||
v-model="password"
|
||||
/>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<v-btn
|
||||
type="submit"
|
||||
variant="text"
|
||||
color="primary"
|
||||
class="justify-self-end"
|
||||
append-icon="mdi-arrow-right"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ t("next") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const password = ref("")
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const props = defineProps<{ loading?: boolean; currentFactor?: any; ticket?: any }>()
|
||||
const emits = defineEmits(["swap", "update:ticket", "update:loading"])
|
||||
|
||||
async function submit() {
|
||||
emits("update:loading", true)
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/id/auth`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
ticket_id: props.ticket?.id,
|
||||
factor_id: props.currentFactor?.id,
|
||||
code: password.value,
|
||||
}),
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
error.value = null
|
||||
password.value = ""
|
||||
emits("update:ticket", data["ticket"])
|
||||
if (data["is_finished"]) emits("swap", "completed")
|
||||
else emits("swap", "mfa")
|
||||
}
|
||||
emits("update:loading", false)
|
||||
}
|
||||
|
||||
const inputType = computed(() => {
|
||||
switch (props.currentFactor?.type) {
|
||||
case 0:
|
||||
return "text"
|
||||
case 1:
|
||||
return "one-time-password"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
})
|
||||
</script>
|
@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-grow-1">
|
||||
<v-card class="mb-3">
|
||||
<v-list density="compact" color="primary">
|
||||
<v-list-item
|
||||
v-for="(item, idx) in factors ?? []"
|
||||
:key="idx"
|
||||
:prepend-icon="getFactorType(item)?.icon"
|
||||
:title="getFactorType(item)?.label"
|
||||
:active="focus === item.id"
|
||||
:disabled="getFactorAvailable(item)"
|
||||
@click="focus = item.id"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-alert v-show="error" variant="tonal" type="error" class="text-xs mb-3">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<v-btn variant="text" color="primary" class="justify-self-end" append-icon="mdi-arrow-right" @click="submit">
|
||||
{{ t("next") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const focus = ref<number | null>(null)
|
||||
const factors = ref<any[]>([])
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const props = defineProps<{ ticket?: any }>()
|
||||
const emits = defineEmits(["swap", "update:loading", "update:currentFactor"])
|
||||
|
||||
async function load() {
|
||||
emits("update:loading", true)
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/id/auth/factors?ticketId=${props.ticket.id}`)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
factors.value = await res.json()
|
||||
}
|
||||
emits("update:loading", false)
|
||||
}
|
||||
|
||||
onMounted(() => load())
|
||||
|
||||
async function submit() {
|
||||
if (!focus.value) return
|
||||
|
||||
emits("update:loading", true)
|
||||
const res = await fetch(`${config.public.solarNetworkApi}/cgi/id/auth/factors/${focus.value}`, {
|
||||
method: "POST",
|
||||
})
|
||||
if (res.status !== 200 && res.status !== 204) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const item = factors.value.find((item: any) => item.id === focus.value)
|
||||
emits("update:currentFactor", item)
|
||||
emits("swap", "applicator")
|
||||
error.value = null
|
||||
focus.value = null
|
||||
}
|
||||
emits("update:loading", false)
|
||||
}
|
||||
|
||||
function getFactorType(item: any) {
|
||||
switch (item.type) {
|
||||
case 0:
|
||||
return { icon: "mdi-form-textbox-password", label: t("multiFactorTypePassword") }
|
||||
case 1:
|
||||
return { icon: "mdi-email-fast", label: t("multiFactorTypeEmail") }
|
||||
}
|
||||
}
|
||||
|
||||
function getFactorAvailable(factor: any) {
|
||||
const blacklist: number[] = props.ticket?.factor_trail ?? []
|
||||
return blacklist.includes(factor.id)
|
||||
}
|
||||
</script>
|
@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<v-card :title="t('download')" :subtitle="t('downloadDescription')" density="comfortable">
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="item in props.items"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.title"
|
||||
:subtitle="item.desc"
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ items: [{ title: string, icon: string, desc: string, url: string }] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class="my-2">
|
||||
<div v-if="status == 'pending'">{{ t("loading") }}</div>
|
||||
<post-item v-else class="no-margin-post" :post="post" :force-show-content="props.forceShowContent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PostItem from "~/components/PostItem.vue"
|
||||
|
||||
const props = defineProps<{ id: number, forceShowContent?: boolean }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const { status, data: post } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/posts/${props.id}`)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.no-margin-post {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
@ -1,182 +0,0 @@
|
||||
<template>
|
||||
<v-expand-transition>
|
||||
<v-alert v-if="error" variant="tonal" type="error" class="text-xs mb-3">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-data-table-server
|
||||
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"
|
||||
>
|
||||
<template v-slot:item="{ item }: { item: any }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>
|
||||
<div class="item-texture-cell">
|
||||
<v-img
|
||||
cover
|
||||
aspect-ratio="1"
|
||||
width="28"
|
||||
height="28"
|
||||
color="grey-lighten-2"
|
||||
rounded="sm"
|
||||
:src="`${config.public.solarNetworkApi}/cgi/uc/attachments/${item.attachment.rid}`"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-progress-circular
|
||||
size="x-small"
|
||||
width="3"
|
||||
color="grey-lighten-4"
|
||||
indeterminate
|
||||
></v-progress-circular>
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
|
||||
<v-code class="px-2 w-fit font-mono">
|
||||
{{ item.attachment.rid }}
|
||||
</v-code>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ props.packPrefix + item.alias }}</td>
|
||||
<td>
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="warning"
|
||||
icon="mdi-pencil"
|
||||
class="ms-[-8px]"
|
||||
:to="`/creator/stickers/${item.pack_id}/${item.id}/edit`"
|
||||
/>
|
||||
|
||||
<v-dialog max-width="480">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="error"
|
||||
icon="mdi-delete"
|
||||
:disabled="submitting"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<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.
|
||||
</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="Delete"
|
||||
color="error"
|
||||
@click="() => { deleteSticker(item); isActive.value = false }"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { solarFetch } from "~/utils/request"
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{ packId: number, packPrefix?: string }>()
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
const dataDefinitions: { [id: string]: any[] } = {
|
||||
stickers: [
|
||||
{ align: "start", key: "id", title: "ID" },
|
||||
{ align: "start", key: "attachment", title: "Texture" },
|
||||
{ align: "start", key: "name", title: "Name" },
|
||||
{ align: "start", key: "alias", title: "Alias" },
|
||||
{ align: "start", key: "actions", title: "Actions", sortable: false },
|
||||
],
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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(),
|
||||
}),
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
stickers.value = data["data"]
|
||||
pagination.stickers.total = data["count"]
|
||||
}
|
||||
reverting.stickers = false
|
||||
}
|
||||
|
||||
onMounted(() => readStickers({}))
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
async function deleteSticker(item: any) {
|
||||
submitting.value = true
|
||||
|
||||
const res = await solarFetch(`/cgi/uc/stickers/${item.id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
await readStickers({})
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-texture-cell {
|
||||
display: grid;
|
||||
grid-template-columns: 28px auto;
|
||||
gap: 6px;
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<v-dialog max-width="640">
|
||||
<template v-slot:activator="{ props }">
|
||||
<slot name="activator" v-bind="{ props }" />
|
||||
</template>
|
||||
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card title="Create Bot Key" :subtitle="`for bot @${props.item.name}`">
|
||||
<v-form @submit.prevent="(evt) => { submit(evt).then(() => isActive.value = false) }">
|
||||
<v-card-text class="pt-0 px-5">
|
||||
<v-expand-transition>
|
||||
<v-alert v-if="error" variant="tonal" type="error" class="text-xs mb-5">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field label="Name" name="name" variant="outlined" hide-details />
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-textarea auto-grow rows="1" label="Description" name="description" variant="outlined" hide-details />
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field type="number" label="Lifecycle" name="lifecycle" variant="outlined"
|
||||
hint="How long will this key last (in seconds)" clearable persistent-hint />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
text="Cancel"
|
||||
color="grey"
|
||||
@click="isActive.value = false"
|
||||
/>
|
||||
<v-btn
|
||||
text="Create"
|
||||
type="submit"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ item: any }>()
|
||||
const emits = defineEmits(["completed"])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
async function submit(evt: SubmitEvent) {
|
||||
const data: any = Object.fromEntries(new FormData(evt.target as HTMLFormElement).entries())
|
||||
if (!data.name) return
|
||||
|
||||
data.lifecycle = parseInt(data.lifecycle)
|
||||
if (Number.isNaN(data.lifecycle)) delete data.lifecycle
|
||||
|
||||
submitting.value = true
|
||||
|
||||
const res = await solarFetch(`/cgi/id/dev/bots/${props.item.id}/keys`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
if (res.status != 200) {
|
||||
error.value = await res.text()
|
||||
throw new Error(error.value)
|
||||
} else {
|
||||
emits("completed")
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
}
|
||||
</script>
|
@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<v-dialog max-width="640">
|
||||
<template v-slot:activator="{ props }">
|
||||
<slot name="activator" v-bind="{ props }" />
|
||||
</template>
|
||||
|
||||
<v-card title="Bot Keys" :subtitle="`of bot @${props.item.name}`">
|
||||
<v-card-text class="pb-0 pt-0">
|
||||
<v-card variant="outlined">
|
||||
<v-data-table-server
|
||||
density="default"
|
||||
:headers="dataDefinitions.keys"
|
||||
:items="keys"
|
||||
:items-length="pagination.keys.total"
|
||||
:loading="reverting.keys"
|
||||
v-model:items-per-page="pagination.keys.pageSize"
|
||||
@update:options="readKeys"
|
||||
item-value="id"
|
||||
class="overflow-y-auto text-no-wrap"
|
||||
>
|
||||
<template v-slot:item="{ item }: { item: any }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>
|
||||
<p>{{ item.name }}</p>
|
||||
<p class="text-xs">{{ item.description }}</p>
|
||||
</td>
|
||||
<td>{{ new Date(item.created_at).toLocaleString() }}</td>
|
||||
<td>
|
||||
<dev-bot-token-grant :item="item">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="info"
|
||||
icon="mdi-key-variant"
|
||||
/>
|
||||
</template>
|
||||
</dev-bot-token-grant>
|
||||
|
||||
<v-dialog max-width="480">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="error"
|
||||
icon="mdi-delete"
|
||||
:disabled="submitting"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card :title="`Delete token ${item.name}?`">
|
||||
<v-card-text>
|
||||
This action will delete the token and invalid it immediately.
|
||||
</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="Delete"
|
||||
color="error"
|
||||
@click="() => { revokeKey(item); isActive.value = false }"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-card>
|
||||
</v-card-text>
|
||||
|
||||
<div class="flex justify-end px-5.5 py-5">
|
||||
<dev-bot-token-create :item="props.item" @completed="readKeys({})">
|
||||
<template #activator="{ props }">
|
||||
<v-btn variant="flat" text="Create" append-icon="mdi-plus" v-bind="props" />
|
||||
</template>
|
||||
</dev-bot-token-create>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { solarFetch } from "~/utils/request"
|
||||
|
||||
const props = defineProps<{ item: any }>()
|
||||
|
||||
const keys = ref<any[]>([])
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
const dataDefinitions: { [id: string]: any[] } = {
|
||||
keys: [
|
||||
{ align: "start", key: "id", title: "ID" },
|
||||
{ align: "start", key: "name", title: "Name" },
|
||||
{ align: "start", key: "created_at", title: "Created At" },
|
||||
{ align: "start", key: "actions", title: "Actions", sortable: false },
|
||||
],
|
||||
}
|
||||
|
||||
const reverting = reactive({ keys: false })
|
||||
const pagination = reactive({
|
||||
keys: { page: 1, pageSize: 5, total: 0 },
|
||||
})
|
||||
|
||||
async function readKeys({ page, itemsPerPage }: { page?: number; itemsPerPage?: number }) {
|
||||
if (itemsPerPage) pagination.keys.pageSize = itemsPerPage
|
||||
if (page) pagination.keys.page = page
|
||||
|
||||
reverting.keys = true
|
||||
const res = await solarFetch(
|
||||
`/cgi/id/dev/bots/${props.item.id}/keys?` +
|
||||
new URLSearchParams({
|
||||
take: pagination.keys.pageSize.toString(),
|
||||
offset: ((pagination.keys.page - 1) * pagination.keys.pageSize).toString(),
|
||||
}),
|
||||
)
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
keys.value = data["data"]
|
||||
pagination.keys.total = data["count"]
|
||||
}
|
||||
reverting.keys = false
|
||||
}
|
||||
|
||||
onMounted(() => readKeys({}))
|
||||
|
||||
async function revokeKey(item: any) {
|
||||
submitting.value = true
|
||||
const res = await solarFetch(`/cgi/id/dev/bots/${item.account_id}/keys/${item.id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
await readKeys({ page: 1 })
|
||||
}
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
const submitting = ref(false)
|
||||
</script>
|
@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<v-dialog max-width="640">
|
||||
<template v-slot:activator="{ props }">
|
||||
<slot name="activator" v-bind="{ props }" />
|
||||
</template>
|
||||
|
||||
<v-card title="Bot Key" :subtitle="`#${props.item.id.toString().padStart(8, '0')}`">
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span>Granted</span>
|
||||
<v-icon :icon="getIcon(props.item.ticket.last_grant_at != null)" size="16" />
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span>Lifecycle</span>
|
||||
<span class="font-mono">{{ props.item.lifecycle ?? "-" }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-alert v-if="error" variant="tonal" type="error" class="text-xs mt-5">
|
||||
{{ t("errorOccurred", [error]) }}
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-expand-transition>
|
||||
<div v-if="token" class="flex flex-col gap-2 mt-5">
|
||||
<div>
|
||||
<p class="mb-0.25">Access Token</p>
|
||||
<v-code class="font-mono px-3 mx-[-4px] overflow-y-auto text-no-wrap">
|
||||
{{ token.access_token }}
|
||||
</v-code>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0.25">Refresh Token</p>
|
||||
<v-code class="font-mono px-3 mx-[-4px] overflow-y-auto text-no-wrap">
|
||||
{{ token.refresh_token }}
|
||||
</v-code>
|
||||
</div>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</v-card-text>
|
||||
|
||||
<div class="flex justify-end px-5.5 py-5">
|
||||
<v-btn variant="tonal" text="Roll / Grant" append-icon="mdi-refresh" :loading="submitting" @click="getToken" />
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const props = defineProps<{ item: any }>()
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
const token = ref<null | { access_token: string, refresh_token: string }>(null)
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
function getIcon(value: boolean): string {
|
||||
if (value) return "mdi-check"
|
||||
else return "mdi-close"
|
||||
}
|
||||
|
||||
async function getToken() {
|
||||
submitting.value = true
|
||||
|
||||
let code = props.item.ticket.grant_token
|
||||
if (props.item.ticket.last_grant_at != null) {
|
||||
const res = await solarFetch(`/cgi/id/dev/bots/${props.item.account_id}/keys/${props.item.id}/roll`, {
|
||||
method: "POST",
|
||||
})
|
||||
if (res.status != 200) {
|
||||
error.value = await res.text()
|
||||
submitting.value = false
|
||||
return
|
||||
} else {
|
||||
code = (await res.json()).ticket.grant_token
|
||||
}
|
||||
}
|
||||
|
||||
const res = await solarFetch("/cgi/id/auth/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
grant_type: "grant_token",
|
||||
code: code,
|
||||
}),
|
||||
})
|
||||
if (res.status != 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
token.value = await res.json()
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
}
|
||||
</script>
|
@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<v-list-item :active="route.hash.replace('#', '') == link.id" :to="{ hash: '#'+link.id }">
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-menu-right" :style="`padding-left: ${props.padding ?? 0}rem`" />
|
||||
</template>
|
||||
{{ link.text }}
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ link: any, padding?: number }>()
|
||||
|
||||
const route = useRoute()
|
||||
</script>
|
@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<template v-for="link in links">
|
||||
<docs-table-of-content-link :link="link" :padding="props.padding" />
|
||||
|
||||
<table-of-content-links v-if="link.children" :links="link.children" :padding="(props.padding ?? 0) + 2" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ links: any[], padding?: number }>()
|
||||
</script>
|
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<v-list density="compact" nav color="primary">
|
||||
<template v-for="link in links">
|
||||
<docs-table-of-content-link :link="link" :padding="props.padding" />
|
||||
|
||||
<docs-table-of-content-links v-if="link.children" :links="link.children" :padding="(props.padding ?? 0) + 2" />
|
||||
</template>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ links: any[], padding?: number }>()
|
||||
</script>
|
30
components/posts/PostContent.tsx
Normal file
30
components/posts/PostContent.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { PortableText } from "@portabletext/react";
|
||||
import { client } from "@/sanity/lib/client";
|
||||
import imageUrlBuilder from "@sanity/image-url";
|
||||
import Link from "next/link";
|
||||
import ImageViewer from "@/components/articles/ImageViewer";
|
||||
|
||||
export default function PostContent({ content }: { content: any }) {
|
||||
const imageBuilder = imageUrlBuilder(client);
|
||||
|
||||
const componentSet = {
|
||||
types: {
|
||||
image: ({ value }: any) => {
|
||||
const image = imageBuilder.image(value);
|
||||
return <ImageViewer src={image.url()} alt={value.alt} />;
|
||||
}
|
||||
},
|
||||
marks: {
|
||||
link: ({ children, value }: any) => {
|
||||
const rel = !value.href.startsWith("/") ? "noreferrer noopener" : undefined;
|
||||
return (
|
||||
<Link href={value.href} rel={rel}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <PortableText value={content} components={componentSet} />;
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<v-carousel show-arrows="hover" cycle hide-delimiters>
|
||||
<v-carousel-item v-for="(item, i) in props.products" :key="i" :src="item?.thumbnail" cover>
|
||||
<v-sheet color="rgba(0, 0, 0, .4)" height="calc(100% + 24px)" class="p-5">
|
||||
<v-row class="fill-height" align="center" justify="center">
|
||||
<v-col class="text-center">
|
||||
<h1 class="text-4xl font-bold text-white" :class="item?.archived ? 'line-through' : null">
|
||||
{{ item?.title }}
|
||||
</h1>
|
||||
<p class="text-lg text-white">{{ item?.description }}</p>
|
||||
|
||||
<div class="flex justify-center mt-3">
|
||||
<v-btn variant="text" color="white" prepend-icon="mdi-school" :text="t('learnMore')" :to="item._path" />
|
||||
<v-btn
|
||||
v-if="item?.url"
|
||||
variant="text"
|
||||
color="white"
|
||||
prepend-icon="mdi-launch"
|
||||
:text="t('open')"
|
||||
:href="item?.url"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<v-chip
|
||||
v-if="item?.archived"
|
||||
label
|
||||
prepend-icon="mdi-archive"
|
||||
variant="text"
|
||||
color="warning"
|
||||
size="small"
|
||||
>
|
||||
{{ t("productArchived") }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ products: any[] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
icon: mdi-airplane-landing
|
||||
title: Welcome to Landing
|
||||
description: Welcome to Solsynth's Knowledge Base - The Solar Archive
|
||||
---
|
||||
|
||||
![Solar Archive Thumbnail](/thumbnails/docs/solar-archive-thumbnail.webp)
|
||||
|
||||
Welcome to the Solsynth Archive!
|
||||
|
||||
The Solsynth Archive, also known as the Solar Archive, is the largest known product database of Solsynth LLC. It is operated by Solsynth LLC and community-driven, with content and resources provided by the community, while being officially monitored and corrected.
|
||||
|
||||
The archive is still under construction, but in the future, you will be able to access all our materials here.
|
||||
|
||||
You can contribute to our documentation by forking our [Capital](https://git.solsynth.dev/Goatworks/Capital) repository and submitting PRs to modify the files under `content/<lang>/docs`. Contributions are welcome, whether it’s adding new content or correcting inaccuracies.
|
||||
|
||||
*P.S. You can use Solarpass for one-click login to the Solsynth Code Repository.*
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
icon: mdi-web
|
||||
title: Solar Network
|
||||
description: The Next-Generation Social Network by Solsynth LLC
|
||||
---
|
||||
|
||||
![Solar Archive Thumbnail](/thumbnails/docs/solar-network-user-manual.webp)
|
||||
|
||||
Solar Network is a social network developed by Solsynth LLC, aiming to become the next-generation social network.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
The Solar Network project follows the classic frontend-backend separation architecture, with the entire project divided into two parts: the frontend (Solian) and the backend (Hydrogen.Dealer, Hydrogen.Passport, etc.).
|
||||
|
||||
### Frontend
|
||||
|
||||
The frontend of Solar Network is a cross-platform client built with Flutter. For more details, check the [related page](solar-network/solian).
|
||||
|
||||
### Backend
|
||||
|
||||
The backend of Solar Network follows what we define as a "mid-service" architecture. Compared to microservices, each individual service is responsible for more tasks and is larger in scope, allowing us to maintain multiple projects more easily. At the same time, issues with a single service won't cause an overall outage.
|
||||
|
||||
At the center of it all is our core service — Hydrogen.Dealer, which serves as both the service discovery system and mid-service gateway. It is also the only external interface for Solar Network, with `api.sn.solsynth.dev` being the gateway exposed by Hydrogen.Dealer.
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
icon: mdi-palette
|
||||
title: Creator Program
|
||||
description: Welcome to the Solar Network Creator Program, Let's Co-create Solar Network
|
||||
---
|
||||
|
||||
The Creator Program is an initiative by Solar Network designed to encourage users to create content. The goal of the program is to help creators use Solar Network to produce higher-quality content.
|
||||
|
||||
Join the Creator Program to receive more official support, get early access to new features, and provide valuable feedback to shape the future of Solar Network!
|
@ -1,38 +0,0 @@
|
||||
---
|
||||
icon: mdi-sticker-emoji
|
||||
title: Stickers and Sticker Packs
|
||||
description: Stickers, Emotes, and Emojis
|
||||
---
|
||||
|
||||
Stickers help users express their emotions better on Solar Network. This article introduces how to upload and use a sticker.
|
||||
|
||||
## Sticker Packs
|
||||
|
||||
Stickers must be part of a sticker pack. To create a sticker pack, go to the "Creator Hub" > "Stickers" section on the website sidebar.
|
||||
|
||||
## Stickers
|
||||
|
||||
Before creating a sticker, you’ll need to prepare the content. It is recommended to use a PNG or GIF image that is 1024x1024 pixels (minimum 128x128). The background can be transparent or not, but avoid solid color fills. Large white fills may cause discomfort for users in dark mode, akin to a flashbang.
|
||||
|
||||
Once the sticker pack is created, open it and select the plus symbol under "Actions" to add a sticker.
|
||||
|
||||
You might need to upload an attachment for the sticker material. After uploading, fill in the "Attachment" field with the Random ID (the series of characters after the #) from the completed upload. If the content displays correctly, the connection is successful.
|
||||
|
||||
After that, simply fill out the form to finish.
|
||||
|
||||
## Usage
|
||||
|
||||
To use a sticker, type a placeholder in your content, formatted as `:<pack prefix><sticker alias>:`.
|
||||
For example, if a sticker pack has the prefix `solar` and a sticker alias `Hello`, the resulting placeholder would be `:solarHello:`.
|
||||
|
||||
Don’t worry if that seems confusing—most of the time, we provide automatic suggestions. Simply type a colon in the Solian text box, followed by part of the placeholder, and suggestions will appear.
|
||||
|
||||
### Size Variations
|
||||
|
||||
In Solar Network, you may notice stickers appear in different sizes due to Smart Resize. The rules are as follows:
|
||||
|
||||
1. If only one sticker is present, it will appear at 128x128.
|
||||
2. If three or fewer stickers are present, they will appear at 32x32.
|
||||
3. If more than three stickers are present, or if stickers are embedded within text, they will appear at 20x20.
|
||||
|
||||
These adjustments are applied within a single paragraph.
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
icon: mdi-oci
|
||||
title: Open Program
|
||||
description: Welcome to the Solar Network Open Program, Let Us Help Your Application Grow
|
||||
---
|
||||
|
||||
The Open Program is a collection of developer-friendly APIs and tools from Solar Network.
|
||||
We adhere to principles that aim not to burden developers — no unnecessary encryption of parameters, no parameter obfuscation, and user-friendly API interfaces. We provide RESTful API endpoints to ensure the best possible developer experience.
|
||||
|
||||
Start exploring now and see what you can do with Solar Network!
|
@ -1,71 +0,0 @@
|
||||
---
|
||||
icon: mdi-pencil-ruler
|
||||
title: API Standards
|
||||
description: The guidelines we follow when designing Solar Network service APIs
|
||||
---
|
||||
|
||||
This article covers the paradigms we follow when designing Solar Network APIs, helping you better interact with our APIs for secondary development.
|
||||
|
||||
## Minimization
|
||||
|
||||
Our APIs aim to be minimalistic. Unlike some major platforms, where the response includes not only data but also a bunch of status codes, messages, and request IDs, we keep such information in the HTTP headers. The HTTP response body contains only the raw data, with no extra information (for paginated endpoints, an additional field for total count will be included).
|
||||
|
||||
## CRUD Operations
|
||||
|
||||
Our APIs generally follow RESTful design patterns. If you're unfamiliar with RESTful principles, here’s how we practice it:
|
||||
|
||||
### Request Methods
|
||||
|
||||
- `GET` for fetching data
|
||||
- `POST` for creating or performing some operations
|
||||
- `PUT` for updating (though in RESTful principles it's also defined for creation, we don’t use it that way)
|
||||
- `PATCH` for updating (rarely used)
|
||||
- `DELETE` for removing data
|
||||
|
||||
### Path Mapping
|
||||
|
||||
If you use `POST` to create data at an endpoint, using `GET` on the same endpoint will typically list the data.
|
||||
Appending `/<id>` to the path will fetch a specific data entry. Switching the request method to `PUT` updates the entry, and using `DELETE` removes it.
|
||||
If additional actions are needed, append paths after `/<id>`, usually for operations handled via `POST`.
|
||||
|
||||
Here’s an example of path mapping for posts:
|
||||
|
||||
*Note: `:id` is a path parameter.*
|
||||
|
||||
- `GET /posts` - Retrieves a list of posts (paginated)
|
||||
- `GET /posts/:id` - Retrieves a specific post
|
||||
- `GET /posts/:id/replies` - Retrieves replies for a specific post (paginated)
|
||||
- `POST /posts` - ~~Creates a post~~ (removed in the new version due to post types; use the specific post type creation endpoint)
|
||||
- `PUT /posts/:id` - ~~Updates a post~~ (removed in the new version due to post types; use the specific post type update endpoint)
|
||||
- `DELETE /posts/:id` - Deletes a post
|
||||
- `POST /posts/:id/pin` - Pins a post
|
||||
- `POST /posts/:id/react` - Reacts to a post
|
||||
|
||||
## Error Handling
|
||||
|
||||
We don’t understand why, despite HTTP providing a complete set of status codes, other large companies still create their own. For HTTP status codes, here’s a summary of common meanings:
|
||||
|
||||
- `500` - Internal Server Error — No need to worry; just file an issue if it happens frequently.
|
||||
- `400` - Bad Request — Check the documentation and request body.
|
||||
- `404` - Data not found or incorrect API path.
|
||||
- `403` - Forbidden — You don’t have permission.
|
||||
- `401` - Unauthorized — API token required but not provided.
|
||||
- `200` - Success
|
||||
- `204` - No Content — Common for delete operations (though often forgotten during API development).
|
||||
|
||||
If the response status is not `2xx`, we usually return a `plain/text` response instead of `application/json`, providing a simple line of text indicating the error.
|
||||
|
||||
> If you’re not good at English, don’t keep asking us about errors — use a translator! Why else would we write error messages?
|
||||
|
||||
## Super Gateway
|
||||
|
||||
The Super Gateway refers to our [Hydrogen.Dealer](https://git.solsynth.dev/Hydrogen/Dealer). In most cases, you won’t directly access our services; requests are forwarded through the Dealer gateway. We’re not even sure why we created this.
|
||||
|
||||
Our API base URL is `api.sn.solsynth.dev`. How do you use it? It’s simple. Access `/cgi/<service name>`, and this path will be forwarded to the corresponding service’s `/api` endpoint. In the latest version, we also introduced aliases for these services, making the URLs more readable.
|
||||
|
||||
- `/cgi/id` or `/cgi/auth` — Authentication service [Hydrogen.Passport](https://git.solsynth.dev/Hydrogen/Passport)
|
||||
- `/cgi/uc` or `/cgi/files` — Attachment service [Hydrogen.Paperclip](https://git.solsynth.dev/Hydrogen/Paperclip)
|
||||
- `/cgi/co` or `/cgi/interactive` — Post service [Hydrogen.Interactive](https://git.solsynth.dev/Hydrogen/Interactive)
|
||||
- `/cgi/im` or `/cgi/messaging` — Messaging service [Hydrogen.Messaging](https://git.solsynth.dev/Hydrogen/Messaging)
|
||||
|
||||
> Fun fact: You might have noticed that the new aliases are actually the subdomains used before we had the Super Gateway.
|
@ -1,104 +0,0 @@
|
||||
---
|
||||
icon: mdi-open-in-app
|
||||
title: Solian Chain
|
||||
description: Solian is the official cross-platform client developed by Solsynth LLC.
|
||||
---
|
||||
|
||||
Solian is the cross-platform Solar Network client built with Flutter, and currently, it’s our only frontend.
|
||||
|
||||
# Usage
|
||||
|
||||
To use Solian, you can either download the client or open it directly in your browser. Thanks to Flutter’s cross-platform support, you can access the web version of Solian at https://lian.solsynth.dev. However, due to browser limitations, some features may be missing or affected.
|
||||
|
||||
## Download
|
||||
|
||||
There are many ways to download Solsynth, but make sure to download from officially certified channels.
|
||||
|
||||
1. The official release version from the repository: https://git.solsynth.dev/Hydrogen/Solian/releases
|
||||
2. The test version from the official file storage: https://files.solsynth.dev/production01/solian
|
||||
3. Official TestFlight (iOS and some macOS): https://testflight.apple.com/join/YJ0lmN6O
|
||||
|
||||
The Windows version is a portable version. You can place it in a directory you're familiar with and run it directly.
|
||||
The web version also supports PWA (Progressive Web Application), which can replace some desktop usage.
|
||||
|
||||
## Installation
|
||||
|
||||
Below are the technical instructions for installing Solian on different platforms.
|
||||
|
||||
### Android
|
||||
|
||||
It is recommended to download the latest test version from the **file storage**. It has the latest fixes and is the most stable. ~~The test version is more stable than the stable version.~~
|
||||
|
||||
You can open and install the downloaded APK file directly. For Chinese phones, additional steps may be required for verification, but please avoid searching for and downloading from built-in app stores.
|
||||
|
||||
### iOS/macOS
|
||||
|
||||
Use TestFlight for installation. First, click the link above to download the TestFlight app. Then, click "Start Testing" in the second step of the link to join the test.
|
||||
|
||||
TestFlight has a limited number of testing slots. Once the time is right, we will release Solian on the App Store (non-China region), where you can search and download it.
|
||||
|
||||
### Windows
|
||||
|
||||
After downloading from any trusted source, extract it to a directory, and you can start using it.
|
||||
|
||||
**Note:** It seems that, due to a potential Flutter support issue, the Windows version often freezes for a while during the first startup before displaying the main window. Please be patient and avoid clicking repeatedly, as it may take 5 to 30 seconds. Repeated clicking may open multiple windows.
|
||||
|
||||
### Linux
|
||||
|
||||
Please build it yourself. I believe you can do it — good luck!
|
||||
|
||||
## Build It Yourself
|
||||
|
||||
### Preparing the Environment
|
||||
|
||||
Building Solian requires the Flutter SDK. Please download the latest version from the official site. Alternatively, you can download it from a China mirror.
|
||||
After installing Flutter, follow the official documentation to install other platform-specific dependencies (e.g., Windows requires VS2022, Android requires Android Studio, and for iOS/macOS, it’s better to use the official pre-built version).
|
||||
|
||||
In addition to installing the Flutter SDK, we need Rust for system-level dependencies. Please download the latest version from the official Rust site.
|
||||
|
||||
Now that we have Flutter and Rust, we need one more thing — SQLite3, to support local databases for chat and future modules.
|
||||
For Linux, you need to install the corresponding SQLite3 development dependencies:
|
||||
|
||||
```sh
|
||||
# for ubuntu
|
||||
sudo apt-get -y install libsqlite3-0 libsqlite3-dev
|
||||
```
|
||||
|
||||
For Windows, download the
|
||||
[sqlite3.dll](https://github.com/tekartik/sqflite/raw/master/sqflite_common_ffi/lib/src/windows/sqlite3.dll)
|
||||
and place it in the running directory.
|
||||
No additional steps are needed for macOS or mobile builds.
|
||||
|
||||
### Building the Code
|
||||
|
||||
Next, it’s time to build the code. Ensure that you have `git` installed on your build machine. Alternatively, you can download the code as a compressed archive.
|
||||
Once `git` is installed, use the following command to clone the code:
|
||||
|
||||
```sh
|
||||
git clone https://git.solsynth.dev/Hydrogen/Solian.git
|
||||
```
|
||||
|
||||
Navigate to the corresponding directory and install dependencies using the following command:
|
||||
|
||||
```sh
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
This will download dependencies from [pub.dev](https://pub.dev), hosted by Google. Connectivity within mainland China might be questionable. Refer to mirror sites for solutions.
|
||||
|
||||
Once the dependencies are installed, you can proceed with the build. Just one line of code:
|
||||
|
||||
```sh
|
||||
# for windows
|
||||
flutter build windows
|
||||
# for macos
|
||||
flutter build macos
|
||||
# for linux
|
||||
flutter build linux
|
||||
# for ios
|
||||
flutter build ipa
|
||||
# for android
|
||||
flutter build apk
|
||||
```
|
||||
|
||||
You can also build other formats for Android, such as `aab`, but please prepare the necessary signing materials yourself.
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/acefield.webp
|
||||
title: AceField
|
||||
description: An experimental multiplayer top-down view shooting game that created by Solsynth LLC affiliation Highland Entertainment.
|
||||
author: [littlesheep]
|
||||
url: https://files.solsynth.dev/production01/acefield
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 or above
|
||||
url: https://files.solsynth.dev/production01/acefield/AceField_MacOS_arm64.dmg
|
||||
- title: Windows
|
||||
icon: mdi-microsoft-windows
|
||||
desc: Windows 7 or above
|
||||
url: https://files.solsynth.dev/production01/acefield/Windows_x86_64.zip
|
||||
- title: Linux
|
||||
icon: mdi-linux
|
||||
desc: Any linux distro
|
||||
url: https://files.solsynth.dev/production01/acefield/Linux_x86_64.zip
|
||||
---
|
||||
|
||||
AceField which is stands for wonderful place to battle.
|
||||
We can't just use the name Battlefield because it already became a trademark of Electronic Arts.
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=914}
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/dietary-guard.webp
|
||||
title: DietaryGuard
|
||||
description: A simple app that help you keep dietary, so not die.
|
||||
author: [littlesheep]
|
||||
downloads:
|
||||
- title: iOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 10 or above, via Testflight
|
||||
url: https://testflight.apple.com/join/pYb6wRbr
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 or above
|
||||
url: https://files.solsynth.dev/production01/dietary-guard/app-arm64-v8a-release.apk
|
||||
---
|
||||
|
||||
A simple app to look up the ingredients of your food,
|
||||
with a little something along the way to keep you from eating something you shouldn't eat and going to the hospital.
|
||||
|
||||
## Highlight
|
||||
|
||||
1. Get authoritative and accurate food nutritional data via USDA FoodData Central API.
|
||||
2. Customize the alert rules to know at a glance what food is not suitable for you to eat.
|
||||
3. Simple and easy to use
|
||||
4. Lightweight software, less than 8M.
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=887}
|
@ -1,55 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/rhythm-box.webp
|
||||
title: RhythmBox
|
||||
description: Yet another Spotify third-party client.
|
||||
author: [littlesheep]
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 12 or above, via sideload
|
||||
url: https://files.solsynth.dev/production01/rhythm-box/ios-ipa.ipa
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 or above. Due to codesign issue, please compile for your self.
|
||||
url: https://git.solsynth.dev/LittleSheep/RhythmBox
|
||||
- title: Windows
|
||||
icon: mdi-microsoft-windows
|
||||
desc: Built by GitHub Actions
|
||||
url: https://github.com/Solsynth/RhythmBox
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 or above
|
||||
url: https://files.solsynth.dev/production01/rhythm-box/app-arm64-v8a-release.apk
|
||||
---
|
||||
|
||||
Another Spotify third-party client. Multi-platform support, as it is built using Flutter.
|
||||
|
||||
The project is inspired and supported by [spotube](https://spotube.krtirtho.dev).
|
||||
Their original app is good enough. But I just want to redesign the UI and make it ready to add more features and more backend support.
|
||||
|
||||
## Highlights
|
||||
|
||||
Compared to the original spotube. The project adds more audio sources, such as Netease Cloud Music, Kugou, and provides the ability to use it in mainland China.
|
||||
|
||||
The project also focuses on the playback experience of VOCALOID songs.
|
||||
We improved the search and ranking algorithms so that queries will select fewer cover versions in favor of the original.
|
||||
|
||||
Due to the termination of jiosaavn's service in Asia (other regions may also be affected). We removed the jiosaavn audio source.
|
||||
|
||||
## Roadmap
|
||||
|
||||
Seet at [GitHub](https://github.com/Solsynth/RhythmBox) or [Solsynth Git Repository](https://git.solsynth.dev/LittleSheep/RhythmBox)
|
||||
|
||||
## License
|
||||
|
||||
This project is open source under the APGLv3 license. The original spotube project is open source under the BSD-Clause4 license, copyright Kingkor Roy Tirtho.
|
||||
|
||||
All rights to this project are owned by LittleSheep and Solsynth LLC.
|
||||
|
||||
## Download
|
||||
|
||||
**Note: The Windows version is built via Github Actions. To download, go to the GitHub repository at the link below, find the checkmark next to the most recent commit, and select the `Details` item in the `build-exe` pop-up window. Expand the step-by-step log for Archive production artifacts, which will contain a download link to unzip it. You will need to be logged into your GitHub account to download.**
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=923}
|
@ -1,48 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/roadsign.webp
|
||||
title: RoadSign
|
||||
description: The HTTP server that powered us. Great ability, and easy to use
|
||||
author: [littlesheep]
|
||||
---
|
||||
|
||||
RoadSign is an HTTP server developed by Solsynth LLC.
|
||||
Its support for HTTP protocol is not excellent, but it is definitely handy for accelerating your project deployment!
|
||||
It even made us abandon Netlify and Vercel.
|
||||
|
||||
## Highlight Features
|
||||
|
||||
- RoadSign CLI deploys projects with one line of command
|
||||
- Full control over your traffic
|
||||
- Featured Transformer to modify requests
|
||||
- Built-in Warden thread management
|
||||
|
||||
## Installation
|
||||
|
||||
It is recommended to use docker for installation. The following is an example docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
roadsign:
|
||||
image: xsheep2010/roadsign:delta
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 81:81
|
||||
volumes:
|
||||
- "/srv/roadsign/config:/config"
|
||||
- "/srv/roadsign/workdir:/workdir"
|
||||
- "/srv/roadsign/settings.toml:/settings.toml"
|
||||
```
|
||||
|
||||
It is recommended to have RoadSign behind a real reverse proxy, so do not listen to 443 and 80 here, use 8000 to let the reverse proxy do the upstream.
|
||||
Port 81 is the management API port that the side-loading API needs to use, which can be changed in the settings.
|
||||
|
||||
It is also recommended to install RoadSign CLI on your local machine
|
||||
|
||||
```sh
|
||||
$ npm i -g roadsign-cli
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
Watch the full RoadSign CLI deployment project demo at Asciiema 👉 https://asciinema.org/a/678744
|
@ -1,39 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/solar-network.webp
|
||||
title: Solar Network
|
||||
description: Next Generation Network Center
|
||||
author: [littlesheep]
|
||||
url: https://sn.solsynth.dev
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 or above, via Testflight
|
||||
url: https://testflight.apple.com/join/YJ0lmN6O
|
||||
- title: iOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 12 or above, via Testflight
|
||||
url: https://testflight.apple.com/join/YJ0lmN6O
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 or above
|
||||
url: https://files.solsynth.dev/production01/solian/app-arm64-v8a-release.apk
|
||||
- title: Web
|
||||
icon: mdi-web
|
||||
desc: Web-based version with support for major browsers
|
||||
url: https://sn.solsynth.dev
|
||||
---
|
||||
|
||||
Solar Network is a groundbreaking, multi-functional platform that seamlessly integrates social interaction, real-time chat, and high-quality audio and video calls to create an engaging and interactive unified experience.
|
||||
With Solar Network, users can not only easily create and manage their own communities, but also stay connected with friends, fans and team members anytime, anywhere.
|
||||
Whether it's discussing work projects, sharing life moments, or enjoying a fun time, Solar Network provides a smooth and efficient way to communicate.
|
||||
The platform is designed to meet a wide range of social needs, enabling every user to find a sense of belonging and build deep connections with others in a welcoming and diverse community.
|
||||
|
||||
## Support
|
||||
|
||||
If you need any support from us, go ahead and email [lily@solsynth.dev](mailto:lily@solsynth.dev). We're always waiting for you.
|
||||
|
||||
## Download
|
||||
|
||||
Download Solar Network official client Solian to use Solar Network.
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
@ -1,50 +0,0 @@
|
||||
---
|
||||
title: Privacy Policy
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
We take your privacy seriously.
|
||||
This privacy policy outlines the types of personal information we collect,
|
||||
how we use it, and the measures we take to protect your data.
|
||||
|
||||
## Information Collection
|
||||
|
||||
We collect personal information only when necessary to provide our services.
|
||||
This may include your name, email address, and other relevant details.
|
||||
|
||||
## Use of Information
|
||||
|
||||
We use your personal information to:
|
||||
|
||||
- Provide and improve our services
|
||||
- Communicate with you about updates or important information
|
||||
- Ensure compliance with legal obligations
|
||||
|
||||
## Data Sharing
|
||||
|
||||
We do not sell, trade, or share your personal information with third parties except as required by law.
|
||||
|
||||
## Data Security
|
||||
|
||||
We implement robust security measures to protect your personal information from unauthorized access,
|
||||
alteration, disclosure, or destruction.
|
||||
|
||||
## Your Rights
|
||||
|
||||
You have the right to:
|
||||
|
||||
- Access the personal information we hold about you
|
||||
- Request corrections to your personal information
|
||||
- Request the deletion of your personal information
|
||||
|
||||
## Contact Us
|
||||
|
||||
If you have any questions or concerns about this privacy policy or our data practices,
|
||||
please contact us at lily@solsynth.dev.
|
||||
|
||||
## Changes to This Policy
|
||||
|
||||
We may update this privacy policy from time to time.
|
||||
Any changes will be posted on this page, and we will notify you of any significant changes.
|
@ -1,77 +0,0 @@
|
||||
---
|
||||
title: User Agreement
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
This Agreement applies to all Solsynth LLC products, including but not limited to Solar Network, Solian, DietaryGuard, AceField.
|
||||
|
||||
## Provision and Discontinuance of Service
|
||||
|
||||
Solsynth LLC will provide equal service to all living things in the world, including grasshoppers.
|
||||
We also reserve the right to stop service to any user. We do not require prior notice for discontinuing services to some users.
|
||||
|
||||
## User Generated Content
|
||||
|
||||
Any content posted on Solar Network (including but not limited to posts, articles, attachments) grants Solsynth LLC the right to display it by default.
|
||||
Unless otherwise stated by the user, all rights are reserved by the original poster, and reprints should be authorized by the original poster.
|
||||
|
||||
### Reproduction Recognition
|
||||
|
||||
Unless specifically stated by the poster, all content is subject to the definition of reprint in this section.
|
||||
|
||||
Republishing means uploading the content of the original post to another platform or to the Solar Network, either unchanged or with minor modifications, provided that simultaneous reposting of the post, embedded components, and links to the presentation do not constitute republishing.
|
||||
Republishing also requires attribution when authorized by the original poster.
|
||||
|
||||
### Freedom of Speech
|
||||
|
||||
We do not remove user-generated content except in cases of misuse of resources. We will not ask any user to remove any content.
|
||||
|
||||
However, Solsynth LLC reserves the right to restrict and stop the display of content to the public that violates community guidelines (e.g., obscenity, violence, gore, anti-social, terrorist organizations, etc.).
|
||||
|
||||
Although you have 100% freedom of speech on Solar Network. However, please be aware that freedom of speech does not mean that you will not be held accountable for what you say.
|
||||
|
||||
#### Restriction and Discontinuation
|
||||
|
||||
- Restriction of Display: Discontinuation of related tweets, while retaining the right to access them directly through resource identifiers and sharing links.
|
||||
|
||||
- Cease display: stop all access to the resource by anyone other than the author.
|
||||
|
||||
## Resource Misuse Prevention Policy
|
||||
|
||||
Although there are no capacity limitations for using Solar Network's data hosting services, resources determined to be abusive will be disenfranchised from some features.
|
||||
Solsynth LLC reserves the right to reclaim space on previously uploaded resources for deletion.
|
||||
|
||||
### Determination of Misuse
|
||||
|
||||
- Uploading without using: e.g. uploading excessive attachments in Solar Network's Interactive Attachment Pool and not linking them to posts.
|
||||
- Meaningless Posts: meaningless shuffling or wasting of Solar Network's storage resources
|
||||
- Misuse: using Solar Network's public resources as if they were your own dedicated pool (see the Wiki's Dedicated Pools page for details).
|
||||
|
||||
The Solsynth Trust & Safety Team is ultimately responsible for determining misuse.
|
||||
|
||||
## Secondary Releases
|
||||
|
||||
A secondary release is when our assets are downloaded and re-hosted on another site.
|
||||
|
||||
### Product Secondary Release
|
||||
|
||||
Unless otherwise stated, Solsynth LLC products are not available for secondary distribution, please do not download our product builds and upload them twice to another site.
|
||||
Please do not download our product builds and upload them to other sites. **Secondary distribution for commercial use is not permitted. **.
|
||||
|
||||
What you should do is post a link to our product on another site. Or use the embedded component. And indicate Solsynth LLC All Rights Reserved.
|
||||
|
||||
If you want to build a mirror site of our products, please contact us to waive this rule.
|
||||
|
||||
### Secondary distribution of source code
|
||||
|
||||
We do not allow any form of redistribution of source code (except for Forks).
|
||||
This includes, but is not limited to, mirroring code repositories on GitHub or the Solsynth Code Repository to other Git providers such as GitLab, Gitee, and so on.
|
||||
**Selling source code twice is not allowed. **
|
||||
|
||||
For more information on source code usage regulations, please follow the open source license used by the project.
|
||||
|
||||
If you would like to set up a mirror of our source code, please contact us to waive this policy.
|
||||
|
||||
*****
|
||||
|
||||
Solsynth LLC reserves the right of final interpretation of this agreement.
|
@ -1,18 +0,0 @@
|
||||
---
|
||||
icon: mdi-airplane-landing
|
||||
title: 欢迎着陆
|
||||
description: 欢迎来到 Solsynth 的知识库 —— 太阳能档案
|
||||
---
|
||||
|
||||
![Solar Archive Thumbnail](/thumbnails/docs/solar-archive-thumbnail.webp)
|
||||
|
||||
欢迎来到 Solsynth 资料库!
|
||||
|
||||
Solsynth 资料库,又称太阳档案馆,现知的最大 Solsynth LLC 产品资料库。由 Solsynth LLC 运营。由社区提供内容及资源,官方监督改正。
|
||||
|
||||
我们目前还在修建档案馆,但是未来这里将可以查阅到我们的所有资料。
|
||||
|
||||
你可以通过 Fork 我们的 [Capital](https://git.solsynth.dev/Goatworks/Capital) 来编辑 `content/<lang>/docs` 内部的文件并提交 PR 来贡献我们的文档。
|
||||
无论是新增内容、修改不正确的地方,都欢迎贡献。
|
||||
|
||||
*P.S. 你可以使用 Solarpass 一键登陆 Solsynth Code Repository*
|
@ -1,25 +0,0 @@
|
||||
---
|
||||
icon: mdi-web
|
||||
title: Solar Network
|
||||
description: Solsynth LLC 的下一代社交网络
|
||||
---
|
||||
|
||||
![Solar Archive Thumbnail](/thumbnails/docs/solar-network-user-manual.webp)
|
||||
|
||||
Solar Network 是 Solsynth LLC 开发的社交网络,目标成为下一代社交网络。
|
||||
|
||||
## 技术栈
|
||||
|
||||
Solar Network 项目是经典的前后端分离项目,整体项目分为两部分:前端 (Solian),后端 (Hydrogen.Dealer, Hydrogen.Passport, etc...)
|
||||
|
||||
### 前端
|
||||
|
||||
Solar Network 的前端是 Flutter 构建的全平台支持客户端,详情可以查看 [相关页面](solar-network/solian)
|
||||
|
||||
### 后端
|
||||
|
||||
Solar Network 的后端根据我们的定义是一个「中服务」架构,它相比微服务相比,单个服务负责的东西更多,体量更大,这可以帮我们更好地维护多个项目,
|
||||
但同时部分服务出现问题不会造成整体 Outage
|
||||
|
||||
在这一切的中心是我们的核心服务 —— Hydrogen.Dealer,服务发现和中服务网关,同时是唯一一个 Solar Network 地外部接口,
|
||||
`api.sn.solsynth.dev` 就是 Hydrogen.Dealer 暴露出来的网关
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
icon: mdi-palette
|
||||
title: 创作者计划
|
||||
description: 欢迎来到 Solar Network 创作者计划,在这里共创 Solar Network
|
||||
---
|
||||
|
||||
创作者计划是 Solar Network 为了推动用户创作内容而诞生的一个企划,企划旨在帮助创作者更好的使用 Solar Network 产出更高质量的内容。
|
||||
|
||||
加入创作者计划,获得更多官方支持,第一时间使用新功能,为未来的 Solar Network 提出宝贵的建议吧!
|
@ -1,40 +0,0 @@
|
||||
---
|
||||
icon: mdi-sticker-emoji
|
||||
title: 贴图及贴图包
|
||||
description: Stickers, Emotes 和 Emoji
|
||||
---
|
||||
|
||||
贴图可以帮助用户通过 Solar Network 更好的表达他们的情绪,这篇文章将会向你介绍如何上传、使用一个贴图
|
||||
|
||||
## 贴图包
|
||||
|
||||
贴图必须跟随一个贴图包,要创建一个贴图包,你可以转到官网侧边栏的「Creator Hub」>「Stickers」来创建一个
|
||||
|
||||
## 贴图
|
||||
|
||||
贴图的创建,你首先需要准备内容。建议为一个 1024x1024 像素大小(最小为 128x128 的大小,否则可能造成效果不佳)的 PNG 或 GIF 图片,
|
||||
透明背景或内容背景均可,但请不要使用纯色填充。大面积的白色填充可能会给暗色模式的用户带来闪光弹。
|
||||
|
||||
之后打开刚刚创建的贴图包,在「Actions」中点选加号标识来创建一个贴图。
|
||||
|
||||
你可能需要上传一个附件来添加贴图的材质,并使用上传完成后界面的 Random ID (井号后面的一连串字符) 填写在 Attachment 字段里,
|
||||
能成功显示出内容即代表连接成功
|
||||
|
||||
之后完成表单的填写即可。
|
||||
|
||||
## 使用
|
||||
|
||||
使用贴图,需要在你的内容中键入一个文字占位符,具体的组成为 `:<pack prefix><sticker alias>:`。
|
||||
例如一个贴图包的前缀(`prefix`)为 `solar`,一个该贴图包的的贴图别名(`alias`)为 `Hello`,组成出来的占位符为 `:solarHello:`
|
||||
|
||||
不过不明白也别担心,大多数情况我们都有自动提示。只需在 Solian 内文本框键入一个英文冒号,再开始键入占位符的一部分即可。
|
||||
|
||||
### 大小变化
|
||||
|
||||
在 Solar Network 中,你可能看到大小不同的贴图,这是因为 Smart Resize 在背后发功。具体规则如下。
|
||||
|
||||
1. 当仅有一个贴图,将使用 128x128 的大小
|
||||
2. 当有三个及一下的题图,将使用 32x32 的大小
|
||||
3. 当有三个以上的贴图,或贴图夹杂在文本内时,将使用 20x20 的大小
|
||||
|
||||
这一系列的匹配都将发生在一个段落中。
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
icon: mdi-oci
|
||||
title: 开放计划
|
||||
description: 欢迎来到 Solar Network 开放计划,让我们助力你的应用成长
|
||||
---
|
||||
|
||||
开放计划是 Solar Network 一系列的开发者友好的 API 和小工具集合。
|
||||
我们坚持不为开发者添堵的规则,非必要不加密参数,永不混淆参数,人性化 API 接口,RESTful API 端点,尽可能的让开发者体验好。
|
||||
|
||||
现在就开始浏览,看看你能用 Solar Network 做些什么吧!
|
@ -1,70 +0,0 @@
|
||||
---
|
||||
icon: mdi-pencil-ruler
|
||||
title: API 标准
|
||||
description: 在设计 Solar Network 服务 API 时惯用的准则
|
||||
---
|
||||
|
||||
这篇文章是关于我们平时在设计 Solar Network API 时的范式是怎样的,能够帮助你更好的调用我们的 API 来进行第二次开发
|
||||
|
||||
## 最小化
|
||||
|
||||
我们的 API 一般追求极简,不像某些大平台的 API 一样除了数据之外的格式还有一大堆什么状态码、信息、请求 ID 什么的。这些信息我们都选择放在 HTTP 的 Header 部分。HTTP 的响应体就是纯粹的数据,无其他信息(需要分页的数据接口会额外返回一个数据总数)。
|
||||
|
||||
## 增删查改
|
||||
|
||||
我们的 API 基本上都是遵循 RESTful 设计范式的,如果你不知道什么是 RESTful,可以看以下我们理解的实践的 RESTful
|
||||
|
||||
### 请求方法
|
||||
|
||||
- `GET` 查询
|
||||
- `POST` 创建、进行某种操作
|
||||
- `PUT` 更新(虽在 RESTful 中也被定义为创建数据的行为,但是我们不使用)
|
||||
- `PATCH` 更新(不常用)
|
||||
- `DELETE` 删除
|
||||
|
||||
### 路径映射
|
||||
|
||||
假如你 POST 了一个地址来创建数据,那么用 GET 方法访问相同的地址大概就是列出数据的列表。
|
||||
在其后面加上 `/<id>` 就是单独读取某个数据,将请求方法改成 PUT 便是更新该条数据,改成 DELETE 就是删除该条数据。
|
||||
如果在 `/<id>` 再加上东西基本上就是 POST 方法来执行某个操作。
|
||||
例如以下是我们帖子的路径映射
|
||||
|
||||
*注:`:id` 系路径参数*
|
||||
|
||||
- `GET /posts` 获取帖子列表(分页)
|
||||
- `GET /posts/:id` 获取单个帖子
|
||||
- `GET /posts/:id/replies` 获取单个帖子的回复(分页)
|
||||
- `POST /posts` ~~创建帖子~~(于新版本因为引入帖子类型移除,需使用对应类型的创建接口)
|
||||
- `PUT /posts/:id` ~~更新帖子~~(于新版本因为引入帖子类型移除,需使用对应类型的更新接口)
|
||||
- `DELETE /posts/:id` 删除帖子
|
||||
- `POST /posts/:id/pin` 置顶帖子
|
||||
- `POST /posts/:id/react` 对帖子作出反应
|
||||
|
||||
## 错误处理
|
||||
|
||||
我们不理解为什么 HTTP 有给一套完善的状态码系统,其他大厂却仍选择自立门户。关于响应的 HTTP 状态码,以下是一些常用的含义代表。
|
||||
|
||||
- `500` 服务器内部错误 —— 你不用管,如果多见记得抛 issue
|
||||
- `400` 请求参数错误 —— 看文档,核查请求体
|
||||
- `404` 数据不存在或是接口路径不对
|
||||
- `403` 没有权限
|
||||
- `401` 需要授权 —— 需要授权的 API 但你没有提供 API 令牌
|
||||
- `200` 成功
|
||||
- `204` 无内容 —— 常见于删除 *虽然后时候写 API 会忘记删除内容时改成这个*
|
||||
|
||||
如果响应不是 `2xx` 的状态码,一般我们都不会返回 `application/json` 的数据,而是一个 `plain/text`,一行简单的文字来代表你犯了什么错。
|
||||
|
||||
> 如果你是英语白痴,遇到报错别老来问我们,用用翻译好吗?不然我们写报错信息干嘛。
|
||||
|
||||
## 超级网关
|
||||
|
||||
超级网关指的是我们的 [Hydrogen.Dealer](https://git.solsynth.dev/Hydrogen/Dealer),一般情况下你都不会直接访问我们的服务,都是走 Dealer 的网关转发的。虽然我们也不知道为什么写了个这个东西。
|
||||
|
||||
我们 API 的地址为 `api.sn.solsynth.dev`,怎么用呢?很简单。访问 `/cgi/<service name>` 即可,这样的地址会被转发到对应服务的 `/api` 端点。新版本我们还给这些服务加了点别名,这样你的 URL 可以变得更好看点。
|
||||
|
||||
- `/cgi/id` 或 `/cgi/auth` —— 授权服务 [Hydrogen.Passport](https://git.solsynth.dev/Hydrogen/Passport)
|
||||
- `/cgi/uc` 或 `/cgi/files` —— 附件服务 [Hydrogen.Paperclip](https://git.solsynth.dev/Hydrogen/Paperclip)
|
||||
- `/cgi/co` 或 `/cgi/interactive` —— 帖子服务 [Hydrogen.Interactive](https://git.solsynth.dev/Hydrogen/Interactive)
|
||||
- `/cgi/im` 或 `/cgi/messaging` —— 聊天服务 [Hydrogen.Messaging](https://git.solsynth.dev/Hydrogen/Messaging)
|
||||
|
||||
> 冷知识:你可能注意到了我们新配置的别名其实就是之前没有超级网关时他们使用的子域名。
|
@ -1,106 +0,0 @@
|
||||
---
|
||||
icon: mdi-open-in-app
|
||||
title: Solian 索链
|
||||
description: Solian 是由 Solsynth LLC 官方编写的全平台支持客户端。
|
||||
---
|
||||
|
||||
Solian 是由 Flutter 编写的全平台 Solar Network 客户端,也是我们目前唯一的前端。
|
||||
|
||||
# 使用
|
||||
|
||||
想要使用 Solian,你可以下载客户端,也可以直接在浏览器中打开。得益于 Flutter 的全平台支持,你可以在 https://lian.solsynth.dev 访问到 Solian 网页版。但由于浏览器限制,部分功能可能欠缺或受到影响。
|
||||
|
||||
## 下载
|
||||
|
||||
下载 Solsynth 的方式很多,但一定请从官方认证的渠道下载。
|
||||
|
||||
1. 官方仓库发布的正式版本 https://git.solsynth.dev/Hydrogen/Solian/releases
|
||||
2. 官方文件托管柜发布的测试版本 https://files.solsynth.dev/production01/solian
|
||||
3. 官方 TestFlight (iOS 与少量 macOS) https://testflight.apple.com/join/YJ0lmN6O
|
||||
|
||||
Windows 版本系免安装版本,放置于一个您熟悉的目录即可使用。
|
||||
Web 版本同时支持 PWA 渐进式网页应用,可以替代一部分桌面端使用。
|
||||
|
||||
## 安装
|
||||
|
||||
以下是个平台安装 Solian 的技术要领。
|
||||
|
||||
### Android
|
||||
|
||||
推荐从**文件托管柜**下载最新测试版,版本最新,修复最全,最稳定。~~测试版比稳定版稳定~~
|
||||
|
||||
下载下来的 APK 档案可以直接打开安装。中国版手机可能需要额外步骤验证,但请不要使用自带应用商店搜索下载。
|
||||
|
||||
### iOS/macOS
|
||||
|
||||
使用 TestFlight 安装。可以点击上方链接首先下载安装 TestFlight App。再点击上方链接的第二部开始测试来参加测试。
|
||||
|
||||
TestFlight 的测试名额有限,等到时机成熟我们会将 Solian 发布于非中国区的 App Store,可以前往 App Store 搜索下载。
|
||||
|
||||
### Windows
|
||||
|
||||
Windows 从任意可信渠道下载后解压到一个目录即可使用。
|
||||
|
||||
**注意:** Windows 版本不知是否属于 Flutter 的支持问题,在第一次启动加载时总是会卡好一会才弹出主窗口。不用反复点击,请耐心等待,可能会使用
|
||||
5 到 30 秒。如果多次点击可能会打开多个窗口。
|
||||
|
||||
### Linux
|
||||
|
||||
请自行构建。我相信你们可以的,加油哦~
|
||||
|
||||
## 自行构建
|
||||
|
||||
### 环境准备
|
||||
|
||||
构建 Solian 需要使用 Flutter SDK,请在官网下载最新版安装。也可以从中国镜像站下载安装。
|
||||
安装完成 Flutter 请根据官方文档下载其他对应平台需要的开发依赖,例如 Windows 需要 VS2022、Android 需要 Android
|
||||
Studio、iOS/macOS 我劝你还是用官方版本构建的吧。
|
||||
|
||||
除开安装 Flutter SDK,我们还需要使用 Rust 做系统级依赖支持。请从 Rust 官方下载最新版本。
|
||||
|
||||
现在我们有了 Flutter、Rust,还少一个东西,为了实现聊天及未来的其他模块本地数据库支持。
|
||||
Linux 版本还需要安装对应的 SQLite3 开发依赖。
|
||||
|
||||
```sh
|
||||
# for ubuntu
|
||||
sudo apt-get -y install libsqlite3-0 libsqlite3-dev
|
||||
```
|
||||
|
||||
Windows 需要下载
|
||||
[sqlite3.dll](https://github.com/tekartik/sqflite/raw/master/sqflite_common_ffi/lib/src/windows/sqlite3.dll)
|
||||
放置在运行目录。
|
||||
macOS 及手机端构建不需要其他操作。
|
||||
|
||||
### 构建代码
|
||||
|
||||
之后就是构建代码的时候了。确保你在构建机器上安装了 `git` 版本管理工具。或者你想直接下载代码压缩档案也不是不行。
|
||||
确保 `git` 安装之后可以使用以下命令克隆代码。
|
||||
|
||||
```sh
|
||||
git clone https://git.solsynth.dev/Hydrogen/Solian.git
|
||||
```
|
||||
|
||||
之后导航到对应目录,使用以下命令安装依赖。
|
||||
|
||||
```sh
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
该操作会从 [pub.dev](https://pub.dev) 上下载依赖,而 pub.dev 是由 Google 托管提供。所以中国大陆的连接性要被打个问号。具体可以参考中国大陆镜像站点查询解决方案。
|
||||
|
||||
完成依赖获取后就可编译了,一行命令就搞定。
|
||||
|
||||
```sh
|
||||
# for windows
|
||||
flutter build windows
|
||||
# for macos
|
||||
flutter build macos
|
||||
# for linux
|
||||
flutter build linux
|
||||
# for ios
|
||||
flutter build ipa
|
||||
# for android
|
||||
flutter build apk
|
||||
```
|
||||
|
||||
你也可以为 Android 平台构建 `aab` 等其他格式的应用包。但是对应签名素材请自行准备。
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/acefield.webp
|
||||
title: AceField
|
||||
description: 由索尔辛茨附属高岛互娱制作的一款实验性多人自上而下视角射击游戏。
|
||||
author: [littlesheep]
|
||||
url: https://files.solsynth.dev/production01/acefield
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 或以上
|
||||
url: https://files.solsynth.dev/production01/acefield/AceField_MacOS_arm64.dmg
|
||||
- title: Windows
|
||||
icon: mdi-microsoft-windows
|
||||
desc: Windows 7 或以上
|
||||
url: https://files.solsynth.dev/production01/acefield/Windows_x86_64.zip
|
||||
- title: Linux
|
||||
icon: mdi-linux
|
||||
desc: 主流 Linux 发行版
|
||||
url: https://files.solsynth.dev/production01/acefield/Linux_x86_64.zip
|
||||
---
|
||||
|
||||
AceField 是 “战斗的好地方” 的缩写。
|
||||
我们不能只用《战地》这个名字,因为它是 Electronic Arts 的商标。
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=914}
|
@ -1,28 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/dietary-guard.webp
|
||||
title: 膳食管家
|
||||
description: 一个简单的查询食物成份的应用程式
|
||||
author: [littlesheep]
|
||||
downloads:
|
||||
- title: iOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 10 或以上,通过 Testflight
|
||||
url: https://testflight.apple.com/join/pYb6wRbr
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 或以上
|
||||
url: https://files.solsynth.dev/production01/dietary-guard/app-arm64-v8a-release.apk
|
||||
---
|
||||
|
||||
一个简单的查询食物成份的应用,还能顺便提示你一点东西,避免你吃到什么不该吃的吃进医院。
|
||||
|
||||
## 高光
|
||||
|
||||
1. 透过 USDA FoodData Central API 获取权威、准确的食品营养成分数据
|
||||
2. 自定义告警规则,不适合自己食用的食物一目了然
|
||||
3. 功能简单,上手容易
|
||||
4. 轻量软件,只有 8M 不到
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=887}
|
@ -1,55 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/rhythm-box.webp
|
||||
title: RhythmBox
|
||||
description: 又一个 Spotify 第三方客户端,但不止步于此。
|
||||
author: [littlesheep]
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 12 或以上,通过侧载自行安装
|
||||
url: https://files.solsynth.dev/production01/rhythm-box/ios-ipa.ipa
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 或以上,因为签名问题,请自行编译使用
|
||||
url: https://git.solsynth.dev/LittleSheep/RhythmBox
|
||||
- title: Windows
|
||||
icon: mdi-microsoft-windows
|
||||
desc: 通过 GitHub Actions 构建
|
||||
url: https://github.com/Solsynth/RhythmBox
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 或以上
|
||||
url: https://files.solsynth.dev/production01/rhythm-box/app-arm64-v8a-release.apk
|
||||
---
|
||||
|
||||
又一个 Spotify 第三方客户端。支持多平台,因为使用 Flutter 构建。
|
||||
|
||||
该项目受到 [spotube](https://spotube.krtirtho.dev) 的启发和支持。
|
||||
他们的原始应用程序已经足够好了。但我只想重新设计用户界面,并使其准备好添加更多功能和更多后端支持。
|
||||
|
||||
## 亮点
|
||||
|
||||
与原始 spotube 相比。该项目添加了更多音频源,例如网易云音乐、酷狗,并提供在中国大陆使用的能力。
|
||||
|
||||
同时,该项目还专注于 VOCALOID 歌曲的播放体验。
|
||||
我们改进了搜索和排名算法,使查询将减少选择翻唱版本,而是选择原始版本。
|
||||
|
||||
由于 jiosaavn 在亚洲地区的终止服务(其他地区可能也受到影响)。我们删除了 jiosaavn 音频源。
|
||||
|
||||
## 开发计划
|
||||
|
||||
在 [GitHub](https://github.com/Solsynth/RhythmBox) 或 [Solsynth Git Repository](https://git.solsynth.dev/LittleSheep/RhythmBox) 查看
|
||||
|
||||
## 许可
|
||||
|
||||
本项目在 APGLv3 许可证下开源。原始 spotube 项目在 BSD-Clause4 许可证下开源,版权归 Kingkor Roy Tirtho 所有。
|
||||
|
||||
本项目的所有权利归 LittleSheep 和 Solsynth LLC 所有。
|
||||
|
||||
## 下载
|
||||
|
||||
**注:Windows 版本通过 Github Actions 编译,下载请前往下述链接的 GitHub 仓库找到最近提交旁「打勾」的图标,在弹出的提示窗口选择 `build-exe` 那项的 「Details」。展开 Archive production artifacts 的步骤日志,里面会包含一个下载链接,解压服用即可。下载需要登陆 GitHub 账号。**
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=923}
|
@ -1,47 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/roadsign.webp
|
||||
title: RoadSign
|
||||
description: 为我们的网络提供动力的 HTTP 服务器。功能强大,使用方便
|
||||
author: [littlesheep]
|
||||
---
|
||||
|
||||
RoadSign 是由 Solsynth LLC 开发的 HTTP 服务器,其对 HTTP 协议的支持算不上优秀,
|
||||
但是对于加速你的项目部署,一定算得上趁手!甚至让我们抛弃了 Netlify 和 Vercel。
|
||||
|
||||
## 特色
|
||||
|
||||
- RoadSign CLI 一行命令部署项目
|
||||
- 完全控制你的流量
|
||||
- 特色的 Transformer 来修改请求
|
||||
- 内置 Warden 线程管理
|
||||
|
||||
## 安装
|
||||
|
||||
推荐使用 docker 进行安装,以下是示例 docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
roadsign:
|
||||
image: xsheep2010/roadsign:delta
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 81:81
|
||||
volumes:
|
||||
- "/srv/roadsign/config:/config"
|
||||
- "/srv/roadsign/workdir:/workdir"
|
||||
- "/srv/roadsign/settings.toml:/settings.toml"
|
||||
```
|
||||
|
||||
推荐让 RoadSign 在一个真正的反向代理后,所以在此不监听 443 和 80,使用 8000 让反向代理做上流。
|
||||
其中 81 端口是侧载 API 需要使用的管理 API 端口,可以在设置内修改。
|
||||
|
||||
同时推荐在本地机器上安装 RoadSign CLI
|
||||
|
||||
```sh
|
||||
$ npm i -g roadsign-cli
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
在 Asciiema 观看完整的 RoadSign CLI 部署项目演示 👉 https://asciinema.org/a/678744
|
@ -1,39 +0,0 @@
|
||||
---
|
||||
thumbnail: /thumbnails/products/solar-network.webp
|
||||
title: Solar Network
|
||||
description: 下一代网络中心
|
||||
author: littlesheep
|
||||
url: https://sn.solsynth.dev
|
||||
downloads:
|
||||
- title: macOS
|
||||
icon: mdi-apple
|
||||
desc: macOS 12 或以上,通过 TestFlight
|
||||
url: https://testflight.apple.com/join/YJ0lmN6O
|
||||
- title: iOS
|
||||
icon: mdi-apple-ios
|
||||
desc: iOS 12 或以上,通过 Testflight
|
||||
url: https://testflight.apple.com/join/YJ0lmN6O
|
||||
- title: Android
|
||||
icon: mdi-android
|
||||
desc: Android 9 或以上
|
||||
url: https://files.solsynth.dev/production01/solian/app-arm64-v8a-release.apk
|
||||
- title: Web
|
||||
icon: mdi-web
|
||||
desc: 基于 Web 的版本,支持主流浏览器
|
||||
url: https://sn.solsynth.dev
|
||||
---
|
||||
|
||||
Solar Network 是一个开创性的多功能平台,它将社交互动、实时聊天和高质量音视频通话无缝集成,打造出一个极具吸引力和互动性的统一体验。
|
||||
通过 Solar Network,用户不仅可以轻松创建和管理自己的社区,还能够随时随地与好友、粉丝和团队成员保持紧密联系。
|
||||
无论是讨论工作项目、分享生活点滴,还是享受娱乐时光,Solar Network 都能为您提供顺畅、高效的沟通桥梁。
|
||||
这个平台旨在满足各种社交需求,使每一位用户都能在一个温馨而多样化的社区中找到归属感,并与他人建立深厚的联系。
|
||||
|
||||
## 支持
|
||||
|
||||
如果你需要任何形式的支持或者有任何疑问,欢迎写邮件至 [lily@solsynth.dev](mailto:lily@solsynth.dev)。我们将竭诚为你服务。
|
||||
|
||||
## 下载
|
||||
|
||||
下载 Solar Network 官方客户端 Solian 来使用 Solar Network。
|
||||
|
||||
:embed-download-link{:items='downloads'}
|
@ -1,44 +0,0 @@
|
||||
---
|
||||
title: 隐私策略
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
我们非常重视您的隐私。本隐私政策概述了我们收集的个人信息类型、使用方式以及我们采取的保护措施。
|
||||
|
||||
## 信息收集
|
||||
|
||||
我们仅在提供服务时收集必要的个人信息。这可能包括您的姓名、电子邮件地址以及其他相关信息。
|
||||
|
||||
## 信息使用
|
||||
|
||||
我们使用您的个人信息来:
|
||||
|
||||
- 提供和改进我们的服务
|
||||
- 与您沟通更新或重要信息
|
||||
- 确保遵守法律义务
|
||||
|
||||
## 数据共享
|
||||
|
||||
我们不会出售、交易或与第三方分享您的个人信息,法律要求除外。
|
||||
|
||||
## 数据安全
|
||||
|
||||
我们实施了强有力的安全措施,以保护您的个人信息免受未经授权的访问、更改、披露或销毁。
|
||||
|
||||
## 您的权利
|
||||
|
||||
您有权:
|
||||
|
||||
- 访问我们持有的关于您的个人信息
|
||||
- 请求更正您的个人信息
|
||||
- 请求删除您的个人信息
|
||||
|
||||
## 联系我们
|
||||
|
||||
如果您对本隐私政策或我们的数据处理方式有任何疑问或顾虑,请通过[您的联系方式]与我们联系。
|
||||
|
||||
## 政策变更
|
||||
|
||||
我们可能会不时更新本隐私政策。任何更改将发布在此页面上,且我们会通知您任何重大更改。
|
@ -1,77 +0,0 @@
|
||||
---
|
||||
title: 用户协议
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
本协议适用于所有 Solsynth LLC 的产品,包括但不限于 Solar Network、Solian、DietaryGuard、AceField。
|
||||
|
||||
## 服务的提供与中断
|
||||
|
||||
Solsynth LLC 将向世界上所有的生物提供同等的服务,包括草履虫。
|
||||
同时也保留向任意用户停止提供服务的权利。关于停止部分用户的服务,我们不需要提前通知。
|
||||
|
||||
## 用户生成内容
|
||||
|
||||
任意发布在 Solar Network 上的内容(包括但不限于帖子、文章、附件)都默认授权 Solsynth LLC 予以展示的权利。
|
||||
除非用户特别声明,所有内容均为原帖主保留所有权利,转载请先向原帖主授权。
|
||||
|
||||
### 转载的认定
|
||||
|
||||
无帖主特别声明,所有内容均适用本条转载的定义。
|
||||
|
||||
转载指将原帖的内容原封不动或略作改动上传到别的平台或 Solar Network。但同时转帖、嵌入式组件与展示展开的链接不构成转载。
|
||||
转载即时在原帖主授权的情况下也需表明出处。
|
||||
|
||||
### 言论的自由
|
||||
|
||||
除滥用资源的情况,我们不会将用户生成内容进行删除。也不会做出要求任何用户删除任何内容的要求。
|
||||
|
||||
但 Solsynth LLC 始终保留对于违反社区准则的内容(如淫秽、暴力、血腥、反社会、恐怖组织等)限制与停止向公众展示的权利。
|
||||
|
||||
尽管在 Solar Network 上你拥有 100% 的言论自由。但还请清楚,言论自由不代表不用对自己的言论负责。
|
||||
|
||||
#### 限制展示与停止展示
|
||||
|
||||
- 限制展示:停止相关的推送,但是任保留直接通过资源标识符和分享连接访问的权利
|
||||
|
||||
- 停止展示:全面停止除作者之外任何人访问该资源的权利
|
||||
|
||||
## 防止资源滥用条例
|
||||
|
||||
尽管使用 Solar Network 的数据托管服务并无任何的容量限制,但经过判定的滥用资源将会被取消使用部分功能的权利。
|
||||
并且之前上传的资源 Solsynth LLC 有权对其进行删除空间回收。
|
||||
|
||||
### 滥用的认定
|
||||
|
||||
- 传而不用:例如在 Solar Network 的 Interactive 附件池中过度上传附件并不将附件与帖子连接
|
||||
- 无意义帖:无意义洗版或浪费 Solar Network 的存储资源
|
||||
- 走错片场:将 Solar Network 公有资源当作自己的专用资源池使用(详见维基《专用资源池》页面)
|
||||
|
||||
滥用的认定最终解释权归属于 Solsynth Trust & Safety Team
|
||||
|
||||
## 二次发布
|
||||
|
||||
二次发布指将我们的资产下载并重新托管到别站。
|
||||
|
||||
### 制品二次发布
|
||||
|
||||
除特殊声明,Solsynth LLC 的产品均不允许二次发布,请勿将我们的产品构建下载并二次上传于其他站点。
|
||||
**二次作为商用发布更是不允许的。**
|
||||
|
||||
你应该做的是将我们的产品链接贴上他站。或使用嵌入式组件。并且表明 Solsynth LLC 版权所有。
|
||||
|
||||
若您想搭建我们制品的镜像站,请与我们取得联系以豁免此条例。
|
||||
|
||||
### 源码二次发布
|
||||
|
||||
我们不允许任何形式的源码二次发布(Fork 除外)。
|
||||
包括但不限于,将 GitHub 或 Solsynth Code Repository 上的代码仓库镜像于 GitLab、Gitee 等其他 Git 提供者。
|
||||
**二次售卖源码更是不允许的。**
|
||||
|
||||
关于更多的源码使用条例,请遵循项目使用的开源许可证。
|
||||
|
||||
若您想搭建我们源码的镜像站,请与我们取得联系以豁免此条例。
|
||||
|
||||
*****
|
||||
|
||||
Solsynth LLC 保留对此协议的最终解释权
|
@ -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,3 +0,0 @@
|
||||
export default defineI18nConfig(() => ({
|
||||
fallbackLocale: 'en',
|
||||
}))
|
@ -1,80 +0,0 @@
|
||||
{
|
||||
"brandName": "Solsynth",
|
||||
"brandNameFormal": "Solsynth LLC",
|
||||
"navProducts": "Products",
|
||||
"navActivity": "Activity",
|
||||
"navActivityCaption": "Explore our official recent activities.",
|
||||
"navGallery": "Gallery",
|
||||
"navGalleryCaption": "Explore files that uploaded by Solar Network users.",
|
||||
"navPosts": "Posts",
|
||||
"navPostsCaption": "Explore posts that posted by Solar Network users.",
|
||||
"navPostsCaptionWithTag": "Explore posts with tag {0}.",
|
||||
"navPostsCaptionWithCategory": "Explore posts in category {0}.",
|
||||
"navPostsCaptionWithPublisher": "Explore posts posted by {0}.",
|
||||
"indexIntroduce": "An energetic software company that create wonderful software which everyone love.",
|
||||
"indexProductListHint": "See some of our products just there",
|
||||
"indexActivities": "Activities",
|
||||
"indexActivitiesCaption": "Keep in touch,\nand learn what we doing recently.",
|
||||
"indexActivitiesHint": "See some posts in our realm just here",
|
||||
"userMenuDashboard": "Dashboard",
|
||||
"userMenuSignOut": "Sign Out",
|
||||
"userMenuSignIn": "Sign In",
|
||||
"userMenuSignUp": "Create Account",
|
||||
"next": "Next",
|
||||
"errorOccurred": " Something went wrong... {0}",
|
||||
"username": "Name",
|
||||
"nickname": "Nick",
|
||||
"email": "Email Address",
|
||||
"password": "Password",
|
||||
"copyright": "Copyright",
|
||||
"signUpTitle": "Create an account",
|
||||
"signUpCaption": "Create an account on Solar Network. Then enjoy all our services.",
|
||||
"signUpCompleted": "You successfully created an account on Solar Network. Now sign in to your account and start exploring!",
|
||||
"signUpCompletedAction": "Let's go!",
|
||||
"signInTitle": "Sign In",
|
||||
"signInCaption": "Sign in via your account to access the entire Solar Network.",
|
||||
"multiFactorCaption": "We need to verify that the person trying to access your account is you.",
|
||||
"multiFactorHint": "Check your inbox",
|
||||
"multiFactorTypeEmail": "Email One-time-password",
|
||||
"multiFactorTypePassword": "Password",
|
||||
"signInCompleted": "All Done",
|
||||
"signInCompletedCaption": "Welcome back! You just signed in right now! We're going to direct you to dashboard...",
|
||||
"authorizeTitle": "Connect to third-party",
|
||||
"authorizeCaption": "One Solar Network account, entire Internet.",
|
||||
"authorizeErrorHint": "It's usually not our fault. Try bringing this link to give feedback to the developer of the app you came from.",
|
||||
"authorizeRedirectHint": "After approve their request, you will be redirect to",
|
||||
"authorizeCompleted": "Authorized",
|
||||
"authorizeCompletedCaption": "You're done! We successfully established connection between you and {0}.",
|
||||
"authorizeCompletedRedirect": "Now you can continue your their app, we will redirect you soon.",
|
||||
"authorizeCompletedRedirectHint": "Teleporting you to...",
|
||||
"decline": "Decline",
|
||||
"approve": "Approve",
|
||||
"transferredToSolianHint": "This part of the functionality has been transferred to our application Solian, please download it or open it in your browser. To learn more, please visit the project description page.",
|
||||
"personalize": "Personalize",
|
||||
"personalizeCaption": "Bring your own color to the Solar Network.",
|
||||
"security": "Security",
|
||||
"securityCaption": "Guard your Solar Network account.",
|
||||
"userActivity": "Activity",
|
||||
"userActivityCaption": "Recent posts of this user.",
|
||||
"productArchived": "Archived",
|
||||
"callbackHint": "You need to sign in before access that page. After you signed in, you will be redirected to:",
|
||||
"lastUpdatedAt": "Last updated at {0}",
|
||||
"learnMore": "Learn more",
|
||||
"open": "Open",
|
||||
"openInSite": "Open in Website",
|
||||
"postReplies": "Replies",
|
||||
"postRepliesCaption": "All replies of this post",
|
||||
"language": "Language",
|
||||
"embedWidget": "Solar Network Embed Widget",
|
||||
"continueReading": "Continue Reading",
|
||||
"download": "Download",
|
||||
"downloadDescription": "Pick the right version for you",
|
||||
"attachmentUpload": "Upload new",
|
||||
"attachmentCreate": "Create Attachment",
|
||||
"attachmentCreateCaption": "Use Solar Network host your files",
|
||||
"attachmentUploadProgress": "Uploading",
|
||||
"attachmentUploadCompleted": "Uploaded",
|
||||
"upload": "Upload",
|
||||
"cancel": "Cancel",
|
||||
"seeMore": "See more"
|
||||
}
|
@ -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,80 +0,0 @@
|
||||
{
|
||||
"brandName": "索尔辛茨",
|
||||
"brandNameFormal": "索尔辛茨实业有限公司",
|
||||
"navProducts": "制品",
|
||||
"navActivity": "动态",
|
||||
"navActivityCaption": "了解我们官方近期的活动。",
|
||||
"navGallery": "相簿",
|
||||
"navGalleryCaption": "探索整个 Solar Network 上的文件。",
|
||||
"navPosts": "帖子",
|
||||
"navPostsCaption": "探索整个 Solar Network 上的帖子。",
|
||||
"navPostsCaptionWithTag": "探索带有 {0} 标签的帖子",
|
||||
"navPostsCaptionWithCategory": "探索被分类为 {0} 的帖子",
|
||||
"navPostsCaptionWithPublisher": "探索 {0} 发表的帖子",
|
||||
"indexIntroduce": "一家充满活力的软件公司,创造了人人喜爱的精彩软件。",
|
||||
"indexProductListHint": "在这里看看我们的一些产品",
|
||||
"indexActivities": "动态",
|
||||
"indexActivitiesCaption": "开发软件,闭门造车是大忌,了解我们最近在做什么。",
|
||||
"indexActivitiesHint": "看看我们领域中的一些帖子",
|
||||
"userMenuDashboard": "仪表盘",
|
||||
"userMenuSignOut": "登出",
|
||||
"userMenuSignIn": "登陆",
|
||||
"userMenuSignUp": "注册帐号",
|
||||
"next": "下一步",
|
||||
"errorOccurred": "发生错误了… {0}",
|
||||
"username": "用户名",
|
||||
"nickname": "显示名",
|
||||
"email": "邮件地址",
|
||||
"password": "密码",
|
||||
"copyright": "版权所有",
|
||||
"signUpTitle": "创建账号",
|
||||
"signUpCaption": "在 Solar Network 上创建一个帐号,以享受我们所有的服务。",
|
||||
"signUpCompleted": "您已成功创建 Solar Network 账户。现在登录您的账户,开始探索吧!",
|
||||
"signUpCompletedAction": "出发",
|
||||
"signInTitle": "登陆",
|
||||
"signInCaption": "通过您的账户登录以访问整个 Solar Network。",
|
||||
"multiFactorCaption": "我们需要验证试图访问您账户的人是您本人。",
|
||||
"multiFactorHint": "检查您的收件箱",
|
||||
"multiFactorTypeEmail": "电子邮件一次性密码",
|
||||
"multiFactorTypePassword": "账号密码",
|
||||
"signInCompleted": "完成",
|
||||
"signInCompletedCaption": "欢迎回来!您刚刚登录成功!我们将引导您进入仪表板...",
|
||||
"transferredToSolianHint": "此部分功能已转移到我们的应用程序 Solian,请下载或在浏览器中打开。如需了解更多信息,请访问项目描述页面。",
|
||||
"personalize": "个性化",
|
||||
"personalizeCaption": "为 Solar Network 染上你的色彩。",
|
||||
"security": "安全",
|
||||
"securityCaption": "保护您的 Solar Network 账户。",
|
||||
"userActivity": "活动",
|
||||
"userActivityCaption": "此用户的最新帖子。",
|
||||
"productArchived": "已归档",
|
||||
"callbackHint": "访问该页面前,您需要先登录。登录后,我们会将把您重定向到:",
|
||||
"authorizeTitle": "连接到第三方",
|
||||
"authorizeCaption": "一个 Solar Network 账户,连接整个互联网。",
|
||||
"authorizeErrorHint": "通常这不是我们的错误。尝试将此链接反馈给您来源应用的开发者。",
|
||||
"authorizeRedirectHint": "在批准他们的请求后,您将被重定向到",
|
||||
"authorizeCompleted": "已授权",
|
||||
"authorizeCompletedCaption": "完成!我们已成功在您与 {0} 之间建立连接。",
|
||||
"authorizeCompletedRedirect": "现在您可以继续使用他们的应用,我们会很快将您重定向。",
|
||||
"authorizeCompletedRedirectHint": "正在传送到…",
|
||||
"decline": "拒绝",
|
||||
"approve": "批准",
|
||||
"lastUpdatedAt": "最后更新于 {0}",
|
||||
"learnMore": "了解更多",
|
||||
"open": "打开",
|
||||
"openInSite": "在站点里打开",
|
||||
"postReplies": "回复",
|
||||
"postRepliesCaption": "该帖子的全部回复",
|
||||
"language": "语言",
|
||||
"embedWidget": "Solar Network 嵌入式组件",
|
||||
"continueReading": "继续阅读",
|
||||
"download": "下载",
|
||||
"downloadDescription": "选择适合你的版本下载",
|
||||
"attachmentUpload": "新传附件",
|
||||
"attachmentCreate": "新建附件",
|
||||
"attachmentCreateCaption": "使用 Solar Network 来托管你的文件",
|
||||
"attachmentUploadProgress": "上传中",
|
||||
"attachmentUploadCompleted": "上传完成",
|
||||
"upload": "上传",
|
||||
"cancel": "取消",
|
||||
"seeMore": "查看更多"
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<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>
|
||||
|
||||
<nuxt-link to="/dev" exact>
|
||||
<h2 class="mt-1">Creator Hub</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<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-list density="compact" nav color="primary">
|
||||
<v-list-item title="Back" prepend-icon="mdi-arrow-left" to="/" exact />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 my-1" />
|
||||
|
||||
<v-list density="compact" nav color="primary">
|
||||
<v-list-item title="Stickers" prepend-icon="mdi-sticker-emoji" to="/creator/stickers" exact />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 mb-4 mt-1" />
|
||||
|
||||
<copyright no-centered service="capital" class="px-5" />
|
||||
|
||||
<footer-links class="px-5 mt-3" />
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<slot />
|
||||
</v-main>
|
||||
</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"
|
||||
})
|
||||
</script>
|
@ -1,59 +0,0 @@
|
||||
<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>
|
||||
|
||||
|
||||
<nuxt-link to="/" exact>
|
||||
<h2 class="mt-1">Solsynth LLC</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<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-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>
|
||||
|
||||
<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>
|
||||
|
||||
<v-divider class="border-opacity-50 mb-4 mt-0.5" />
|
||||
|
||||
<copyright no-centered :service="['roadsign', 'capital']" class="px-5" />
|
||||
|
||||
<footer-links class="px-5 mt-3" />
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<slot />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const openDrawer = ref(false)
|
||||
</script>
|
@ -1,56 +0,0 @@
|
||||
<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>
|
||||
|
||||
<nuxt-link to="/dev" exact>
|
||||
<h2 class="mt-1">Developer Portal</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<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-list density="compact" nav color="primary">
|
||||
<v-list-item title="Back" prepend-icon="mdi-arrow-left" to="/" exact />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 my-1" />
|
||||
|
||||
<v-list density="compact" nav color="primary">
|
||||
<v-list-item title="Bots" prepend-icon="mdi-robot" to="/dev/bots" exact />
|
||||
</v-list>
|
||||
|
||||
<v-divider class="border-opacity-50 mb-4 mt-1" />
|
||||
|
||||
<copyright no-centered service="capital" class="px-5" />
|
||||
|
||||
<footer-links class="px-5 mt-3" />
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<slot />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const openDrawer = ref(false)
|
||||
|
||||
useHead({
|
||||
titleTemplate: "%s | Solsynth Dev Portal"
|
||||
})
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user