|
|
|
|
@@ -289,8 +289,8 @@ function make_entry_row(entry, children_map) {
|
|
|
|
|
span.title = `Filter by tag: ${tag}`;
|
|
|
|
|
span.addEventListener('click', (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
state.filter_tag = tag;
|
|
|
|
|
render();
|
|
|
|
|
const base = state.active_type ? 'type/' + state.active_type : '';
|
|
|
|
|
navigate(base ? base + '/tag/' + tag : 'tag/' + tag);
|
|
|
|
|
});
|
|
|
|
|
tags_el.appendChild(span);
|
|
|
|
|
}
|
|
|
|
|
@@ -306,8 +306,8 @@ function make_entry_row(entry, children_map) {
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function render_tree_node(entry, children_map, is_child = false) {
|
|
|
|
|
if (!is_child && !node_or_descendants_match(entry, children_map)) { return null; }
|
|
|
|
|
function render_tree_node(entry, children_map) {
|
|
|
|
|
if (!entry_matches_filter(entry)) { return null; }
|
|
|
|
|
|
|
|
|
|
const children = children_map.get(entry.id) ?? [];
|
|
|
|
|
const node = document.createElement('div');
|
|
|
|
|
@@ -318,7 +318,8 @@ function render_tree_node(entry, children_map, is_child = false) {
|
|
|
|
|
const children_el = document.createElement('div');
|
|
|
|
|
children_el.className = 'task-children';
|
|
|
|
|
for (const child of children) {
|
|
|
|
|
children_el.appendChild(render_tree_node(child, children_map, true));
|
|
|
|
|
const child_node = render_tree_node(child, children_map);
|
|
|
|
|
if (child_node) { children_el.appendChild(child_node); }
|
|
|
|
|
}
|
|
|
|
|
node.appendChild(children_el);
|
|
|
|
|
}
|
|
|
|
|
@@ -337,7 +338,7 @@ function render_nav() {
|
|
|
|
|
const all_btn = document.createElement('button');
|
|
|
|
|
all_btn.className = 'nav-btn' + (state.active_view === 'entries' && state.active_type === null ? ' active' : '');
|
|
|
|
|
all_btn.textContent = 'All';
|
|
|
|
|
all_btn.addEventListener('click', () => navigate('all'));
|
|
|
|
|
all_btn.addEventListener('click', () => navigate(''));
|
|
|
|
|
nav.appendChild(all_btn);
|
|
|
|
|
|
|
|
|
|
for (const et of state.entry_types) {
|
|
|
|
|
@@ -409,7 +410,7 @@ function render_entries(container) {
|
|
|
|
|
if (val === state.filter_status) { opt.selected = true; }
|
|
|
|
|
status_sel.appendChild(opt);
|
|
|
|
|
}
|
|
|
|
|
status_sel.addEventListener('change', () => { state.filter_status = status_sel.value; render(); });
|
|
|
|
|
status_sel.addEventListener('change', () => { set_filter('filter_status', status_sel.value); });
|
|
|
|
|
filter_bar.appendChild(status_sel);
|
|
|
|
|
|
|
|
|
|
const priority_sel = document.createElement('select');
|
|
|
|
|
@@ -419,7 +420,7 @@ function render_entries(container) {
|
|
|
|
|
if (val === state.filter_priority) { opt.selected = true; }
|
|
|
|
|
priority_sel.appendChild(opt);
|
|
|
|
|
}
|
|
|
|
|
priority_sel.addEventListener('change', () => { state.filter_priority = priority_sel.value; render(); });
|
|
|
|
|
priority_sel.addEventListener('change', () => { set_filter('filter_priority', priority_sel.value); });
|
|
|
|
|
filter_bar.appendChild(priority_sel);
|
|
|
|
|
|
|
|
|
|
const tag_sel = document.createElement('select');
|
|
|
|
|
@@ -432,14 +433,18 @@ function render_entries(container) {
|
|
|
|
|
if (tag === state.filter_tag) { opt.selected = true; }
|
|
|
|
|
tag_sel.appendChild(opt);
|
|
|
|
|
}
|
|
|
|
|
tag_sel.addEventListener('change', () => { state.filter_tag = tag_sel.value; render(); });
|
|
|
|
|
tag_sel.addEventListener('change', () => {
|
|
|
|
|
const base = state.active_type ? 'type/' + state.active_type : '';
|
|
|
|
|
const tag_hash = tag_sel.value ? (base ? base + '/tag/' + tag_sel.value : 'tag/' + tag_sel.value) : base;
|
|
|
|
|
navigate(tag_hash);
|
|
|
|
|
});
|
|
|
|
|
filter_bar.appendChild(tag_sel);
|
|
|
|
|
|
|
|
|
|
const search_input = document.createElement('input');
|
|
|
|
|
search_input.type = 'text';
|
|
|
|
|
search_input.placeholder = 'Search…';
|
|
|
|
|
search_input.value = state.search;
|
|
|
|
|
search_input.addEventListener('input', () => { state.search = search_input.value; render(); });
|
|
|
|
|
search_input.addEventListener('input', () => { set_filter('search', search_input.value); });
|
|
|
|
|
filter_bar.appendChild(search_input);
|
|
|
|
|
|
|
|
|
|
container.appendChild(filter_bar);
|
|
|
|
|
@@ -460,7 +465,7 @@ function render_entries(container) {
|
|
|
|
|
list.appendChild(make_entry_row(entry, children_map));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const visible = roots.filter(e => node_or_descendants_match(e, children_map));
|
|
|
|
|
const visible = roots.filter(e => entry_matches_filter(e));
|
|
|
|
|
if (!visible.length) {
|
|
|
|
|
const empty = document.createElement('div');
|
|
|
|
|
empty.className = 'empty-state';
|
|
|
|
|
@@ -590,11 +595,27 @@ function apply_hash(hash) {
|
|
|
|
|
state.active_type = null;
|
|
|
|
|
state.active_entry_id = null;
|
|
|
|
|
state.active_edit = null;
|
|
|
|
|
} else if (hash.startsWith('type/')) {
|
|
|
|
|
} else if (hash === '' || hash.startsWith('type/') || hash.startsWith('tag/')) {
|
|
|
|
|
state.active_view = 'entries';
|
|
|
|
|
state.active_type = hash.slice(5);
|
|
|
|
|
state.active_entry_id = null;
|
|
|
|
|
state.active_edit = null;
|
|
|
|
|
if (hash.startsWith('type/')) {
|
|
|
|
|
const rest = hash.slice(5);
|
|
|
|
|
const tag_idx = rest.indexOf('/tag/');
|
|
|
|
|
if (tag_idx !== -1) {
|
|
|
|
|
state.active_type = rest.slice(0, tag_idx);
|
|
|
|
|
state.filter_tag = rest.slice(tag_idx + 5);
|
|
|
|
|
} else {
|
|
|
|
|
state.active_type = rest;
|
|
|
|
|
state.filter_tag = '';
|
|
|
|
|
}
|
|
|
|
|
} else if (hash.startsWith('tag/')) {
|
|
|
|
|
state.active_type = null;
|
|
|
|
|
state.filter_tag = hash.slice(4);
|
|
|
|
|
} else {
|
|
|
|
|
state.active_type = null;
|
|
|
|
|
state.filter_tag = '';
|
|
|
|
|
}
|
|
|
|
|
} else if (/^\d+$/.test(hash)) {
|
|
|
|
|
state.active_view = 'entry';
|
|
|
|
|
state.active_entry_id = Number(hash);
|
|
|
|
|
@@ -626,14 +647,38 @@ function navigate_new(type, parent_id) {
|
|
|
|
|
navigate(hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function build_url(hash) {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (state.filter_status !== 'open') { params.set('status', state.filter_status || 'all'); }
|
|
|
|
|
if (state.filter_priority) { params.set('priority', state.filter_priority); }
|
|
|
|
|
if (state.search) { params.set('search', state.search); }
|
|
|
|
|
const qs = params.toString();
|
|
|
|
|
return (qs ? '?' + qs : '') + '#' + hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function apply_search() {
|
|
|
|
|
const params = new URLSearchParams(location.search);
|
|
|
|
|
const s = params.get('status');
|
|
|
|
|
state.filter_status = (s === null || s === '') ? 'open' : (s === 'all' ? '' : s);
|
|
|
|
|
state.filter_priority = params.get('priority') ?? '';
|
|
|
|
|
state.search = params.get('search') ?? '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function navigate(hash) {
|
|
|
|
|
history.pushState(null, '', '#' + hash);
|
|
|
|
|
history.pushState(null, '', build_url(hash));
|
|
|
|
|
apply_hash(hash);
|
|
|
|
|
render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function set_filter(key, val) {
|
|
|
|
|
state[key] = val;
|
|
|
|
|
history.replaceState(null, '', build_url(location.hash.slice(1)));
|
|
|
|
|
render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener('popstate', () => {
|
|
|
|
|
apply_hash(location.hash.slice(1));
|
|
|
|
|
apply_search();
|
|
|
|
|
render();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -733,8 +778,13 @@ function render_entry_detail(container) {
|
|
|
|
|
|
|
|
|
|
for (const tag of entry.tags) {
|
|
|
|
|
const span = document.createElement('span');
|
|
|
|
|
span.className = 'tag';
|
|
|
|
|
span.className = 'tag clickable-tag';
|
|
|
|
|
span.textContent = tag;
|
|
|
|
|
span.title = `Filter by tag: ${tag}`;
|
|
|
|
|
span.addEventListener('click', () => {
|
|
|
|
|
const base = entry.type ? 'type/' + entry.type : '';
|
|
|
|
|
navigate(base ? base + '/tag/' + tag : 'tag/' + tag);
|
|
|
|
|
});
|
|
|
|
|
meta.appendChild(span);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -790,7 +840,7 @@ function render_edit_view(container) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const back_hash = ae.mode === 'edit' ? String(ae.id) : (ae.type ? 'type/' + ae.type : 'all');
|
|
|
|
|
const back_hash = ae.mode === 'edit' ? String(ae.id) : (ae.type ? 'type/' + ae.type : '');
|
|
|
|
|
let current_parent_id = ae.parent_id ?? entry?.parent_id ?? null;
|
|
|
|
|
|
|
|
|
|
// View wrapper
|
|
|
|
|
@@ -1163,6 +1213,7 @@ async function init() {
|
|
|
|
|
state.entry_types = types_res.entry_types;
|
|
|
|
|
state.entries = entries_res.entries;
|
|
|
|
|
|
|
|
|
|
apply_search();
|
|
|
|
|
apply_hash(location.hash.slice(1));
|
|
|
|
|
render();
|
|
|
|
|
}
|
|
|
|
|
|