Compare commits
No commits in common. "master" and "archive/nuxtjs" have entirely different histories.
master
...
archive/nu
@ -1,28 +1,22 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&family=Noto+Sans+SC:wght@100..900&family=Noto+Sans+TC:wght@100..900&family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap");
|
||||
@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 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.v-application {
|
||||
overflow: auto !important;
|
||||
html, body, #app, .v-application {
|
||||
overflow: auto !important;
|
||||
|
||||
font-family: "Nunito", "Noto Sans SC", "Noto Sans TC", "Noto Sans JP", sans-serif !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-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;
|
||||
.font-mono, code, pre {
|
||||
font-family: "Roboto Mono", monospace !important;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
|
||||
<title>Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z" style="fill: #fff"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.99671,19.88935a5.14625,5.14625,0,0,1,2.45058-4.31771,5.26776,5.26776,0,0,0-4.15039-2.24376c-1.74624-.1833-3.43913,1.04492-4.329,1.04492-.90707,0-2.27713-1.02672-3.75247-.99637a5.52735,5.52735,0,0,0-4.65137,2.8367c-2.01111,3.482-.511,8.59939,1.41551,11.414.96388,1.37823,2.09037,2.91774,3.56438,2.86315,1.4424-.05983,1.98111-.91977,3.7222-.91977,1.72494,0,2.23035.91977,3.73427.88506,1.54777-.02512,2.52292-1.38435,3.453-2.77563a11.39931,11.39931,0,0,0,1.579-3.21589A4.97284,4.97284,0,0,1,24.99671,19.88935Z"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.15611,11.47681a5.06687,5.06687,0,0,0,1.159-3.62989,5.15524,5.15524,0,0,0-3.33555,1.72582,4.82131,4.82131,0,0,0-1.18934,3.4955A4.26259,4.26259,0,0,0,22.15611,11.47681Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M42.30178,27.13965h-4.7334l-1.13672,3.35645H34.42678l4.4834-12.418h2.083l4.4834,12.418H43.43752Zm-4.24316-1.54883h3.752L39.961,20.14355H39.9092Z"/>
|
||||
<path d="M55.1592,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.64455,21.34766,55.1592,23.16406,55.1592,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30178,29.01563,53.249,27.81934,53.249,25.96973Z"/>
|
||||
<path d="M65.12453,25.96973c0,2.81348-1.50635,4.62109-3.77881,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C63.6094,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91064,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26662,29.01563,63.21389,27.81934,63.21389,25.96973Z"/>
|
||||
<path d="M71.70949,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51758-3.61426,2.625,0,4.42383,1.47168,4.48438,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60547,1.626,3.60547,3.44238,0,2.32324-1.84961,3.77832-4.793,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z"/>
|
||||
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z"/>
|
||||
<path d="M86.064,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72512,30.6084,86.064,28.82617,86.064,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S92.7593,27.93164,92.7593,25.96973Z"/>
|
||||
<path d="M96.18508,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z"/>
|
||||
<path d="M109.38332,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10109,25.13477Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z"/>
|
||||
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z"/>
|
||||
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z"/>
|
||||
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z"/>
|
||||
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 696 KiB |
Before Width: | Height: | Size: 441 KiB |
Before Width: | Height: | Size: 787 KiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 770 KiB |
Before Width: | Height: | Size: 749 KiB |
Before Width: | Height: | Size: 461 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 2.0 MiB |
@ -22,8 +22,8 @@ const { t } = useI18n()
|
||||
const projects: { [id: string]: [string, string] } = {
|
||||
"solar-network": ["Solar Network", "https://solsynth.dev/products/solar-network"],
|
||||
"capital": ["Capital", "https://git.solsynth.dev/Goatworks/Capital"],
|
||||
"passport": ["HyperNet.Passport", "https://git.solsynth.dev/HyperNet/Passport"],
|
||||
"paperclip": ["HyperNet.Paperclip", "https://git.solsynth.dev/HyperNet/Paperclip"],
|
||||
"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>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<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/basic-law" class="hover:underline">Term of Service</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>
|
||||
|
@ -3,12 +3,10 @@
|
||||
<v-card-text>
|
||||
<div class="mb-3 flex flex-row gap-4">
|
||||
<nuxt-link :to="`/users/${post.publisher?.name}`">
|
||||
<v-avatar :image="getAttachmentUrl(post.publisher?.avatar)" icon="mdi-account-circle" />
|
||||
<v-avatar :image="post.publisher?.avatar" />
|
||||
</nuxt-link>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span
|
||||
>
|
||||
<span>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span>
|
||||
<span v-if="post.body?.title" class="text-md">{{ post.body?.title }}</span>
|
||||
<span v-if="post.body?.description" class="text-sm">{{ post.body?.description }}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">
|
||||
@ -31,7 +29,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<article v-if="(post.type == 'story' || props.forceShowContent) && post.body?.content" class="text-base prose max-w-none">
|
||||
<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>
|
||||
|
||||
@ -43,19 +41,24 @@
|
||||
</v-card>
|
||||
|
||||
<div class="text-sm flex flex-col">
|
||||
<span class="flex flex-row gap-1">
|
||||
<span> {{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }}, </span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="flex flex-row gap-1">
|
||||
<span>
|
||||
{{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }},
|
||||
</span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
{{ new Date(post.published_at).toLocaleString() }}
|
||||
</span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
{{ new Date(post.published_at).toLocaleString() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="post.tags?.length > 0" class="text-xs text-grey flex flex-row gap-1 mt-3">
|
||||
<div
|
||||
v-if="post.tags?.length > 0"
|
||||
class="text-xs text-grey flex flex-row gap-1 mt-3"
|
||||
>
|
||||
<nuxt-link
|
||||
v-for="tag in post.tags"
|
||||
:to="`/posts/tags/${tag.alias}`"
|
||||
@ -70,12 +73,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ post: any; forceShowContent?: boolean; noClickableAttachment?: boolean }>()
|
||||
const props = defineProps<{ post: any, forceShowContent?: boolean, noClickableAttachment?: boolean }>()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const url = computed(() =>
|
||||
props.post?.alias ? `/posts/${props.post?.alias_prefix}/${props.post?.alias}` : `/posts/${props.post?.id}`,
|
||||
)
|
||||
const url = computed(() => props.post.alias ? `/posts/${props.post.area_alias}/${props.post.alias}` : `/posts/${props.post.id}`)
|
||||
</script>
|
||||
|
@ -19,8 +19,8 @@ async function loadPost({ done }: any) {
|
||||
offset: posts.value.length.toString(),
|
||||
})
|
||||
|
||||
if (props.author) {
|
||||
searchQueries.set("author", props.author)
|
||||
if (props.publisher) {
|
||||
searchQueries.set("author", props.publisher)
|
||||
}
|
||||
if (props.realm) {
|
||||
searchQueries.set("realm", props.realm)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="my-2">
|
||||
<post-item v-if="status === 'success'" class="no-margin-post" :post="post" :force-show-content="props.forceShowContent" />
|
||||
<div v-else>{{ t("loading") }}</div>
|
||||
<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>
|
||||
|
||||
|
@ -5,11 +5,13 @@
|
||||
</v-alert>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-data-table
|
||||
<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"
|
||||
>
|
||||
@ -72,24 +74,23 @@
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card :title="`Delete sticker #${item.id}?`">
|
||||
<v-card-text>
|
||||
This action will delete this sticker, all content used it will no longer show your sticker. But the
|
||||
attachment will still exists.
|
||||
This action will delete this sticker, all content used it will no longer show your sticker.
|
||||
But the attachment will still exists.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn text="Cancel" color="grey" @click="isActive.value = false"></v-btn>
|
||||
<v-btn
|
||||
text="Cancel"
|
||||
color="grey"
|
||||
@click="isActive.value = false"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
text="Delete"
|
||||
color="error"
|
||||
@click="
|
||||
() => {
|
||||
deleteSticker(item)
|
||||
isActive.value = false
|
||||
}
|
||||
"
|
||||
@click="() => { deleteSticker(item); isActive.value = false }"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@ -98,7 +99,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-data-table-server>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -107,7 +108,7 @@ import { solarFetch } from "~/utils/request"
|
||||
const config = useRuntimeConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{ packId: number; packPrefix?: string }>()
|
||||
const props = defineProps<{ packId: number, packPrefix?: string }>()
|
||||
|
||||
const error = ref<null | string>(null)
|
||||
|
||||
@ -124,20 +125,34 @@ const dataDefinitions: { [id: string]: any[] } = {
|
||||
const stickers = ref<any>([])
|
||||
|
||||
const reverting = reactive({ stickers: false })
|
||||
const pagination = reactive({
|
||||
stickers: { page: 1, pageSize: 5, total: 0 },
|
||||
})
|
||||
|
||||
async function readStickers({ page, itemsPerPage }: { page?: number; itemsPerPage?: number }) {
|
||||
if (itemsPerPage) pagination.stickers.pageSize = itemsPerPage
|
||||
if (page) pagination.stickers.page = page
|
||||
|
||||
async function readStickers() {
|
||||
reverting.stickers = true
|
||||
const res = await solarFetch("/cgi/uc/stickers/packs/" + props.packId)
|
||||
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["stickers"]
|
||||
stickers.value = data["data"]
|
||||
pagination.stickers.total = data["count"]
|
||||
}
|
||||
reverting.stickers = false
|
||||
}
|
||||
|
||||
onMounted(() => readStickers())
|
||||
onMounted(() => readStickers({}))
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
@ -150,7 +165,7 @@ async function deleteSticker(item: any) {
|
||||
if (res.status !== 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
await readStickers()
|
||||
await readStickers({})
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
|
14
components/docs/TableOfContentLink.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<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>
|
11
components/docs/TableOfContentLinks.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<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>
|
13
components/docs/TableOfContents.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<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>
|
17
content/en/docs/index.mdx
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
icon: mdi-airplane-landing
|
||||
title: Welcome to Landing
|
||||
description: Welcome to Solsynth's Knowledge Base - The Solar Archive
|
||||
---
|
||||
|
||||

|
||||
|
||||
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.*
|
23
content/en/docs/solar-network.mdx
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
icon: mdi-web
|
||||
title: Solar Network
|
||||
description: The Next-Generation Social Network by Solsynth LLC
|
||||
---
|
||||
|
||||

|
||||
|
||||
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.
|
9
content/en/docs/solar-network/creator.mdx
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
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!
|
38
content/en/docs/solar-network/creator/stickers.mdx
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
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.
|
10
content/en/docs/solar-network/open-project.mdx
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
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!
|
71
content/en/docs/solar-network/open-project/api-standard.mdx
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
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.
|
104
content/en/docs/solar-network/solian/index.mdx
Normal file
@ -0,0 +1,104 @@
|
||||
---
|
||||
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.
|
@ -22,6 +22,6 @@ downloads:
|
||||
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-download-link{:items='downloads'}
|
||||
|
||||
:embed-post-item{id=914}
|
||||
|
@ -1,54 +0,0 @@
|
||||
---
|
||||
title: User Agreement / Basic Law
|
||||
date: 2025-03-19T16:12:21.897Z
|
||||
---
|
||||
|
||||
This User Agreement (a.k.a. the Basic Law) is the framework of rules for all Solsynth LLC products, and other related regulations should comply with it, or the entry will not be effective.
|
||||
|
||||
This User Agreement will be referred to herein as “these Terms and Conditions” and Solsynth LLC will refer to it as “we” and “us”.
|
||||
As used herein, account number, account, and Solarpass refer to the User's account on the Solar Network.
|
||||
|
||||
## Scope of Application
|
||||
|
||||
1. The User Agreement applies to all Solsynth LLC products, including but not limited to Solar Network, DietaryGuard, and AceField. 2.
|
||||
2. Any prior events will still be subject to the latest revised version of these Terms and Conditions. 3.
|
||||
3. All Solar Network users are deemed to have agreed to the contents of these Terms and Conditions and any subsequent updates to these Terms and Conditions upon registration.
|
||||
4. The final interpretation of these Terms and Conditions is the responsibility of Solsynth LLC and the legislator of the relevant entry.
|
||||
|
||||
## Amendments and Updates to the Terms and Conditions
|
||||
|
||||
1. Changes to these Regulations shall be made by Solsynth LLC and the Legislator.
|
||||
2. The Legislative Councilor's proposal to amend the Basic Law shall be supported by a referendum of more than **3/2** of the users and shall not be subject to the one-vote right of passage.
|
||||
3. Legislators' proposals to amend other sub-regulations shall be supported by a referendum of more than **one-half** of the users.
|
||||
4. Some special regulations are protected from amendment by legislators.
|
||||
5. Regarding any subsequent updates to the ordinance, we will notify the content of the update by means of “notification on the website” or “email push”.
|
||||
|
||||
## Provision and Discontinuation of Services
|
||||
|
||||
1. Solsynth LLC will provide the same service to all natural persons in the world. 2.
|
||||
2. We also reserve the right to discontinue the service to any user, and in principle we will inform the reason for discontinuance.
|
||||
3. after the termination or suspension of the service, the user has the right to ask us to delete or export all user data.
|
||||
4. In case of violation of the relevant regulations, the user will receive three kinds of penalties: **Warning, Suspension and Disablement**.
|
||||
- Warning (Strike): The warning will not have any practical effect on the User and will be automatically revoked after 180 days without any bad behavior. If the user receives another warning within the warning period, the penalty will be upgraded to suspension.
|
||||
- Suspension: There are two types of suspension: “Full Suspension” and “Partial Suspension”. Full Suspension” shall, in principle, have a time limit for entry into force.
|
||||
- Full Suspension: The user will not be allowed to access any Solar Network and other services, and will not be able to log in to Solarpass.
|
||||
- Partial deactivation: Partial disabling of the user's rights, e.g. uploading of files, publishing of posts, etc.
|
||||
- Disablement: The user's entire account and all rights of Solsynth LLC to use other services are disabled. We also reserve the right to delete the relevant data.
|
||||
5. A natural person can register and own only one Solarpass account, and we reserve the right to take action against other sub-accounts of the same User for deletion of data.
|
||||
6. The transfer and sale of Solarpass accounts are strictly prohibited. If such behavior is discovered, measures will be taken to delete the relevant data immediately.
|
||||
7. If a user opens a sub-account in any way during the penalty period in an attempt to evade the penalty, the sub-account shall be subject to deletion of data and the penalty shall be escalated or the time limit extended, as the case may be.
|
||||
8. Bot accounts opened through the Developer Portal are not considered sub-accounts. *For more information on the use of bot accounts, please refer to the Developer Rules (/terms/developer-rules).
|
||||
|
||||
## 4. User Generated Content
|
||||
|
||||
1. we do not assume any responsibility for user-generated content posted on our Products. 2.
|
||||
2. Regarding copyright infringement of content published by users on our products, we will remove the content in question; if we agree that there is a large amount of copyright infringement by the publisher, we will impose penalties of **warning and suspension of rights** depending on the situation.
|
||||
3. In principle, we do not restrict users' freedom of expression, with the exception of the following cases, in which we will remove the content and penalize the publisher according to the circumstances:
|
||||
- Copyright infringement
|
||||
- Board-washing, meaningless content *See [community-safety-law](/terms/community-safety-law)* for details.
|
||||
- Spreading rumors, fear-mongering, extremist speech *See the Community Safety Laws for more information.
|
||||
4. With regard to the files uploaded by the User on Solar Network, they are considered to be public content on the Internet; at the moment of completion of the upload the User is considered to have authorized us with the required copyright to display the respective content.
|
||||
|
||||
## 5. User Privacy Protection
|
||||
|
||||
*The contents of this chapter are detailed in the [privacy-policy](/terms/privacy-policy)*
|
@ -1,48 +1,50 @@
|
||||
---
|
||||
title: Privacy Policy / Privacy Protection Law
|
||||
date: 2025-03-19T16:12:21.897Z
|
||||
title: Privacy Policy
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
This regulation is an expansion of Chapter 5 of the contents of the “Basic Law”. This entry applies the security protection policy (direct modification by the legislator is not allowed due to the design of user data security).
|
||||
## Introduction
|
||||
|
||||
We take your privacy very seriously. This Privacy Policy outlines the types of personal information we collect, how we use it, and the protective measures we take.
|
||||
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.
|
||||
|
||||
## 5.1 Information Collection
|
||||
## Information Collection
|
||||
|
||||
We only collect personal information that is necessary to provide our services. This includes:
|
||||
We collect personal information only when necessary to provide our services.
|
||||
This may include your name, email address, and other relevant details.
|
||||
|
||||
- Email
|
||||
- Telephone number *optional*
|
||||
- Name *optional
|
||||
- Address *optional
|
||||
- Date of Birth *Optional
|
||||
- Gender *selectable
|
||||
- Internet Address
|
||||
- Device Identifier
|
||||
- User behavior data
|
||||
|
||||
## 5.2 Use of Information
|
||||
## Use of Information
|
||||
|
||||
We use your personal information to:
|
||||
|
||||
- Provide data necessary for the provision and use of our services
|
||||
- communicate with you about updates to regulations or other important information
|
||||
- analyze services to improve the quality of our services
|
||||
- Provide and improve our services
|
||||
- Communicate with you about updates or important information
|
||||
- Ensure compliance with legal obligations
|
||||
|
||||
## 5.3 Data Sharing
|
||||
## Data Sharing
|
||||
|
||||
We do not sell or trade your personal information.
|
||||
We do not sell, trade, or share your personal information with third parties except as required by law.
|
||||
|
||||
We share some of your personal information, which may include device identifiers and behavioral data, with our partner Google Analytics to help us analyze and improve our services, as described in Google's Privacy Policy (https://policies.google.com/privacy).
|
||||
## Data Security
|
||||
|
||||
## 5.4 Data Security
|
||||
We implement robust security measures to protect your personal information from unauthorized access,
|
||||
alteration, disclosure, or destruction.
|
||||
|
||||
We have implemented strong security measures (including, but not limited to, the use of industry-leading encryption algorithms, a database key rotation policy, etc.) to protect your personal information from unauthorized access, alteration, disclosure or destruction.
|
||||
## Your Rights
|
||||
|
||||
## 5.5 Your Rights
|
||||
|
||||
Regardless of the penalties imposed on your account, you always have the right to:
|
||||
You have the right to:
|
||||
|
||||
- Access the personal information we hold about you
|
||||
- Request correction of your personal information
|
||||
- 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.
|
||||
|
77
content/en/terms/user-agreement.mdx
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
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.
|
18
content/zh-CN/docs/index.mdx
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
icon: mdi-airplane-landing
|
||||
title: 欢迎着陆
|
||||
description: 欢迎来到 Solsynth 的知识库 —— 太阳能档案
|
||||
---
|
||||
|
||||

|
||||
|
||||
欢迎来到 Solsynth 资料库!
|
||||
|
||||
Solsynth 资料库,又称太阳档案馆,现知的最大 Solsynth LLC 产品资料库。由 Solsynth LLC 运营。由社区提供内容及资源,官方监督改正。
|
||||
|
||||
我们目前还在修建档案馆,但是未来这里将可以查阅到我们的所有资料。
|
||||
|
||||
你可以通过 Fork 我们的 [Capital](https://git.solsynth.dev/Goatworks/Capital) 来编辑 `content/<lang>/docs` 内部的文件并提交 PR 来贡献我们的文档。
|
||||
无论是新增内容、修改不正确的地方,都欢迎贡献。
|
||||
|
||||
*P.S. 你可以使用 Solarpass 一键登陆 Solsynth Code Repository*
|
25
content/zh-CN/docs/solar-network.mdx
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
icon: mdi-web
|
||||
title: Solar Network
|
||||
description: Solsynth LLC 的下一代社交网络
|
||||
---
|
||||
|
||||

|
||||
|
||||
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 暴露出来的网关
|
9
content/zh-CN/docs/solar-network/creator.mdx
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
icon: mdi-palette
|
||||
title: 创作者计划
|
||||
description: 欢迎来到 Solar Network 创作者计划,在这里共创 Solar Network
|
||||
---
|
||||
|
||||
创作者计划是 Solar Network 为了推动用户创作内容而诞生的一个企划,企划旨在帮助创作者更好的使用 Solar Network 产出更高质量的内容。
|
||||
|
||||
加入创作者计划,获得更多官方支持,第一时间使用新功能,为未来的 Solar Network 提出宝贵的建议吧!
|
40
content/zh-CN/docs/solar-network/creator/stickers.mdx
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
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 的大小
|
||||
|
||||
这一系列的匹配都将发生在一个段落中。
|
10
content/zh-CN/docs/solar-network/open-project.mdx
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
icon: mdi-oci
|
||||
title: 开放计划
|
||||
description: 欢迎来到 Solar Network 开放计划,让我们助力你的应用成长
|
||||
---
|
||||
|
||||
开放计划是 Solar Network 一系列的开发者友好的 API 和小工具集合。
|
||||
我们坚持不为开发者添堵的规则,非必要不加密参数,永不混淆参数,人性化 API 接口,RESTful API 端点,尽可能的让开发者体验好。
|
||||
|
||||
现在就开始浏览,看看你能用 Solar Network 做些什么吧!
|
@ -0,0 +1,70 @@
|
||||
---
|
||||
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)
|
||||
|
||||
> 冷知识:你可能注意到了我们新配置的别名其实就是之前没有超级网关时他们使用的子域名。
|
106
content/zh-CN/docs/solar-network/solian/index.mdx
Normal file
@ -0,0 +1,106 @@
|
||||
---
|
||||
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,54 +0,0 @@
|
||||
---
|
||||
title: 用户协议 / 基本法
|
||||
date: 2025-03-19T16:12:21.897Z
|
||||
---
|
||||
|
||||
本用户协议(又称基本法)是 Solsynth LLC 所有产品的规则框架,其他相关的条例应该遵守本条例,否则该条目不生效。
|
||||
|
||||
本文将省略称呼本用户协议为「本条例」,Solsynth LLC 称为「我们」。
|
||||
本文中帐号、帐户、及 Solarpass 均指代用户在 Solar Network 上开设的帐号。
|
||||
|
||||
## 1. 适用范围
|
||||
|
||||
1. 用户协议适用于所有 Solsynth LLC 的产品,包括但不限于 Solar Network、DietaryGuard 及 AceField。
|
||||
2. 任何发生在以前的事件仍然适用最新版修订版的本条例。
|
||||
3. 所有 Solar Network 用户在注册时视为同意本条例的内容以及其后续更新。
|
||||
4. 条例的最终解释权归属于 Solsynth LLC 及相关条目立法委员。
|
||||
|
||||
## 2. 条例的修改和更新
|
||||
|
||||
1. 本条例的修改由 Solsynth LLC 和立法委员共同完成。
|
||||
2. 立法委员提出关于修改「基本法」的提案应当得到超过**二分之三**的用户公投支持并不适用一票通过权。
|
||||
3. 立法委员提出关于修改其他子条例的提案应当得到超过**二分之一**的用户公投支持。
|
||||
4. 部份特殊条例实行保护方针不允许立法委员修改。
|
||||
5. 关于后续任何的条例更新,我们将采取「站内通知」或「邮件推送」的方式通知内容更新。
|
||||
|
||||
## 3. 服务的提供与中断
|
||||
|
||||
1. Solsynth LLC 将向世界上所有的自然人提供同等的服务。
|
||||
2. 我们同时保留向任何用户停止服务的权利,原则上我们会告知停止服务的原因。
|
||||
3. 在用户的服务被终止或停权之后,用户有权向我们要求删除或导出所有的用户资料。
|
||||
4. 用户在违反相关条例时,会收到**警告、停权、禁用**三种处罚措施。
|
||||
- 警告 (Strike): 不会对用户造成任何实际上的影响,警告会在无任何不良行为 180 天后自动撤销。若用户在警告期内再次获得警告,处罚将升级为停权。
|
||||
- 停权:停权分为两种类型「完全停权」和「部份停权」。其中「完全停权」原则上应有生效时限。
|
||||
- 完全停权:用户将不允许存取任何 Solar Network 和其他服务内容,同时也会无法登陆 Solarpass。
|
||||
- 部份停权:禁用用户的部份权利,例如上传文件、发布帖子等。
|
||||
- 禁用:禁用用户的整个帐号和所有 Solsynth LLC 使用其他服务的权利。同时我们保留删除相关数据的权利。
|
||||
5. 一个自然人只能注册、拥有一个 Solarpass 帐号,我们有权对其他同用户的子帐号采取删除数据的措施。
|
||||
6. 关于 Solarpass 帐号的转让、出售是绝对禁止的行为,关于发现相关行为将立即采取删除相关数据的措施。
|
||||
7. 若用户在处罚期间采取任何方式开设子帐号试图逃避处罚,应当对子帐号采取删除数据的措施,并且视情况升级处罚或延长时限。
|
||||
8. 通过「开发者门户」开设的机器人帐号不属于子帐号范畴。*关于「机器人帐号」的使用规定,详见 [开发者守则](/terms/developer-rules)*
|
||||
|
||||
## 4. 用户生成内容
|
||||
|
||||
1. 我们不承担任何关于用户在我们产品上发表的内容的责任。
|
||||
2. 关于用户在我们产品上发布的内容侵犯版权时,我们会对相关内容进行删除;若同意发布者有大量侵犯版权的情况,根据情况处以**警告及停权**的处罚。
|
||||
3. 我们原则上不会限制用户的言论自由,但以下情况例外,我们会根据情况对相关内容进行删除并处罚发布者:
|
||||
- 侵犯版权
|
||||
- 洗板,无意义的内容 *详见 [社区治安条例](/terms/community-safety-law)*
|
||||
- 散播谣言、恐慌、极端主义的言论 *详见 [社区治安条例](/terms/community-safety-law)*
|
||||
4. 关于用户上传在 Solar Network 上的文件,视为互联网上的公开内容;在用户上传完成的即刻起,视为用户授权我们所需的版权展示相关的内容。
|
||||
|
||||
## 5. 用户隐私保护
|
||||
|
||||
*本章内容详见 [隐私保护法](/terms/privacy-policy)*
|
@ -1,48 +1,44 @@
|
||||
---
|
||||
title: 隐私策略 / 隐私保护法
|
||||
date: 2025-03-19T16:12:21.897Z
|
||||
title: 隐私策略
|
||||
date: 2024-08-15T15:18:48.218Z
|
||||
---
|
||||
|
||||
本条例是对「基本法」内容第五章的扩充。本条目适用安全保护方针(因设计用户数据安全,不允许立法委员直接修改)。
|
||||
## 简介
|
||||
|
||||
我们非常重视您的隐私。本隐私政策概述了我们收集的个人信息类型、使用方式以及我们采取的保护措施。
|
||||
|
||||
## 5.1 信息收集
|
||||
## 信息收集
|
||||
|
||||
我们仅在提供服务时收集必要的个人信息。这包括:
|
||||
我们仅在提供服务时收集必要的个人信息。这可能包括您的姓名、电子邮件地址以及其他相关信息。
|
||||
|
||||
- 电子邮件
|
||||
- 电话号码 *可选*
|
||||
- 姓名 *可选*
|
||||
- 地址 *可选*
|
||||
- 出生日期 *可选*
|
||||
- 性别 *可选*
|
||||
- 互联网地址
|
||||
- 设备标识符
|
||||
- 用户行为数据
|
||||
|
||||
## 5.2 信息使用
|
||||
## 信息使用
|
||||
|
||||
我们使用您的个人信息来:
|
||||
|
||||
- 提供和我们的服务使用的必要数据
|
||||
- 与您沟通相关条例更新或其他重要信息
|
||||
- 分析服务提升我们服务的质量
|
||||
- 提供和改进我们的服务
|
||||
- 与您沟通更新或重要信息
|
||||
- 确保遵守法律义务
|
||||
|
||||
## 5.3 数据共享
|
||||
## 数据共享
|
||||
|
||||
我们不会出售、交易您的个人信息。
|
||||
我们不会出售、交易或与第三方分享您的个人信息,法律要求除外。
|
||||
|
||||
我们与我们的合作伙伴 Google Analytics 共享您部份的个人信息,这可能包括设备标识符和行为数据,来帮助我们分析和改进我们的服务,详见 [Google 的隐私政策](https://policies.google.com/privacy)。
|
||||
## 数据安全
|
||||
|
||||
## 5.4 数据安全
|
||||
我们实施了强有力的安全措施,以保护您的个人信息免受未经授权的访问、更改、披露或销毁。
|
||||
|
||||
我们实施了强有力的安全措施(包括但不限于使用业界领先的加密算法,实行数据库密钥轮换政策等),以保护您的个人信息免受未经授权的访问、更改、披露或销毁。
|
||||
## 您的权利
|
||||
|
||||
## 5.5 您的权利
|
||||
|
||||
无论您的帐号被如何处罚,您一直有权:
|
||||
您有权:
|
||||
|
||||
- 访问我们持有的关于您的个人信息
|
||||
- 请求更正您的个人信息
|
||||
- 请求删除您的个人信息
|
||||
|
||||
## 联系我们
|
||||
|
||||
如果您对本隐私政策或我们的数据处理方式有任何疑问或顾虑,请通过[您的联系方式]与我们联系。
|
||||
|
||||
## 政策变更
|
||||
|
||||
我们可能会不时更新本隐私政策。任何更改将发布在此页面上,且我们会通知您任何重大更改。
|
||||
|
77
content/zh-CN/terms/user-agreement.mdx
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
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 保留对此协议的最终解释权
|
20
generator/goat-language-translator.ts
Normal file
@ -0,0 +1,20 @@
|
||||
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))
|
@ -69,16 +69,6 @@
|
||||
"continueReading": "Continue Reading",
|
||||
"download": "Download",
|
||||
"downloadDescription": "Pick the right version for you",
|
||||
"downloadSwitchPrerelease": "Switch to pre-release",
|
||||
"downloadSwitchRelease": "Switch to regular release",
|
||||
"downloadForApple": "Looking for iOS / macOS version?",
|
||||
"downloadTestFlight": "TestFlight",
|
||||
"downloadTestFlightDescription": "For pre-release version",
|
||||
"downloadForDesktop": "Looking for desktop version?",
|
||||
"downloadForDesktopDescription": "If the release does not contain the desktop version, you can still download the latest build from GitHub Action",
|
||||
"downloadWithoutDownload": "Want have a try without downloading?",
|
||||
"downloadWeb": "Web",
|
||||
"downloadWebChina": "with China Mainland Optimized",
|
||||
"attachmentUpload": "Upload new",
|
||||
"attachmentCreate": "Create Attachment",
|
||||
"attachmentCreateCaption": "Use Solar Network host your files",
|
||||
@ -86,34 +76,5 @@
|
||||
"attachmentUploadCompleted": "Uploaded",
|
||||
"upload": "Upload",
|
||||
"cancel": "Cancel",
|
||||
"seeMore": "See more",
|
||||
"solarNetworkDescription": "A open, free, and friendly social network.",
|
||||
"solarNetworkBeforeYouStart": "Before you start",
|
||||
"solarNetworkBeforeYouStartDescription": "Learn some culture and basics of Solar Network",
|
||||
"solarNetworkFreedomOfSpeech": "Freedom of Speech",
|
||||
"solarNetworkFreedomOfSpeechDescription": "While Solar Network protects your freedom of speech and does not manually delete posts, this does not mean you are not responsible for your words. Additionally, when the 'flag the post' feature is activated, your posts will be hidden from other users. We still encourage users to prioritize harmony and minimize conflicts.",
|
||||
"solarNetworkConfirmAccount": "Confirm Account",
|
||||
"solarNetworkConfirmAccountDescription": "After registering, please check your bound email for the account confirmation email. Otherwise, your account will be reclaimed within 24 hours, and during this period, no permissions will be assigned, affecting most functionalities.",
|
||||
"solarNetworkNoImpersonation": "No Impersonation",
|
||||
"solarNetworkNoImpersonationDescription": "Do not impersonate individuals either within or outside the platform, especially those with a certain level of recognition. Regardless of intent, if it causes misunderstanding among users, we reserve the right to take action on the relevant account and content.",
|
||||
"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",
|
||||
"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?"
|
||||
"seeMore": "See more"
|
||||
}
|
||||
|
55
lang/tb-SG.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"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": "咩→咩↓"
|
||||
}
|
@ -69,16 +69,6 @@
|
||||
"continueReading": "继续阅读",
|
||||
"download": "下载",
|
||||
"downloadDescription": "选择适合你的版本下载",
|
||||
"downloadSwitchPrerelease": "切换至预发行版本",
|
||||
"downloadSwitchRelease": "切换至稳定版本",
|
||||
"downloadForApple": "使用 iOS / macOS 的设备?",
|
||||
"downloadTestFlight": "测试飞机 (TestFlight)",
|
||||
"downloadTestFlightDescription": "提供预发行版本",
|
||||
"downloadForDesktop": "使用桌面设备?",
|
||||
"downloadForDesktopDescription": "通常如果发行未包含桌面版本,你仍然可以从 GitHub Action 处下载最新的构建",
|
||||
"downloadWithoutDownload": "想不下载尝试一下?",
|
||||
"downloadWeb": "网页版",
|
||||
"downloadWebChina": "中国大陆特供版本 (优化过的内容分发网络)",
|
||||
"attachmentUpload": "新传附件",
|
||||
"attachmentCreate": "新建附件",
|
||||
"attachmentCreateCaption": "使用 Solar Network 来托管你的文件",
|
||||
@ -86,38 +76,5 @@
|
||||
"attachmentUploadCompleted": "上传完成",
|
||||
"upload": "上传",
|
||||
"cancel": "取消",
|
||||
"seeMore": "查看更多",
|
||||
"solarNetworkDescription": "开放、包容、和谐",
|
||||
"solarNetworkBeforeYouStart": "桥豆麻袋",
|
||||
"solarNetworkBeforeYouStartDescription": "在你开始之前,了解一些关于 Solar Network 文化和常识",
|
||||
"solarNetworkFreedomOfSpeech": "言论自由",
|
||||
"solarNetworkFreedomOfSpeechDescription": "尽管 Solar Network 保护你的言论自由,不会手动对帖子进行删除。但是这不代表你可以对你的言论不负责。同时 Solar Network 上的「吹哨」功能生效时会对其他用户隐藏你的帖子。我们还是希望用户能以和为贵,少发生争吵。",
|
||||
"solarNetworkConfirmAccount": "确认账户",
|
||||
"solarNetworkConfirmAccountDescription": "在注册之后记得前往您绑定的邮件获取确认帐号的邮件,否则您的帐号会在 24 小时内被回收,并且期间不会分配权限,影响绝大部分功能使用。",
|
||||
"solarNetworkNoImpersonation": "不要冒充他人",
|
||||
"solarNetworkNoImpersonationDescription": "不要冒充在站内 / 站外的人物,对方有一定知名度的甚是。无论出发点如何,对用户造成了误解时我们保留权利处理相关帐号和内容。",
|
||||
"solarNetworkReadDialog": "阅读错误提示",
|
||||
"solarNetworkReadDialogDescription": "遇到报错提示不要第一时间截图抱怨,尝试理解为什么这件事情发生。其次在开发频道或 GitHub 寻求帮助,不要发帖抱怨。",
|
||||
"solarNetworkToS": "还有,如果你继续注册 Solarpass 帐号,这意味着你同意我们的各项条款",
|
||||
"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": "阅读文档"
|
||||
"seeMore": "查看更多"
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
<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-app-bar-nav-icon @click="openDrawer = !openDrawer" />
|
||||
<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="/creator" exact>
|
||||
<h2>Creator Hub</h2>
|
||||
<nuxt-link to="/dev" exact>
|
||||
<h2 class="mt-1">Creator Hub</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
@ -38,10 +45,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const openDrawer = ref(false)
|
||||
|
||||
useHead({
|
||||
titleTemplate: "%s | Solsynth Creator Hub",
|
||||
titleTemplate: "%s | Solsynth Creator Hub"
|
||||
})
|
||||
</script>
|
||||
|
@ -1,48 +1,41 @@
|
||||
<template>
|
||||
<v-app-bar app flat color="surface" class="app-bar-blur">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center pr-8 relative">
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" class="z-10" />
|
||||
<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 class="z-10">
|
||||
<h2 v-if="isLargeScreen">Solsynth LLC</h2>
|
||||
<v-icon v-else icon="mdi-home" />
|
||||
|
||||
<nuxt-link to="/" exact>
|
||||
<h2 class="mt-1">Solsynth LLC</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<div class="absolute left-0 right-0 flex justify-center gap-2 w-screen">
|
||||
<v-btn v-if="isLargeScreen" v-for="item in navItems" :to="item.to" exact :prepend-icon="item.icon">{{
|
||||
t(item.title)
|
||||
}}</v-btn>
|
||||
<v-menu location="bottom center" v-else>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-dots-horizontal-circle" slim size="small" />
|
||||
</template>
|
||||
<v-list nav slim class="w-[280px]">
|
||||
<v-list-item v-for="item in navItems" :to="item.to" :prepend-icon="item.icon">
|
||||
<v-list-item-title>{{ t(item.title) }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<locale-select class="z-10" />
|
||||
<user-menu class="z-10" />
|
||||
<locale-select />
|
||||
<user-menu />
|
||||
</v-container>
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer v-model="openDrawer" location="left" width="300" temporary order="-1">
|
||||
<v-navigation-drawer v-model="openDrawer" location="left" width="300" floating>
|
||||
<v-list density="compact" nav color="primary">
|
||||
<v-list-item title="Developer Portal" prepend-icon="mdi-code-tags" to="/dev" exact />
|
||||
<v-list-item title="Creator Hub" prepend-icon="mdi-pencil" to="/creator" exact />
|
||||
<v-list-item :title="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="Code Repository" prepend-icon="mdi-git" href="https://git.solsynth.dev" target="_blank" />
|
||||
<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" />
|
||||
@ -58,45 +51,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBreakpoints, breakpointsVuetifyV3 } from "@vueuse/core"
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n()
|
||||
|
||||
const openDrawer = ref(false)
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsVuetifyV3)
|
||||
const isLargeScreen = computed(() => breakpoints.isGreaterOrEqual("md").valueOf())
|
||||
|
||||
interface NavItem {
|
||||
icon: string
|
||||
title: string
|
||||
to: string
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
icon: "mdi-shape",
|
||||
title: "navProducts",
|
||||
to: "/products",
|
||||
},
|
||||
{
|
||||
icon: "mdi-note-text",
|
||||
title: "navPosts",
|
||||
to: "/posts",
|
||||
},
|
||||
{
|
||||
icon: "mdi-image-multiple",
|
||||
title: "navGallery",
|
||||
to: "/gallery",
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.app-bar-blur {
|
||||
-webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 40%, rgba(0, 0, 0, 0.5) 65%, rgba(0, 0, 0, 0) 100%);
|
||||
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 40%, rgba(0, 0, 0, 0.5) 65%, rgba(0, 0, 0, 0) 100%);
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,10 +1,17 @@
|
||||
<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 pr-8">
|
||||
<v-app-bar-nav-icon @click="openDrawer = !openDrawer" />
|
||||
<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>Developer Portal</h2>
|
||||
<h2 class="mt-1">Developer Portal</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
@ -38,12 +45,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "~/assets/logo-w-shadow.png"
|
||||
import Logo from "../assets/logo-w-shadow.png"
|
||||
|
||||
const { t } = useI18n()
|
||||
const openDrawer = ref(false)
|
||||
|
||||
useHead({
|
||||
titleTemplate: "%s | Solsynth Dev Portal",
|
||||
titleTemplate: "%s | Solsynth Dev Portal"
|
||||
})
|
||||
</script>
|
||||
|
115
layouts/docs.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<v-app-bar flat color="primary">
|
||||
<v-container fluid class="mx-auto d-flex align-center justify-center pe-8">
|
||||
<v-app-bar-nav-icon class="me-1" @click="openDrawer = !openDrawer" />
|
||||
|
||||
<nuxt-link to="/docs" exact>
|
||||
<h2 class="mt-0.5">Solsynth Knowledge Base</h2>
|
||||
</nuxt-link>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<locale-select @update="reload" />
|
||||
<user-menu />
|
||||
</v-container>
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer v-model="openDrawer" location="left" width="300" floating>
|
||||
<content-navigation v-slot="{ navigation }" :query="navQuery" :key="route.path">
|
||||
<v-list density="compact" nav color="primary" class="mt-1">
|
||||
<v-list-item
|
||||
v-if="navNotRoot"
|
||||
title="Previous"
|
||||
prepend-icon="mdi-page-previous"
|
||||
exact
|
||||
@click="previousNav"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
v-if="navigation[0]?.children"
|
||||
v-for="link of fullyFlatMap(navigation[0])"
|
||||
:key="link._path"
|
||||
:title="link.title"
|
||||
:to="link._path"
|
||||
:prepend-icon="link.icon ?? 'mdi-text-box'"
|
||||
exact
|
||||
/>
|
||||
|
||||
<v-list-item v-else title="No Content" prepend-icon="mdi-close-box-outline" disabled />
|
||||
</v-list>
|
||||
</content-navigation>
|
||||
|
||||
<v-divider class="border-opacity-50 mb-4 mt-0.5" />
|
||||
|
||||
<copyright no-centered service="capital" class="px-5" />
|
||||
|
||||
<footer-links class="px-5 mt-3" />
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar color="transparent" density="compact" class="backdrop-blur-md">
|
||||
<v-app-bar-nav-icon icon="mdi-home" to="/" class="ms-4" />
|
||||
<div class="flex items-center justify-center h-[60px]">
|
||||
<v-breadcrumbs :items="breadcrumb" density="compact" class="px-0 mt-0.5"></v-breadcrumbs>
|
||||
</div>
|
||||
</v-app-bar>
|
||||
|
||||
<v-main>
|
||||
<slot />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { locale } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const breadcrumb = computed(() => {
|
||||
const arr = route.path.replace(/^\/|\/$/g, "").split("/")
|
||||
arr.shift()
|
||||
return arr.map((x, idx) => ({
|
||||
title: x,
|
||||
to: `/docs/${arr.slice(0, idx + 1).join("/")}`,
|
||||
}))
|
||||
})
|
||||
|
||||
const navNotRoot = computed(() => route.path.split("/").length > 2)
|
||||
const navQuery = ref(calcNavQuery())
|
||||
|
||||
function calcNavQuery(path?: string) {
|
||||
return {
|
||||
where: {
|
||||
_path: new RegExp("^\\/" + (path ?? route.path).replace(/^\/|\/$/g, "") + "\\/[^\\/]+\\/?$"),
|
||||
_locale: getLocale(locale),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
watch(route, (value) => {
|
||||
navQuery.value = calcNavQuery(value.path)
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
function previousNav() {
|
||||
const arr = route.path.split("/")
|
||||
arr.pop()
|
||||
navigateTo(arr.join("/"))
|
||||
}
|
||||
|
||||
function fullyFlatMap(input: any): any[] {
|
||||
const result: any[] = []
|
||||
const pathSet = new Set<string>()
|
||||
for (const item of input?.children ?? []) {
|
||||
result.push(item)
|
||||
result.push(...fullyFlatMap(item))
|
||||
}
|
||||
return result.filter((x) => {
|
||||
if (pathSet.has(x._path)) return false
|
||||
pathSet.add(x._path)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = ref(false)
|
||||
|
||||
function reload() {
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
@ -1,6 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
// No further supported path prefix localization
|
||||
if (to.path.startsWith("/zh-CN")) {
|
||||
return navigateTo(to.fullPath.replace("/zh-CN", ""))
|
||||
}
|
||||
})
|
@ -19,7 +19,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
posts: {
|
||||
includeAppSources: false,
|
||||
sources: ["/api/sitemap/posts"],
|
||||
sources: [
|
||||
"/api/sitemap/posts",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -35,6 +37,7 @@ export default defineNuxtConfig({
|
||||
locales: [
|
||||
{ code: "en", name: "English", file: "en-US.json" },
|
||||
{ code: "zh-CN", name: "简体中文", file: "zh-CN.json" },
|
||||
{ code: "tb-SG", name: "音调羊文", file: "tb-SG.json" },
|
||||
],
|
||||
lazy: true,
|
||||
langDir: "lang",
|
||||
@ -53,8 +56,8 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
"/.well-known/**": {
|
||||
proxy: "/api/well-known/**",
|
||||
"/.well-known/openid-configuration": {
|
||||
proxy: "/api/well-known/openid-configuration",
|
||||
},
|
||||
},
|
||||
|
||||
@ -64,7 +67,9 @@ export default defineNuxtConfig({
|
||||
title: "Solsynth LLC",
|
||||
titleTemplate: "%s | Solsynth",
|
||||
meta: [],
|
||||
link: [{ rel: "icon", type: "image/png", href: "/icon.png" }],
|
||||
link: [
|
||||
{ rel: "icon", type: "image/png", href: "/icon.png" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@ -74,62 +79,11 @@ export default defineNuxtConfig({
|
||||
},
|
||||
highlight: {
|
||||
theme: { default: "github-light", dark: "github-dark" },
|
||||
langs: [
|
||||
"json",
|
||||
"yaml",
|
||||
"toml",
|
||||
"java",
|
||||
"javascript",
|
||||
"astro",
|
||||
"css",
|
||||
"scss",
|
||||
"dart",
|
||||
"go",
|
||||
"typescript",
|
||||
"c",
|
||||
"csharp",
|
||||
"cpp",
|
||||
"bat",
|
||||
"bash",
|
||||
"sh",
|
||||
"dockerfile",
|
||||
"erlang",
|
||||
"fsharp",
|
||||
"markdown",
|
||||
"log",
|
||||
"lua",
|
||||
"objc",
|
||||
"swift",
|
||||
"regex",
|
||||
"ruby",
|
||||
"rust",
|
||||
"postcss",
|
||||
"blade",
|
||||
"asciidoc",
|
||||
"cmake",
|
||||
"cobol",
|
||||
"pascal",
|
||||
"nginx",
|
||||
"angular-html",
|
||||
"angular-ts",
|
||||
"gdscript",
|
||||
"gdshader",
|
||||
"gdresource",
|
||||
"groovy",
|
||||
"gql",
|
||||
"python",
|
||||
"crystal",
|
||||
"sql",
|
||||
"plsql",
|
||||
"kotlin",
|
||||
"html",
|
||||
"vue",
|
||||
"gleam",
|
||||
"julia",
|
||||
"lisp",
|
||||
"xml",
|
||||
"csv",
|
||||
],
|
||||
langs: ["json", "yaml", "toml", "java", "javascript", "astro", "css", "scss", "dart", "go", "typescript", "c", "csharp",
|
||||
"cpp", "bat", "bash", "sh", "dockerfile", "erlang", "fsharp", "markdown", "log",
|
||||
"lua", "objc", "swift", "regex", "ruby", "rust", "postcss", "blade", "asciidoc", "cmake", "cobol", "pascal",
|
||||
"nginx", "angular-html", "angular-ts", "gdscript", "gdshader", "gdresource", "groovy", "gql", "python",
|
||||
"crystal", "sql", "plsql", "kotlin", "html", "vue", "gleam", "julia", "lisp", "xml", "csv"],
|
||||
},
|
||||
locales: ["en", "zh-CN"],
|
||||
defaultLocale: "en",
|
||||
@ -143,12 +97,6 @@ export default defineNuxtConfig({
|
||||
transpile: ["vuetify"],
|
||||
},
|
||||
|
||||
umami: {
|
||||
id: "eef151fb-07e2-461b-8b7f-2547aab735d4",
|
||||
host: "https://us.umami.is",
|
||||
autoTrack: true,
|
||||
},
|
||||
|
||||
modules: [
|
||||
"@unocss/nuxt",
|
||||
"@nuxt/content",
|
||||
@ -157,8 +105,7 @@ export default defineNuxtConfig({
|
||||
"@pinia/nuxt",
|
||||
"@nuxtjs/i18n",
|
||||
"nuxt-schema-org",
|
||||
"@vueuse/motion/nuxt",
|
||||
"nuxt-umami",
|
||||
"nuxt-gtag",
|
||||
(_options, nuxt) => {
|
||||
nuxt.hooks.hook("vite:extendConfig", (config) => {
|
||||
// @ts-expect-error
|
||||
@ -167,6 +114,10 @@ export default defineNuxtConfig({
|
||||
},
|
||||
],
|
||||
|
||||
gtag: {
|
||||
id: "G-ZFJ7RX0JXF",
|
||||
},
|
||||
|
||||
vite: {
|
||||
vue: {
|
||||
template: {
|
||||
|
28
package.json
@ -11,35 +11,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@nuxt/content": "^2.13.4",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/kit": "^3.16.0",
|
||||
"@nuxtjs/i18n": "^8.5.6",
|
||||
"@nuxtjs/sitemap": "^6.1.5",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@pinia/nuxt": "^0.5.5",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"@nuxt/content": "^2.13.2",
|
||||
"@nuxt/image": "^1.8.0",
|
||||
"@nuxtjs/i18n": "^8.5.5",
|
||||
"@nuxtjs/sitemap": "^6.1.1",
|
||||
"@pinia/nuxt": "^0.5.4",
|
||||
"feed": "^4.2.2",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt": "^3.13.2",
|
||||
"nuxt-gtag": "^2.1.0",
|
||||
"nuxt-schema-org": "^3.5.0",
|
||||
"nuxt-umami": "3.2.0",
|
||||
"pinia": "^2.3.1",
|
||||
"nuxt-schema-org": "^3.4.0",
|
||||
"pinia": "^2.2.2",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"unhead": "1.9.0",
|
||||
"unified": "^11.0.5",
|
||||
"vue": "^3.5.13"
|
||||
"vue": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/nuxt": "^0.61.9",
|
||||
"@unocss/preset-typography": "^0.61.9",
|
||||
"@unocss/reset": "^0.61.9",
|
||||
"vite-plugin-vuetify": "^2.1.0",
|
||||
"vuetify": "^3.7.16"
|
||||
"vite-plugin-vuetify": "^2.0.4",
|
||||
"vuetify": "^3.7.2"
|
||||
}
|
||||
}
|
||||
|
37
pages/activity/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<v-container class="content-container mx-auto">
|
||||
<div class="my-3 mx-[1.5ch]">
|
||||
<div class="flex gap-1">
|
||||
<h1 class="text-2xl">{{ t("navActivity") }}</h1>
|
||||
<v-btn size="x-small" variant="text" icon="mdi-rss" slim to="/activity/feed" />
|
||||
</div>
|
||||
<span>{{ t("navActivityCaption") }}</span>
|
||||
</div>
|
||||
|
||||
<post-list class="mx-[-2.5ch]" :realm="config.public.solarRealm" />
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
useHead({
|
||||
title: t("navActivity"),
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: t("navActivity"),
|
||||
ogTitle: t("navActivity"),
|
||||
description: t("navActivityCaption"),
|
||||
ogDescription: t("navActivityCaption"),
|
||||
ogType: "website",
|
||||
})
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-container {
|
||||
max-width: 70ch !important;
|
||||
}
|
||||
</style>
|
@ -6,7 +6,13 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<v-btn color="primary" text="New" append-icon="mdi-plus" variant="tonal" to="/creator/stickers/new" />
|
||||
<v-btn
|
||||
color="primary"
|
||||
text="New"
|
||||
append-icon="mdi-plus"
|
||||
variant="tonal"
|
||||
to="/creator/stickers/new"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +24,10 @@
|
||||
|
||||
<div class="mt-5">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel v-for="item in data" :key="'sticker-pack#' + item.id">
|
||||
<v-expansion-panel
|
||||
v-for="item in data"
|
||||
:key="'sticker-pack#'+item.id"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<p>{{ item.name }}</p>
|
||||
@ -78,17 +87,16 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn text="Cancel" color="grey" @click="isActive.value = false"></v-btn>
|
||||
<v-btn
|
||||
text="Cancel"
|
||||
color="grey"
|
||||
@click="isActive.value = false"
|
||||
></v-btn>
|
||||
|
||||
<v-btn
|
||||
text="Delete"
|
||||
color="error"
|
||||
@click="
|
||||
() => {
|
||||
deletePack(item)
|
||||
isActive.value = false
|
||||
}
|
||||
"
|
||||
@click="() => { deletePack(item); isActive.value = false }"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@ -123,7 +131,6 @@ useHead({
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const ua = useUserinfo()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<null | string>(null)
|
||||
@ -133,7 +140,7 @@ const data = ref<any[]>([])
|
||||
async function readPacks() {
|
||||
loading.value = true
|
||||
|
||||
const res = await solarFetch(`/cgi/uc/stickers/packs?take=10&author=${ua.userinfo?.id}&offset=${data.value.length}`)
|
||||
const res = await solarFetch(`/cgi/uc/stickers/packs?take=10&offset=${data.value.length}`)
|
||||
if (res.status != 200) {
|
||||
error.value = await res.text()
|
||||
} else {
|
||||
|
105
pages/docs/[...slug].vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<v-container class="px-12 docs-page-container">
|
||||
<div class="docs-container">
|
||||
<div class="docs-content">
|
||||
<div class="mt-5">
|
||||
<div class="flex gap-2 items-center">
|
||||
<v-icon :icon="page.icon ?? 'mdi-text-box'" size="28" />
|
||||
<h1 class="text-2xl">{{ page.title }}</h1>
|
||||
</div>
|
||||
<p>{{ page.description }}</p>
|
||||
</div>
|
||||
|
||||
<article class="text-base prose xl:text-lg docs-article">
|
||||
<content-renderer :value="page">
|
||||
<content-renderer-markdown :value="page" />
|
||||
</content-renderer>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<v-dialog max-width="540" v-model="dialogOpen" close-on-content-click>
|
||||
<v-card title="Table of Contents" density="compact">
|
||||
<div class="mt-[-12px]">
|
||||
<docs-table-of-contents v-if="page.body.toc.links?.length > 0" :links="page.body.toc.links" />
|
||||
<v-empty-state v-else text="No Headers Available" />
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-fab
|
||||
app
|
||||
appear
|
||||
size="large"
|
||||
location="bottom end"
|
||||
icon="mdi-menu"
|
||||
@click="dialogOpen = !dialogOpen"
|
||||
/>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { definePageMeta } from "#imports"
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { data: page } = await useAsyncData<any>("page", queryContent(route.path).where({ _locale: getLocale() }).findOne)
|
||||
|
||||
const drawerTab = ref(0)
|
||||
const dialogOpen = ref(false)
|
||||
|
||||
if (page.value == null) {
|
||||
throw createError({
|
||||
status: 404,
|
||||
statusMessage: "Document Page Not Found",
|
||||
})
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: "docs",
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: page.value.title,
|
||||
titleTemplate: "%s | Solsynth Knowledge Base",
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: page.value.title,
|
||||
ogTitle: page.value.title,
|
||||
description: page.value.description,
|
||||
ogDescription: page.value.description,
|
||||
ogUrl: `${useRuntimeConfig().public.siteUrl}${route.fullPath}`,
|
||||
publisher: "Solar Archive",
|
||||
ogSiteName: "Solsynth Capital",
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.docs-article {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.docs-content {
|
||||
max-width: 75ch;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.docs-article img {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #eee;
|
||||
transition: box-shadow .3s ease-in-out;
|
||||
}
|
||||
|
||||
.docs-article img:hover {
|
||||
box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);;
|
||||
}
|
||||
|
||||
|
||||
html, body, .v-application, .docs-article {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
@ -27,9 +27,9 @@
|
||||
<div class="flex flex-col" v-if="attachment?.metadata?.ratio">
|
||||
<span class="text-xs font-bold">Aspect Ratio</span>
|
||||
<span>
|
||||
{{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
|
||||
{{ attachment?.metadata?.ratio.toFixed(2) }}
|
||||
</span>
|
||||
{{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
|
||||
{{ attachment?.metadata?.ratio.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col" v-if="attachment?.mimetype">
|
||||
<span class="text-xs font-bold">Mimetype</span>
|
||||
@ -44,19 +44,13 @@
|
||||
|
||||
<div class="text-xs text-grey flex flex-col mx-[2.5ch]">
|
||||
<span>Solar Network Attachment Web Preview</span>
|
||||
<span
|
||||
>Powered by
|
||||
<a class="underline" target="_blank" href="https://git.solsynth.dev/HyperNet/Paperclip"
|
||||
>HyperNet.Paperclip</a
|
||||
></span
|
||||
>
|
||||
<span>Powered by <a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip">Hydrogen.Paperclip</a></span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "~/utils/format"
|
||||
import { useDisplay } from "vuetify"
|
||||
|
||||
const route = useRoute()
|
||||
@ -67,9 +61,7 @@ const firstVideo = ref<string | null>()
|
||||
|
||||
const isMediumScreen = useDisplay().mdAndUp
|
||||
|
||||
const { data: attachment } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`,
|
||||
)
|
||||
const { data: attachment } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`)
|
||||
|
||||
definePageMeta({
|
||||
layout: "minimal",
|
||||
@ -84,19 +76,15 @@ if (!attachment.value) {
|
||||
|
||||
const title = computed(() => `Attachment ${attachment.value?.id}`)
|
||||
|
||||
watch(
|
||||
attachment,
|
||||
(value) => {
|
||||
if (value.mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
watch(attachment, (value) => {
|
||||
if (value.mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
|
||||
if (value.mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
if (value.mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
useHead({
|
||||
title: title.value,
|
||||
@ -118,4 +106,16 @@ useSeoMeta({
|
||||
publisher: "Solar Network",
|
||||
ogSiteName: "Solsynth Capital",
|
||||
})
|
||||
|
||||
function formatBytes(bytes: number, decimals = 2) {
|
||||
if (!+bytes) return "0 Bytes"
|
||||
|
||||
const k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
||||
</script>
|
||||
|
@ -93,7 +93,7 @@ onMounted(() => {
|
||||
|
||||
const poolOptions = [
|
||||
{ label: "Interactive", description: "Public indexable, no lifecycle.", value: "interactive" },
|
||||
{ label: "Messaging", description: "Has lifecycle, will be deleted after 14 days.", value: "messaging" },
|
||||
{ label: "Messaging", description: "Has lifecycle, will delete after 14 days.", value: "messaging" },
|
||||
{ label: "Sticker", description: "Public indexable, privilege required.", value: "sticker", disabled: true },
|
||||
{ label: "Dedicated Pool", description: "Your own configuration, coming soon.", value: "dedicated", disabled: true },
|
||||
]
|
||||
@ -201,12 +201,12 @@ async function uploadSingleMultipart(chunkId: string) {
|
||||
const chunkIdx: number = multipartInfo.value["file_chunks"][chunkId]
|
||||
const chunk = content.value.slice(chunkIdx * multipartSize.value, (chunkIdx + 1) * multipartSize.value)
|
||||
|
||||
const data = new FormData()
|
||||
data.set("file", chunk)
|
||||
|
||||
const resp = await solarFetch(`/cgi/uc/attachments/multipart/${multipartInfo.value.rid}/${chunkId}`, {
|
||||
method: "POST",
|
||||
body: chunk,
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
body: data,
|
||||
signal: AbortSignal.timeout(3 * 60 * 1000),
|
||||
})
|
||||
if (resp.status != 200) throw new Error(await resp.text())
|
||||
|
185
pages/index.vue
@ -1,74 +1,39 @@
|
||||
<template>
|
||||
<canvas ref="canvasRef" class="fixed top-0 left-0 w-screen h-screen opacity-50"></canvas>
|
||||
|
||||
<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">
|
||||
<img
|
||||
v-motion="{
|
||||
initial: {
|
||||
y: 100,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8 },
|
||||
},
|
||||
}"
|
||||
:src="Logo"
|
||||
alt="Company Logo"
|
||||
class="w-32 h-32 mb-4"
|
||||
/>
|
||||
<h1 class="text-4xl font-bold">Welcome to {{ t("brandName") }}</h1>
|
||||
<p class="mt-2 text-lg">Building cool, open-source, and elegant apps for human.</p>
|
||||
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#products">{{ t("learnMore") }}</v-btn>
|
||||
</section>
|
||||
|
||||
<section class="content-section py-16" id="products">
|
||||
<div class="container mx-auto text-center">
|
||||
<h2 class="text-3xl font-bold">Our Projects</h2>
|
||||
<p>Take a peek of our works.</p>
|
||||
<v-card class="mt-12">
|
||||
<v-row class="content-section">
|
||||
<v-col cols="12" md="4" class="flex justify-start">
|
||||
<div class="flex flex-col items-start">
|
||||
<h1 class="text-4xl font-bold">{{ t("brandName") }}</h1>
|
||||
<p class="text-lg mt-3 max-w-2/3">
|
||||
{{ t("indexIntroduce") }}
|
||||
</p>
|
||||
<p class="text-grey mt-2">
|
||||
{{ t("indexProductListHint") }}
|
||||
<v-icon icon="mdi-arrow-right" size="16" class="mb-0.5" />
|
||||
</p>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8">
|
||||
<v-card>
|
||||
<product-carousel class="carousel-section" :products="products as any[]" />
|
||||
</v-card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="content-section">
|
||||
<v-col cols="12" md="6">
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
title="GitHub"
|
||||
subtitle="The place hosts most of our public projects' code"
|
||||
prepend-icon="mdi-github"
|
||||
href="https://github.com/Solsynth"
|
||||
target="_blank"
|
||||
/>
|
||||
<v-list-item
|
||||
lines="two"
|
||||
title="Solsynth Code Repository"
|
||||
subtitle="Our self-hosted git server, may contains some unpublished projects' code"
|
||||
prepend-icon="mdi-git"
|
||||
href="https://git.solsynth.dev/explore"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-list>
|
||||
<v-col cols="12" md="8">
|
||||
<v-card class="h-[500px]">
|
||||
<activity-list class="carousel-section" />
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" class="flex justify-end" order="first" order-md="last">
|
||||
<v-col cols="12" md="4" class="flex justify-end" order="first" order-md="last">
|
||||
<div class="text-right flex flex-col items-end">
|
||||
<h2 class="text-4xl font-bold">
|
||||
We<br />
|
||||
❤️ Open-source
|
||||
</h2>
|
||||
<p class="text-md mt-3 max-w-2/3">
|
||||
No software can run without the support of open source software, and our software is no exception.
|
||||
Therefore, we feel it is important to contribute to open source as well.
|
||||
<h2 class="text-4xl font-bold">{{ t("indexActivities") }}</h2>
|
||||
<p class="text-lg mt-3 max-w-2/3">
|
||||
{{ t("indexActivitiesCaption") }}
|
||||
</p>
|
||||
<p class="text-grey mt-2">
|
||||
<v-icon icon="mdi-arrow-left" size="16" class="mb-0.5" />
|
||||
Check out our GitHub
|
||||
{{ t("indexActivitiesHint") }}
|
||||
</p>
|
||||
</div>
|
||||
</v-col>
|
||||
@ -77,8 +42,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Logo from "~/assets/logo-w-shadow.png"
|
||||
|
||||
import { getLocale } from "~/utils/locale"
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -98,100 +61,13 @@ useSeoMeta({
|
||||
})
|
||||
|
||||
const { data: products } = await useAsyncData("products", () => {
|
||||
return queryContent("/products")
|
||||
.where({ _locale: getLocale(), archived: { $ne: true } })
|
||||
.limit(5)
|
||||
.find()
|
||||
})
|
||||
|
||||
const canvasRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
const isDarkMode = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
|
||||
const canvas: HTMLCanvasElement = canvasRef.value!
|
||||
const ctx = canvas.getContext("2d")!
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
canvas.width = window.innerWidth * dpr
|
||||
canvas.height = window.innerHeight * dpr
|
||||
|
||||
let particles: Particle[] = []
|
||||
const numParticles = 100
|
||||
|
||||
class Particle {
|
||||
x: number
|
||||
y: number
|
||||
vx: number
|
||||
vy: number
|
||||
size: number
|
||||
|
||||
constructor() {
|
||||
this.x = Math.random() * canvas.width
|
||||
this.y = Math.random() * canvas.height
|
||||
this.vx = (Math.random() - 0.5) * 1.5
|
||||
this.vy = (Math.random() - 0.5) * 1.5
|
||||
this.size = Math.random() * 3 + 1
|
||||
}
|
||||
|
||||
move() {
|
||||
this.x += this.vx
|
||||
this.y += this.vy
|
||||
if (this.x <= 0 || this.x >= canvas.width) this.vx *= -1
|
||||
if (this.y <= 0 || this.y >= canvas.height) this.vy *= -1
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.beginPath()
|
||||
ctx.arc(this.x * dpr, this.y * dpr, this.size * dpr, 0, Math.PI * 2)
|
||||
ctx.fillStyle = isDarkMode ? "rgba(255, 255, 255, 0.8)" : "rgba(0, 0, 0, 0.8)"
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
particles = []
|
||||
for (let i = 0; i < numParticles; i++) {
|
||||
particles.push(new Particle())
|
||||
}
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
let dx = particles[i].x - particles[j].x
|
||||
let dy = particles[i].y - particles[j].y
|
||||
let distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(particles[i].x * dpr, particles[i].y * dpr)
|
||||
ctx.lineTo(particles[j].x * dpr, particles[j].y * dpr)
|
||||
ctx.strokeStyle = isDarkMode ? "rgba(255, 255, 255, 0.2)" : "rgba(0, 0, 0, 0.2)"
|
||||
ctx.lineWidth = 0.5 * dpr
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
particles.forEach((p) => {
|
||||
p.move()
|
||||
p.draw()
|
||||
})
|
||||
drawLines()
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
init()
|
||||
animate()
|
||||
return queryContent("/products").where({ _locale: getLocale(), archived: { $ne: true } }).limit(5).find()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.carousel-section {
|
||||
height: 120rem;
|
||||
height: 96rem;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
@ -200,10 +76,3 @@ onMounted(() => {
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,19 +1,14 @@
|
||||
<template>
|
||||
<v-container class="content-container mx-auto">
|
||||
<div class="my-3 flex flex-row gap-4">
|
||||
<nuxt-link :to="`/publishers/${post.publisher?.name}`">
|
||||
<v-avatar :image="getAttachmentUrl(post.publisher?.avatar)" />
|
||||
<nuxt-link :to="`/users/${post.publisher?.name}`">
|
||||
<v-avatar :image="post.publisher?.avatar" />
|
||||
</nuxt-link>
|
||||
<div class="flex flex-col">
|
||||
<span>
|
||||
{{ post.publisher?.nick }}
|
||||
<span class="text-xs">@{{ post.publisher?.name }}</span>
|
||||
</span>
|
||||
<span>{{ post.publisher?.nick }} <span class="text-xs">@{{ post.publisher?.name }}</span></span>
|
||||
<span v-if="post.body?.title" class="text-md">{{ post.body?.title }}</span>
|
||||
<span v-if="post.body?.description" class="text-sm">{{ post.body?.description }}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">{{
|
||||
post.publisher?.description
|
||||
}}</span>
|
||||
<span v-if="!post.body?.title && !post.body?.description" class="text-sm">{{ post.publisher?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -25,18 +20,22 @@
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
<article v-if="post.body?.content" class="text-base prose xl:text-lg mx-auto">
|
||||
<article class="text-base prose xl:text-lg mx-auto">
|
||||
<m-d-c :value="post.body?.content"></m-d-c>
|
||||
</article>
|
||||
|
||||
<v-card v-if="post.body?.attachments?.length > 0" class="mb-5">
|
||||
<attachment-carousel :attachments="post.body?.attachments" @update:metadata="(args) => (attachments = args)" />
|
||||
<attachment-carousel :attachments="post.body?.attachments" @update:metadata="args => attachments = args" />
|
||||
</v-card>
|
||||
|
||||
<div class="mb-3 text-sm flex flex-col">
|
||||
<span class="flex flex-row gap-1">
|
||||
<span> {{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }}, </span>
|
||||
<span> {{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }} </span>
|
||||
<span>
|
||||
{{ post.metric.reply_count }} {{ post.metric.reply_count > 1 ? "replies" : "reply" }},
|
||||
</span>
|
||||
<span>
|
||||
{{ post.metric.reaction_count }} {{ post.metric.reaction_count > 1 ? "reactions" : "reaction" }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{{ post.type.startsWith("a") ? "An" : "A" }} {{ post.type }} posted on
|
||||
@ -44,7 +43,10 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="post.tags?.length > 0" class="text-xs text-grey flex flex-row gap-1 mb-3">
|
||||
<div
|
||||
v-if="post.tags?.length > 0"
|
||||
class="text-xs text-grey flex flex-row gap-1 mb-3"
|
||||
>
|
||||
<nuxt-link
|
||||
v-for="tag in post.tags"
|
||||
:to="`/posts/tags/${tag.alias}`"
|
||||
@ -69,6 +71,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
alias: ["/posts/:area/:id"],
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
@ -76,9 +82,17 @@ 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/${route.params.id}`)
|
||||
const { data: post } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/posts/${slug.value}`)
|
||||
|
||||
if (!post.value) {
|
||||
const { data: publisher } = await $fetch<any>(`${config.public.solarNetworkApi}/cgi/co/publishers/${route.params.id}`)
|
||||
@ -94,29 +108,21 @@ if (!post.value) {
|
||||
navigateTo(`/posts/${post.value.area_alias}/${post.value.alias}`)
|
||||
}
|
||||
|
||||
const title = computed(() =>
|
||||
post.value.body?.title
|
||||
? `${post.value.body?.title} by @${post.value.publisher.name}`
|
||||
: `Post by @${post.value.publisher.name}`,
|
||||
)
|
||||
const title = computed(() => post.value.body?.title ? `${post.value.body?.title} by @${post.value.publisher.name}` : `Post by @${post.value.publisher.name}`)
|
||||
const description = computed(() => post.value.body?.description ?? post.value.body?.content.substring(0, 280).trim())
|
||||
|
||||
watch(
|
||||
attachments,
|
||||
(value) => {
|
||||
if (post.value.body?.thumbnail) {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${post.value.body?.thumbnail}`
|
||||
}
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
watch(attachments, (value) => {
|
||||
if (post.value.body?.thumbnail) {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${post.value.body?.thumbnail}`
|
||||
}
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "image") {
|
||||
firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
if (value.length > 0 && value[0].mimetype.split("/")[0] == "video") {
|
||||
firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${attachments.value[0].rid}`
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
useHead({
|
||||
title: title.value,
|
||||
@ -142,7 +148,7 @@ useSeoMeta({
|
||||
ogSiteName: "Solsynth Capital",
|
||||
})
|
||||
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/posts/${route.params.id.toString().replace('/', ':')}`)
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/posts/view/${slug.value}`)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
@ -1,436 +0,0 @@
|
||||
<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" id="intro">
|
||||
<div class="pt-1/3 mb-4 w-full relative">
|
||||
<img :src="AlphaScreenshot" class="absolute bottom-2 left-0 right-0" />
|
||||
<img
|
||||
v-motion="{
|
||||
initial: {
|
||||
y: 100,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.8 },
|
||||
},
|
||||
}"
|
||||
:src="Icon"
|
||||
alt="Solar Network Logo"
|
||||
class="w-32 h-32 p-2 z-10 mx-auto icon-glow bg-white dark:bg-black shadow-2xl rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
<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="#features">{{ t("learnMore") }}</v-btn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="4">
|
||||
<v-card :title="t('solarNetworkFreedomOfSpeech')" prepend-icon="mdi-account-voice" density="comfortable">
|
||||
<v-card-text>{{ t("solarNetworkFreedomOfSpeechDescription") }}</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-card :title="t('solarNetworkConfirmAccount')" prepend-icon="mdi-account-check" density="comfortable">
|
||||
<v-card-text>{{ t("solarNetworkConfirmAccountDescription") }}</v-card-text>
|
||||
</v-card>
|
||||
<v-card
|
||||
:title="t('solarNetworkNoImpersonation')"
|
||||
prepend-icon="mdi-account-cancel"
|
||||
density="comfortable"
|
||||
class="mt-2"
|
||||
>
|
||||
<v-card-text>{{ t("solarNetworkNoImpersonationDescription") }}</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-card :title="t('solarNetworkReadDialog')" prepend-icon="mdi-alert-circle" density="comfortable">
|
||||
<v-card-text>{{ t("solarNetworkReadDialogDescription") }}</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<p class="text-sm mt-4">{{ t("solarNetworkToS") }}</p>
|
||||
<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" 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
|
||||
><sup>®</sup>
|
||||
</p>
|
||||
<v-btn
|
||||
v-if="hasPrerelease"
|
||||
slim
|
||||
density="compact"
|
||||
prepend-icon="mdi-beta"
|
||||
variant="text"
|
||||
style="text-transform: none"
|
||||
color="white"
|
||||
@click="showPrerelease = !showPrerelease"
|
||||
>
|
||||
{{ showPrerelease ? t("downloadSwitchRelease") : t("downloadSwitchPrerelease") }}
|
||||
</v-btn>
|
||||
<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"
|
||||
:title="showPrerelease ? 'Latest pre-release' : 'Latest release'"
|
||||
density="comfortable"
|
||||
>
|
||||
<v-card-text v-if="currentRelease.status.value === 'success'">
|
||||
<p class="text-xs">
|
||||
<code>{{ currentRelease.data.value?.tag_name }}</code>
|
||||
</p>
|
||||
<p class="font-bold text-lg">{{ latestRelease.data.value?.name }}</p>
|
||||
<article class="prose prose-sm max-h-[360px] overflow-y-auto" style="max-width: unset">
|
||||
<m-d-c :value="currentRelease.data.value!.body!" />
|
||||
</article>
|
||||
</v-card-text>
|
||||
<div v-else>
|
||||
<v-progress-circular class="px-5 my-3" indeterminate />
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card prepend-icon="mdi-download" title="Distributions" density="comfortable">
|
||||
<div v-if="currentRelease.status.value === 'success'">
|
||||
<v-list density="comfortable" slim>
|
||||
<v-list-item
|
||||
v-for="asset in currentRelease.data.value!.assets"
|
||||
:key="asset.id"
|
||||
:title="asset.label ?? asset.name"
|
||||
:subtitle="formatBytes(asset.size)"
|
||||
:href="asset.browser_download_url"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-list>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-progress-circular class="px-5 my-3" indeterminate />
|
||||
</div>
|
||||
<v-card-text>
|
||||
<p class="text-sm opacity-50 mb-2">{{ t("downloadForApple") }}</p>
|
||||
<div class="flex align-center gap-2.5">
|
||||
<nuxt-link
|
||||
to="https://apps.apple.com/us/app/solian/id6499032345?itscg=30200&itsct=apps_box_link&mttnsubad=6499032345"
|
||||
target="_blank"
|
||||
>
|
||||
<img :src="AppStoreDownload" />
|
||||
</nuxt-link>
|
||||
<div>
|
||||
<nuxt-link to="https://testflight.apple.com/join/YJ0lmN6O" target="_blank" class="underline">
|
||||
{{ t("downloadTestFlight") }}
|
||||
</nuxt-link>
|
||||
<p class="text-xs opacity-40">{{ t("downloadTestFlightDescription") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm opacity-50 mt-4">{{ t("downloadForDesktop") }}</p>
|
||||
<p class="text-sm">{{ t("downloadForDesktopDescription") }}</p>
|
||||
|
||||
<p class="text-sm opacity-50 mt-4">{{ t("downloadWithoutDownload") }}</p>
|
||||
<div class="text-sm flex gap-2 underline">
|
||||
<nuxt-link to="https://sn.solsynth.dev" target="_blank">{{ t("downloadWeb") }}</nuxt-link>
|
||||
<nuxt-link to="https://sn.solsynth.dev?cdn=cn" target="_blank">{{ t("downloadWebChina") }}</nuxt-link>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</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({
|
||||
owner: "Solsynth",
|
||||
repo: "HyperNet.Surface",
|
||||
})
|
||||
return resp.data
|
||||
})
|
||||
const latestPrerelease = useAsyncData("sn-latest-prerelease", async () => {
|
||||
const octo = new Octokit({})
|
||||
const resp = await octo.repos.listReleases({
|
||||
owner: "Solsynth",
|
||||
repo: "HyperNet.Surface",
|
||||
per_page: 1,
|
||||
})
|
||||
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;
|
||||
}
|
||||
|
||||
.icon-glow {
|
||||
-webkit-filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
|
||||
filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.icon-glow {
|
||||
-webkit-filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
|
||||
filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<v-container class="mx-auto">
|
||||
<v-img v-if="urlOfBanner" :src="urlOfBanner" :aspect-ratio="16 / 5" class="rounded-md mb-3" cover />
|
||||
|
||||
<div class="mx-[2.5ch]">
|
||||
<div class="my-5 mx-4 flex flex-row gap-4">
|
||||
<v-avatar :image="urlOfAvatar" />
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
>{{ account?.nick }} <span class="text-xs">@{{ account?.name }}</span></span
|
||||
>
|
||||
<span class="text-sm">{{ account?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-7">
|
||||
<v-card rounded="xl" class="mx-[-5px]">
|
||||
<v-tabs v-model="tab" align-tabs="start" color="primary" hide-slider>
|
||||
<v-tab :value="1">{{ t("userActivity") }}</v-tab>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<v-row>
|
||||
<v-col row="12" lg="8">
|
||||
<post-list class="mx-[-2.5ch] mt-[-16px]" v-if="account" :author="account.name" />
|
||||
</v-col>
|
||||
<v-col row="12" lg="4" order="first" order-lg="last">
|
||||
<div class="sticky top-0 h-fit">
|
||||
<v-card prepend-icon="mdi-information-outline" title="About">
|
||||
<v-card-text>
|
||||
<p><b>Description</b></p>
|
||||
<p>{{ account.description }}</p>
|
||||
<p class="mt-3"><b>Joined At</b></p>
|
||||
<p>{{ new Date(account.created_at).toLocaleString() }}</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const tab = ref(1)
|
||||
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publishers/${route.params.name}`)
|
||||
|
||||
if (account.value == null) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "User Not Found",
|
||||
})
|
||||
}
|
||||
|
||||
const urlOfAvatar = computed(() =>
|
||||
account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0,
|
||||
)
|
||||
const urlOfBanner = computed(() =>
|
||||
account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0,
|
||||
)
|
||||
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/accounts/view/${route.params.name}`)
|
||||
</script>
|
@ -6,34 +6,34 @@
|
||||
<div class="my-5 mx-4 flex flex-row gap-4">
|
||||
<v-avatar :image="urlOfAvatar" />
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
>{{ account?.nick }} <span class="text-xs">@{{ account?.name }}</span></span
|
||||
>
|
||||
<p>
|
||||
{{ accountStatus.status ? accountStatus.status.label : accountStatus["is_online"] ? "Online" : "Offline" }}
|
||||
</p>
|
||||
<span>{{ account?.nick }} <span class="text-xs">@{{ account?.name }}</span></span>
|
||||
<span class="text-sm">{{ account?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-7">
|
||||
<v-card rounded="xl" class="mx-[-5px]">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
align-tabs="start"
|
||||
color="primary"
|
||||
hide-slider
|
||||
>
|
||||
<v-tab :value="1">{{ t("userActivity") }}</v-tab>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" lg="8">
|
||||
<v-card>
|
||||
<v-card-text v-if="accountPageStatus.valueOf() === 'success'">
|
||||
<div class="prose prose-sm" style="max-width: unset">
|
||||
<m-d-c :value="accountPage.content" />
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text v-else>
|
||||
<p class="font-italic">The user has no account page.</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-col row="12" lg="8">
|
||||
<post-list class="mx-[-2.5ch] mt-[-16px]" v-if="account" :author="account.name" />
|
||||
</v-col>
|
||||
<v-col cols="12" lg="4" order="first" order-lg="last">
|
||||
<v-col row="12" lg="4" order="first" order-lg="last">
|
||||
<div class="sticky top-0 h-fit">
|
||||
<v-card prepend-icon="mdi-identifier" title="About">
|
||||
<v-card-text>
|
||||
<p><b>Description</b></p>
|
||||
<p>{{ account?.profile.description }}</p>
|
||||
<p>{{ account.description }}</p>
|
||||
<p class="mt-3"><b>Joined At</b></p>
|
||||
<p>{{ new Date(account.created_at).toLocaleString() }}</p>
|
||||
</v-card-text>
|
||||
@ -46,10 +46,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
alias: ["/@:name(.*)*"],
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const tab = ref(1)
|
||||
|
||||
const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}`)
|
||||
|
||||
if (account.value == null) {
|
||||
@ -59,17 +65,8 @@ if (account.value == null) {
|
||||
})
|
||||
}
|
||||
|
||||
const { data: accountPage, status: accountPageStatus } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}/page`,
|
||||
)
|
||||
const { data: accountStatus, status: accountStatusStatus } = await useFetch<any>(
|
||||
`${config.public.solarNetworkApi}/cgi/id/users/${route.params.name}/status`,
|
||||
)
|
||||
const urlOfAvatar = computed(() => account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0)
|
||||
const urlOfBanner = computed(() => account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0)
|
||||
|
||||
const urlOfAvatar = computed(() =>
|
||||
account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0,
|
||||
)
|
||||
const urlOfBanner = computed(() =>
|
||||
account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0,
|
||||
)
|
||||
const externalOpenLink = computed(() => `${config.public.solianUrl}/accounts/view/${route.params.name}`)
|
||||
</script>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<v-avatar :image="urlOfAvatar" />
|
||||
<div class="flex flex-col">
|
||||
<span>{{ auth.userinfo?.nick }} <span class="text-xs">@{{ auth.userinfo?.name }}</span></span>
|
||||
<span class="text-sm">{{ auth.userinfo?.profile?.description }}</span>
|
||||
<span class="text-sm">{{ auth.userinfo?.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card class="w-28 aspect-square" href="https://kb.solsynth.dev" target="_blank">
|
||||
<v-card class="w-28 aspect-square" to="/docs">
|
||||
<v-card-text class="flex flex-col justify-center items-center text-center h-full">
|
||||
<v-icon icon="mdi-library" size="32" />
|
||||
<span class="text-sm mt-1.75">Knowledge Base</span>
|
||||
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 18 KiB |
@ -5,7 +5,7 @@ export default defineSitemapEventHandler(async () => {
|
||||
const result = await res.json()
|
||||
|
||||
return result.data.map((item: any) => asSitemapUrl({
|
||||
loc: item.alias ? `/posts/${item.alias_prefix}/${item.alias}` : `/posts/${item.id}`,
|
||||
loc: item.alias ? `/posts/${item.area_alias}/${item.alias}` : `/posts/${item.id}`,
|
||||
lastmod: item.edited_at ?? item.published_at,
|
||||
priority: 0.7,
|
||||
_sitemap: "posts",
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { defineEventHandler } from 'h3'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const resp = await fetch(`${config.public.solarNetworkApi}/cgi/id/well-known/jwks`)
|
||||
|
||||
return await resp.json()
|
||||
})
|
@ -1,20 +1,31 @@
|
||||
import { defineEventHandler } from 'h3'
|
||||
export default defineEventHandler((event) => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const config = useRuntimeConfig();
|
||||
const siteUrl = config.public.siteUrl
|
||||
|
||||
const resp = await fetch(`${config.public.solarNetworkApi}/cgi/id/well-known/openid-configuration`)
|
||||
const out: Record<string, any> = await resp.json()
|
||||
|
||||
out['authorization_endpoint'] = `${siteUrl}/auth/authorize`
|
||||
out['jwks_uri'] = `${siteUrl}/.well-known/jwks`
|
||||
|
||||
for (const [k, v] of Object.entries(out)) {
|
||||
if (typeof v === 'string' && v.startsWith('https://id.solsynth.dev/api')) {
|
||||
out[k] = v.replace('https://id.solsynth.dev/api', `${config.public.solarNetworkApi}/cgi/id`)
|
||||
}
|
||||
return {
|
||||
"authorization_endpoint": `${config.public.siteUrl}/auth/authorize`,
|
||||
"grant_types_supported": [
|
||||
"authorization_code",
|
||||
"implicit",
|
||||
"refresh_token",
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"HS512",
|
||||
],
|
||||
"issuer": config.public.siteUrl,
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"token",
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public",
|
||||
],
|
||||
"token_endpoint": `${config.public.solarNetworkApi}/cgi/id/auth/token`,
|
||||
"token_endpoint_auth_methods_supported": [
|
||||
"client_secret_post",
|
||||
],
|
||||
"token_endpoint_auth_signing_alg_values_supported": [
|
||||
"HS512",
|
||||
],
|
||||
"userinfo_endpoint": `${config.public.solarNetworkApi}/cgi/id/users/me`,
|
||||
}
|
||||
|
||||
return out
|
||||
})
|
||||
})
|
||||
|
@ -3,11 +3,11 @@ import { ref } from "vue"
|
||||
import { solarFetch } from "~/utils/request"
|
||||
|
||||
export function useAtk() {
|
||||
return useCookie("solar_network_atk", { path: "/", maxAge: 31556952000 })
|
||||
return useCookie("__hydrogen_atk", { path: "/", maxAge: 31556952000 })
|
||||
}
|
||||
|
||||
export function useRtk() {
|
||||
return useCookie("solar_network_rtk", { path: "/", maxAge: 31556952000 })
|
||||
return useCookie("__hydrogen_rtk", { path: "/", maxAge: 31556952000 })
|
||||
}
|
||||
|
||||
export function useLoggedInState() {
|
||||
@ -22,23 +22,18 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
let fetchCompleter: Completer<boolean> | null = null
|
||||
let refreshCompleter: Completer<string> | null = null
|
||||
|
||||
const lastRefreshedAt = ref<Date | null>(null)
|
||||
|
||||
function setTokenSet(atk: string, rtk: string) {
|
||||
lastRefreshedAt.value = new Date()
|
||||
useAtk().value = atk
|
||||
useRtk().value = rtk
|
||||
}
|
||||
|
||||
async function getAtk() {
|
||||
const atk = useAtk()
|
||||
if (!useLoggedInState().value) return atk.value
|
||||
|
||||
const parts = atk.value?.split(".") ?? []
|
||||
if (parts.length != 3) return atk.value
|
||||
|
||||
const payload = JSON.parse(atob(parts[1]))
|
||||
const exp: number = payload["exp"]
|
||||
|
||||
if (exp > Date.now() / 1000) {
|
||||
return atk.value
|
||||
if (!useLoggedInState().value) return useAtk().value
|
||||
if (lastRefreshedAt.value != null && Math.floor(Math.abs(Date.now() - lastRefreshedAt.value.getTime()) / 60000) < 3) {
|
||||
return useAtk().value
|
||||
}
|
||||
|
||||
if (refreshCompleter != null) {
|
||||
@ -62,7 +57,7 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
throw new Error(err)
|
||||
} else {
|
||||
const out = await res.json()
|
||||
console.log("[Passport] Access token has been refreshed now.")
|
||||
console.log("[PASSPORT] Access token has been refreshed now.")
|
||||
setTokenSet(out["access_token"], out["refresh_token"])
|
||||
refreshCompleter.complete(out["access_token"])
|
||||
return out["access_token"]
|
||||
@ -102,12 +97,12 @@ export const useUserinfo = defineStore("userinfo", () => {
|
||||
fetchCompleter = null
|
||||
}
|
||||
|
||||
return { userinfo, isLoggedIn, isReady, fetchCompleter, setTokenSet, getAtk, readProfiles }
|
||||
return { userinfo, lastRefreshedAt, isLoggedIn, isReady, fetchCompleter, setTokenSet, getAtk, readProfiles }
|
||||
})
|
||||
|
||||
export class Completer<T> {
|
||||
public readonly promise: Promise<T>
|
||||
public complete: (value: PromiseLike<T> | T) => void
|
||||
public complete: (value: (PromiseLike<T> | T)) => void
|
||||
private reject: (reason?: any) => void
|
||||
|
||||
public constructor() {
|
||||
|
@ -1,11 +0,0 @@
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
if (!+bytes) return "0 Bytes"
|
||||
|
||||
const k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
@ -14,13 +14,3 @@ export async function solarFetch(input: string, init?: RequestInit) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getAttachmentUrl(identifier: string | undefined): string | undefined {
|
||||
if (identifier == null || identifier.length == 0) {
|
||||
return undefined
|
||||
}
|
||||
if (identifier.startsWith("http")) {
|
||||
return identifier
|
||||
}
|
||||
return `${useRuntimeConfig().public.solarNetworkApi}/cgi/uc/attachments/${identifier}`
|
||||
}
|
||||
|