feat(fronted): add markdown rendering to todo detail view

This commit is contained in:
Julian Lobbes 2023-05-31 23:30:41 +01:00
parent e33501eaa6
commit 8f80db4fc1
7 changed files with 96 additions and 9 deletions

View file

@ -8,12 +8,16 @@
"name": "fastapi-svelte-template", "name": "fastapi-svelte-template",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"dompurify": "^3.0.3",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"jwt-decode": "^3.1.2" "jwt-decode": "^3.1.2",
"marked": "^5.0.4"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^1.2.4", "@sveltejs/adapter-node": "^1.2.4",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
"@types/dompurify": "^3.0.2",
"@types/marked": "^5.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"sass": "^1.62.1", "sass": "^1.62.1",
@ -2028,12 +2032,27 @@
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
"dev": true "dev": true
}, },
"node_modules/@types/dompurify": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz",
"integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==",
"dev": true,
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true "dev": true
}, },
"node_modules/@types/marked": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.0.tgz",
"integrity": "sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==",
"dev": true
},
"node_modules/@types/pug": { "node_modules/@types/pug": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
@ -2046,6 +2065,12 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true "dev": true
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==",
"dev": true
},
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -2371,6 +2396,11 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true "dev": true
}, },
"node_modules/dompurify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
},
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.0.3", "version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
@ -2767,6 +2797,17 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/marked": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-5.0.4.tgz",
"integrity": "sha512-r0W8/DK56fAkV0qfUCO9cEt/VlFWUzoJOqEigvijmsVkTuPOHckh7ZutNJepRO1AxHhK96/9txonHg4bWd/aLA==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -5654,12 +5695,27 @@
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
"dev": true "dev": true
}, },
"@types/dompurify": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz",
"integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==",
"dev": true,
"requires": {
"@types/trusted-types": "*"
}
},
"@types/estree": { "@types/estree": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true "dev": true
}, },
"@types/marked": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.0.tgz",
"integrity": "sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==",
"dev": true
},
"@types/pug": { "@types/pug": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
@ -5672,6 +5728,12 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true "dev": true
}, },
"@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==",
"dev": true
},
"any-promise": { "any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -5880,6 +5942,11 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true "dev": true
}, },
"dompurify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
},
"dotenv": { "dotenv": {
"version": "16.0.3", "version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
@ -6192,6 +6259,11 @@
"@jridgewell/sourcemap-codec": "^1.4.13" "@jridgewell/sourcemap-codec": "^1.4.13"
} }
}, },
"marked": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-5.0.4.tgz",
"integrity": "sha512-r0W8/DK56fAkV0qfUCO9cEt/VlFWUzoJOqEigvijmsVkTuPOHckh7ZutNJepRO1AxHhK96/9txonHg4bWd/aLA=="
},
"merge2": { "merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

View file

@ -12,6 +12,8 @@
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^1.2.4", "@sveltejs/adapter-node": "^1.2.4",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
"@types/dompurify": "^3.0.2",
"@types/marked": "^5.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"sass": "^1.62.1", "sass": "^1.62.1",
@ -26,7 +28,9 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"dompurify": "^3.0.3",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"jwt-decode": "^3.1.2" "jwt-decode": "^3.1.2",
"marked": "^5.0.4"
} }
} }

View file

@ -0,0 +1,11 @@
import { marked } from 'marked';
import dompurify from 'dompurify';
export function markdownToHtml(text: string): string {
// Remove zerowidth chars from beginning of input string
// This is probably not necessary...
text = text.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, "");
// Return markdown parsed as HTML and subsequently sanitized
return dompurify.sanitize(marked.parse(text));
}

View file

@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { TodoDetailPage } from "./+page"; import type { TodoDetailPage } from './+page';
import { markdownToHtml } from '$lib/utils/markdown';
export let data: TodoDetailPage; export let data: TodoDetailPage;
</script> </script>
<h1>{data.todo.title}</h1> <h1>{data.todo.title}</h1>
<div>
{@html markdownToHtml(data.todo.description)}
</div>

View file

@ -13,6 +13,6 @@ export const load = (async ({ params }) => {
}) satisfies PageLoad; }) satisfies PageLoad;
export interface UserProfilePage { export interface TodoDetailPage {
todo: TodoItem todo: TodoItem
} }

View file

@ -5,3 +5,4 @@
</script> </script>
<h1>Profile page for {data.user.email}</h1> <h1>Profile page for {data.user.email}</h1>
<a href={`/users/${data.user.id}/todos`}>{data.user.first_name}'s Todos</a>

View file

@ -17,11 +17,6 @@
isLink: true, isLink: true,
linkTarget: '/todos/%id%', linkTarget: '/todos/%id%',
}, },
{
field: "description",
heading: "Description",
sortable: false,
},
{ {
field: "done", field: "done",
heading: "Done", heading: "Done",