added continuation features
This commit is contained in:
parent
47893a4a30
commit
42ad089b04
|
|
@ -32,6 +32,7 @@
|
|||
- **Visual tree view** - Expandable/collapsible navigation
|
||||
- **Hide system folders** - Toggle to hide `_attachments`, `_templates` and other underscore-prefixed folders from sidebar
|
||||
- **Tab inserts tab** - Toggle to make Tab key insert a tab character in the editor instead of changing focus
|
||||
- **List & quote continuation (Enter)** - At the end of a line, Enter continues blockquotes (`>`), bullets (`-` / `*` / `+`), and task lists (`- [ ]` / `- [x]`); pressing Enter again on an empty continued line exits the list or quote. Disabled inside fenced code blocks and when using Shift+Enter (plain newline)
|
||||
|
||||
### Export & Print
|
||||
- **HTML Export** - Download notes as standalone HTML files with all styling, images, diagrams, and math embedded
|
||||
|
|
|
|||
|
|
@ -857,6 +857,31 @@ function noteApp() {
|
|||
this.autoSave();
|
||||
},
|
||||
|
||||
/**
|
||||
* Enter: continue blockquote / bullet / task list; second Enter on empty item exits (see editor-markdown-continue.js).
|
||||
*/
|
||||
handleEditorEnterKey(event) {
|
||||
if (typeof EditorMarkdownContinue === 'undefined') return;
|
||||
const textarea = event.target;
|
||||
if (!textarea || textarea.id !== 'note-editor') return;
|
||||
|
||||
const result = EditorMarkdownContinue.tryEnter(
|
||||
this.noteContent,
|
||||
textarea.selectionStart,
|
||||
textarea.selectionEnd,
|
||||
event
|
||||
);
|
||||
if (!result.handled) return;
|
||||
|
||||
event.preventDefault();
|
||||
this.noteContent = result.text;
|
||||
this.$nextTick(() => {
|
||||
textarea.selectionStart = textarea.selectionEnd = result.cursor;
|
||||
});
|
||||
this.autoSave();
|
||||
this.updateSyntaxHighlight();
|
||||
},
|
||||
|
||||
// Sort mode configuration
|
||||
sortModes: ['a-z', 'z-a', 'newest', 'oldest', 'largest', 'smallest'],
|
||||
sortModeIcons: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Markdown editor helpers: Enter continues blockquotes, bullets, and task lists.
|
||||
* Plain functions only; no DOM. Used by app.js handleEditorEnterKey.
|
||||
*/
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* True if cursor position lies inside a ``` fenced code block (toggle per line).
|
||||
*/
|
||||
function cursorInMarkdownCodeFence(text, pos) {
|
||||
const before = text.slice(0, pos);
|
||||
let lineStart = 0;
|
||||
let inFence = false;
|
||||
for (;;) {
|
||||
const nl = before.indexOf('\n', lineStart);
|
||||
const end = nl === -1 ? before.length : nl;
|
||||
const line = before.slice(lineStart, end);
|
||||
const trimmed = line.trimStart();
|
||||
if (trimmed.startsWith('```')) {
|
||||
inFence = !inFence;
|
||||
}
|
||||
if (nl === -1) break;
|
||||
lineStart = nl + 1;
|
||||
}
|
||||
return inFence;
|
||||
}
|
||||
|
||||
function getLineBounds(text, pos) {
|
||||
const lineStart = text.lastIndexOf('\n', Math.max(0, pos - 1)) + 1;
|
||||
let lineEnd = text.indexOf('\n', pos);
|
||||
if (lineEnd === -1) lineEnd = text.length;
|
||||
return {
|
||||
lineStart,
|
||||
lineEnd,
|
||||
line: text.slice(lineStart, lineEnd),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{ continuePrefix: string, isEmpty: boolean } | null}
|
||||
*/
|
||||
function parseContinuationContext(line) {
|
||||
const quoteMatch = line.match(/^(\s*)((?:>\s*)+)/);
|
||||
let quoteFull = '';
|
||||
let rest = line;
|
||||
if (quoteMatch) {
|
||||
quoteFull = quoteMatch[1] + quoteMatch[2];
|
||||
rest = line.slice(quoteMatch[0].length);
|
||||
}
|
||||
|
||||
const taskMatch = rest.match(/^(\s*)([-*+])\s+\[([xX ])\]\s*(.*)$/);
|
||||
if (taskMatch) {
|
||||
const inner = taskMatch[1];
|
||||
const ch = taskMatch[2];
|
||||
const mark = taskMatch[3];
|
||||
const cont = taskMatch[4];
|
||||
const marker = ch + ' [' + mark + '] ';
|
||||
const isEmpty = cont.trim() === '';
|
||||
return { continuePrefix: quoteFull + inner + marker, isEmpty };
|
||||
}
|
||||
|
||||
const bulletMatch = rest.match(/^(\s*)([-*+])\s+(.*)$/);
|
||||
if (bulletMatch) {
|
||||
const inner = bulletMatch[1];
|
||||
const ch = bulletMatch[2];
|
||||
const cont = bulletMatch[3];
|
||||
const marker = ch + ' ';
|
||||
const isEmpty = cont.trim() === '';
|
||||
return { continuePrefix: quoteFull + inner + marker, isEmpty };
|
||||
}
|
||||
|
||||
if (quoteFull) {
|
||||
const isEmpty = rest.trim() === '';
|
||||
return { continuePrefix: quoteFull, isEmpty };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {number} selStart
|
||||
* @param {number} selEnd
|
||||
* @param {KeyboardEvent} event
|
||||
* @returns {{ handled: false } | { handled: true, text: string, cursor: number }}
|
||||
*/
|
||||
function tryEnter(text, selStart, selEnd, event) {
|
||||
if (event.key !== 'Enter') return { handled: false };
|
||||
if (event.defaultPrevented) return { handled: false };
|
||||
if (event.isComposing) return { handled: false };
|
||||
if (event.shiftKey) return { handled: false };
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) return { handled: false };
|
||||
if (selStart !== selEnd) return { handled: false };
|
||||
if (cursorInMarkdownCodeFence(text, selStart)) return { handled: false };
|
||||
|
||||
const { lineStart, lineEnd, line } = getLineBounds(text, selStart);
|
||||
if (selStart !== lineEnd) return { handled: false };
|
||||
|
||||
const lineForParse = line.replace(/\r/g, '');
|
||||
const ctx = parseContinuationContext(lineForParse);
|
||||
if (!ctx) return { handled: false };
|
||||
|
||||
if (ctx.isEmpty) {
|
||||
let delEnd = lineEnd;
|
||||
if (delEnd < text.length && text.charAt(delEnd) === '\n') {
|
||||
delEnd += 1;
|
||||
}
|
||||
const newText = text.slice(0, lineStart) + text.slice(delEnd);
|
||||
return { handled: true, text: newText, cursor: lineStart };
|
||||
}
|
||||
|
||||
const insert = '\n' + ctx.continuePrefix;
|
||||
const newText = text.slice(0, lineEnd) + insert + text.slice(lineEnd);
|
||||
return { handled: true, text: newText, cursor: lineEnd + insert.length };
|
||||
}
|
||||
|
||||
global.EditorMarkdownContinue = {
|
||||
tryEnter,
|
||||
parseContinuationContext,
|
||||
getLineBounds,
|
||||
cursorInMarkdownCodeFence,
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
|
|
@ -2682,6 +2682,7 @@
|
|||
x-model="noteContent"
|
||||
@input="autoSave(); updateSyntaxHighlight()"
|
||||
@scroll="syncOverlayScroll()"
|
||||
@keydown.enter="handleEditorEnterKey($event)"
|
||||
@keydown.tab="handleTabKey($event)"
|
||||
@drop="onEditorDrop($event)"
|
||||
@dragover.prevent="onEditorDragOver($event)"
|
||||
|
|
@ -3433,6 +3434,7 @@
|
|||
</button>
|
||||
</nav>
|
||||
|
||||
<script src="/static/editor-markdown-continue.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
|
||||
<!-- PWA Service Worker Registration -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue