Files
gobsidian/web/templates/edit_text.html
2025-08-25 18:32:31 +01:00

196 lines
7.4 KiB
HTML

{{define "edit_text"}}
{{template "base" .}}
{{end}}
{{define "edit_text_content"}}
<div class="max-w-4xl mx-auto p-6">
<!-- Header -->
<div class="mb-6">
<div class="flex items-center justify-between mb-4">
<h1 class="text-3xl font-bold text-white">Edit: {{.title}}</h1>
<div class="flex items-center space-x-3">
<button id="format-btn" type="button" class="btn-secondary">
<i class="fas fa-wand-magic-sparkles mr-2"></i>Format
</button>
<label class="text-sm text-gray-300 mr-2 inline-flex items-center">
<input id="format-on-save" type="checkbox" class="mr-2">
Format on save
</label>
<button type="submit" form="edit-text-form" class="btn-primary">
<i class="fas fa-save mr-2"></i>Save
</button>
</div>
</div>
{{if .folder_path}}
<p class="text-gray-400">
<i class="fas fa-folder mr-2"></i>
<a href="/folder/{{.folder_path}}" class="text-blue-400 hover:text-blue-300">{{.folder_path}}</a>
</p>
{{end}}
</div>
<!-- Edit Form -->
<form id="edit-text-form" class="space-y-6">
<div class="bg-gray-800 rounded-lg p-6">
<div class="mb-2">
<div class="flex items-center justify-between mb-2">
<label for="content" class="block text-sm font-medium text-gray-300">Content ({{.file_ext}})</label>
<div class="text-xs text-gray-500">Ctrl+S to save</div>
</div>
<textarea id="content" name="content" rows="24" class="editor-textarea">{{.content}}</textarea>
<div id="cm-container" class="mt-3"></div>
</div>
</div>
</form>
</div>
{{end}}
{{define "edit_text_scripts"}}
<!-- CodeMirror -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/yaml/yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script>
<style>
.CodeMirror { height: auto; min-height: 24rem; background-color: #1f2937; color: #d1d5db; border: 1px solid #4b5563; border-radius: 0.5rem; }
.cm-s-default .CodeMirror-gutters { background: #1f2937; border-right: 1px solid #374151; }
.cm-s-default .CodeMirror-linenumber { color: #94a3b8; }
</style>
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<script>
const form = document.getElementById('edit-text-form');
const contentEl = document.getElementById('content');
const formatBtn = document.getElementById('format-btn');
const formatOnSaveEl = document.getElementById('format-on-save');
const fileExt = '{{.file_ext}}'.toLowerCase();
const filePath = '{{.file_path}}';
// Initialize CodeMirror
let cm = null;
function getModeByExt(ext) {
switch (ext) {
case 'json': return { name: 'javascript', json: true };
case 'yaml':
case 'yml': return 'yaml';
case 'xml': return 'xml';
case 'html': return 'htmlmixed';
case 'css': return 'css';
case 'js': return 'javascript';
default: return null;
}
}
function ensureCodeMirror() {
if (cm) return cm;
cm = CodeMirror(document.getElementById('cm-container'), {
value: contentEl.value,
lineNumbers: true,
mode: getModeByExt(fileExt) || undefined,
tabSize: 2,
indentUnit: 2,
theme: 'default',
});
// Keep textarea hidden but in sync
contentEl.style.display = 'none';
cm.on('change', () => { contentEl.value = cm.getValue(); });
return cm;
}
function formatJSON(text) {
try { return JSON.stringify(JSON.parse(text), null, 2); } catch { return text; }
}
function formatYAML(text) {
try { return jsyaml.dump(jsyaml.load(text), { indent: 2, lineWidth: 120 }); } catch { return text; }
}
function formatXMLLike(text) {
try {
// Basic pretty printer for XML/HTML
const PADDING = ' ';
const reg = /(>)(<)(\/*)/g;
let xml = text.replace(reg, '$1\n$2$3');
let pad = 0;
return xml.split('\n').map((line) => {
let indent = 0;
if (line.match(/.+<\/\w[^>]*>$/)) {
indent = 0;
} else if (line.match(/^<\/\w/)) {
if (pad) pad -= 1;
} else if (line.match(/^<\w([^>]*[^\/])?>.*$/)) {
indent = 1;
} else {
indent = 0;
}
const padding = PADDING.repeat(pad);
pad += indent;
return padding + line;
}).join('\n');
} catch {
return text;
}
}
function doFormat() {
const current = cm ? cm.getValue() : contentEl.value;
if (fileExt === 'json') {
const out = formatJSON(current);
if (cm) cm.setValue(out); else contentEl.value = out;
return;
}
if (fileExt === 'yaml' || fileExt === 'yml') {
const out = formatYAML(current);
if (cm) cm.setValue(out); else contentEl.value = out;
return;
}
if (fileExt === 'html' || fileExt === 'xml') {
const out = formatXMLLike(current);
if (cm) cm.setValue(out); else contentEl.value = out;
return;
}
// no-op for other types
}
// Init editor lazily
ensureCodeMirror();
formatBtn?.addEventListener('click', () => {
doFormat();
showNotification('Formatted', 'success');
});
form.addEventListener('submit', function(e) {
e.preventDefault();
if (formatOnSaveEl && formatOnSaveEl.checked) {
doFormat();
}
const formData = new FormData();
formData.append('content', cm ? cm.getValue() : contentEl.value);
fetch('/edit_text/' + filePath, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showNotification('File saved', 'success');
if (data.redirect) {
setTimeout(() => { window.location.href = data.redirect; }, 500);
}
} else {
throw new Error(data.error || 'Save failed');
}
})
.catch(err => showNotification('Error: ' + err.message, 'error'));
});
// Ctrl+S to save
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
form.dispatchEvent(new Event('submit'));
}
});
</script>
{{end}}