diff --git a/frontend/editor-poc.html b/frontend/editor-poc.html index a8d628a..dc7aca6 100644 --- a/frontend/editor-poc.html +++ b/frontend/editor-poc.html @@ -1703,55 +1703,56 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig var before = ta.value.slice(0, start); var after = ta.value.slice(end); - // Smart insertion: if we matched a prefix, replace it - var lineStart = before.lastIndexOf('\n') + 1; - var lineBefore = before.slice(lineStart); - var ctx = detectContext(ta.value, start); + // Lazy-load detectContext for smart insertion + import('/static/js/autocomplete.js').then(function(m) { + var lineStart = before.lastIndexOf('\n') + 1; + var ctx = m.detectContext(ta.value, start); - if (ctx.type === 'wikilink') { - // Replace [[partial with [[filename]] - var bracketPos = before.lastIndexOf('[['); - if (bracketPos >= 0) { - before = before.slice(0, bracketPos) + '[[' + item.insert; + if (ctx.type === 'wikilink') { + var bracketPos = before.lastIndexOf('[['); + if (bracketPos >= 0) { + before = before.slice(0, bracketPos) + '[[' + item.insert; + ta.value = before + after; + var newPos = before.length; + ta.setSelectionRange(newPos, newPos); + } + } else if (ctx.type === 'frontmatter') { + before = before.slice(0, lineStart) + item.insert; ta.value = before + after; - var newPos = before.length; + ta.setSelectionRange(before.length, before.length); + } else { + ta.value = before + item.insert + after; + var newPos = start + item.insert.length; ta.setSelectionRange(newPos, newPos); } - } else if (ctx.type === 'frontmatter') { - // Replace current line with the field - before = before.slice(0, lineStart) + item.insert; - ta.value = before + after; - ta.setSelectionRange(before.length, before.length); - } else { - // Standard insert at cursor - ta.value = before + item.insert + after; - var newPos = start + item.insert.length; - ta.setSelectionRange(newPos, newPos); - } - hideAutocomplete(); - ta.focus(); - markDirty(); - autoHeight(); - updateLineNumbers(); + hideAutocomplete(); + ta.focus(); + markDirty(); + autoHeight(); + updateLineNumbers(); + }); } function triggerAutocomplete() { - var result = suggest(ta.value, ta.selectionStart); - if (result.async) { - // Wikilink: fetch file list + // Lazy-load the autocomplete module on first use + var loadAndTrigger = function() { import('/static/js/autocomplete.js').then(function(m) { - m.searchWikilinks(result.partial || '', function(items) { - showAutocomplete(items, 'Wikilinks'); - }); + var result = m.suggest(ta.value, ta.selectionStart); + if (result.async) { + m.searchWikilinks(result.partial || '', function(items) { + showAutocomplete(items, 'Wikilinks'); + }); + } else if (result.items.length > 0) { + var ctxLabels = { + mermaid: 'Mermaid', code: 'Code', fence_open: 'Langage', + frontmatter: 'Frontmatter', markdown: 'Markdown' + }; + showAutocomplete(result.items, ctxLabels[result.context] || result.context); + } }); - } else if (result.items.length > 0) { - var ctxLabels = { - mermaid: 'Mermaid', code: 'Code', fence_open: 'Langage', - frontmatter: 'Frontmatter', markdown: 'Markdown' - }; - showAutocomplete(result.items, ctxLabels[result.context] || result.context); - } + }; + loadAndTrigger(); } function getCaretCoordinates(textarea) { @@ -1786,8 +1787,8 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig else if (e.key === 'Escape') { e.preventDefault(); hideAutocomplete(); } return; } - // Alt+\ : force autocomplete - if (e.altKey && !e.ctrlKey && !e.metaKey && e.key === '\\') { + // Alt+\ : force autocomplete (key may be '\' or 'Backslash' depending on browser) + if (e.altKey && !e.ctrlKey && !e.metaKey && (e.key === '\\' || e.key === 'Backslash')) { e.preventDefault(); triggerAutocomplete(); return; @@ -1801,24 +1802,30 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig var before = ta.value.slice(0, ta.selectionStart); var after = ta.value.slice(ta.selectionEnd); var lineStart = before.lastIndexOf('\n') + 1; - var currentLine = before.slice(lineStart); - var indent = (currentLine.match(/^(\s*)/) || [''])[0]; + // Reconstruct FULL current line (before cursor + after cursor until newline) + var afterNewline = after.indexOf('\n'); + var restOfLine = afterNewline >= 0 ? after.slice(0, afterNewline) : after; + var fullLine = before.slice(lineStart) + restOfLine; + var cursorInLine = ta.selectionStart - lineStart; + var indent = (fullLine.match(/^(\s*)/) || [''])[0]; // Ordered list: 1. 2. 3. etc - var olMatch = currentLine.match(/^(\s*)(\d+)\.\s/); + var olMatch = fullLine.match(/^(\s*)(\d+)\.\s/); // Unordered list: - * + - var ulMatch = currentLine.match(/^(\s*)([-*+])\s/); + var ulMatch = fullLine.match(/^(\s*)([-*+])\s/); // Task checkbox: - [ ] or - [x] - var taskMatch = currentLine.match(/^(\s*)([-*+])\s+\[([ xX])\]\s/); + var taskMatch = fullLine.match(/^(\s*)([-*+])\s+\[([ xX])\]\s/); if (taskMatch) { - var afterTaskTrim = currentLine.slice(taskMatch[0].length).trim(); - if (afterTaskTrim.length === 0) { - // Empty checkbox: break list (remove this line) + var textAfterMarker = fullLine.slice(taskMatch[0].length); + // Only continue if cursor is at or after the marker text + if (cursorInLine < taskMatch[0].length) return; // cursor in prefix, let browser handle + if (textAfterMarker.trim().length === 0) { + // Empty checkbox: break list e.preventDefault(); - ta.value = before.slice(0, lineStart) + after; + ta.value = before.slice(0, lineStart) + after.slice(restOfLine.length); ta.setSelectionRange(lineStart, lineStart); } else { - // Continue checkbox (empty, unchecked) + // Continue checkbox e.preventDefault(); ta.value = before + '\n' + indent + taskMatch[2] + ' [ ] '; ta.setSelectionRange(ta.value.length, ta.value.length); @@ -1826,6 +1833,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig markDirty(); autoHeight(); updateLineNumbers(); return; } else if (olMatch) { + if (cursorInLine < olMatch[0].length) return; // Auto-increment ordered list e.preventDefault(); var num = parseInt(olMatch[2]) + 1; @@ -1834,11 +1842,12 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig markDirty(); autoHeight(); updateLineNumbers(); return; } else if (ulMatch) { - var afterTrim = currentLine.slice(ulMatch[0].length).trim(); - if (afterTrim.length === 0) { - // Empty bullet: remove it and break list + if (cursorInLine < ulMatch[0].length) return; + var textAfter = fullLine.slice(ulMatch[0].length); + if (textAfter.trim().length === 0) { + // Empty bullet: break list e.preventDefault(); - ta.value = before.slice(0, lineStart) + '\n' + after.slice(after.indexOf('\n') + 1); + ta.value = before.slice(0, lineStart) + after.slice(restOfLine.length); ta.setSelectionRange(lineStart, lineStart); } else { // Continue bullet