fix(frontend): fix spacing in markdown
vi did:web:vt3e.cat
Sun, 14 Jun 2026 16:31:33 +0100
2 files changed,
56 insertions(+),
9 deletions(-)
M
bun.lock
→
bun.lock
@@ -48,6 +48,8 @@ "pkgs/frontend": {
"name": "@stealth-developers/frontend", "version": "0.0.0", "dependencies": { + "dompurify": "^3.4.10", + "marked": "^18.0.5", "pinia": "^3.0.4", "vue": "^3.5.32", "vue-router": "^5.0.4",@@ -55,6 +57,7 @@ },
"devDependencies": { "@stealth-developers/api": "workspace:*", "@tsconfig/node24": "^24.0.4", + "@types/dompurify": "^3.2.0", "@types/node": "^24.12.2", "@vitejs/plugin-vue": "^6.0.6", "@vue/eslint-config-typescript": "^14.7.0",@@ -543,6 +546,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],@@ -554,6 +559,8 @@
"@types/node": ["@types/node@24.12.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ=="], "@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],@@ -734,6 +741,8 @@
"discord-api-types": ["discord-api-types@0.38.47", "", {}, "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA=="], "discord.js": ["discord.js@14.26.2", "", { "dependencies": { "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.1", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.40", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "6.24.1" } }, "sha512-feShi+gULJ6R2MAA4/KkCFnkJcuVrROJrKk4czplzq8gE1oqhqgOy9K0Scu44B8oGeWKe04egquzf+ia6VtXAw=="], + + "dompurify": ["dompurify@3.4.10", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w=="], "drizzle-kit": ["drizzle-kit@1.0.0-beta.9-e89174b", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Xrw3k8E2CbSZr+kqe3k5W4oxd2fbEyczjKtyGIkAq0x9Wqpa/VtAT6Mkh83sIzqG4OSN7lOoUafsDxSE/AR7RA=="],@@ -978,6 +987,8 @@
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + + "marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="], "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
M
pkgs/frontend/src/components/UserMessage.vue
→
pkgs/frontend/src/components/UserMessage.vue
@@ -25,15 +25,33 @@
const renderMarkdown = (msg: Message): string => { if (!msg.content) return '' + let processedContent = msg.content + const lines = processedContent.split('\n') + let inMultilineQuote = false + const mappedLines = lines.map(line => { + if (/^\s*>>>\s*/.test(line)) { + inMultilineQuote = true + return line.replace(/^\s*>>>\s*/, '> ') + } + if (inMultilineQuote) { + return `> ${line}` + } + return line + }) + processedContent = mappedLines.join('\n') + const regex = /<@!?(\d+)>/g - const processedContent = msg.content.replace(regex, (match, id) => { + processedContent = processedContent.replace(regex, (match, id) => { const user = msg.mentions.find(u => u.id === id) return user ? `<span class="inline-mention">@${user.name}</span>` : match }) - const rawHtml = marked.parse(processedContent, { async: false }) as string + const rawHtml = marked.parse(processedContent, { + async: false, + breaks: true + }) as string return DOMPurify.sanitize(rawHtml, { ALLOWED_TAGS: [@@ -114,6 +132,7 @@
.message-content { display: flex; flex-direction: column; + min-width: 0; .message-header { display: flex;@@ -162,41 +181,58 @@ }
.body { color: hsl(var(--text)); - white-space: pre-wrap; word-break: break-word; - :deep(p) { + &:deep(h1), &:deep(h2), &:deep(h3) { + margin: 0.5rem 0; + font-weight: bold; + } + + &:deep(p) { margin: 0 0 0.5rem 0; &:last-child { margin-bottom: 0; } } - :deep(strong) { + &:deep(strong) { font-weight: bold; } - :deep(em) { + &:deep(em) { font-style: italic; } - :deep(code) { + &:deep(code) { font-family: monospace; background-color: hsla(var(--surface1) / 0.5); padding: 0.1rem 0.3rem; border-radius: 0.25rem; + + max-width: 100%; + width: fit-content; } - :deep(pre) { + &:deep(pre) { background-color: hsla(var(--surface1) / 0.5); padding: 0.75rem; - border-radius: 0.5rem; + border-radius: 1rem; overflow-x: auto; margin: 0.5rem 0; code { padding: 0; background: transparent; + } + } + + &:deep(blockquote) { + padding: 0.25rem 0.5rem; + border-left: 4px solid hsla(var(--surface1) / 1); + color: hsl(var(--subtext1)); + p { + margin: 0; + line-height: 1.75; } }