UI-tests und fixes aus tests

This commit is contained in:
th committed 2026-04-09 22:18:45 +02:00
1 parent 73da8fb849
commit 968d980be1
46 files changed
+3853 -61

No files matched your search

+30 -39
View File
@@ -314,7 +314,7 @@ function toggleBoardSplit() {
localStorage.setItem('hyprcrd-board-split', state.boardSplitOpen ? '1' : '0');
boardPreview?.classList.toggle('hidden', !state.boardSplitOpen);
const btn = $('board-split-toggle');
if (btn) btn.classList.toggle('text-t-accent', state.boardSplitOpen);
if (btn) btn.classList.toggle('active', state.boardSplitOpen);
if (!state.boardSplitOpen) {
board.querySelectorAll('.board-card').forEach(el => el.classList.remove('preview-active'));
state.boardPreviewCardId = null;
@@ -332,7 +332,7 @@ $('board-preview-edit')?.addEventListener('click', () => {
if (state.boardSplitOpen) {
boardPreview?.classList.remove('hidden');
const btn = $('board-split-toggle');
if (btn) btn.classList.add('text-t-accent');
if (btn) btn.classList.add('active');
}
$('superview-toggle')?.addEventListener('click', () => {
@@ -340,49 +340,36 @@ $('superview-toggle')?.addEventListener('click', () => {
localStorage.setItem('hyprcrd-superview', state.superView ? '1' : '0');
const btn = $('superview-toggle');
const icon = btn?.querySelector('i');
if (icon) icon.className = `ph ${state.superView ? 'ph-squares-four' : 'ph-list'} text-xs`;
if (btn) btn.classList.toggle('text-t-accent', state.superView);
if (icon) icon.className = `ph ${state.superView ? 'ph-squares-four' : 'ph-list'} text-sm`;
if (btn) btn.classList.toggle('active', state.superView);
renderBoard();
});
if (state.superView) {
const btn = $('superview-toggle');
const icon = btn?.querySelector('i');
if (icon) icon.className = 'ph ph-squares-four text-xs';
if (btn) btn.classList.add('text-t-accent');
if (icon) icon.className = 'ph ph-squares-four text-sm';
if (btn) btn.classList.add('active');
}
$('hide-done')?.addEventListener('click', () => {
state.hideDone = !state.hideDone;
localStorage.setItem('hyprcrd-hide-done', state.hideDone ? '1' : '0');
const btn = $('hide-done');
const icon = btn?.querySelector('i');
if (icon) icon.className = `ph ${state.hideDone ? 'ph-check-square' : 'ph-check-circle'} text-xs`;
if (btn) btn.classList.toggle('text-t-accent', state.hideDone);
renderBoard();
});
if (state.hideDone) {
const btn = $('hide-done');
const icon = btn?.querySelector('i');
if (icon) icon.className = 'ph ph-check-square text-xs';
if (btn) btn.classList.add('text-t-accent');
}
$('hide-termine')?.addEventListener('click', () => {
state.hideTermine = !state.hideTermine;
localStorage.setItem('hyprcrd-hide-termine', state.hideTermine ? '1' : '0');
const btn = $('hide-termine');
const icon = btn?.querySelector('i');
if (icon) icon.className = `ph ${state.hideTermine ? 'ph-calendar-check' : 'ph-calendar-x'} text-xs`;
if (btn) btn.classList.toggle('text-t-accent', state.hideTermine);
renderBoard();
});
// Init toggle state
if (state.hideTermine) {
const btn = $('hide-termine');
const icon = btn?.querySelector('i');
if (icon) icon.className = 'ph ph-calendar-check text-xs';
if (btn) btn.classList.add('text-t-accent');
function initToggle(id, stateKey, lsKey, iconOff, iconOn) {
const btn = $(id);
if (!btn) return;
const apply = () => {
const icon = btn.querySelector('i');
if (icon) icon.className = `ph ${state[stateKey] ? iconOn : iconOff} text-sm`;
btn.classList.toggle('active', state[stateKey]);
};
btn.addEventListener('click', () => {
state[stateKey] = !state[stateKey];
localStorage.setItem(lsKey, state[stateKey] ? '1' : '0');
apply();
renderBoard();
});
apply();
}
initToggle('hide-done', 'hideDone', 'hyprcrd-hide-done', 'ph-check-circle', 'ph-check-square');
initToggle('hide-termine', 'hideTermine', 'hyprcrd-hide-termine', 'ph-calendar-x', 'ph-calendar-check');
initToggle('hide-tagged', 'hideTagged', 'hyprcrd-hide-tagged', 'ph-hash', 'ph-hash-straight');
$('sort-toggle')?.addEventListener('click', () => {
state.sortMode = SORT_MODES[(SORT_MODES.indexOf(state.sortMode) + 1) % SORT_MODES.length];
@@ -485,14 +472,18 @@ export function renderBoard() {
}
}
}
// Hide appointments + birthdays
// Hide appointments + birthdays (tag-based and caldav-synced)
if (state.hideTermine) {
cards = cards.filter(c => { const t = parseTags(c); return !t.includes('termin') && !t.includes('geburtstag'); });
cards = cards.filter(c => { const t = parseTags(c); return !t.includes('termin') && !t.includes('geburtstag') && !c.caldav_uid; });
}
// Hide done
if (state.hideDone) {
cards = cards.filter(c => !parseTags(c).includes('done'));
}
// Hide tagged (cards with any tags)
if (state.hideTagged) {
cards = cards.filter(c => !c.tags);
}
// Tag filter (intersects with bucket filter) — hierarchical: #nase matches #nase/popel
if (state.activeTags.length) {
cards = cards.filter(c => {
+4
View File
@@ -238,6 +238,7 @@ $('cal-sync')?.addEventListener('click', () => triggerSync());
// Auto-sync throttle
let lastAutoSync = 0;
export function autoSync() {
if (window._testRunning) return;
const now = Date.now();
if (now - lastAutoSync < 60000) return;
lastAutoSync = now;
@@ -288,3 +289,6 @@ export function startBackgroundSync() {
backgroundSync();
bgSyncInterval = setInterval(backgroundSync, 45000); // every 45 seconds
}
export function stopBackgroundSync() {
if (bgSyncInterval) { clearInterval(bgSyncInterval); bgSyncInterval = null; }
}
+5 -2
View File
@@ -155,9 +155,12 @@ export function showContextMenu(x, y, card) {
});
tags.classList.add('hidden');
menu.style.left = Math.min(x, innerWidth - 188) + 'px';
menu.style.top = Math.min(y, innerHeight - 300) + 'px';
menu.style.left = '0px';
menu.style.top = '0px';
menu.classList.remove('hidden');
const menuRect = menu.getBoundingClientRect();
menu.style.left = Math.min(x, innerWidth - menuRect.width - 8) + 'px';
menu.style.top = (y + menuRect.height > innerHeight - 8 ? Math.max(8, y - menuRect.height) : y) + 'px';
const toggle = (panel) => { [colors, buckets, members, tags].forEach(p => { if (p !== panel) p.classList.add('hidden'); }); panel.classList.toggle('hidden'); if (panel === tags && !tags.classList.contains('hidden')) setTimeout(() => tagInput.focus(), 0); };
+1 -1
View File
@@ -385,7 +385,7 @@ async function doSave(close, { animate = true } = {}) {
const returnToGallery = state.returnToGalleryAfterEdit;
if (state.editingId) {
const prev = snapshotCard(state.cards.find(c => c.id == state.editingId));
if (prev) pushCardEdit('edit', state.editingId, { title: prev.title, body: prev.body, color: prev.color, bucket_id: prev.bucket_id });
if (prev) pushCardEdit('edit', state.editingId, { title: prev.title, body: prev.body, color: prev.color, bucket_id: prev.bucket_id, due_date: prev.due_date ?? null });
await api(`api/cards/${state.editingId}`, { method: 'PATCH', body: data });
} else {
if (state.activeGroup) {
+8 -4
View File
@@ -99,10 +99,14 @@ export function renderMd(src) {
// Help parser: ensure a list can start after text without requiring manual blank line
.replace(/([^\n])\n(?=(?: {0,3}[-*+]\s| {0,3}\d+\.\s))/g, '$1\n\n')
// Preserve extra blank lines as <br> spacers (3+ newlines = 1+ extra blank lines)
.replace(/\n{3,}/g, m => '\n\n' + '<br>\n'.repeat(m.length - 2))
// Ensure hr rules have one clean blank line before and after, keep extra <br> beyond that
.replace(/<br>\n(---+|___+|\*\*\*+)\n/gm, '\n\n$1\n')
.replace(/(---+|___+|\*\*\*+)\n<br>/gm, '$1\n\n');
// Insert <br> BETWEEN paragraphs but not before block-level elements
.replace(/\n{3,}/g, m => {
const extra = m.length - 2;
return '\n\n' + '<br class="spacer">\n\n'.repeat(extra > 3 ? 3 : extra);
})
// Ensure hr rules have clean blank lines
.replace(/<br class="spacer">\n\n(---+|___+|\*\*\*+)/gm, '\n\n$1')
.replace(/(---+|___+|\*\*\*+)\n\n<br class="spacer">/gm, '$1\n\n');
const linked = processed.replace(/\[\[([^\]]+)\]\]/g, (_, title) => {
const card = state.cards.find(c => c.title.toLowerCase() === title.toLowerCase());
+1
View File
@@ -14,6 +14,7 @@ export const state = {
sortMode: localStorage.getItem('hyprcrd-sort') || 'manual',
hideTermine: localStorage.getItem('hyprcrd-hide-termine') === '1',
hideDone: localStorage.getItem('hyprcrd-hide-done') === '1',
hideTagged: localStorage.getItem('hyprcrd-hide-tagged') === '1',
superView: localStorage.getItem('hyprcrd-superview') === '1',
// Board split preview
+6 -2
View File
@@ -40,10 +40,14 @@ export function pushCardEdit(label, cardId, prevData) {
export function pushCardDelete(label, card) {
pushUndo(label, async () => {
await api('api/cards', { method: 'POST', body: {
const created = await api('api/cards', { method: 'POST', body: {
title: card.title, body: card.body, color: card.color,
bucket_id: card.bucket_id, position: card.position,
bucket_id: card.bucket_id, parent_id: card.parent_id, position: card.position,
}});
// Restore fields that POST doesn't accept
if (created?.id && card.due_date) {
await api(`api/cards/${created.id}`, { method: 'PATCH', body: { due_date: card.due_date } });
}
});
}