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" } }