Jinja2 syntax highlighting + description field + preview toggle
Some checks failed
Validate / Hassfest (push) Has been cancelled
Some checks failed
Validate / Hassfest (push) Has been cancelled
JinjaEditor:
- Custom StreamLanguage parser for Jinja2 syntax highlighting:
{{ variables }} in blue, {% statements %} in purple, {# comments #} in gray
- Replaced HTML mode (didn't understand Jinja2 syntax)
- Proper monospace font (Consolas/Monaco)
TemplateConfig:
- Added `description` field to model + seed defaults with descriptions
- Description shown on template cards instead of raw template text
- Description input in create/edit form
Preview:
- Toggle behavior: clicking Preview again hides the preview
- Per-slot preview uses preview-raw API (renders current editor content)
i18n: added common.description, templateConfig.descriptionPlaceholder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { EditorView, keymap, placeholder as cmPlaceholder } from '@codemirror/view';
|
||||
import { EditorView, placeholder as cmPlaceholder } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { getTheme } from '$lib/theme.svelte';
|
||||
|
||||
@@ -17,9 +17,41 @@
|
||||
let view: EditorView;
|
||||
const theme = getTheme();
|
||||
|
||||
// Simple Jinja2 stream parser for syntax highlighting
|
||||
const jinjaLang = StreamLanguage.define({
|
||||
token(stream) {
|
||||
// Jinja2 comment {# ... #}
|
||||
if (stream.match('{#')) {
|
||||
stream.skipTo('#}') && stream.match('#}');
|
||||
return 'comment';
|
||||
}
|
||||
// Jinja2 expression {{ ... }}
|
||||
if (stream.match('{{')) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match('}}')) return 'variableName';
|
||||
stream.next();
|
||||
}
|
||||
return 'variableName';
|
||||
}
|
||||
// Jinja2 statement {% ... %}
|
||||
if (stream.match('{%')) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match('%}')) return 'keyword';
|
||||
stream.next();
|
||||
}
|
||||
return 'keyword';
|
||||
}
|
||||
// Regular text
|
||||
while (stream.next()) {
|
||||
if (stream.peek() === '{') break;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
const extensions = [
|
||||
html(), // Jinja2 is close enough to HTML template syntax for highlighting
|
||||
jinjaLang,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
onchange(update.state.doc.toString());
|
||||
@@ -27,10 +59,14 @@
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme({
|
||||
'&': { fontSize: '13px', fontFamily: 'monospace' },
|
||||
'&': { fontSize: '13px', fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace" },
|
||||
'.cm-content': { minHeight: `${rows * 1.5}em`, padding: '8px' },
|
||||
'.cm-editor': { borderRadius: '0.375rem', border: '1px solid var(--color-border)' },
|
||||
'.cm-focused': { outline: '2px solid var(--color-primary)', outlineOffset: '0px' },
|
||||
// Jinja2 syntax colors
|
||||
'.ͼc': { color: '#e879f9' }, // keyword ({% %}) - purple
|
||||
'.ͼd': { color: '#38bdf8' }, // variableName ({{ }}) - blue
|
||||
'.ͼ5': { color: '#6b7280' }, // comment ({# #}) - gray
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -50,7 +86,6 @@
|
||||
return () => view.destroy();
|
||||
});
|
||||
|
||||
// Sync external value changes (e.g. when editing different config)
|
||||
$effect(() => {
|
||||
if (view && view.state.doc.toString() !== value) {
|
||||
view.dispatch({
|
||||
|
||||
Reference in New Issue
Block a user