diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 101c931..5e199cd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "lucide-react": "^0.545.0", "markdown-it": "^14.1.0", "markdown-it-deflist": "^3.0.0", + "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -4233,6 +4234,12 @@ "integrity": "sha512-OxPmQ/keJZwbubjiQWOvKLHwpV2wZ5I3Smc81OjhwbfJsjdRrvD5aLTQxmZzzePeO0kbGzAo3Krk4QLgA8PWLg==", "license": "MIT" }, + "node_modules/markdown-it-link-attributes": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz", + "integrity": "sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==", + "license": "MIT" + }, "node_modules/markdown-it-task-lists": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index d2c96c6..cf76e6a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "lucide-react": "^0.545.0", "markdown-it": "^14.1.0", "markdown-it-deflist": "^3.0.0", + "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", "prop-types": "^15.8.1", "react": "^18.3.1", diff --git a/frontend/src/components/dashboard/Note.jsx b/frontend/src/components/dashboard/Note.jsx index 885973b..3c009b2 100644 --- a/frontend/src/components/dashboard/Note.jsx +++ b/frontend/src/components/dashboard/Note.jsx @@ -188,6 +188,37 @@ const Note = ({ activePage, onContentChange, content = '', onSave }) => { }; const handleKeyDown = (e) => { + // 🔹 Auto-insert bullet or number on new line for lists + if (e.key === 'Enter') { + const textarea = e.target; + const { selectionStart, selectionEnd, value } = textarea; + + // Get current line text before cursor + const beforeCursor = value.substring(0, selectionStart); + const currentLine = beforeCursor.split('\n').pop(); + + // Match bullet (-, *, +) or numbered list (1.) + const match = currentLine.match(/^(\s*[-*+]|\s*\d+\.)\s+/); + + if (match) { + e.preventDefault(); + const bullet = match[0]; + const newValue = + value.substring(0, selectionStart) + `\n${bullet}` + value.substring(selectionEnd); + + setEditorContent(newValue); + addToHistory(newValue); + + // Move cursor to the right position after the bullet + requestAnimationFrame(() => { + const newPos = selectionStart + bullet.length + 1; + textarea.selectionStart = textarea.selectionEnd = newPos; + }); + return; // Stop further key handling + } + } + + // 🔹 Handle keyboard shortcuts (undo, redo, bold, italic, save) if (e.ctrlKey || e.metaKey) { switch (e.key.toLowerCase()) { case 'z': diff --git a/frontend/src/components/home/ExampleNote.jsx b/frontend/src/components/home/ExampleNote.jsx index c5988e8..e139e67 100644 --- a/frontend/src/components/home/ExampleNote.jsx +++ b/frontend/src/components/home/ExampleNote.jsx @@ -60,7 +60,7 @@ const ExampleNote = () => {

diff --git a/frontend/src/utils/markdownRenderer.js b/frontend/src/utils/markdownRenderer.js index 7316f54..b12436a 100644 --- a/frontend/src/utils/markdownRenderer.js +++ b/frontend/src/utils/markdownRenderer.js @@ -21,8 +21,7 @@ const md = new MarkdownIt({ return hljs.highlight(code, { language: lang, ignoreIllegals: true }).value; } return hljs.highlightAuto(code).value; - // eslint-disable-next-line no-unused-vars - } catch (err) { + } catch { return code; } }, @@ -31,7 +30,6 @@ const md = new MarkdownIt({ enabled: true, label: false, labelAfter: false, - // Let the plugin handle its own classes itemClass: 'task-list-item', containerClass: 'contains-task-list', }) @@ -51,24 +49,22 @@ md.inline.ruler.push('strikethrough', (state) => { let pos = start + 2; while (pos < state.posMax) { - if (state.src.charCodeAt(pos) === marker) { - if (state.src.charCodeAt(pos + 1) === marker) { - const token = state.push('strikethrough_open', 'del', 1); - token.markup = '~~'; + if (state.src.charCodeAt(pos) === marker && state.src.charCodeAt(pos + 1) === marker) { + const token = state.push('strikethrough_open', 'del', 1); + token.markup = '~~'; - state.pos = start + 2; - const oldPos = state.pos; - state.pos = pos; + state.pos = start + 2; + const oldPos = state.pos; + state.pos = pos; - const content = state.src.slice(oldPos, pos); - state.push('text', content, 0); + const content = state.src.slice(oldPos, pos); + state.push('text', content, 0); - const closeToken = state.push('strikethrough_close', 'del', -1); - closeToken.markup = '~~'; + const closeToken = state.push('strikethrough_close', 'del', -1); + closeToken.markup = '~~'; - state.pos = pos + 2; - return true; - } + state.pos = pos + 2; + return true; } pos++; } @@ -101,7 +97,6 @@ md.inline.ruler.push('highlight', (state) => { return true; }); -// renderer rules for strikethrough and highlight md.renderer.rules.strikethrough_open = () => ''; md.renderer.rules.strikethrough_close = () => ''; md.renderer.rules.highlight_open = () => ''; @@ -109,6 +104,7 @@ md.renderer.rules.highlight_close = () => ''; /** * Custom rule for inline math using $formula$ + * Uses KaTeX to render inline math expressions * @param {Object} state - markdown-it state object * @returns {boolean} True if formula was rendered */ @@ -119,7 +115,7 @@ md.inline.ruler.push('math_inline', (state) => { let end = start + 1; while (end < state.src.length && state.src[end] !== '$') { if (state.src[end] === '\\' && end + 1 < state.src.length) { - end += 2; // Skip escaped characters + end += 2; continue; } end++; @@ -132,18 +128,16 @@ md.inline.ruler.push('math_inline', (state) => { try { const rendered = katex.renderToString(content, { - // Render the math content using KaTeX displayMode: false, throwOnError: false, }); - const token = state.push('math_inline', 'span', 0); // new token for the rendered math + const token = state.push('math_inline', 'span', 0); token.content = rendered; state.pos = end + 1; return true; - // eslint-disable-next-line no-unused-vars - } catch (err) { + } catch { return false; } }); @@ -172,7 +166,7 @@ const addTailwindClasses = (html) => { // Blockquotes .replace( /

/g, - '
' + '
' ) // Code blocks - handle pre > code blocks first .replace( @@ -234,10 +228,11 @@ const addTailwindClasses = (html) => { export const renderMarkdown = (text) => { if (!text || typeof text !== 'string') return ''; + // Render markdown to raw HTML via markdown-it const html = md.render(text); + // Add Tailwind classes to various HTML tags for consistent styling const styledHtml = addTailwindClasses(html); - // console.log('Styled HTML before sanitization:', styledHtml); // Sanitize the HTML with DOMPurify - allow necessary attributes and tags const sanitizedHtml = DOMPurify.sanitize(styledHtml, { @@ -245,8 +240,6 @@ export const renderMarkdown = (text) => { ADD_ATTR: ['class', 'target', 'disabled', 'checked', 'type'], ADD_TAGS: ['input', 'mark'], // Allow input for checkboxes and mark for highlights }); - - // console.log('Final sanitized HTML:', sanitizedHtml); - - return sanitizedHtml; + const cleanedHtml = sanitizedHtml; + return cleanedHtml; }; diff --git a/package.json b/package.json index 10cd281..b6363ac 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "license": "ISC", "packageManager": "pnpm@10.18.3", "devDependencies": { - "concurrently": "^9.2.1" + "concurrently": "^9.2.1", + "eslint": "^8.57.1" } }