const TPL = `<div style="padding: 10px; border-top: 1px solid var(--main-border-color); contain: none;"> <strong>Todo Highlighting Applied</strong> </div>`; class MoodlogHighlighterWidget extends api.NoteContextAwareWidget { static get parentWidget() { //console.log("MDLG: Getting parent widget: center-pane"); return 'center-pane'; } get position() { //console.log("MDLG: Setting position: 100"); return 100; } isEnabled() { const enabled = super.isEnabled() && this.note.type === 'text' && this.note.hasLabel('todotxt'); //console.log(`MDLG: isEnabled: ${enabled}`); return enabled; } doRender() { //console.log("MDLG: Rendering widget..."); this.$widget = $(TPL); //console.log("MDLG: Widget rendered:", this.$widget); return this.$widget; } async refreshWithNote(note) { //console.log("MDLG: Refreshing with note:", note); const { content } = await note.getNoteComplement(); //console.log("MDLG: Note content fetched:", content); // Check if content is fetched properly if (!content) { //console.log("MDLG: No content fetched. Exiting refresh."); return; } const highlightedContent = this.applyHighlighting(content); //console.log("MDLG: Highlighted content:", highlightedContent); if (highlightedContent !== content) { //console.log("MDLG: Updating note content with highlighted version"); // Use the setNoteContent function to update the note content try { await setNoteContent(note.noteId, highlightedContent); //console.log("MDLG: Content updated with highlighted version"); } catch (error) { //console.error("MDLG: Error updating content:", error); } } else { //console.log("MDLG: No changes to content, skipping update."); } } applyHighlighting(content) { // Replace <br> with \n, then remove all other HTML tags const plainTextContent = content.replace(/<br\s*\/?\>/gi, '\n').replace(/<[^>]*>/g, ' '); // Regular expressions for Todo.txt syntax const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g; const completedTaskPattern = /^\s*x\s+.*/g; const priorityPattern = /^(\s*\([A-E]\))\s+/; const projectPattern = /(?:\s|^)(\+\S+)/g; const contextPattern = /(?:\s|^)(@\S+)/g; const waitPattern = /\bWAIT\b/gi; const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g; function escapeHtml(str) { return str.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char]); } let highlightedLines = plainTextContent.split("\n").map(line => { // If line starts with 'x', make the whole line grey if (/^\s*x\s+/.test(line)) { return `<span style="color: grey;">${line}</span>`; } // Highlight priority with different colours based on level line = line.replace(priorityPattern, (match, priority) => { const level = priority.match(/[A-E]/)?.[0]; const colors = { 'A': 'rgb(255, 0, 0)', // Red 'B': 'rgb(255, 102, 0)', // Orange 'C': 'rgb(255, 204, 0)', // Yellow 'D': 'rgb(0, 153, 255)', // Blue 'E': 'rgb(102, 204, 102)' // Green }; const color = colors[level] || 'black'; return `<span style="color: ${color}; font-weight: bold;">${escapeHtml(priority)}</span> `; }); // Highlight date line = line.replace(datePattern, (match) => { return `<span style="color: rgb(188, 55, 237);">${escapeHtml(match)}</span>`; }); // Highlight project tags line = line.replace(projectPattern, (_, tag) => `<span style="color: rgb(224, 218, 36);"> ${escapeHtml(tag)}</span>`); // Highlight context tags line = line.replace(contextPattern, (_, tag) => `<span style="color: rgb(255, 165, 10);"> ${escapeHtml(tag)}</span>`); // Highlight WAIT command line = line.replace(waitPattern, `<span style="color: rgb(255, 69, 0);">WAIT</span>`); // Highlight custom attributes line = line.replace(customAttributePattern, `<span style="color: rgb(100, 149, 237);">$1</span>`); // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @ line = line.replace(/^\s+/, ''); return line.trim(); }); return highlightedLines.join("<br>"); } async entitiesReloadedEvent({ loadResults }) { //console.log("MDLG: Entities reloaded event triggered"); if (loadResults.isNoteContentReloaded(this.noteId)) { //console.log("MDLG: Note content reloaded, refreshing widget..."); //this.refresh(); } else { //console.log("MDLG: No content reload detected."); } } } console.log("TodoHighlight: Starting todotxt Highlighter Widget"); module.exports = MoodlogHighlighterWidget; function setNoteContent(noteId, content) { /*const noteElement = document.querySelector(`note-detail-readonly-text-content`); if (noteElement) { noteElement.innerHTML = content; // Update the displayed content console.log(`MDLG: Updated note ${noteId} in the DOM.`); } else { console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`); }*/ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]); }