Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/dashboard/Note.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/home/ExampleNote.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const ExampleNote = () => {
</div>
</div>
<p
className="mt-3 text-sm text-[color:var(--color-neutral-content)] leading-relaxed h-38 overflow-y-auto"
className="mt-3 text-sm text-[color:var(--color-neutral-content)] leading-relaxed min-h-48 max-h-80 overflow-y-auto p-3 rounded-md border border-[color:var(--color-base-300)] bg-[color:var(--color-base-200)]"
contentEditable={isEditing}
suppressContentEditableWarning={true}
>
Expand Down
51 changes: 22 additions & 29 deletions frontend/src/utils/markdownRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
},
Expand All @@ -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',
})
Expand All @@ -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++;
}
Expand Down Expand Up @@ -101,14 +97,14 @@ md.inline.ruler.push('highlight', (state) => {
return true;
});

// renderer rules for strikethrough and highlight
md.renderer.rules.strikethrough_open = () => '<del class="line-through opacity-75">';
md.renderer.rules.strikethrough_close = () => '</del>';
md.renderer.rules.highlight_open = () => '<mark class="bg-yellow-200 px-1 rounded">';
md.renderer.rules.highlight_close = () => '</mark>';

/**
* 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
*/
Expand All @@ -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++;
Expand All @@ -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;
}
});
Expand Down Expand Up @@ -172,7 +166,7 @@ const addTailwindClasses = (html) => {
// Blockquotes
.replace(
/<blockquote>/g,
'<blockquote class="border-l-4 border-primary pl-4 italic my-4 text-base-content/80">'
'<blockquote class="border-l-4 border-primary pl-4 italic my-4 text-base-content/80 bg-base-200/30 rounded">'
)
// Code blocks - handle pre > code blocks first
.replace(
Expand Down Expand Up @@ -234,19 +228,18 @@ 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, {
USE_PROFILES: { html: true },
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;
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"license": "ISC",
"packageManager": "pnpm@10.18.3",
"devDependencies": {
"concurrently": "^9.2.1"
"concurrently": "^9.2.1",
"eslint": "^8.57.1"
}
}