Finish solar network product page

This commit is contained in:
LittleSheep 2025-03-19 23:40:10 +08:00
parent 1f2c4f33cb
commit 9726d8f805
4 changed files with 265 additions and 24 deletions

View File

@ -99,5 +99,21 @@
"solarNetworkReadDialog": "Read Error Messages",
"solarNetworkReadDialogDescription": "When encountering an error message, do not immediately take a screenshot and complain. Try to understand why the issue occurred. Then, seek help in the development channel or on GitHub instead of making complaint posts.",
"solarNetworkToS": "And, if you continue registering, means you accept our Terms & Conditions",
"solarNetworkToSCheck": "Check them out"
"solarNetworkToSCheck": "Check them out",
"solarNetworkFeat": "Features",
"solarNetworkFeatDescription": "Explore the core features of Solar Network",
"solarNetworkFeatDashboard": "Dashboard",
"solarNetworkFeatDashboardDescription": "A single place to information around the site, anytime, anywhere.",
"solarNetworkFeatExplore": "Explore",
"solarNetworkFeatExploreDescription": "Enjoy what you love, free from ads and algorithmic noise.",
"solarNetworkFeatChat": "Chat",
"solarNetworkFeatChatDescription": "Bridge distances, stay connected with friends and communities effortlessly.",
"solarNetworkFeatNews": "News",
"solarNetworkFeatNewsDescription": "Even without traveling afar, stay informed about the world's stories.",
"solarNetworkFeatStickers": "Stickers",
"solarNetworkFeatStickersDescription": "Express yourself beyond words with playful and vivid stickers.",
"solarNetworkFeatCompose": "Compose",
"solarNetworkFeatComposeDescription": "Write freely, speak boldly—your voice deserves to be heard.",
"solarNetworkJumpIn": "Jump into the community",
"solarNetworkNeedHelp": "Need help?"
}

View File

@ -99,5 +99,25 @@
"solarNetworkReadDialog": "阅读错误提示",
"solarNetworkReadDialogDescription": "遇到报错提示不要第一时间截图抱怨,尝试理解为什么这件事情发生。其次在开发频道或 GitHub 寻求帮助,不要发帖抱怨。",
"solarNetworkToS": "还有,如果你继续注册 Solarpass 帐号,这意味着你同意我们的各项条款",
"solarNetworkToSCheck": "阅读这些条款"
"solarNetworkToSCheck": "阅读这些条款",
"solarNetworkFeat": "特色功能",
"solarNetworkFeatDescription": "浏览 Solar Network 的一些核心功能",
"solarNetworkFeatDashboard": "冲浪板",
"solarNetworkFeatDashboardDescription": "一处汇聚万千动向,随时捕捉站内资讯。",
"solarNetworkFeatExplore": "探索",
"solarNetworkFeatExploreDescription": "不受广告与算法羁绊,纯粹欣赏你热爱的风景。",
"solarNetworkFeatChat": "聊天",
"solarNetworkFeatChatDescription": "超越时空阻隔,与朋友和社群自在畅谈,情感相连。",
"solarNetworkFeatNews": "新闻",
"solarNetworkFeatNewsDescription": "纵然足不出户,依然洞悉世间冷暖,知晓天下风云。",
"solarNetworkFeatStickers": "贴图",
"solarNetworkFeatStickersDescription": "一枚贴图,胜过千言万语,趣味横生,情感尽现。",
"solarNetworkFeatCompose": "撰写",
"solarNetworkFeatComposeDescription": "在无拘无束的天地间,自由书写,勇敢表达,世界在倾听。",
"solarNetworkHighlightPosts": "Solar Favorite",
"solarNetworkHighlightPostsDescription": "Solar Network 社区用户中精选出来的精华帖",
"solarNetworkJumpIn": "现在加入",
"solarNetworkNeedHelp": "需要寻求帮助?",
"askHelpContactUs": "联系我们",
"askHelpReadTheDocs": "阅读文档"
}

View File

@ -69,10 +69,6 @@
</template>
<script setup lang="ts">
definePageMeta({
alias: ["/posts/:area/:id"],
})
const route = useRoute()
const config = useRuntimeConfig()
@ -80,17 +76,9 @@ const attachments = ref<any[]>([])
const firstImage = ref<string | null>()
const firstVideo = ref<string | null>()
const slug = computed(() => {
if (route.params.area) {
return `${route.params.area}:${route.params.id}`
} else {
return route.params.id
}
})
const { t } = useI18n()
const { data: post } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/posts/${slug.value}`)
const { data: post } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/posts/${route.params.id}`)
if (!post.value) {
const { data: publisher } = await $fetch<any>(`${config.public.solarNetworkApi}/cgi/co/publishers/${route.params.id}`)
@ -154,7 +142,7 @@ useSeoMeta({
ogSiteName: "Solsynth Capital",
})
const externalOpenLink = computed(() => `${config.public.solianUrl}/posts/view/${slug.value}`)
const externalOpenLink = computed(() => `${config.public.solianUrl}/posts/${route.params.id.toString().replace('/', ':')}`)
</script>
<style scoped>

View File

@ -1,6 +1,6 @@
<template>
<v-container class="flex flex-col my-2 px-12 gap-[4rem]">
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="intro">
<section class="content-section flex flex-col items-center justify-center text-center" id="intro">
<div class="pt-1/3 mb-4 w-full relative">
<img :src="AlphaScreenshot" class="absolute bottom-2 left-0 right-0" />
<img
@ -23,12 +23,67 @@
<div>
<h1 class="text-4xl font-bold">Solar Network</h1>
<p class="mt-2 text-lg">{{ t("solarNetworkDescription") }}</p>
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#products">{{ t("learnMore") }}</v-btn>
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#features">{{ t("learnMore") }}</v-btn>
</div>
</section>
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="reminders">
<h1 class="text-3xl font-bold">{{ t("solarNetworkBeforeYouStart") }}</h1>
<section class="content-section flex flex-col items-center justify-center text-center" id="features">
<h2 class="text-3xl font-bold">{{ t("solarNetworkFeat") }}</h2>
<p class="text-lg mb-4">{{ t("solarNetworkFeatDescription") }}</p>
<v-card class="w-full">
<v-tabs v-model="featuresTab" align-tabs="center" color="primary">
<v-tab
:prepend-icon="feat.icon"
:text="feat.title"
:value="feat.icon"
v-for="feat in features"
@mouseover="featuresTab = feat.icon"
/>
</v-tabs>
<v-tabs-window v-model="featuresTab">
<v-tabs-window-item :value="feat.icon" v-for="feat in features">
<v-card flat>
<v-img :aspect-ratio="16 / 9" :src="feat.image" cover></v-img>
<v-card-text>
<p class="text-lg mb-1">
{{ feat.description }}
</p>
</v-card-text>
</v-card>
</v-tabs-window-item>
</v-tabs-window>
</v-card>
</section>
<section class="content-section flex flex-col items-center justify-center" id="highlight-posts">
<v-row class="w-full" dense>
<v-col cols="12" md="6">
<div
class="max-h-[500px] overflow-y-auto posts-container"
ref="highlight-posts"
v-if="highlightPosts.status.value === 'success'"
>
<div v-for="post in highlightPosts.data.value">
<post-item :post="post" force-show-content class="mx-0" />
</div>
</div>
<v-progress-circular v-else indeterminate />
</v-col>
<v-col cols="12" md="6" class="text-right">
<h2 class="text-3xl font-bold">{{ t("solarNetworkHighlightPosts") }}<sup>®</sup></h2>
<p>{{ t("solarNetworkHighlightPostsDescription") }}</p>
<v-btn variant="text" color="white" slim prepend-icon="mdi-plus" href="#reminders">{{
t("solarNetworkJumpIn")
}}</v-btn>
</v-col>
</v-row>
</section>
<section class="content-section flex flex-col items-center justify-center text-center" id="reminders">
<h2 class="text-3xl font-bold">{{ t("solarNetworkBeforeYouStart") }}</h2>
<p class="text-lg">{{ t("solarNetworkBeforeYouStartDescription") }}</p>
<div class="max-h-[500px] w-full mt-4 text-left">
@ -63,8 +118,8 @@
<nuxt-link class="underline text-sm" to="/terms">{{ t("solarNetworkToSCheck") }}</nuxt-link>
</section>
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="downloads">
<h1 class="text-3xl font-bold">{{ t("download") }}</h1>
<section class="content-section flex flex-col items-center justify-center text-center" id="downloads">
<h3 class="text-3xl font-bold">{{ t("download") }}</h3>
<p class="text-lg">
File-hosting & versioning by
<nuxt-link class="underline" to="https://github.com/Solsynth/HyperNet.Surface" target="_blank">GitHub</nuxt-link
@ -82,8 +137,8 @@
>
{{ showPrerelease ? t("downloadSwitchRelease") : t("downloadSwitchPrerelease") }}
</v-btn>
<div class="max-h-[500px] w-full mt-4 text-left">
<v-row dense>
<div class="w-full mt-4 text-left">
<v-row dense class="flex-1">
<v-col cols="12" md="6">
<v-card
prepend-icon="mdi-alert-decagram"
@ -152,19 +207,101 @@
</v-row>
</div>
</section>
<section class="content-section flex flex-col items-center justify-center" id="help">
<h2 class="text-2xl font-bold text-center mb-4">{{ t("solarNetworkNeedHelp") }}</h2>
<div class="flex flex-col gap-2 w-[480px] max-w-screen">
<v-card :title="t('askHelpContactUs')" prepend-icon="mdi-email-fast" density="comfortable">
<v-card-text>
Contact our customer server at
<nuxt-link to="mailto:lily@solsynth.dev" class="underline">
<address>lily@solsynth.dev</address>
</nuxt-link>
</v-card-text>
</v-card>
<v-card :title="t('askHelpReadTheDocs')" prepend-icon="mdi-page-next" density="comfortable">
<v-card-text class="flex flex-col">
<nuxt-link to="https://kb.solsynth.dev" class="underline">Visit Goatpedia</nuxt-link>
<nuxt-link to="https://github.com/Solsynth/HyperNet.Surface/issues" class="underline"
>Visit GitHub Issue Tracker</nuxt-link
>
</v-card-text>
</v-card>
</div>
</section>
<copyright :service="['capital', 'roadsign']" />
</v-container>
</template>
<script lang="ts" setup>
import Icon from "~/assets/products/solar-network/icon.png"
import AlphaScreenshot from "~/assets/products/solar-network/alpha.webp"
import ScreenshotDashboard from "~/assets/products/solar-network/ft-dashboard.png"
import ScreenshotExplore from "~/assets/products/solar-network/ft-explore.png"
import ScreenshotChat from "~/assets/products/solar-network/ft-chat.png"
import ScreenshotNews from "~/assets/products/solar-network/ft-news.png"
import ScreenshotStickers from "~/assets/products/solar-network/ft-stickers.png"
import ScreenshotCompose from "~/assets/products/solar-network/ft-posting.png"
import AppStoreDownload from "~/assets/products/app-store-download.svg"
useHead({
title: "Solar Network",
})
import { formatBytes } from "~/utils/format"
import { Octokit } from "@octokit/rest"
const { t } = useI18n()
const featuresTab = ref()
interface FeatureItem {
title: string
description: string
icon: string
image: string
}
const features: FeatureItem[] = [
{
title: t("solarNetworkFeatDashboard"),
description: t("solarNetworkFeatDashboardDescription"),
icon: "mdi-view-dashboard",
image: ScreenshotDashboard,
},
{
title: t("solarNetworkFeatExplore"),
description: t("solarNetworkFeatExploreDescription"),
icon: "mdi-compass",
image: ScreenshotExplore,
},
{
title: t("solarNetworkFeatChat"),
description: t("solarNetworkFeatChatDescription"),
icon: "mdi-chat",
image: ScreenshotChat,
},
{
title: t("solarNetworkFeatNews"),
description: t("solarNetworkFeatNewsDescription"),
icon: "mdi-newspaper",
image: ScreenshotNews,
},
{
title: t("solarNetworkFeatStickers"),
description: t("solarNetworkFeatStickersDescription"),
icon: "mdi-sticker",
image: ScreenshotStickers,
},
{
title: t("solarNetworkFeatCompose"),
description: t("solarNetworkFeatComposeDescription"),
icon: "mdi-pencil",
image: ScreenshotCompose,
},
]
const latestRelease = useAsyncData("sn-latest-release", async () => {
const octo = new Octokit({})
const resp = await octo.repos.getLatestRelease({
@ -183,17 +320,97 @@ const latestPrerelease = useAsyncData("sn-latest-prerelease", async () => {
return resp.data[0]
})
const highlightPosts = useAsyncData("sn-highlight-posts", async () => {
const resp = await solarFetch("/cgi/co/recommendations")
const data = await resp.json()
return data
})
const showPrerelease = ref(false)
const currentRelease = computed(() => (showPrerelease.value ? latestPrerelease : latestRelease))
const hasPrerelease = computed<boolean>(
() => latestPrerelease.data?.value?.tag_name != latestRelease.data?.value?.tag_name,
)
const highlightPostContainer = useTemplateRef("highlight-posts")
function autoScroll() {
console.log("Auto scroll is called.")
const scrollSpeed = 1
let animationFrameId: number
let isScrolling = true
function scroll() {
if (!isScrolling) return
const container = highlightPostContainer.value!
if (container.scrollTop + container.clientHeight >= container.scrollHeight) {
container.scroll(0, 0)
} else {
container.scrollBy(0, scrollSpeed)
}
animationFrameId = requestAnimationFrame(scroll)
}
scroll()
const container = highlightPostContainer.value!
container.addEventListener("mouseenter", () => {
isScrolling = false
cancelAnimationFrame(animationFrameId)
})
container.addEventListener("mouseleave", () => {
if (!isScrolling) {
isScrolling = true
scroll()
}
})
}
watch(
highlightPostContainer,
(data) => {
if (data != null) {
autoScroll()
}
},
{ immediate: true, deep: true },
)
</script>
<style scoped>
.posts-container {
padding-top: 20px;
padding-bottom: 20px;
position: relative;
height: 500px;
overflow: hidden;
scrollbar-width: none;
mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
-webkit-mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
}
.posts-container::-webkit-scrollbar {
display: none;
}
.content-section {
min-height: calc(100vh - 80px);
height: auto;
display: flex;
place-items: center;
}