feat: use markdown-it instead of marked for descriptions

This commit is contained in:
Sam 2023-03-23 10:05:17 +01:00
parent 90667bc285
commit da67d12b60
No known key found for this signature in database
GPG Key ID: B4EF20DDE721CAA1
7 changed files with 89 additions and 29 deletions

View File

@ -16,7 +16,7 @@
"@sveltejs/adapter-node": "^1.2.2", "@sveltejs/adapter-node": "^1.2.2",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
"@types/luxon": "^3.2.0", "@types/luxon": "^3.2.0",
"@types/marked": "^4.0.8", "@types/markdown-it": "^12.2.3",
"@types/node": "^18.15.3", "@types/node": "^18.15.3",
"@types/sanitize-html": "^2.8.1", "@types/sanitize-html": "^2.8.1",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
@ -28,10 +28,10 @@
"prettier-plugin-svelte": "^2.8.1", "prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0", "svelte": "^3.54.0",
"svelte-check": "^3.0.1", "svelte-check": "^3.0.1",
"sveltestrap": "^5.10.0",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
"vite": "^4.0.0", "vite": "^4.0.0"
"sveltestrap": "^5.10.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
@ -42,7 +42,7 @@
"bootstrap-icons": "^1.10.3", "bootstrap-icons": "^1.10.3",
"jose": "^4.13.1", "jose": "^4.13.1",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"marked": "^4.2.12", "markdown-it": "^13.0.1",
"sanitize-html": "^2.10.0" "sanitize-html": "^2.10.0"
} }
} }

View File

@ -7,7 +7,7 @@ specifiers:
'@sveltejs/adapter-node': ^1.2.2 '@sveltejs/adapter-node': ^1.2.2
'@sveltejs/kit': ^1.5.0 '@sveltejs/kit': ^1.5.0
'@types/luxon': ^3.2.0 '@types/luxon': ^3.2.0
'@types/marked': ^4.0.8 '@types/markdown-it': ^12.2.3
'@types/node': ^18.15.3 '@types/node': ^18.15.3
'@types/sanitize-html': ^2.8.1 '@types/sanitize-html': ^2.8.1
'@typescript-eslint/eslint-plugin': ^5.45.0 '@typescript-eslint/eslint-plugin': ^5.45.0
@ -20,7 +20,7 @@ specifiers:
eslint-plugin-svelte3: ^4.0.0 eslint-plugin-svelte3: ^4.0.0
jose: ^4.13.1 jose: ^4.13.1
luxon: ^3.3.0 luxon: ^3.3.0
marked: ^4.2.12 markdown-it: ^13.0.1
prettier: ^2.8.0 prettier: ^2.8.0
prettier-plugin-svelte: ^2.8.1 prettier-plugin-svelte: ^2.8.1
sanitize-html: ^2.10.0 sanitize-html: ^2.10.0
@ -39,16 +39,15 @@ dependencies:
bootstrap-icons: 1.10.3 bootstrap-icons: 1.10.3
jose: 4.13.1 jose: 4.13.1
luxon: 3.3.0 luxon: 3.3.0
marked: 4.2.12 markdown-it: 13.0.1
sanitize-html: 2.10.0 sanitize-html: 2.10.0
sveltestrap: 5.10.0_svelte@3.55.1
devDependencies: devDependencies:
'@sveltejs/adapter-auto': 2.0.0_@sveltejs+kit@1.11.0 '@sveltejs/adapter-auto': 2.0.0_@sveltejs+kit@1.11.0
'@sveltejs/adapter-node': 1.2.2_@sveltejs+kit@1.11.0 '@sveltejs/adapter-node': 1.2.2_@sveltejs+kit@1.11.0
'@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4 '@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4
'@types/luxon': 3.2.0 '@types/luxon': 3.2.0
'@types/marked': 4.0.8 '@types/markdown-it': 12.2.3
'@types/node': 18.15.3 '@types/node': 18.15.3
'@types/sanitize-html': 2.8.1 '@types/sanitize-html': 2.8.1
'@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i '@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i
@ -60,6 +59,7 @@ devDependencies:
prettier-plugin-svelte: 2.9.0_jrsxveqmsx2uadbqiuq74wlc4u prettier-plugin-svelte: 2.9.0_jrsxveqmsx2uadbqiuq74wlc4u
svelte: 3.55.1 svelte: 3.55.1
svelte-check: 3.1.0_svelte@3.55.1 svelte-check: 3.1.0_svelte@3.55.1
sveltestrap: 5.10.0_svelte@3.55.1
tslib: 2.5.0 tslib: 2.5.0
typescript: 4.9.5 typescript: 4.9.5
vite: 4.1.4_@types+node@18.15.3 vite: 4.1.4_@types+node@18.15.3
@ -349,7 +349,6 @@ packages:
/@popperjs/core/2.11.6: /@popperjs/core/2.11.6:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@rollup/plugin-commonjs/24.0.1_rollup@3.18.0: /@rollup/plugin-commonjs/24.0.1_rollup@3.18.0:
resolution: {integrity: sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==} resolution: {integrity: sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==}
@ -532,12 +531,23 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
/@types/linkify-it/3.0.2:
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
dev: true
/@types/luxon/3.2.0: /@types/luxon/3.2.0:
resolution: {integrity: sha512-lGmaGFoaXHuOLXFvuju2bfvZRqxAqkHPx9Y9IQdQABrinJJshJwfNCKV+u7rR3kJbiqfTF/NhOkcxxAFrObyaA==} resolution: {integrity: sha512-lGmaGFoaXHuOLXFvuju2bfvZRqxAqkHPx9Y9IQdQABrinJJshJwfNCKV+u7rR3kJbiqfTF/NhOkcxxAFrObyaA==}
dev: true dev: true
/@types/marked/4.0.8: /@types/markdown-it/12.2.3:
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==} resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
dependencies:
'@types/linkify-it': 3.0.2
'@types/mdurl': 1.0.2
dev: true
/@types/mdurl/1.0.2:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true dev: true
/@types/node/18.15.3: /@types/node/18.15.3:
@ -753,7 +763,6 @@ packages:
/argparse/2.0.1: /argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/array-union/2.1.0: /array-union/2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
@ -953,6 +962,11 @@ packages:
domelementtype: 2.3.0 domelementtype: 2.3.0
domhandler: 5.0.3 domhandler: 5.0.3
/entities/3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
dev: false
/entities/4.4.0: /entities/4.4.0:
resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@ -1459,6 +1473,12 @@ packages:
type-check: 0.4.0 type-check: 0.4.0
dev: true dev: true
/linkify-it/4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
dependencies:
uc.micro: 1.0.6
dev: false
/locate-path/6.0.0: /locate-path/6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1507,10 +1527,19 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true dev: true
/marked/4.2.12: /markdown-it/13.0.1:
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==}
engines: {node: '>= 12'}
hasBin: true hasBin: true
dependencies:
argparse: 2.0.1
entities: 3.0.1
linkify-it: 4.0.1
mdurl: 1.0.1
uc.micro: 1.0.6
dev: false
/mdurl/1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: false dev: false
/merge2/1.4.1: /merge2/1.4.1:
@ -1976,6 +2005,7 @@ packages:
/svelte/3.55.1: /svelte/3.55.1:
resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true
/sveltestrap/5.10.0_svelte@3.55.1: /sveltestrap/5.10.0_svelte@3.55.1:
resolution: {integrity: sha512-k6Ob+6G2AMYvBidXHBKM9W28fJqFHbmosqCe/NC8pv6TV7K+v47Yw+zmnLWkjqCzzmjkSLkL48SrHZrlWc9mYQ==} resolution: {integrity: sha512-k6Ob+6G2AMYvBidXHBKM9W28fJqFHbmosqCe/NC8pv6TV7K+v47Yw+zmnLWkjqCzzmjkSLkL48SrHZrlWc9mYQ==}
@ -1984,7 +2014,7 @@ packages:
dependencies: dependencies:
'@popperjs/core': 2.11.6 '@popperjs/core': 2.11.6
svelte: 3.55.1 svelte: 3.55.1
dev: false dev: true
/text-table/0.2.0: /text-table/0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -2044,6 +2074,10 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/uc.micro/1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: false
/undici/5.20.0: /undici/5.20.0:
resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==}
engines: {node: '>=12.18'} engines: {node: '>=12.18'}

View File

@ -0,0 +1,11 @@
import MarkdownIt from "markdown-it";
import sanitize from "sanitize-html";
const md = new MarkdownIt({
html: false,
breaks: true,
}).disable(["heading", "link", "table"]);
export default function renderMarkdown(src: string | null) {
return src ? sanitize(md.render(src)) : null;
}

View File

@ -1,7 +1,4 @@
<script lang="ts"> <script lang="ts">
import { marked } from "marked";
import sanitizeHtml from "sanitize-html";
import type { PageData } from "./$types"; import type { PageData } from "./$types";
import { import {
@ -33,11 +30,12 @@
import { apiFetchClient } from "$lib/api/fetch"; import { apiFetchClient } from "$lib/api/fetch";
import ErrorAlert from "$lib/components/ErrorAlert.svelte"; import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import renderMarkdown from "$lib/api/markdown";
export let data: PageData; export let data: PageData;
let bio: string | null; let bio: string | null;
$: bio = data.bio ? sanitizeHtml(marked.parse(data.bio, { breaks: true })) : null; $: bio = renderMarkdown(data.bio);
let memberPage: number = 0; let memberPage: number = 0;
let memberSlice: PartialMember[] = []; let memberSlice: PartialMember[] = [];

View File

@ -1,7 +1,4 @@
<script lang="ts"> <script lang="ts">
import { marked } from "marked";
import sanitizeHtml from "sanitize-html";
import FieldCard from "$lib/components/FieldCard.svelte"; import FieldCard from "$lib/components/FieldCard.svelte";
import type { PageData } from "./$types"; import type { PageData } from "./$types";
@ -12,11 +9,12 @@
import { memberAvatars, pronounDisplay, WordStatus } from "$lib/api/entities"; import { memberAvatars, pronounDisplay, WordStatus } from "$lib/api/entities";
import { PUBLIC_BASE_URL } from "$env/static/public"; import { PUBLIC_BASE_URL } from "$env/static/public";
import { userStore } from "$lib/store"; import { userStore } from "$lib/store";
import renderMarkdown from "$lib/api/markdown";
export let data: PageData; export let data: PageData;
let bio: string | null; let bio: string | null;
$: bio = data.bio ? sanitizeHtml(marked.parse(data.bio, { breaks: true })) : null; $: bio = renderMarkdown(data.bio);
const favNames = data.names.filter((entry) => entry.status === WordStatus.Favourite); const favNames = data.names.filter((entry) => entry.status === WordStatus.Favourite);
const favPronouns = data.pronouns.filter((entry) => entry.status === WordStatus.Favourite); const favPronouns = data.pronouns.filter((entry) => entry.status === WordStatus.Favourite);

View File

@ -335,8 +335,8 @@
accept="image/png, image/jpeg, image/gif, image/webp" accept="image/png, image/jpeg, image/gif, image/webp"
/> />
<p class="text-muted mt-3"> <p class="text-muted mt-3">
<Icon name="info-circle-fill" /> Only PNG, JPEG, GIF, and WebP can be used as avatars. <Icon name="info-circle-fill" aria-hidden /> Only PNG, JPEG, GIF, and WebP can be used
Avatars cannot be larger than 1 MB, and animated avatars will be made static. as avatars. Avatars cannot be larger than 1 MB, and animated avatars will be made static.
</p> </p>
<a href="" on:click={() => (avatar = "")}>Remove avatar</a> <a href="" on:click={() => (avatar = "")}>Remove avatar</a>
</div> </div>
@ -357,6 +357,15 @@
<FormGroup floating label="Bio ({bio.length}/{MAX_DESCRIPTION_LENGTH})"> <FormGroup floating label="Bio ({bio.length}/{MAX_DESCRIPTION_LENGTH})">
<textarea style="min-height: 100px;" class="form-control" bind:value={bio} /> <textarea style="min-height: 100px;" class="form-control" bind:value={bio} />
</FormGroup> </FormGroup>
<p class="text-muted mt-3">
<Icon name="info-circle-fill" aria-hidden /> Your bio supports limited
<a
class="text-reset"
href="https://commonmark.org/help/"
target="_blank"
rel="noopener noreferrer">Markdown</a
>.
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -282,8 +282,9 @@
accept="image/png, image/jpeg, image/gif, image/webp" accept="image/png, image/jpeg, image/gif, image/webp"
/> />
<p class="text-muted mt-3"> <p class="text-muted mt-3">
<Icon name="info-circle-fill" /> Only PNG, JPEG, GIF, and WebP images can be used as avatars. <Icon name="info-circle-fill" aria-hidden /> Only PNG, JPEG, GIF, and WebP images can be
Avatars cannot be larger than 1 MB, and animated avatars will be made static. used as avatars. Avatars cannot be larger than 1 MB, and animated avatars will be made
static.
</p> </p>
<p> <p>
<a href="" on:click={() => (avatar = "")}>Remove avatar</a> <a href="" on:click={() => (avatar = "")}>Remove avatar</a>
@ -299,6 +300,15 @@
<FormGroup floating label="Bio ({bio.length}/{MAX_DESCRIPTION_LENGTH})"> <FormGroup floating label="Bio ({bio.length}/{MAX_DESCRIPTION_LENGTH})">
<textarea style="min-height: 100px;" class="form-control" bind:value={bio} /> <textarea style="min-height: 100px;" class="form-control" bind:value={bio} />
</FormGroup> </FormGroup>
<p class="text-muted mt-3">
<Icon name="info-circle-fill" aria-hidden /> Your bio supports limited
<a
class="text-reset"
href="https://commonmark.org/help/"
target="_blank"
rel="noopener noreferrer">Markdown</a
>.
</p>
</div> </div>
</div> </div>
</div> </div>