feat(fronted): add markdown rendering to todo detail view
This commit is contained in:
parent
e33501eaa6
commit
8f80db4fc1
7 changed files with 96 additions and 9 deletions
74
frontend/package-lock.json
generated
74
frontend/package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
frontend/src/lib/utils/markdown.ts
Normal file
11
frontend/src/lib/utils/markdown.ts
Normal 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));
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -13,6 +13,6 @@ export const load = (async ({ params }) => {
|
||||||
|
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|
||||||
export interface UserProfilePage {
|
export interface TodoDetailPage {
|
||||||
todo: TodoItem
|
todo: TodoItem
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Add table
Reference in a new issue