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:
parent
9eee694a2c
commit
97c4ad9f6c
37
backend/src/services/nameGenerator.js
Normal file
37
backend/src/services/nameGenerator.js
Normal 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)]
|
||||||
|
}
|
||||||
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
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;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary: #667eea;
|
--primary: #667eea;
|
||||||
--primary-dark: #5568d3;
|
--primary-dark: #5568d3;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user