feat(filters): save current filter as smart collection — the bridge (item 3.5)
The bookshelf's save modal grows a second action: "Save as Collection" POSTs the current (completed, scope-stamped) tree to /api/collections — the same validated write path every tree collection uses — and lands you on the new collection's page. The qf engine then drives it everywhere: the shelf's live shelf-count, the collections page, the replay chain for episode trees. The legacy "Save Filter" (bookmark the wire params) stays as the sibling action. This is the plan's bridge: a book search graduated into a live shelf with one click, authored in the ONE filter system.
This commit is contained in:
1 parent
4f71eda6be
commit
94bd69d22d
3 files changed
+47
-3
No files matched your search
@@ -179,6 +179,45 @@ Alpine.data("bookshelf", () => ({
|
||||
// Show success toast
|
||||
showToast("Filter saved successfully", "success");
|
||||
},
|
||||
// Item 3.5 bridge: the current tree becomes a live smart collection.
|
||||
// The qf engine then drives it everywhere — shelf, collections page,
|
||||
// play chains — with zero re-authoring.
|
||||
async saveFilterAsCollection() {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
showToast("Not authenticated", "error");
|
||||
return;
|
||||
}
|
||||
const nameInput = document.getElementById("save-filter-name") as HTMLInputElement | null;
|
||||
const name = nameInput?.value?.trim() ?? "";
|
||||
if (!name) {
|
||||
showToast("Give the collection a name first", "error");
|
||||
return;
|
||||
}
|
||||
const builder = builderData();
|
||||
const tree = builder?.scopedTree?.();
|
||||
if (!tree) {
|
||||
showToast("Add a complete filter rule first", "error");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch("/api/collections", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({ name, filter_tree: tree }),
|
||||
});
|
||||
if (response.ok) {
|
||||
this.showSaveModal = false;
|
||||
showToast("Saved as a smart collection", "success");
|
||||
window.location.href = `/collections/${((await response.json()) as { id: string }).id}`;
|
||||
} else {
|
||||
const body = (await response.json().catch(() => ({}))) as { error?: string };
|
||||
showToast(body.error || "Failed to save the collection", "error");
|
||||
}
|
||||
} catch {
|
||||
showToast("Failed to save the collection", "error");
|
||||
}
|
||||
},
|
||||
// Load a saved filter into the form
|
||||
async loadFilter(event: Event) {
|
||||
const button = event.target as HTMLElement;
|
||||
|
||||
Reference in new issue
Block a user