diff --git a/public/index.php b/public/index.php index fa87a79..88ef4ed 100644 --- a/public/index.php +++ b/public/index.php @@ -720,12 +720,6 @@ if (str_starts_with($route, '/api/')) { - - diff --git a/public/js/modules/board.js b/public/js/modules/board.js index f404225..7d89ab5 100644 --- a/public/js/modules/board.js +++ b/public/js/modules/board.js @@ -448,18 +448,26 @@ async function handleCardDrop(e, el, targetId) { return; } - // Compute new order + // Compute new order — must mirror renderBoard's filtering let filtered; if (state.activeGroup) { const parent = state.cards.find(c => c.id == state.activeGroup); filtered = parent ? sortedCards([parent, ...getChildren(parent.id)]) : []; } else { - filtered = sortedCards(state.cards.filter(c => { - if (c.parent_id) return false; - if (state.activeBucket === 'none') return !c.bucket_id; - if (state.activeBucket !== 'all') return c.bucket_id == state.activeBucket; - return true; - })); + const root = state.cards.filter(c => !c.parent_id); + if (state.activeBucket === 'favorites') { + const favs = getFavorites(); + filtered = sortedCards(root.filter(c => favs.includes(String(c.id)))); + } else if (state.activeBucket === 'journal') { + filtered = root.filter(c => /^\d{4}-\d{2}-\d{2}/.test(c.title)); + filtered.sort((a, b) => b.title.localeCompare(a.title)); + } else if (state.activeBucket === 'none') { + filtered = sortedCards(root.filter(c => !c.bucket_id && !/^\d{4}-\d{2}-\d{2}/.test(c.title))); + } else if (state.activeBucket !== 'all' && state.activeBucket !== 'current') { + filtered = sortedCards(root.filter(c => c.bucket_id == state.activeBucket)); + } else { + filtered = sortedCards(root); + } } const ids = filtered.map(c => String(c.id)).filter(i => i !== String(dragId)); ids.splice(wasBefore ? ids.indexOf(String(targetId)) : ids.indexOf(String(targetId)) + 1, 0, String(dragId)); diff --git a/public/js/modules/context-menu.js b/public/js/modules/context-menu.js index 6958fb9..6a8a39f 100644 --- a/public/js/modules/context-menu.js +++ b/public/js/modules/context-menu.js @@ -122,6 +122,25 @@ export function showContextMenu(x, y, card) { renderCtxTags(); tagInput.value = ''; tagInput.addEventListener('input', () => renderCtxTags(tagInput.value)); + tagInput.addEventListener('keydown', async e => { + if (e.key !== 'Enter') return; + e.preventDefault(); + const raw = tagInput.value.replace(/^#/, '').trim().toLowerCase(); + if (!raw) return; + const tag = raw; + if (!cardTags.includes(tag)) { + const newBody = (card.body || '').trimEnd() + ' #' + tag; + card.body = newBody; + cardTags.push(tag); + if (!allTags.includes(tag)) { allTags.push(tag); allTags.sort(); } + await api(`api/cards/${card.id}`, { method: 'PATCH', body: { body: newBody } }); + const c = state.cards.find(x => x.id == card.id); + if (c) { c.body = newBody; c.tags = cardTags.join(','); } + window._renderBoard(); + } + tagInput.value = ''; + renderCtxTags(''); + }); tags.classList.add('hidden'); menu.style.left = Math.min(x, innerWidth - 188) + 'px'; diff --git a/public/js/modules/detail.js b/public/js/modules/detail.js index afe0f54..6a2d789 100644 --- a/public/js/modules/detail.js +++ b/public/js/modules/detail.js @@ -613,20 +613,6 @@ document.addEventListener('keydown', e => { // Zen mode // Tree view toggle // Extract selection to new card -$('detail-extract')?.addEventListener('click', async () => { - const sel = window.getSelection(); - const text = sel?.toString(); - if (!text?.trim()) return; - await window._extractToCard(text); -}); - -$('detail-tree')?.addEventListener('click', () => { - const body = $('detail-body'); - if (!body) return; - body.classList.toggle('tree'); - $('detail-tree')?.classList.toggle('text-t-accent', body.classList.contains('tree')); - $('detail-tree')?.classList.toggle('border-t-accent', body.classList.contains('tree')); -}); $('detail-zen')?.addEventListener('click', () => { detail.classList.toggle('zen'); @@ -644,13 +630,29 @@ const splitSearch = $('split-search'); const splitPicks = $('split-picks'); const splitContent = $('split-content'); +function findTaggedCards(card) { + if (!card) return []; + const myTags = parseTags(card); + if (!myTags.length) return []; + return state.cards + .filter(c => c.id != card.id) + .map(c => { + const ct = parseTags(c); + const shared = myTags.filter(t => ct.some(ct2 => ct2 === t || ct2.startsWith(t + '/') || t.startsWith(ct2 + '/'))); + return shared.length ? { card: c, score: shared.length } : null; + }) + .filter(Boolean) + .sort((a, b) => b.score - a.score) + .slice(0, 12) + .map(s => s.card); +} + function findRelatedCards(card) { if (!card) return []; const id = card.id; const title = (card.title || '').toLowerCase(); const body = (card.body || '').toLowerCase(); - // Extract significant words (3+ chars) from title and body const stopwords = new Set(['the','and','for','that','this','with','from','are','was','were','been','have','has','had','not','but','what','all','can','her','his','how','its','may','new','now','old','see','way','who','did','get','let','say','she','too','use','eine','ein','der','die','das','und','ist','von','mit','den','dem','des','auf','fuer','auch','sich','noch','wie','man','aber','nach','bei','nur','dann','wenn','oder','als','bis','zum','zur','kann','doch','ich','wir','sie','aus','vor']); const words = `${card.title} ${stripMd(card.body)}`.toLowerCase() .split(/[^a-zA-Z0-9äöüß]+/) @@ -664,16 +666,11 @@ function findRelatedCards(card) { const ct = (c.title || '').toLowerCase(); const cb = stripMd(c.body || '').toLowerCase(); - // Backlink: this card references the other if (body.includes(`[[${c.title.toLowerCase()}]]`)) score += 80; - // Backlink: the other references this card if ((c.body || '').toLowerCase().includes(`[[${title}]]`)) score += 80; - // Same bucket if (card.bucket_id && c.bucket_id == card.bucket_id) score += 15; - // Same group if (card.parent_id && c.parent_id == card.parent_id) score += 20; - // Word overlap for (const w of wordSet) { if (ct.includes(w)) score += 6; if (cb.includes(w)) score += 2; @@ -687,19 +684,34 @@ function findRelatedCards(card) { .map(s => s.card); } -function renderSplitPicks(cards, label) { - if (!cards.length) { - splitPicks.innerHTML = `