detail: optional bucket-switch on open from search / link
New toggle under Settings → Behavior → Interactions: "Switch to card's bucket on open (search / link)". When enabled, openDetail calls activateBucket(card.bucket_id) so the board and bucket bar update to show the card's context. Skipped when already on 'all' or the correct bucket. Default off. Uses activateBucket() (from buckets.js) instead of raw state assignment so the bucket-bar highlight tracks the switch. Tests: two cases — setting on switches activeBucket + verifies the bucket-tab.active in the bar matches; setting off leaves the bucket unchanged. Both create/delete probe cards in try/finally and restore the setting + activeBucket in afterEach. Bumps to 1.9.27. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6fa42eb267
commit
c934673c4a
6 files changed
+96
-3
No files matched your search
@@ -1926,6 +1926,7 @@ if (str_starts_with($route, '/api/')) {
|
||||
</div>
|
||||
<div class="toggle-row" data-toggle="setting-confirm-delete"><span class="toggle-label">Confirm before delete</span><div class="toggle-switch" id="setting-confirm-delete"></div></div>
|
||||
<div class="toggle-row" data-toggle="setting-auto-save"><span class="toggle-label">Auto-save on close (editor)</span><div class="toggle-switch" id="setting-auto-save"></div></div>
|
||||
<div class="toggle-row" data-toggle="setting-open-switches-bucket"><span class="toggle-label">Switch to card's bucket on open (search / link)</span><div class="toggle-switch" id="setting-open-switches-bucket"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { state, parseTags, tagChipHtml, getChildren, getGroupCards, getRecent, m
|
||||
import { renderMd, stripMd, firstImage, addCodeCopyButtons, formatTodoItems } from './markdown.js';
|
||||
import { pushCardDelete, pushCardEdit, snapshotCard } from './undo.js';
|
||||
import { showDialog } from './dialog.js';
|
||||
import { activateBucket } from './buckets.js';
|
||||
|
||||
const detail = $('detail');
|
||||
const scroll = $('detail-scroll');
|
||||
@@ -57,6 +58,18 @@ export function openDetail(id, options = {}) {
|
||||
if (!card) return;
|
||||
markRecent(id);
|
||||
|
||||
// Optional: switch the board to the card's bucket so the user
|
||||
// sees context. Only when opened via search, wikilink, or
|
||||
// programmatic call — not when the user clicked a board card
|
||||
// (they're already in the right bucket). Gated by a setting
|
||||
// under Behavior → Interactions.
|
||||
if (card.bucket_id
|
||||
&& localStorage.getItem('hyprcrd-open-switches-bucket') === '1'
|
||||
&& state.activeBucket !== 'all'
|
||||
&& String(state.activeBucket) !== String(card.bucket_id)) {
|
||||
activateBucket(card.bucket_id);
|
||||
}
|
||||
|
||||
const el = $('board')?.querySelector(`[data-id="${id}"]`);
|
||||
if (el) el.style.borderBottom = `3px solid ${card.color}`;
|
||||
|
||||
|
||||
@@ -450,6 +450,7 @@ function initBehaviorSettings() {
|
||||
'setting-show-done-badge': { key: 'hyprcrd-show-done-badge', def: '1' },
|
||||
'setting-confirm-delete': { key: 'hyprcrd-confirm-delete', def: '1' },
|
||||
'setting-auto-save': { key: 'hyprcrd-auto-save', def: '0' },
|
||||
'setting-open-switches-bucket': { key: 'hyprcrd-open-switches-bucket', def: '0' },
|
||||
};
|
||||
for (const [id, cfg] of Object.entries(selects)) {
|
||||
const el = $(id);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll } from './runner.js?v=4';
|
||||
import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=4';
|
||||
|
||||
describe('Detail View', () => {
|
||||
afterEach(() => closeAll());
|
||||
@@ -71,3 +71,81 @@ describe('Detail View', () => {
|
||||
expect($('#detail')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Detail: open-switches-bucket', () => {
|
||||
const SETTING_KEY = 'hyprcrd-open-switches-bucket';
|
||||
const BUCKET_KEY = 'hyprcrd-active-bucket';
|
||||
|
||||
afterEach(async () => {
|
||||
await closeAll();
|
||||
// Restore runner baseline.
|
||||
window.state.activeBucket = 'all';
|
||||
localStorage.setItem(BUCKET_KEY, 'all');
|
||||
});
|
||||
|
||||
it('switches to the card bucket when the setting is on', async () => {
|
||||
const buckets = await fetch('api/buckets').then(r => r.json());
|
||||
if (!Array.isArray(buckets) || !buckets.length) return;
|
||||
const b = buckets[0];
|
||||
const token = csrf();
|
||||
const h = { 'Content-Type': 'application/json', 'X-CSRF-Token': token };
|
||||
let card;
|
||||
const prev = localStorage.getItem(SETTING_KEY);
|
||||
try {
|
||||
card = await fetch('api/cards', { method: 'POST', headers: h,
|
||||
body: JSON.stringify({ title: '__test_switch_bucket__', body: '', bucket_id: b.id })
|
||||
}).then(r => r.json());
|
||||
await window._reloadAll();
|
||||
await sleep(100);
|
||||
|
||||
// Start on a DIFFERENT bucket so the switch is visible.
|
||||
const other = buckets.length > 1 ? buckets[1] : buckets[0];
|
||||
window.state.activeBucket = String(other.id);
|
||||
|
||||
// Enable the setting.
|
||||
localStorage.setItem(SETTING_KEY, '1');
|
||||
|
||||
window._openDetail(card.id);
|
||||
await sleep(150);
|
||||
|
||||
expect(String(window.state.activeBucket)).toBe(String(b.id));
|
||||
// Bucket bar must highlight the switched-to bucket.
|
||||
const activeTab = document.querySelector('#bucket-bar .bucket-tab.active[data-bucket]');
|
||||
expect(activeTab).toExist();
|
||||
expect(activeTab.dataset.bucket).toBe(String(b.id));
|
||||
} finally {
|
||||
localStorage.setItem(SETTING_KEY, prev ?? '0');
|
||||
if (card?.id) await fetch(`api/cards/${card.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': token } });
|
||||
await window._reloadAll();
|
||||
}
|
||||
});
|
||||
|
||||
it('does NOT switch when the setting is off', async () => {
|
||||
const buckets = await fetch('api/buckets').then(r => r.json());
|
||||
if (!Array.isArray(buckets) || buckets.length < 2) return;
|
||||
const token = csrf();
|
||||
const h = { 'Content-Type': 'application/json', 'X-CSRF-Token': token };
|
||||
let card;
|
||||
const prev = localStorage.getItem(SETTING_KEY);
|
||||
try {
|
||||
card = await fetch('api/cards', { method: 'POST', headers: h,
|
||||
body: JSON.stringify({ title: '__test_no_switch__', body: '', bucket_id: buckets[0].id })
|
||||
}).then(r => r.json());
|
||||
await window._reloadAll();
|
||||
await sleep(100);
|
||||
|
||||
window.state.activeBucket = String(buckets[1].id);
|
||||
localStorage.setItem(SETTING_KEY, '0');
|
||||
|
||||
window._openDetail(card.id);
|
||||
await sleep(100);
|
||||
|
||||
// Should stay on the other bucket.
|
||||
expect(String(window.state.activeBucket)).toBe(String(buckets[1].id));
|
||||
} finally {
|
||||
localStorage.setItem(SETTING_KEY, prev ?? '0');
|
||||
if (card?.id) await fetch(`api/cards/${card.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': token } });
|
||||
await window._reloadAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user