UI-tests und fixes aus tests
This commit is contained in:
1 parent
73da8fb849
commit
968d980be1
46 files changed
+3853
-61
No files matched your search
+30
-39
@@ -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 => {
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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); };
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user