1
0
Fork 0
mirror of https://github.com/mat-1/matdoesdev.git synced 2025-08-02 06:36:04 +00:00
Use mdsvex for rendering blog posts instead of marked.
This commit is contained in:
mat 2022-06-28 03:37:20 +00:00 committed by GitHub
parent 1d5f76de48
commit 27b5fa5b3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 185 additions and 134 deletions

View file

@ -3,5 +3,6 @@
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"files.associations": { "*.svx": "markdown" }
}

View file

@ -22,6 +22,7 @@
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.2.1",
"mdsvex": "^0.10.6",
"prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.4.0",
"svelte": "^3.48.0",
@ -37,11 +38,9 @@
"@sveltejs/adapter-static": "^1.0.0-next.21",
"@types/js-yaml": "^4.0.4",
"cookie": "^0.4.1",
"html-minifier": "^4.0.0",
"js-yaml": "^4.1.0",
"marked": "^4.0.3"
"html-minifier": "^4.0.0"
},
"engines": {
"node": ">=16"
}
}
}

View file

@ -1,36 +1,14 @@
<script lang="ts" context="module">
import type { APIBlogPost } from './[slug].json'
import type { Load } from '@sveltejs/kit'
export const prerender = true
export const load: Load = async ({ params, fetch }) => {
const slug: string = params.slug ?? ''
const resp = await fetch(`/blog/${slug}.json`)
if (resp.status === 404)
return {
status: 404,
}
const post: APIBlogPost = await resp.json()
return {
props: {
title: post.title,
html: post.html,
published: new Date(post.published),
},
}
}
</script>
<script lang="ts">
<script context="module">
import img from './components/img.svelte'
import BackAnchor from '$lib/BackAnchor.svelte'
export let title: string
export let html: string
export let published: Date
// mdsvex moment
export { img as image, img }
</script>
<script>
export let title = 'Untitled'
export let published = ''
</script>
<div class="article-container">
@ -40,10 +18,10 @@
<article>
<div class="article-header">
<h1>{title}</h1>
<time>{published.toLocaleDateString()}</time>
<time>{new Date(published).toLocaleDateString()}</time>
</div>
{@html html}
<slot />
</article>
</div>

View file

@ -2,6 +2,8 @@
import type { BlogPostPreview } from 'src/routes/blog/index.json'
export let post: BlogPostPreview
// HACK: we have to do this otherwise sveltekit does a dumb
const postHtml = `${post.html}<sty` + `le>${post.css}</style>`
</script>
<a href="/blog/{post.slug}" class="preview-anchor">
@ -12,7 +14,9 @@
</div>
<div class="disappearing-text-preview" />
<div class="preview">{@html post.html}</div>
<div class="preview">
{@html postHtml}
</div>
</article>
</a>

View file

@ -1,21 +1,42 @@
import yaml from 'js-yaml'
import path from 'path'
import fs from 'fs'
export const postsDir = 'src/posts' as const
export const postsDir = 'src/routes/blog' as const
export async function listBlogPostSlugs(): Promise<string[]> {
await fs.promises.readdir(postsDir)
const existingPosts: string[] = await fs.promises.readdir(postsDir)
// https://stackoverflow.com/a/46842181
async function filter<T>(arr: T[], callback: (item: T) => Promise<boolean>): Promise<T[]> {
const fail = Symbol()
return (
await Promise.all(arr.map(async (item) => ((await callback(item)) ? item : fail)))
).filter((i) => i !== fail) as any
}
return await filter(existingPosts, (slug) =>
fs.promises
.stat(path.join(postsDir, slug, 'index.svx'))
.then(() => true)
.catch(() => false)
)
}
interface BlogPost {
title: string
published: string
body: string
html: string
css: string
slug: string
}
/** Checks whether a slug is valid or not */
async function doesBlogPostExist(slug: string) {
const existingPosts: string[] = await fs.promises.readdir(postsDir)
return existingPosts.includes(slug)
if (!existingPosts.includes(slug)) return false
return true
}
/** Checks whether an asset exists in a blog post */
@ -32,29 +53,77 @@ export async function doesAssetExist(postSlug: string, assetName: string): Promi
export async function getPost(slug: string): Promise<BlogPost | null> {
if (!doesBlogPostExist(slug)) return null
const url = new URL(`protocol://-/blog/${slug}`)
const { default: post, metadata } = await import(`../routes/blog/${slug}/index.svx`)
// ok the post exists, so we can safely read the md file
const postMarkdown = (
await fs.promises.readFile(path.join(postsDir, slug, 'index.md'), 'utf8')
).replace(/\r\n/g, '\n')
// const postMarkdown = (
// await fs.promises.readFile(path.join(postsDir, slug, 'index.md'), 'utf8')
// ).replace(/\r\n/g, '\n')
const [_, yamlMetadata = null, markdownContent = null] =
postMarkdown.match(/^---\n([\w\W]+?)\n---\n([\w\W]+)$/) ?? []
// const [_, yamlMetadata = null, markdownContent = null] =
// postMarkdown.match(/^---\n([\w\W]+?)\n---\n([\w\W]+)$/) ?? []
if (yamlMetadata === null) throw new Error(`Blog post "${slug}" has no metadata.`)
if (markdownContent === null) throw new Error(`Blog post "${slug}" has no content.`)
// if (yamlMetadata === null) throw new Error(`Blog post "${slug}" has no metadata.`)
// if (markdownContent === null) throw new Error(`Blog post "${slug}" has no content.`)
const metadata: NonNullable<any> = yaml.load(yamlMetadata)
// const metadata: NonNullable<any> = yaml.load(yamlMetadata)
// make sure the post has all the required metadata
const requiredFields = ['title', 'published']
for (const requiredField of requiredFields)
if (!(requiredField in metadata))
throw new Error(`Blog post "${slug}" is missing metadata field "${requiredField}"`)
// // make sure the post has all the required metadata
// const requiredFields = ['title', 'published']
// for (const requiredField of requiredFields)
// if (!(requiredField in metadata))
// throw new Error(`Blog post "${slug}" is missing metadata field "${requiredField}"`)
const result: {
title: string
head: string
css: Set<{
map: null
code: string
}>
} = { title: '', head: '', css: new Set() }
const renderHtml = post.$$render(
result,
{},
{},
{},
new Map([
[
'__svelte__',
{
page: {
// HACK: this is necessary so the hack with images works
// probably a war crime :)
subscribe: (r: any) => {
r({ url })
},
},
navigating: {
subscribe: () => {
return
},
},
},
],
])
)
// HACK: i'm probably comitting a felony by putting this here
// but i couldn't come up with a better solution
const html = /^[\w\W]*?<\/div>\s*([\w\W]+)<\/article>[\w\W]*?$/.exec(renderHtml)?.[1] ?? ''
const css = Array.from(result.css)
.map((css) => css.code)
.join('')
return {
title: metadata.title,
published: new Date(metadata.published).toString(),
body: markdownContent.trim(),
html,
css,
slug,
}
}

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { page } from '$app/stores'
export let src: string
export let alt: string
page.subscribe((p) => {
// hack so images work
if (!src.includes('//') && !src.startsWith('/')) {
src = `${p.url.pathname}/${src}`
}
})
</script>
<img {src} {alt} />

View file

@ -1,30 +0,0 @@
import { marked } from 'marked'
export function markdownToHtml(md: string, baseUrl?: string): string {
const renderer: Partial<marked.Renderer> = {
image(href: string, title: string, text: string) {
// href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)
href = baseUrl ? resolveUrl(baseUrl, href) : href
if (href === null) return text
let out = `<img src="${href}" alt="${text}"`
if (title) out += ` title="${title}"`
out += '/>'
return out
},
}
marked.use({ renderer })
return marked.parse(md, { baseUrl, breaks: true })
}
// https://nodejs.org/api/url.html#urlresolvefrom-to
function resolveUrl(from: string, to: string): string {
const resolvedUrl = new URL(to, new URL(from, 'resolve://'))
if (resolvedUrl.protocol === 'resolve:') {
// `from` is a relative URL.
const { pathname, search, hash } = resolvedUrl
return pathname + search + hash
}
return resolvedUrl.toString()
}

View file

@ -1,5 +1,4 @@
import { getPost } from '$lib/blog'
import { markdownToHtml } from '$lib/utils'
import type { RequestHandler } from '@sveltejs/kit'
export interface APIBlogPost {
@ -25,7 +24,7 @@ export const get: RequestHandler = async ({ params }) => {
body: {
title: post.title,
published: post.published,
html: markdownToHtml(post.body, `/blog/${post.slug}/index.md`),
html: post.html,
} as APIBlogPost,
}
}

View file

@ -1,5 +1,4 @@
import { doesAssetExist, getPost, postsDir } from '$lib/blog'
import { markdownToHtml } from '$lib/utils'
import type { RequestHandler } from '@sveltejs/kit'
import path from 'path'
import fs from 'fs'

View file

@ -1,19 +1,16 @@
import { getPost } from '$lib/blog'
import { markdownToHtml } from '$lib/utils'
import { getPost, listBlogPostSlugs } from '$lib/blog'
import type { RequestHandler } from '@sveltejs/kit'
import fs from 'fs'
const postsDir = 'src/posts'
export interface BlogPostPreview {
title: string
published: string
html: string
css: string
slug: string
}
export const get: RequestHandler = async () => {
const existingPosts: string[] = await fs.promises.readdir(postsDir)
const existingPosts: string[] = await listBlogPostSlugs()
const posts = (
await Promise.all(
@ -26,14 +23,10 @@ export const get: RequestHandler = async () => {
return {
title: blogPost.title,
published: blogPost.published,
// cut it off after 255 characters because that's a nice number
html: markdownToHtml(
blogPost.body
.slice(0, 512)
.replace(/!\[[^\]]+?\]\([^)]+?\)/g, '')
.replace(/\[([^\]]+?)\]\([^)]+?\)/g, '$1'),
`/blog/${blogPost.slug}/index.md`
),
// HACK: remove images, i WILL parse html with regex and you won't stop me
// TODO: cut off the html so it's not sending a bunch of unnecessary data over the network
html: blogPost.html.replace(/<img.+?\/?>/g, ''),
css: blogPost.css,
slug: blogPost.slug,
}
})

View file

@ -64,13 +64,3 @@ Titles:
Image:
!\[description](https://image)
<-
**Float left**
`<- text <-`
<-
->
**Float right**
`-> text ->`
->

View file

@ -13,7 +13,7 @@ It all started on April 27th, 2020. I was bored and wanted to make a Hypixel For
# madcausebad11
I didnt do anything with this idea until a couple weeks later on May 14th, when I remembered it, and was actually motivated to create it. I asked around on the SkyBlock Community Discord for what it should be called and what it should do, and I decided on calling it madcausebad11 (name chosen by @TatorCheese), and making it say “thats crazy but I dont remember asking” (@Bliziq chose that one) to all posts that mentioned being scammed.
![Screenshot of a post on the Hypixel Forums where a user named madcausebad11 says "thats crazy but i dont remember asking"](thats-crazy-but-i-dont-remember-asking.png)
![Screenshot of a post on the Hypixel Forums where a user named madcausebad11 says 'thats crazy but i dont remember asking'](thats-crazy-but-i-dont-remember-asking.png)
When that had been decided, I started working on the code. It was written in Python, using BeautifulSoup to scrape the web pages and aiohttp to make the requests. After an hour of writing code, madcausebad11 was working.
Less than an hour after the bot started working, it got banned for the reason “spam”.

View file

@ -67,7 +67,7 @@ We also found out that they had deleted their webhook, which meant we couldn't s
The first two quickly left, but one sent us a message before leaving.
!["wtf are you"](https://i.matdoes.dev/VJMW9)
!['wtf are you'](https://i.matdoes.dev/VJMW9)
We asked kzh to join back again.

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View file

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -4,6 +4,7 @@ published: 2019-04-25T01:08:27.057+00:00
---
Welcome to mat does dev. You might have some questions, so I'm here to answer them.
![mat does dev](favicon.png)
---

View file

@ -1,11 +1,19 @@
import staticAdapter from '@sveltejs/adapter-static'
import preprocess from 'svelte-preprocess'
import { mdsvex } from 'mdsvex'
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
preprocess: [
preprocess(),
mdsvex({
layout: './src/lib/PostLayout.svelte'
}),
],
extensions: ['.svelte', '.svx'],
kit: {
adapter: staticAdapter({}),
@ -25,7 +33,10 @@ const config = {
// }
// : {},
},
},
}
export default config

View file

@ -202,6 +202,11 @@
dependencies:
source-map "^0.6.1"
"@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@typescript-eslint/eslint-plugin@^4.31.1":
version "4.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276"
@ -341,11 +346,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@ -1069,13 +1069,6 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -1138,10 +1131,15 @@ magic-string@^0.26.2:
dependencies:
sourcemap-codec "^1.4.8"
marked@^4.0.3:
version "4.0.13"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.13.tgz#4fd46ca93da46448f3d83f054d938c4f905a258d"
integrity sha512-lS/ZCa4X0gsRcfWs1eoh6dLnHr9kVH3K1t2X4M/tTtNouhZ7anS1Csb6464VGLQHv8b2Tw1cLeZQs58Jav8Rzw==
mdsvex@^0.10.6:
version "0.10.6"
resolved "https://registry.yarnpkg.com/mdsvex/-/mdsvex-0.10.6.tgz#5ba975f4616e5255ca31cd93d33e2c2a22845631"
integrity sha512-aGRDY0r5jx9+OOgFdyB9Xm3EBr9OUmcrTDPWLB7a7g8VPRxzPy4MOBmcVYgz7ErhAJ7bZ/coUoj6aHio3x/2mA==
dependencies:
"@types/unist" "^2.0.3"
prism-svelte "^0.4.7"
prismjs "^1.17.1"
vfile-message "^2.0.4"
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
@ -1304,6 +1302,16 @@ prettier@^2.4.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
prism-svelte@^0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/prism-svelte/-/prism-svelte-0.4.7.tgz#fbc6709450b4e2ed660ddb82c3718817fc584cbe"
integrity sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==
prismjs@^1.17.1:
version "1.28.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6"
integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@ -1626,6 +1634,13 @@ uglify-js@^3.5.1:
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.3.tgz#9aa82ca22419ba4c0137642ba0df800cb06e0471"
integrity sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==
unist-util-stringify-position@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da"
integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==
dependencies:
"@types/unist" "^2.0.2"
upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
@ -1643,6 +1658,14 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
vfile-message@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
dependencies:
"@types/unist" "^2.0.0"
unist-util-stringify-position "^2.0.0"
vite@^2.9.10:
version "2.9.12"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.12.tgz#b1d636b0a8ac636afe9d83e3792d4895509a941b"