Basic post viewer

This commit is contained in:
LittleSheep 2024-12-17 22:12:19 +08:00
parent f730579eeb
commit fc4c884ade
11 changed files with 189 additions and 94 deletions

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"semi": false,
"trailingComma": "es5"
}

View File

@ -1,48 +0,0 @@
# Astro Starter Kit: Basics
```sh
npm create astro@latest -- --template basics
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src/
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

View File

@ -1,12 +1,14 @@
// @ts-check
import { defineConfig } from 'astro/config';
import { defineConfig } from 'astro/config'
import tailwind from '@astrojs/tailwind';
import tailwind from '@astrojs/tailwind'
import icon from 'astro-icon';
import icon from 'astro-icon'
import mdx from '@astrojs/mdx';
// https://astro.build/config
export default defineConfig({
integrations: [tailwind(), icon()],
prefetch: true
});
integrations: [tailwind(), icon(), mdx()],
prefetch: true,
})

BIN
bun.lockb

Binary file not shown.

View File

@ -9,14 +9,18 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^4.0.2",
"@astrojs/tailwind": "^5.1.3",
"@iconify-json/material-symbols": "^1.2.10",
"astro": "^5.0.5",
"astro-icon": "^1.1.4",
"marked": "^15.0.4",
"sanitize-html": "^2.13.1",
"tailwindcss": "^3.4.16"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"@types/sanitize-html": "^2.13.0",
"daisyui": "^4.12.22"
}
}
}

View File

@ -2,6 +2,12 @@
import { Image } from "astro:assets";
import CompanyLogo from "../assets/images/company-logo.png";
interface Props {
title?: string;
}
const { title } = Astro.props;
---
<!doctype html>
@ -10,7 +16,7 @@ import CompanyLogo from "../assets/images/company-logo.png";
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>Solsynth LLC</title>
<title>{title ? `${title} | Solsynth LLC` : "Solsynth LLC"}</title>
</head>
<body>
<div class="navbar backdrop-blur fixed top-0 left-0 right-0 z-10">
@ -20,7 +26,7 @@ import CompanyLogo from "../assets/images/company-logo.png";
<Image
src={CompanyLogo}
alt="company logo"
class="h-7 w-7 navbar-company-logo"
class="h-8 w-8 p-1 bg-white rounded-lg shadow-sm"
/>
<span>Solsynth</span>
</a>
@ -56,17 +62,4 @@ import CompanyLogo from "../assets/images/company-logo.png";
width: 100%;
height: 100%;
}
.navbar-company-logo {
-webkit-filter: drop-shadow(1px 1px 0 black) drop-shadow(-1px -1px 0 black);
filter: drop-shadow(1px 1px 0 black) drop-shadow(-1px -1px 0 black);
}
@media (prefers-color-scheme: dark) {
.navbar-company-logo {
-webkit-filter: drop-shadow(1px 1px 0 white)
drop-shadow(-1px -1px 0 white);
filter: drop-shadow(1px 1px 0 white) drop-shadow(-1px -1px 0 white);
}
}
</style>

View File

@ -59,7 +59,7 @@ import ProductSnPreviewImage from "../assets/images/product-solar-network.webp";
</a>
</div>
<a class="link flex items-center gap-1 mt-3">
<a class="link flex items-center gap-1 mt-3" href="/products/solar-network">
<span>Learn more about Solar Network</span>
<Icon name="material-symbols:arrow-right-alt" />
</a>

View File

@ -0,0 +1,108 @@
---
export const prerender = false
import sanitizeHtml from 'sanitize-html'
import { Icon } from 'astro-icon/components'
import { marked } from 'marked'
import Layout from '../../layouts/Layout.astro'
import { getAttachmentUrl, fetchAttachmentMeta } from '../../scripts/attachment'
const { slug } = Astro.params
const baseUrl = import.meta.env.PUBLIC_SOLAR_NETWORK_URL
const resp = await fetch(`${baseUrl}/cgi/co/posts/${slug}`)
if (resp.status !== 200) {
return new Response(null, { status: 404 })
}
const data = await resp.json()
const rawContent = await marked(data.body.content as string, {
breaks: data.type == 'story',
})
const content = sanitizeHtml(rawContent)
const attachments = await fetchAttachmentMeta(data.body.attachments)
---
<Layout title={data.body?.title ? data.body.title : `Post #${data.id}`}>
<div role="alert" class="alert shadow-lg px-12 m-0 rounded-none mb-5">
<Icon
name="material-symbols:ungroup"
class="stroke-info fill-info h-6 w-6 shrink-0"
/>
<div>
<h3 class="font-bold">Open in the Solian</h3>
<div class="text-xs">
The most modern, user-friendly, and official Solar Network app.
</div>
</div>
<div class="flex gap-2">
<a class="btn btn-sm" href="/products/solar-network">Get</a>
<a class="btn btn-sm" href={`https://sn.solsynth.dev/posts/${data.id}`}
>Open</a
>
</div>
</div>
<div class="container lg:max-w-[75ch] px-8 mx-auto">
<div class="flex gap-4 items-center mb-5">
<div class="avatar">
<div class="w-12 rounded-full">
<img src={getAttachmentUrl(data.publisher.avatar)} alt="avatar" />
</div>
</div>
<div class="userinfo flex flex-col">
<span class="flex gap-2 items-baseline">
<span class="text-md font-bold">{data.publisher.nick}</span>
<span class="text-xs font-mono">@{data.publisher.name}</span>
</span>
<span class="text-sm line-clamp-2 overflow-ellipsis"
>{data.publisher.description}</span
>
</div>
</div>
{
data.repost_id && (
<div role="alert" class="alert mb-5 py-2 mx-[-4px]">
<Icon
name="material-symbols:format-quote"
class="stroke-info fill-info h-6 w-6 shrink-0"
/>
<span>
This post is reposting post{' '}
<span class="font-mono">#{data.repost_id}</span>
</span>
<div>
<a class="btn btn-sm" href={`/posts/${data.repost_id}`}>
See reposted post
</a>
</div>
</div>
)
}
<article class="prose max-w-none max-md:prose-lg" set:html={content} />
{
attachments && (
<div class="attachment-list mt-5 gap-4 grid grid-cols-1 md:grid-cols-2">
{attachments.map((attachment) => (
<div class="attachment">
<a href={getAttachmentUrl(attachment.rid)} target="_blank">
<img
src={getAttachmentUrl(attachment.rid)}
alt={attachment.alt}
class="rounded-lg"
/>
</a>
</div>
))}
</div>
)
}
</div>
</Layout>

View File

@ -0,0 +1,7 @@
---
import Layout from "../../layouts/Layout.astro";
---
<Layout title="Solar Network">
</Layout>

23
src/scripts/attachment.ts Normal file
View File

@ -0,0 +1,23 @@
export function getAttachmentUrl(identifier: string): string {
if (identifier.startsWith('http')) {
return identifier
}
const baseUrl = import.meta.env.PUBLIC_SOLAR_NETWORK_URL
return `${baseUrl}/cgi/uc/attachments/${identifier}`
}
export async function fetchAttachmentMeta(
identifiers: string[]
): Promise<any[]> {
if (!identifiers) return []
const baseUrl = import.meta.env.PUBLIC_SOLAR_NETWORK_URL
const resp = await fetch(
`${baseUrl}/cgi/uc/attachments?take=${identifiers.length}&id=${identifiers.join(',')}`
)
if (resp.status !== 200) {
throw new Error(`Failed to fetch attachment meta: ${await resp.text()}`)
}
const out = await resp.json()
return out['data']
}

View File

@ -1,38 +1,38 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/typography"), require("daisyui")],
plugins: [require('@tailwindcss/typography'), require('daisyui')],
daisyui: {
themes: [
{
dark: {
primary: "#3f51b5",
secondary: "#4ba6ee",
accent: "#03a9f4",
neutral: "#1f2937",
"base-100": "#000011",
info: "#4994ec",
success: "#67ad5b",
warning: "#f5c344",
error: "#e15241",
light: {
primary: '#3f51b5',
secondary: '#4ba6ee',
accent: '#03a9f4',
neutral: '#4b5563',
'base-100': '#ffffff',
info: '#4994ec',
success: '#67ad5b',
warning: '#f5c344',
error: '#e15241',
},
},
{
light: {
primary: "#3f51b5",
secondary: "#4ba6ee",
accent: "#03a9f4",
neutral: "#4b5563",
"base-100": "#ffffff",
info: "#4994ec",
success: "#67ad5b",
warning: "#f5c344",
error: "#e15241",
dark: {
primary: '#3f51b5',
secondary: '#4ba6ee',
accent: '#03a9f4',
neutral: '#1f2937',
'base-100': '#000011',
info: '#4994ec',
success: '#67ad5b',
warning: '#f5c344',
error: '#e15241',
},
},
],
},
};
}