diff --git a/main.js b/main.js index a2b3470..69dc3bd 100644 --- a/main.js +++ b/main.js @@ -96,16 +96,30 @@ class OrgTodoListView extends ItemView { root.createEl("h4", { text: "TODO list" }); - const input = root.createEl("input", { + const filterWrap = root.createDiv({ cls: "org-todo-filterwrap" }); + this.pillsEl = filterWrap.createDiv({ cls: "org-todo-pills" }); + + const input = filterWrap.createEl("input", { type: "text", placeholder: "Filter… (text or #tag)", }); input.addClass("org-todo-filter"); + this.inputEl = input; input.addEventListener("input", () => { this.filterText = input.value.toLowerCase(); this.renderList(); this.renderTagBar(); }); + // Backspace on an empty box removes the last pill — standard token-input feel. + input.addEventListener("keydown", (e) => { + if (e.key === "Backspace" && input.value === "" && this.activeTags.size > 0) { + const last = [...this.activeTags].pop(); + this.activeTags.delete(last); + this.renderPills(); + this.renderList(); + this.renderTagBar(); + } + }); const toggleWrap = root.createDiv({ cls: "org-todo-toggle" }); const cb = toggleWrap.createEl("input", { type: "checkbox" }); @@ -123,6 +137,7 @@ class OrgTodoListView extends ItemView { this.tasks = await this.scanVault(); this.renderList(); this.renderTagBar(); + this.renderPills(); } async scanFile(file) { @@ -177,6 +192,7 @@ class OrgTodoListView extends ItemView { this.tasks = this.tasks.filter((t) => t.file.path !== path); this.renderList(); this.renderTagBar(); + this.renderPills(); } // Rescan one file and splice its tasks in, replacing its previous entries. @@ -187,8 +203,22 @@ class OrgTodoListView extends ItemView { for (const t of fresh) this.tasks.push(t); this.renderList(); this.renderTagBar(); + this.renderPills(); + } + renderPills() { + this.pillsEl.empty(); + for (const tag of [...this.activeTags].sort()) { + const pill = this.pillsEl.createSpan({ cls: "org-todo-pill" }); + pill.createSpan({ text: "#" + tag }); + const x = pill.createSpan({ text: "×", cls: "org-todo-pill-x" }); + x.addEventListener("click", () => { + this.activeTags.delete(tag); + this.renderPills(); + this.renderList(); + this.renderTagBar(); + }); + } } - renderTagBar() { this.tagBarEl.empty(); @@ -215,6 +245,7 @@ class OrgTodoListView extends ItemView { chip.addEventListener("click", () => { if (this.activeTags.has(tag)) this.activeTags.delete(tag); else this.activeTags.add(tag); + this.renderPills(); this.renderList(); this.renderTagBar(); }); diff --git a/styles.css b/styles.css index b22115c..f2bb380 100644 --- a/styles.css +++ b/styles.css @@ -1,10 +1,45 @@ .org-todo-root { padding: 0 8px; } -.org-todo-filter { - width: 100%; +.org-todo-filterwrap { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + padding: 4px 6px; margin-bottom: 8px; } +.org-todo-pills { + display: contents; /* pills participate directly in the wrap's flex layout */ +} +.org-todo-pill { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 0.78em; + padding: 2px 6px; + border-radius: 10px; + background: var(--interactive-accent); + color: var(--text-on-accent); + white-space: nowrap; +} +.org-todo-pill-x { + cursor: pointer; + font-weight: bold; + opacity: 0.8; +} +.org-todo-pill-x:hover { + opacity: 1; +} +.org-todo-filter { + flex: 1; + min-width: 80px; + border: none; + outline: none; + background: transparent; +} .org-todo-toggle { display: flex; align-items: center;