diff --git a/templates/bookshelf.templ b/templates/bookshelf.templ index 53ce28f5..64b3948b 100644 --- a/templates/bookshelf.templ +++ b/templates/bookshelf.templ @@ -268,6 +268,7 @@ templ BookShelf(
- +
- + + +
diff --git a/templates/bookshelf_templ.go b/templates/bookshelf_templ.go index f253c302..4e5a59ce 100644 --- a/templates/bookshelf_templ.go +++ b/templates/bookshelf_templ.go @@ -548,7 +548,7 @@ func BookShelf( if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/src/bookshelf.ts b/web/src/bookshelf.ts index c10248dd..76aa52be 100644 --- a/web/src/bookshelf.ts +++ b/web/src/bookshelf.ts @@ -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;