Browse Source

Add syntax highlighting for event parsing

master
limina1 7 months ago
parent
commit
c369a4620c
  1. 176
      src/lib/components/ZettelEditor.svelte

176
src/lib/components/ZettelEditor.svelte

@ -2,9 +2,11 @@ @@ -2,9 +2,11 @@
import { Button } from "flowbite-svelte";
import { EyeOutline, QuestionCircleOutline } from "flowbite-svelte-icons";
import { EditorView, basicSetup } from "codemirror";
import { EditorState } from "@codemirror/state";
import { EditorState, StateField, StateEffect } from "@codemirror/state";
import { markdown } from "@codemirror/lang-markdown";
import { oneDark } from "@codemirror/theme-one-dark";
import { ViewPlugin, Decoration, type DecorationSet } from "@codemirror/view";
import { RangeSet } from "@codemirror/state";
import { onMount } from "svelte";
import {
extractSmartMetadata,
@ -58,6 +60,15 @@ import Asciidoctor from "asciidoctor"; @@ -58,6 +60,15 @@ import Asciidoctor from "asciidoctor";
updateEditorContent();
});
// Effect to update syntax highlighting when parsedSections change
$effect(() => {
if (editorView && parsedSections) {
editorView.dispatch({
effects: updateHighlighting.of(parsedSections)
});
}
});
// Effect to create PublicationTree when content changes
// Uses tree processor extension as Michael envisioned:
// "register a tree processor extension in our Asciidoctor instance"
@ -244,19 +255,142 @@ import Asciidoctor from "asciidoctor"; @@ -244,19 +255,142 @@ import Asciidoctor from "asciidoctor";
let editorContainer = $state<HTMLDivElement | null>(null);
let editorView = $state<EditorView | null>(null);
// Create update effect for highlighting
const updateHighlighting = StateEffect.define<any>();
// State field to track header decorations
const headerDecorations = StateField.define<DecorationSet>({
create() {
return Decoration.none;
},
update(decorations, tr) {
// Update decorations when content changes or highlighting is updated
decorations = decorations.map(tr.changes);
for (let effect of tr.effects) {
if (effect.is(updateHighlighting)) {
decorations = createHeaderDecorations(tr.state, effect.value);
}
}
return decorations;
},
provide: (f) => EditorView.decorations.from(f)
});
// Function to create header decorations based on parsed sections
function createHeaderDecorations(state: EditorState, sections: any[]): DecorationSet {
const ranges: Array<{from: number, to: number, decoration: any}> = [];
const doc = state.doc;
const content = doc.toString();
const lines = content.split('\n');
// Create a map of header text to section info for fast lookup
const sectionMap = new Map();
if (sections) {
sections.forEach(section => {
if (section.title) {
sectionMap.set(section.title.toLowerCase().trim(), {
level: section.level,
isEventTitle: true,
eventType: section.eventType,
eventKind: section.eventKind
});
}
});
}
let pos = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const headerMatch = line.match(/^(=+)\s+(.+)$/);
if (headerMatch) {
const level = headerMatch[1].length;
const headerText = headerMatch[2].trim().toLowerCase();
const lineStart = pos;
const lineEnd = pos + line.length;
// Check if this header is an event title
const sectionInfo = sectionMap.get(headerText);
let className: string;
if (sectionInfo && sectionInfo.isEventTitle) {
// Event title - different colors based on event type
if (sectionInfo.eventKind === 30040) {
className = 'cm-header-index-event';
} else if (sectionInfo.eventKind === 30041) {
className = 'cm-header-content-event';
} else {
className = 'cm-header-event-title';
}
} else {
// Regular subheader within content
if (level <= parseLevel) {
className = 'cm-header-potential-event';
} else {
className = 'cm-header-subcontent';
}
}
ranges.push({
from: lineStart,
to: lineEnd,
decoration: Decoration.mark({ class: className })
});
}
pos += line.length + 1; // +1 for newline
}
console.log(`Created ${ranges.length} header decorations`);
return RangeSet.of(ranges.map(r => r.decoration.range(r.from, r.to)));
}
// Initialize CodeMirror editor
function createEditor() {
if (!editorContainer) return;
// Create custom theme with header highlighting based on parse level
// Create custom theme with header highlighting classes
const headerHighlighting = EditorView.theme({
'.cm-asciidoc-header-1': { color: '#6B7280' }, // gray-500
'.cm-asciidoc-header-2-index': { color: '#3B82F6', fontWeight: '600' }, // blue-500 for index
'.cm-asciidoc-header-2-content': { color: '#22C55E', fontWeight: '600' }, // green-500 for content
'.cm-asciidoc-header-3-index': { color: '#3B82F6', fontWeight: '600' },
'.cm-asciidoc-header-3-content': { color: '#22C55E', fontWeight: '600' },
'.cm-asciidoc-header-4': { color: '#6B7280', fontWeight: '600' },
'.cm-asciidoc-header-5': { color: '#6B7280', fontWeight: '600' },
// Event titles (extracted as separate events)
'.cm-header-index-event': {
color: '#3B82F6', // blue-500 for index events (30040)
fontWeight: '700',
fontSize: '1.1em',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderLeft: '3px solid #3B82F6',
paddingLeft: '8px'
},
'.cm-header-content-event': {
color: '#10B981', // emerald-500 for content events (30041)
fontWeight: '700',
fontSize: '1.1em',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderLeft: '3px solid #10B981',
paddingLeft: '8px'
},
'.cm-header-event-title': {
color: '#8B5CF6', // violet-500 for other event types
fontWeight: '700',
fontSize: '1.1em',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
borderLeft: '3px solid #8B5CF6',
paddingLeft: '8px'
},
// Potential events (at parse level but not extracted yet)
'.cm-header-potential-event': {
color: '#F59E0B', // amber-500 for headers at parse level
fontWeight: '600',
textDecoration: 'underline',
textDecorationStyle: 'dotted'
},
// Subcontent headers (below parse level, part of content)
'.cm-header-subcontent': {
color: '#6B7280', // gray-500 for regular subheaders
fontWeight: '500',
fontStyle: 'italic'
}
});
const state = EditorState.create({
@ -264,6 +398,7 @@ import Asciidoctor from "asciidoctor"; @@ -264,6 +398,7 @@ import Asciidoctor from "asciidoctor";
extensions: [
basicSetup,
markdown(), // AsciiDoc is similar to markdown syntax
headerDecorations,
headerHighlighting,
EditorView.updateListener.of((update) => {
if (update.docChanged) {
@ -606,6 +741,29 @@ import Asciidoctor from "asciidoctor"; @@ -606,6 +741,29 @@ import Asciidoctor from "asciidoctor";
</div>
<div class="flex-1 overflow-y-auto p-4 text-sm text-gray-700 dark:text-gray-300 space-y-4">
<!-- Syntax Highlighting Legend -->
<div>
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Header Highlighting</h4>
<div class="space-y-2 text-xs">
<div class="flex items-center space-x-2">
<div class="w-4 h-4 rounded" style="background: linear-gradient(to right, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.1)); border-left: 2px solid #3B82F6;"></div>
<span><strong class="text-blue-600">Blue:</strong> Index Events (30040)</span>
</div>
<div class="flex items-center space-x-2">
<div class="w-4 h-4 rounded" style="background: linear-gradient(to right, rgba(16, 185, 129, 0.2), rgba(16, 185, 129, 0.1)); border-left: 2px solid #10B981;"></div>
<span><strong class="text-green-600">Green:</strong> Content Events (30041)</span>
</div>
<div class="flex items-center space-x-2">
<div class="w-4 h-4 rounded bg-amber-200 dark:bg-amber-800" style="text-decoration: underline;"></div>
<span><strong class="text-amber-600">Amber:</strong> Potential Events (at parse level)</span>
</div>
<div class="flex items-center space-x-2">
<div class="w-4 h-4 rounded bg-gray-200 dark:bg-gray-600" style="font-style: italic;"></div>
<span><strong class="text-gray-600">Gray:</strong> Subheaders (within content)</span>
</div>
</div>
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Publishing Levels</h4>
<ul class="space-y-1 text-xs">

Loading…
Cancel
Save