fix: attempt to fix safari

This commit is contained in:
hugeblank 2024-12-04 06:24:56 -08:00
parent 4bc3dd5ca4
commit 995a6a987a
13 changed files with 346 additions and 287 deletions

View file

@ -16,8 +16,6 @@ npm run dev
Change environment variables:
```sh
PUBLIC_HANDLE="myhandle.bsky.social" # Your handle, or DID
PUBLIC_ABOUT="Welcome to my blog!" # Optional description of the kinds of posts you'll be making, or a greeting.
# PUBLIC_ABOUT Shows up under your Bluesky profile description in the profile card.
```
#### Standalone

361
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"preview": "vite preview -v",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "eslint ."
@ -25,13 +25,11 @@
"tailwindcss": "^3.4.9",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0",
"vite": "^5.0.3"
"vite": "^5.0.3",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15"
},
"dependencies": {
"@sveltejs/adapter-node": "^5.2.9",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"marked": "^15.0.3",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { profile } from "./identity";
const { profile } = $props()
</script>
<div class="p-4 bg-local relative border-gray-500 border-[1px] rounded-[1em] m-2" style="background-image: url({profile.banner}); background-size: cover; background-position: center;">
@ -11,10 +11,6 @@
</div>
<div class="p-3">
<p class="">{profile.description}</p>
{#if profile.about}
<br/>
<p class="">{profile.about}</p>
{/if}
</div>
</div>
</div>

View file

@ -1,56 +0,0 @@
import {env} from "$env/dynamic/public"
async function safeFetch(url: string) {
const response = await fetch(url)
if (!response.ok) throw new Error(response.status + ":" + response.statusText)
return await response.json();
}
interface Profile {
avatar: string,
banner: string,
displayName: string,
did: string,
handle: string,
description: string,
about: string | undefined
}
if (!env.PUBLIC_HANDLE) {
throw new Error("Expected PUBLIC_HANDLE environment variable.")
}
const getProfile = await safeFetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${env.PUBLIC_HANDLE}`)
export const profile: Profile = {
avatar: getProfile["avatar"],
banner: getProfile["banner"],
displayName: getProfile["displayName"],
did: getProfile["did"],
handle: getProfile["handle"],
description: getProfile["description"],
about: env.PUBLIC_ABOUT,
}
export const avatar: string = getProfile["avatar"]
let split = profile.did.split(":")
let diddoc;
if (split[0] === "did") {
if (split[1] === "plc") {
diddoc = await safeFetch(`https://plc.directory/${profile.did}`)
} else if (split[1] === "web") {
diddoc = await safeFetch("https://" + split[2] + "/.well-known/did.json")
} else {
throw new Error("Invalid DID, Not blessed method")
}
} else {
throw new Error("Invalid DID, malformed")
}
let pdsurl;
for (let service of diddoc["service"]) {
if (service["id"] === "#atproto_pds") {
pdsurl = service["serviceEndpoint"]
}
}
if (!pdsurl) {
throw new Error("DID lacks #atproto_pds service")
}
export const pds: string = pdsurl;

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -1,17 +1,29 @@
import { pds, profile } from "$lib/identity";
import { error } from '@sveltejs/kit';
import rehypeStringify from 'rehype-stringify'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
import rehypeRaw from "rehype-raw";
import type { Schema } from '../../../node_modules/rehype-sanitize/lib'
import type { Schema } from '../../node_modules/rehype-sanitize/lib'
import { unified } from 'unified'
import type { Node } from 'unist'
import type { Root, Element } from 'hast'
import type { Plugin } from "unified";
export interface Post {
title: string,
rkey: string,
createdAt: Date,
content: string // content parsed to html
}
export interface MarkdownPost {
title: string,
rkey: string,
createdAt: Date,
mdcontent: string // markdown content
}
// WhiteWind's own custom schema:
// https://github.com/whtwnd/whitewind-blog/blob/7eb8d4623eea617fd562b93d66a0e235323a2f9a/frontend/src/services/DocProvider.tsx#L122
const customSchema = {
@ -63,14 +75,14 @@ const rehypeUpgradeImage: Plugin<any, Root, Node> = () => {
}
}
export async function load({ params }) {
const rawResponse = await fetch(`${pds}/xrpc/com.atproto.repo.getRecord?repo=${profile.did}&collection=com.whtwnd.blog.entry&rkey=${params.rkey}`)
const response = await rawResponse.json()
try {
return {
title: response["value"]["title"],
mdcontent: String(
export async function parse(mdposts: Map<string, MarkdownPost>) {
let posts: Map<string, Post> = new Map()
for (let [ rkey, post ] of mdposts) {
posts.set(rkey, {
title: post.title,
rkey: post.rkey,
createdAt: post.createdAt,
content: String(
await unified()
.use(remarkParse, { fragment: true }) // Parse the MD
.use(remarkGfm) // Parse GH specific MD
@ -79,11 +91,9 @@ export async function load({ params }) {
.use(rehypeUpgradeImage)
.use(rehypeSanitize, customSchema as Schema) // Sanitize the HTML
.use(rehypeStringify) // Stringify
.process(response["value"]["content"])
.process(post.mdcontent)
)
}
} catch (e) {
console.error(e)
error(404)
})
}
return posts
}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import Profile from '$lib/Profile.svelte';
import '../app.css';
let { children } = $props();
let { data, children } = $props();
</script>
<div class="box-border mx-auto px-8 max-w-[1000px] pb-8">
@ -9,7 +9,7 @@
<a class="font-medium text-[x-large]" href="/">Home</a>
<a class="font-medium text-[large]" href="/about">About</a>
</nav>
<Profile/>
<Profile profile={data.profile}/>
{@render children()}
</div>
<footer class="text-center"><a class="hover:underline hover:text-blue-500" href="https://github.com/hugeblank/whitebreeze">WhiteBreeze</a></footer>

82
src/routes/+layout.ts Normal file
View file

@ -0,0 +1,82 @@
import { PUBLIC_HANDLE } from "$env/static/public"
import { parse, type MarkdownPost, type Post } from '$lib/parser';
interface Profile {
avatar: string,
banner: string,
displayName: string,
did: string,
handle: string,
description: string,
pds: string
}
async function safeFetch(url: string) {
const response = await fetch(url)
if (!response.ok) throw new Error(response.status + ":" + response.statusText)
return await response.json();
}
async function getProfile(): Promise<Profile> {
const fetchProfile = await safeFetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${PUBLIC_HANDLE}`)
let split = fetchProfile["did"].split(":")
let diddoc;
if (split[0] === "did") {
if (split[1] === "plc") {
diddoc = await safeFetch(`https://plc.directory/${fetchProfile["did"]}`)
} else if (split[1] === "web") {
diddoc = await safeFetch("https://" + split[2] + "/.well-known/did.json")
} else {
throw new Error("Invalid DID, Not blessed method")
}
} else {
throw new Error("Invalid DID, malformed")
}
let pdsurl;
for (let service of diddoc["service"]) {
if (service["id"] === "#atproto_pds") {
pdsurl = service["serviceEndpoint"]
}
}
if (!pdsurl) {
throw new Error("DID lacks #atproto_pds service")
}
return {
avatar: fetchProfile["avatar"],
banner: fetchProfile["banner"],
displayName: fetchProfile["displayName"],
did: fetchProfile["did"],
handle: fetchProfile["handle"],
description: fetchProfile["description"],
pds: pdsurl
};
}
// TODO: Maybe also add the option to show "url" level records as an env setting.
let profile: Profile;
let posts: Map<string, Post>;
export async function load() {
if (profile === undefined) {
profile = await getProfile();
}
if (posts === undefined) {
const rawResponse = await fetch(`${profile.pds}/xrpc/com.atproto.repo.listRecords?repo=${profile.did}&collection=com.whtwnd.blog.entry`)
const response = await rawResponse.json()
let mdposts: Map<string, MarkdownPost> = new Map();
for (let record of response["records"]) {
const matches = record["uri"].split("/")
const rkey = matches[matches.length - 1]
if (matches && matches.length === 5 && record["value"] && record["value"]["visibility"] === "public") {
mdposts.set(rkey, {
title: record["value"]["title"],
createdAt: new Date(record["value"]["createdAt"]),
mdcontent: record["value"]["content"],
rkey
})
}
}
posts = await parse(mdposts)
}
return { posts, profile };
}

View file

@ -1,32 +0,0 @@
import { pds, profile } from "$lib/identity";
import { error } from '@sveltejs/kit';
interface PostSummary {
title: string,
rkey: string,
createdAt: Date
}
// TODO: Maybe also add the option to show "url" level records as an env setting.
export async function load({ params }) {
const rawResponse = await fetch(`${pds}/xrpc/com.atproto.repo.listRecords?repo=${profile.did}&collection=com.whtwnd.blog.entry`)
try {
const response = await rawResponse.json()
const rx = /\/.{13}$/;
let posts: PostSummary[] = [];
for (let record of response["records"]) {
const matches = rx.exec(record["uri"])
if (matches && matches[0] && record["value"] && record["value"]["visibility"] === "public") {
posts.push({
title: record["value"]["title"],
rkey: matches[0],
createdAt: new Date(record["value"]["createdAt"])
})
}
}
return { posts };
} catch {
//ignored
}
return {posts: []};
}

View file

@ -1,6 +1,6 @@
<script lang="ts">
const {data} = $props()
const posts = data.posts
const posts = Array.from(data.posts.values())
</script>
<svelte:head>

View file

@ -1,15 +1,29 @@
<script>
<script lang="ts">
import type { Post } from '$lib/parser.ts';
let { data } = $props();
// TODO: make it so links scroll to the right spot
let post: Post | undefined = $state(undefined)
if ( data.posts.has(data.rkey)) {
post = data.posts.get(data.rkey) as Post
}
</script>
<svelte:head>
<title>{data.title} - WhiteBreeze</title>
{#if post !== undefined}
<title>{post.title} - WhiteBreeze</title>
{:else}
<title>WhiteBreeze</title>
{/if}
<meta name="description" content="This is where the description goes for SEO" />
</svelte:head>
<h1 class="text-center pt-4 pb-4">{data.title}</h1>
<hr class="pb-4">
<article class="mx-auto max-w-4xl px-2 sm:px-6 lg:px-8 prose dark:prose-invert">
{@html data.mdcontent}
</article>
{#if post !== undefined}
<h1 class="text-center pt-4 pb-4">{post.title}</h1>
<hr class="pb-4">
<article class="mx-auto max-w-4xl px-2 sm:px-6 lg:px-8 prose dark:prose-invert">
{@html post.content}
</article>
{:else}
<h1 class="text-center pt-4 pb-4">No such post</h1>
{/if}

View file

@ -0,0 +1,5 @@
export const load = ({ params }) => {
return {
rkey: params.rkey
}
}