hilfe eingebaut

This commit is contained in:
th committed 2026-04-07 23:57:30 +02:00
1 parent ae6c818848
commit 836826dba5
6 files changed
+532 -25

No files matched your search

+214 -3
View File
@@ -139,7 +139,9 @@ export function openModal(card = null) {
selectColor(card ? card.color : '#38bdf8');
state.previewVisible = true;
preview.classList.remove('hidden');
editorDivider?.classList.remove('hidden');
$('toggle-preview').classList.add('text-t-accent');
applyEditorSplit();
updatePreview();
modal.classList.add('open');
if (linesVisible) updateGutter();
@@ -296,6 +298,7 @@ export function closeModal({ animate = true } = {}) {
else clearMorphGhost();
hideLinkAc();
hideTagAc();
hideSnippetAc();
modal.classList.remove('open');
state.editingId = null;
state.returnToGalleryAfterEdit = false;
@@ -388,13 +391,68 @@ $('md-toolbar')?.addEventListener('click', e => {
cmds[btn.dataset.md]?.();
});
const editorDivider = $('editor-divider');
const EDITOR_SPLIT_KEY = 'hyprcrd-editor-split';
let editorSplit = parseFloat(localStorage.getItem(EDITOR_SPLIT_KEY)) || 50;
const editorPane = $('editor-pane');
function applyEditorSplit() {
if (!state.previewVisible) return;
if (editorPane) editorPane.style.flex = `0 0 ${editorSplit}%`;
preview.style.flex = `0 0 ${100 - editorSplit}%`;
}
$('toggle-preview')?.addEventListener('click', () => {
state.previewVisible = !state.previewVisible;
preview.classList.toggle('hidden', !state.previewVisible);
editorDivider?.classList.toggle('hidden', !state.previewVisible);
$('toggle-preview').classList.toggle('text-t-accent', state.previewVisible);
if (state.previewVisible) {
applyEditorSplit();
} else {
if (editorPane) editorPane.style.flex = '';
preview.style.flex = '';
}
updatePreview();
});
if (editorDivider) {
const bar = editorDivider.firstElementChild;
editorDivider.addEventListener('mouseenter', () => { if (bar) bar.style.opacity = '0.5'; });
editorDivider.addEventListener('mouseleave', () => { if (bar && !editorDivider._dragging) bar.style.opacity = '0.12'; });
editorDivider.addEventListener('mousedown', e => {
e.preventDefault();
editorDivider._dragging = true;
if (bar) bar.style.opacity = '0.7';
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
const container = editorDivider.parentElement;
const startX = e.clientX;
const startSplit = editorSplit;
const onMove = ev => {
const rect = container.getBoundingClientRect();
const dx = ev.clientX - startX;
const pct = startSplit + (dx / rect.width) * 100;
editorSplit = Math.max(25, Math.min(75, pct));
applyEditorSplit();
};
const onUp = () => {
editorDivider._dragging = false;
if (bar) bar.style.opacity = '0.12';
document.body.style.cursor = '';
document.body.style.userSelect = '';
localStorage.setItem(EDITOR_SPLIT_KEY, String(editorSplit));
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
}
$('add-btn')?.addEventListener('click', () => openModal());
$('modal-cancel')?.addEventListener('click', closeModal);
$('modal-save')?.addEventListener('click', saveOnly);
@@ -455,7 +513,147 @@ function completeLinkAc(idx) {
hideLinkAc(); updatePreview(); body.focus();
}
body.addEventListener('input', () => {
const SNIPPET_HINTS = [
{ key: 'dat', label: 'date', desc: '2026-04-07' },
{ key: 'time', label: 'time', desc: '22:53' },
{ key: 'now', label: 'date+time', desc: '2026-04-07 22:53' },
{ key: 'week', label: 'calendar week', desc: 'KW15' },
{ key: 'todo', label: 'checkbox', desc: '- [ ]' },
{ key: 'hr', label: 'divider', desc: '---' },
{ key: 'h1', label: 'heading 1', desc: '# ' },
{ key: 'h2', label: 'heading 2', desc: '## ' },
{ key: 'h3', label: 'heading 3', desc: '### ' },
{ key: 'table', label: 'table', desc: '| | |' },
{ key: 'code', label: 'code block', desc: '```' },
];
const snippetAc = $('snippet-ac');
let snippetAcVisible = false, snippetAcItems = [], snippetAcActive = 0;
function getSnippetQuery() {
const before = body.value.substring(0, body.selectionStart);
const m = before.match(/\?([a-z0-9]*)$/);
if (!m) return null;
// Don't show for math expressions (starts with digit)
if (m[1] && /^\d/.test(m[1])) return null;
return { start: before.length - m[0].length, query: m[1] };
}
function showSnippetAc(query) {
const q = query.toLowerCase();
snippetAcItems = SNIPPET_HINTS.filter(s => !q || s.key.includes(q) || s.label.includes(q));
if (!snippetAcItems.length) { hideSnippetAc(); return; }
snippetAcActive = 0;
snippetAc.innerHTML = snippetAcItems.map((s, i) => `
<div class="ac-item ${i === 0 ? 'active' : ''}" data-idx="${i}">
<span class="ac-title font-pixel" style="min-width:40px">?${esc(s.key)}</span>
<span style="font-size:9px;color:var(--t-dim);margin-left:8px">${esc(s.label)}</span>
<span style="font-size:9px;color:var(--t-border);margin-left:auto">${esc(s.desc)}</span>
</div>`).join('');
snippetAc.querySelectorAll('.ac-item').forEach(el => {
el.addEventListener('mousedown', e => { e.preventDefault(); completeSnippetAc(parseInt(el.dataset.idx)); });
});
const rect = body.getBoundingClientRect();
const lines = body.value.substring(0, body.selectionStart).split('\n');
snippetAc.style.top = Math.min(rect.top + (lines.length - 1) * 22 - body.scrollTop + 26, innerHeight - 200) + 'px';
snippetAc.style.left = Math.min(rect.left + 40, innerWidth - 340) + 'px';
snippetAc.classList.remove('hidden');
snippetAcVisible = true;
}
function hideSnippetAc() { snippetAc.classList.add('hidden'); snippetAcVisible = false; }
function completeSnippetAc(idx) {
const item = snippetAcItems[idx];
if (!item) return;
const info = getSnippetQuery();
if (!info) { hideSnippetAc(); return; }
const snippet = getSnippet(item.key);
if (snippet === null) { hideSnippetAc(); return; }
const before = body.value.substring(0, info.start);
const after = body.value.substring(body.selectionStart);
const cursorInside = snippet.includes('\n') ? snippet.indexOf('\n') + 1 : snippet.length;
body.value = before + snippet + after;
body.selectionStart = body.selectionEnd = info.start + cursorInside;
hideSnippetAc(); updatePreview(); body.focus();
}
document.addEventListener('click', e => {
if (snippetAcVisible && !snippetAc.contains(e.target) && e.target !== body) hideSnippetAc();
});
function getSnippet(key) {
const d = new Date();
const pad = n => String(n).padStart(2, '0');
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
// ISO week number
const jan4 = new Date(d.getFullYear(), 0, 4);
const week = Math.ceil(((d - jan4) / 86400000 + jan4.getDay() + 1) / 7);
const snippets = {
dat: date,
time: time,
now: `${date} ${time}`,
week: `KW${week}`,
todo: '- [ ] ',
hr: '---',
h1: '# ',
h2: '## ',
h3: '### ',
table: '| col1 | col2 | col3 |\n| --- | --- | --- |\n| | | |',
code: '```\n\n```',
};
return snippets[key] ?? null;
}
body.addEventListener('input', e => {
// Inline snippets & calc: ?keyword SPACE or ?expr SPACE
if (e.inputType === 'insertText' && e.data === ' ') {
const pos = body.selectionStart;
const before = body.value.substring(0, pos - 1);
// Text snippets: ?dat, ?time, ?now, ?week, ?todo, ?hr, ?h1-3, ?table, ?code
const sm = before.match(/\?([a-z][a-z0-9]*)$/);
if (sm) {
const snippet = getSnippet(sm[1]);
if (snippet !== null) {
const start = pos - 1 - sm[0].length;
const cursorInside = snippet.includes('\n') ? snippet.indexOf('\n') + 1 : snippet.length;
body.value = body.value.substring(0, start) + snippet + body.value.substring(pos);
body.selectionStart = body.selectionEnd = start + cursorInside;
updatePreview();
return;
}
}
// Math calc: ?3+2 → replaces with result; 34+56? → 34+56=90
const cm = before.match(/\?([0-9][0-9+\-*\/().,%^ ]*[0-9)%])$/);
const cm2 = !cm ? before.match(/([0-9][0-9+\-*\/().,%^ ]*[0-9)%])\?$/) : null;
const calcMatch = cm || cm2;
if (calcMatch) {
try {
const expr = calcMatch[1].replace(/%/g, '/100*').replace(/\^/g, '**');
const result = Function('"use strict"; return (' + expr + ')')();
if (typeof result === 'number' && isFinite(result)) {
const rounded = Math.round(result * 1e10) / 1e10;
const start = pos - 1 - calcMatch[0].length;
if (cm) {
// ?expr → result
body.value = body.value.substring(0, start) + rounded + body.value.substring(pos);
body.selectionStart = body.selectionEnd = start + String(rounded).length;
} else {
// expr? → expr=result
const replacement = calcMatch[1] + '=' + rounded;
body.value = body.value.substring(0, start) + replacement + body.value.substring(pos);
body.selectionStart = body.selectionEnd = start + replacement.length;
}
}
} catch {}
}
}
const snippetInfo = getSnippetQuery();
snippetInfo ? showSnippetAc(snippetInfo.query) : hideSnippetAc();
const linkInfo = getLinkQuery();
linkInfo ? showLinkAc(linkInfo.query) : hideLinkAc();
const tagInfo = getTagQuery();
@@ -511,9 +709,11 @@ function hideTagAc() { tagAc.classList.add('hidden'); tagAcVisible = false; }
document.addEventListener('click', e => {
if (tagAcVisible && !tagAc.contains(e.target) && e.target !== body) hideTagAc();
if (snippetAcVisible && !snippetAc.contains(e.target) && e.target !== body) hideSnippetAc();
});
document.addEventListener('keydown', e => {
if (tagAcVisible && e.key === 'Escape') { hideTagAc(); }
if (tagAcVisible && e.key === 'Escape') hideTagAc();
if (snippetAcVisible && e.key === 'Escape') hideSnippetAc();
});
function completeTagAc(idx) {
@@ -596,6 +796,17 @@ body.addEventListener('keydown', e => {
});
return;
}
if (snippetAcVisible) {
if (e.key === 'ArrowDown') { e.preventDefault(); snippetAcActive = Math.min(snippetAcActive + 1, snippetAcItems.length - 1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); snippetAcActive = Math.max(snippetAcActive - 1, 0); }
else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); completeSnippetAc(snippetAcActive); return; }
else if (e.key === 'Escape') { e.preventDefault(); hideSnippetAc(); return; }
snippetAc.querySelectorAll('.ac-item').forEach((el, i) => {
el.classList.toggle('active', i === snippetAcActive);
if (i === snippetAcActive) el.scrollIntoView({ block: 'nearest' });
});
return;
}
// Alt+Arrow: move line
if (e.altKey && !e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
e.preventDefault();
@@ -611,7 +822,7 @@ body.addEventListener('keydown', e => {
if (e.key === 'Tab') { e.preventDefault(); insertMd(' '); }
});
body.addEventListener('blur', () => { setTimeout(hideLinkAc, 150); setTimeout(hideTagAc, 150); });
body.addEventListener('blur', () => { setTimeout(hideLinkAc, 150); setTimeout(hideTagAc, 150); setTimeout(hideSnippetAc, 150); });
// ── File upload (paste + drag-drop) ──