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

+137 -12
View File
@@ -371,6 +371,7 @@ export function closeDetail(options = {}) {
closeImageFullscreen();
detail.classList.remove('open', 'zen');
removeZenHandles();
document.documentElement.classList.remove('detail-zen-open');
document.body.classList.remove('detail-zen-open');
$('split-right')?.classList.add('hidden');
@@ -614,6 +615,84 @@ document.addEventListener('keydown', e => {
// Tree view toggle
// Extract selection to new card
const ZEN_WIDTH_KEY = 'hyprcrd-zen-width';
let zenWidth = parseInt(localStorage.getItem(ZEN_WIDTH_KEY)) || 680;
let zenHandles = [];
function applyZenWidth() {
if (!scroll) return;
if (detail.classList.contains('zen')) {
scroll.style.maxWidth = zenWidth + 'px';
// Position handles at content edges
const margin = Math.max(0, (window.innerWidth - zenWidth) / 2);
zenHandles.forEach(h => {
h.el.style.left = h.side === 'left' ? (margin - 12) + 'px' : '';
h.el.style.right = h.side === 'right' ? (margin - 12) + 'px' : '';
});
} else {
scroll.style.maxWidth = '';
}
}
function createZenHandles() {
removeZenHandles();
['left', 'right'].forEach(side => {
const el = document.createElement('div');
Object.assign(el.style, {
position: 'fixed', top: '0', bottom: '0', width: '24px',
cursor: 'col-resize', zIndex: '200',
});
const bar = document.createElement('div');
Object.assign(bar.style, {
position: 'absolute', top: '50%', transform: 'translateY(-50%)',
width: '4px', height: '48px', borderRadius: '2px',
background: 'var(--t-muted)', opacity: '0.08', transition: 'opacity 0.2s',
});
bar.style[side === 'left' ? 'right' : 'left'] = '8px';
el.appendChild(bar);
el.addEventListener('mouseenter', () => { bar.style.opacity = '0.5'; });
el.addEventListener('mouseleave', () => { if (!el._dragging) bar.style.opacity = '0.08'; });
let startX, startW;
el.addEventListener('mousedown', e => {
e.preventDefault();
startX = e.clientX;
startW = zenWidth;
el._dragging = true;
bar.style.opacity = '0.7';
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
const onMove = ev => {
const dx = ev.clientX - startX;
zenWidth = Math.max(400, Math.min(window.innerWidth - 80, startW + (side === 'left' ? -dx : dx) * 2));
applyZenWidth();
};
const onUp = () => {
el._dragging = false;
bar.style.opacity = '0.08';
document.body.style.cursor = '';
document.body.style.userSelect = '';
localStorage.setItem(ZEN_WIDTH_KEY, String(zenWidth));
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
document.body.appendChild(el);
zenHandles.push({ el, side });
});
applyZenWidth();
}
function removeZenHandles() {
zenHandles.forEach(h => h.el.remove());
zenHandles = [];
}
$('detail-zen')?.addEventListener('click', () => {
detail.classList.toggle('zen');
const isZen = detail.classList.contains('zen');
@@ -621,8 +700,12 @@ $('detail-zen')?.addEventListener('click', () => {
document.body.classList.toggle('detail-zen-open', isZen);
const icon = $('detail-zen')?.querySelector('i');
if (icon) icon.className = isZen ? 'ph ph-corners-in text-sm' : 'ph ph-corners-out text-sm';
if (isZen) createZenHandles();
else { removeZenHandles(); scroll.style.maxWidth = ''; }
});
window.addEventListener('resize', () => { if (detail.classList.contains('zen')) applyZenWidth(); });
// ── Split pane (right) ──
const splitPane = $('split-right');
@@ -719,18 +802,39 @@ function renderSplitGroups(groups) {
function searchAllCards(query) {
const q = query.toLowerCase().trim();
return state.cards
.filter(c => c.id != state.viewingCard?.id)
const parts = q.split(/\s+/).filter(Boolean);
const tagFilters = parts.filter(p => p.startsWith('#') && p.length > 1).map(p => p.substring(1));
const words = parts.filter(p => !p.startsWith('#'));
const textQ = words.join(' ');
let pool = state.cards.filter(c => c.id != state.viewingCard?.id);
if (tagFilters.length) {
pool = pool.filter(c => {
const ct = parseTags(c);
return tagFilters.every(tf => ct.some(t => t === tf || t.startsWith(tf + '/') || t.includes(tf)));
});
}
if (!words.length) return pool.slice(0, 15);
return pool
.map(c => {
const t = (c.title || '').toLowerCase();
const b = stripMd(c.body || '').toLowerCase();
let score = 0;
if (t.includes(q)) score += 50;
if (t.startsWith(q)) score += 20;
if (b.includes(q)) score += 10;
// fuzzy on title
const fm = fuzzyMatch(q, t);
if (fm !== null) score += 5 + fm;
const tags = (c.tags || '').toLowerCase();
let score = tagFilters.length ? 30 : 0;
if (t.includes(textQ)) score += 50;
if (t.startsWith(textQ)) score += 20;
if (b.includes(textQ)) score += 10;
if (tags.includes(textQ)) score += 40;
for (const w of words) {
if (t.includes(w)) score += 15;
if (b.includes(w)) score += 5;
if (tags.includes(w)) score += 20;
}
if (!score) {
const fm = fuzzyMatch(textQ, t);
if (fm !== null) score += 5 + fm;
}
return score > 0 ? { card: c, score } : null;
})
.filter(Boolean)
@@ -740,6 +844,18 @@ function searchAllCards(query) {
}
let splitCardId = null;
let splitLastQuery = '';
function restoreSplitList() {
if (splitLastQuery) {
splitSearch.value = splitLastQuery;
const results = searchAllCards(splitLastQuery);
renderSplitPicks(results, `${results.length} result${results.length !== 1 ? 's' : ''}`);
} else {
openSplitPane();
}
splitContent.innerHTML = '';
}
function loadSplitCard(id) {
const card = state.cards.find(c => c.id == id);
@@ -747,13 +863,13 @@ function loadSplitCard(id) {
splitCardId = id;
splitPicks.innerHTML = `<div class="flex items-center border-b border-t-border">
<button id="split-back" class="flex items-center gap-1.5 flex-1 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors">
<i class="ph ph-arrow-left text-sm"></i><span class="font-pixel text-[10px]">related cards</span>
<i class="ph ph-arrow-left text-sm"></i><span class="font-pixel text-[10px]">${splitLastQuery ? 'results' : 'related cards'}</span>
</button>
<button id="split-promote" class="flex items-center gap-1.5 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors" title="open as main card">
<i class="ph ph-arrow-line-left text-sm"></i><span class="font-pixel text-[10px]">focus</span>
</button>
</div>`;
splitPicks.querySelector('#split-back').addEventListener('click', openSplitPane);
splitPicks.querySelector('#split-back').addEventListener('click', restoreSplitList);
splitPicks.querySelector('#split-promote').addEventListener('click', () => promoteSplitCard(id));
splitSearch.value = '';
splitContent.innerHTML = `
@@ -779,6 +895,7 @@ function openSplitPane() {
if (!splitPane) return;
splitPane.classList.remove('hidden');
splitSearch.value = '';
splitLastQuery = '';
splitContent.innerHTML = '';
const tagged = findTaggedCards(state.viewingCard);
const related = findRelatedCards(state.viewingCard);
@@ -800,9 +917,17 @@ $('split-close')?.addEventListener('click', () => splitPane.classList.add('hidde
splitSearch?.addEventListener('input', () => {
const q = splitSearch.value.trim();
splitLastQuery = q;
splitContent.innerHTML = '';
if (!q) {
const tagged = findTaggedCards(state.viewingCard);
const related = findRelatedCards(state.viewingCard);
renderSplitPicks(related, 'related');
const taggedIds = new Set(tagged.map(c => c.id));
const relatedOnly = related.filter(c => !taggedIds.has(c.id));
renderSplitGroups([
{ label: 'tags', cards: tagged },
{ label: 'related', cards: relatedOnly },
]);
return;
}
const results = searchAllCards(q);