added theme-aware basic syntax highlight for editor
This commit is contained in:
parent
f930de053e
commit
62625b8897
|
|
@ -61,6 +61,10 @@ function noteApp() {
|
||||||
currentTheme: 'light',
|
currentTheme: 'light',
|
||||||
availableThemes: [],
|
availableThemes: [],
|
||||||
|
|
||||||
|
// Syntax highlighting
|
||||||
|
syntaxHighlightEnabled: false,
|
||||||
|
syntaxHighlightTimeout: null,
|
||||||
|
|
||||||
// Icon rail / panel state
|
// Icon rail / panel state
|
||||||
activePanel: 'files', // 'files', 'search', 'tags', 'settings'
|
activePanel: 'files', // 'files', 'search', 'tags', 'settings'
|
||||||
|
|
||||||
|
|
@ -297,6 +301,7 @@ function noteApp() {
|
||||||
this.loadEditorWidth();
|
this.loadEditorWidth();
|
||||||
this.loadViewMode();
|
this.loadViewMode();
|
||||||
this.loadTagsExpanded();
|
this.loadTagsExpanded();
|
||||||
|
this.loadSyntaxHighlightSetting();
|
||||||
|
|
||||||
// Parse URL and load specific note if provided
|
// Parse URL and load specific note if provided
|
||||||
this.loadNoteFromURL();
|
this.loadNoteFromURL();
|
||||||
|
|
@ -515,6 +520,93 @@ function noteApp() {
|
||||||
await this.applyTheme(themeId);
|
await this.applyTheme(themeId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Syntax highlighting toggle
|
||||||
|
toggleSyntaxHighlight() {
|
||||||
|
this.syntaxHighlightEnabled = !this.syntaxHighlightEnabled;
|
||||||
|
localStorage.setItem('syntaxHighlightEnabled', this.syntaxHighlightEnabled);
|
||||||
|
if (this.syntaxHighlightEnabled) {
|
||||||
|
this.updateSyntaxHighlight();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadSyntaxHighlightSetting() {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('syntaxHighlightEnabled');
|
||||||
|
this.syntaxHighlightEnabled = saved === 'true';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading syntax highlight setting:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update syntax highlight overlay (debounced, called on input)
|
||||||
|
updateSyntaxHighlight() {
|
||||||
|
if (!this.syntaxHighlightEnabled) return;
|
||||||
|
|
||||||
|
clearTimeout(this.syntaxHighlightTimeout);
|
||||||
|
this.syntaxHighlightTimeout = setTimeout(() => {
|
||||||
|
const overlay = document.getElementById('syntax-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.innerHTML = this.highlightMarkdown(this.noteContent);
|
||||||
|
}
|
||||||
|
}, 50); // 50ms debounce
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sync overlay scroll with textarea
|
||||||
|
syncOverlayScroll() {
|
||||||
|
const textarea = document.getElementById('note-editor');
|
||||||
|
const overlay = document.getElementById('syntax-overlay');
|
||||||
|
if (textarea && overlay) {
|
||||||
|
overlay.scrollTop = textarea.scrollTop;
|
||||||
|
overlay.scrollLeft = textarea.scrollLeft;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Highlight markdown syntax
|
||||||
|
highlightMarkdown(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
|
||||||
|
// Escape HTML first
|
||||||
|
let html = this.escapeHtml(text);
|
||||||
|
|
||||||
|
// Frontmatter (must be at start)
|
||||||
|
html = html.replace(/^(---\n[\s\S]*?\n---)/m, '<span class="md-frontmatter">$1</span>');
|
||||||
|
|
||||||
|
// Code blocks (before other patterns to avoid conflicts)
|
||||||
|
html = html.replace(/(```[\s\S]*?```)/g, '<span class="md-codeblock">$1</span>');
|
||||||
|
|
||||||
|
// Headings
|
||||||
|
html = html.replace(/^(#{1,6})\s(.*)$/gm, '<span class="md-heading">$1 $2</span>');
|
||||||
|
|
||||||
|
// Bold (must come before italic)
|
||||||
|
html = html.replace(/\*\*([^*]+)\*\*/g, '<span class="md-bold">**$1**</span>');
|
||||||
|
html = html.replace(/__([^_]+)__/g, '<span class="md-bold">__$1__</span>');
|
||||||
|
|
||||||
|
// Italic
|
||||||
|
html = html.replace(/(?<![*\\])\*([^*\n]+)\*(?!\*)/g, '<span class="md-italic">*$1*</span>');
|
||||||
|
html = html.replace(/(?<![_\\])_([^_\n]+)_(?!_)/g, '<span class="md-italic">_$1_</span>');
|
||||||
|
|
||||||
|
// Inline code
|
||||||
|
html = html.replace(/`([^`\n]+)`/g, '<span class="md-code">`$1`</span>');
|
||||||
|
|
||||||
|
// Wikilinks [[...]]
|
||||||
|
html = html.replace(/\[\[([^\]]+)\]\]/g, '<span class="md-wikilink">[[$1]]</span>');
|
||||||
|
|
||||||
|
// Links [text](url)
|
||||||
|
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<span class="md-link">[$1]</span><span class="md-link-url">($2)</span>');
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
html = html.replace(/^(\s*)([-*+])\s/gm, '$1<span class="md-list">$2</span> ');
|
||||||
|
html = html.replace(/^(\s*)(\d+\.)\s/gm, '$1<span class="md-list">$2</span> ');
|
||||||
|
|
||||||
|
// Blockquotes
|
||||||
|
html = html.replace(/^(>.*)$/gm, '<span class="md-blockquote">$1</span>');
|
||||||
|
|
||||||
|
// Horizontal rules
|
||||||
|
html = html.replace(/^([-*_]{3,})$/gm, '<span class="md-hr">$1</span>');
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
|
||||||
// Apply theme to document
|
// Apply theme to document
|
||||||
async applyTheme(themeId) {
|
async applyTheme(themeId) {
|
||||||
// Load theme CSS from file
|
// Load theme CSS from file
|
||||||
|
|
|
||||||
|
|
@ -438,6 +438,52 @@
|
||||||
color: var(--text-primary) !important;
|
color: var(--text-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Syntax Highlighting Overlay */
|
||||||
|
.editor-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.editor-wrapper .editor-textarea {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.editor-wrapper.syntax-highlight .editor-textarea {
|
||||||
|
color: transparent !important;
|
||||||
|
caret-color: var(--text-primary);
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
.syntax-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
tab-size: 2;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
/* Syntax highlight colors using theme variables */
|
||||||
|
.syntax-overlay .md-heading { color: var(--accent-primary); font-weight: 600; }
|
||||||
|
.syntax-overlay .md-bold { color: var(--text-primary); font-weight: 700; }
|
||||||
|
.syntax-overlay .md-italic { color: var(--text-secondary); font-style: italic; }
|
||||||
|
.syntax-overlay .md-code { color: var(--accent-secondary, var(--accent-primary)); background: var(--bg-tertiary); padding: 1px 3px; border-radius: 3px; }
|
||||||
|
.syntax-overlay .md-link { color: var(--link-color, var(--accent-primary)); }
|
||||||
|
.syntax-overlay .md-link-url { color: var(--text-tertiary); }
|
||||||
|
.syntax-overlay .md-list { color: var(--accent-primary); }
|
||||||
|
.syntax-overlay .md-blockquote { color: var(--text-tertiary); font-style: italic; }
|
||||||
|
.syntax-overlay .md-hr { color: var(--border-primary); }
|
||||||
|
.syntax-overlay .md-wikilink { color: var(--link-internal, var(--accent-primary)); }
|
||||||
|
.syntax-overlay .md-frontmatter { color: var(--text-tertiary); }
|
||||||
|
.syntax-overlay .md-codeblock { color: var(--text-secondary); background: var(--bg-tertiary); }
|
||||||
|
|
||||||
/* Link drop zone cursor */
|
/* Link drop zone cursor */
|
||||||
.editor-textarea.link-drop-target {
|
.editor-textarea.link-drop-target {
|
||||||
cursor: copy !important;
|
cursor: copy !important;
|
||||||
|
|
@ -1381,6 +1427,24 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Syntax Highlighting Toggle -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center justify-between cursor-pointer">
|
||||||
|
<span class="text-xs font-medium" style="color: var(--text-secondary);">Editor Syntax Highlight</span>
|
||||||
|
<div
|
||||||
|
@click="toggleSyntaxHighlight()"
|
||||||
|
class="relative w-10 h-5 rounded-full transition-colors cursor-pointer"
|
||||||
|
:style="syntaxHighlightEnabled ? 'background-color: var(--accent-primary)' : 'background-color: var(--bg-tertiary)'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full transition-transform"
|
||||||
|
:style="'background-color: var(--bg-primary);' + (syntaxHighlightEnabled ? ' transform: translateX(20px);' : '')"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs mt-1" style="color: var(--text-tertiary);">Colorize markdown syntax in editor</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Logout (if auth enabled) - uses authEnabled from main app state -->
|
<!-- Logout (if auth enabled) - uses authEnabled from main app state -->
|
||||||
<div x-show="authEnabled" class="mb-4">
|
<div x-show="authEnabled" class="mb-4">
|
||||||
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);">Account</label>
|
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);">Account</label>
|
||||||
|
|
@ -1806,10 +1870,21 @@
|
||||||
style="min-height: 0;"
|
style="min-height: 0;"
|
||||||
:style="viewMode === 'split' ? `width: ${editorWidth}%;` : 'width: 100%;'"
|
:style="viewMode === 'split' ? `width: ${editorWidth}%;` : 'width: 100%;'"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="editor-wrapper"
|
||||||
|
:class="{'syntax-highlight': syntaxHighlightEnabled}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="syntax-overlay"
|
||||||
|
class="syntax-overlay"
|
||||||
|
x-show="syntaxHighlightEnabled"
|
||||||
|
x-html="syntaxHighlightEnabled ? highlightMarkdown(noteContent) : ''"
|
||||||
|
></div>
|
||||||
<textarea
|
<textarea
|
||||||
id="note-editor"
|
id="note-editor"
|
||||||
x-model="noteContent"
|
x-model="noteContent"
|
||||||
@input="autoSave()"
|
@input="autoSave(); updateSyntaxHighlight()"
|
||||||
|
@scroll="syncOverlayScroll()"
|
||||||
@drop="onEditorDrop($event)"
|
@drop="onEditorDrop($event)"
|
||||||
@dragover.prevent="onEditorDragOver($event)"
|
@dragover.prevent="onEditorDragOver($event)"
|
||||||
@dragenter="onEditorDragEnter($event)"
|
@dragenter="onEditorDragEnter($event)"
|
||||||
|
|
@ -1820,6 +1895,7 @@
|
||||||
:placeholder="draggedItem && dropTarget === 'editor' ? '💡 Drop here to insert at cursor position...' : 'Start writing in markdown...'"
|
:placeholder="draggedItem && dropTarget === 'editor' ? '💡 Drop here to insert at cursor position...' : 'Start writing in markdown...'"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Split view resize handle -->
|
<!-- Split view resize handle -->
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue