Add marked syntax highlighting and random name generator

- Install marked and highlight.js for better markdown rendering
- Create nameGenerator service with 50 unique names
- Update DocumentViewer to use marked instead of regex parsing
- Add syntax highlighting for code blocks
- Improve styling for code with proper colors
This commit is contained in:
Augustin ROUX 2025-10-18 22:57:59 +02:00
parent 9eee694a2c
commit 97c4ad9f6c
5 changed files with 126 additions and 48 deletions

View File

@ -0,0 +1,37 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const namesPath = path.join(__dirname, '../../data/names.txt')
let allNames = []
function loadNames() {
try {
const content = fs.readFileSync(namesPath, 'utf-8')
allNames = content
.split('\n')
.map(name => name.trim())
.filter(name => name.length > 0)
} catch (error) {
console.error('Error loading names:', error)
allNames = ['Alex', 'Jordan', 'Casey', 'Morgan', 'Riley'] // Fallback
}
}
// Load names on module import
loadNames()
export function getRandomNames(count) {
if (count > allNames.length) {
throw new Error(`Cannot get ${count} unique names. Only ${allNames.length} available.`)
}
const shuffled = [...allNames].sort(() => Math.random() - 0.5)
return shuffled.slice(0, count)
}
export function getRandomName() {
return allNames[Math.floor(Math.random() * allNames.length)]
}

View File

@ -9,6 +9,8 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@vueuse/core": "^13.9.0", "@vueuse/core": "^13.9.0",
"highlight.js": "^11.11.1",
"marked": "^16.4.1",
"mermaid": "^11.12.0", "mermaid": "^11.12.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"radix-vue": "^1.9.17", "radix-vue": "^1.9.17",
@ -2284,6 +2286,15 @@
"integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/hookable": { "node_modules/hookable": {
"version": "5.5.3", "version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",

View File

@ -10,6 +10,8 @@
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^13.9.0", "@vueuse/core": "^13.9.0",
"highlight.js": "^11.11.1",
"marked": "^16.4.1",
"mermaid": "^11.12.0", "mermaid": "^11.12.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"radix-vue": "^1.9.17", "radix-vue": "^1.9.17",

View File

@ -1,5 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { marked } from 'marked'
import hljs from 'highlight.js'
const props = defineProps({ const props = defineProps({
document: { document: {
@ -13,60 +15,48 @@ const props = defineProps({
} }
}) })
// Configure marked with syntax highlighting
marked.setOptions({
breaks: true,
gfm: true
})
const renderer = new marked.Renderer()
// Override code block rendering to add syntax highlighting
renderer.code = ({ text, lang }) => {
const language = lang || 'plain'
let highlighted = text
if (hljs.getLanguage(language)) {
highlighted = hljs.highlight(text, { language }).value
} else {
highlighted = hljs.highlight(text, { language: 'plain' }).value
}
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
}
marked.setOptions({ renderer })
const renderedContent = computed(() => { const renderedContent = computed(() => {
if (!props.document) { if (!props.document) {
return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>' return '<p class="empty-state">No document content yet. Start the session to begin collaboration.</p>'
} }
if (props.format === 'md') { if (props.format === 'md') {
return simpleMarkdownToHtml(props.document) return marked(props.document)
} else { } else {
// For txt format, just escape and wrap in pre // For txt format, wrap in pre
return `<pre>${escapeHtml(props.document)}</pre>` const escaped = props.document
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
return `<pre><code>${escaped}</code></pre>`
} }
}) })
function escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
function simpleMarkdownToHtml(md) {
let html = escapeHtml(md)
// Headers
html = html.replace(/^### (.*?)$/gm, '<h3>$1</h3>')
html = html.replace(/^## (.*?)$/gm, '<h2>$1</h2>')
html = html.replace(/^# (.*?)$/gm, '<h1>$1</h1>')
// Bold
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
html = html.replace(/__(.*?)__/g, '<strong>$1</strong>')
// Italic
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>')
html = html.replace(/_(.*?)_/g, '<em>$1</em>')
// Code blocks
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
// Inline code
html = html.replace(/`(.*?)`/g, '<code>$1</code>')
// Links
html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
// Line breaks
html = html.replace(/\n/g, '<br>')
// Lists
html = html.replace(/^\s*[-*] (.*?)$/gm, '<li>$1</li>')
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
html = html.replace(/<\/li>\n<li>/g, '</li><li>')
return html
}
</script> </script>
<template> <template>
@ -78,6 +68,8 @@ function simpleMarkdownToHtml(md) {
</template> </template>
<style scoped> <style scoped>
:import 'highlight.js/styles/atom-one-dark.css';
.document-viewer { .document-viewer {
width: 100%; width: 100%;
} }
@ -96,6 +88,7 @@ function simpleMarkdownToHtml(md) {
line-height: 1.8; line-height: 1.8;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
overflow-x: auto; overflow-x: auto;
font-size: 1rem;
} }
.document-content :deep(h1), .document-content :deep(h1),
@ -126,6 +119,11 @@ function simpleMarkdownToHtml(md) {
color: rgba(255, 255, 255, 0.95); color: rgba(255, 255, 255, 0.95);
} }
.document-content :deep(h4) {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.9);
}
.document-content :deep(p) { .document-content :deep(p) {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -140,7 +138,7 @@ function simpleMarkdownToHtml(md) {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.document-content :deep(code) { .document-content :deep(code:not(.hljs)) {
background: rgba(102, 126, 234, 0.15); background: rgba(102, 126, 234, 0.15);
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
border-radius: 3px; border-radius: 3px;
@ -150,7 +148,7 @@ function simpleMarkdownToHtml(md) {
} }
.document-content :deep(pre) { .document-content :deep(pre) {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(102, 126, 234, 0.3); border: 1px solid rgba(102, 126, 234, 0.3);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
padding: 1rem; padding: 1rem;
@ -159,10 +157,38 @@ function simpleMarkdownToHtml(md) {
margin: 1rem 0; margin: 1rem 0;
font-family: 'Monaco', 'Courier New', monospace; font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.4; line-height: 1.5;
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
} }
.document-content :deep(.hljs) {
background: transparent !important;
color: rgba(255, 255, 255, 0.9) !important;
padding: 0 !important;
}
.document-content :deep(.hljs-string) {
color: #98c379 !important;
}
.document-content :deep(.hljs-number) {
color: #d19a66 !important;
}
.document-content :deep(.hljs-attr) {
color: #61afef !important;
}
.document-content :deep(.hljs-literal),
.document-content :deep(.hljs-type) {
color: #56b6c2 !important;
}
.document-content :deep(.hljs-built_in),
.document-content :deep(.hljs-builtin-name) {
color: #c678dd !important;
}
.document-content :deep(blockquote) { .document-content :deep(blockquote) {
border-left: 4px solid rgba(102, 126, 234, 0.6); border-left: 4px solid rgba(102, 126, 234, 0.6);
padding-left: 1rem; padding-left: 1rem;

View File

@ -1,3 +1,5 @@
@import 'highlight.js/styles/atom-one-dark.css';
:root { :root {
--primary: #667eea; --primary: #667eea;
--primary-dark: #5568d3; --primary-dark: #5568d3;