Repository: sissbruecker/linkding
Pull request #825 (2024-09-14 22:59:21)

Fix several issues around browser back navigation


Patch:
diff --git a/bookmarks/frontend/behaviors/bulk-edit.js b/bookmarks/frontend/behaviors/bulk-edit.js
index 6d9d0bc..45ee131 100644
--- a/bookmarks/frontend/behaviors/bulk-edit.js
+++ b/bookmarks/frontend/behaviors/bulk-edit.js
@@ -4,8 +4,9 @@ class BulkEdit extends Behavior {
   constructor(element) {
     super(element);
 
-    this.active = false;
+    this.active = element.classList.contains("active");
 
+    this.init = this.init.bind(this);
     this.onToggleActive = this.onToggleActive.bind(this);
     this.onToggleAll = this.onToggleAll.bind(this);
     this.onToggleBookmark = this.onToggleBookmark.bind(this);
@@ -13,7 +14,11 @@ class BulkEdit extends Behavior {
 
     this.init();
     // Reset when bookmarks are refreshed
-    document.addEventListener("refresh-bookmark-list-done", () => this.init());
+    document.addEventListener("refresh-bookmark-list-done", this.init);
+  }
+
+  destroy() {
+    document.removeEventListener("refresh-bookmark-list-done", this.init);
   }
 
   init() {
diff --git a/bookmarks/frontend/behaviors/confirm-button.js b/bookmarks/frontend/behaviors/confirm-button.js
index 059e938..454c3cf 100644
--- a/bookmarks/frontend/behaviors/confirm-button.js
+++ b/bookmarks/frontend/behaviors/confirm-button.js
@@ -13,7 +13,10 @@ class ConfirmButtonBehavior extends Behavior {
   }
 
   destroy() {
-    Behavior.interacting = false;
+    this.reset();
+    this.element.setAttribute("type", this.element.dataset.type);
+    this.element.setAttribute("name", this.element.dataset.name);
+    this.element.setAttribute("value", this.element.dataset.value);
   }
 
   onClick(event) {
@@ -70,7 +73,10 @@ class ConfirmButtonBehavior extends Behavior {
   reset() {
     setTimeout(() => {
       Behavior.interacting = false;
-      this.container.remove();
+      if (this.container) {
+        this.container.remove();
+        this.container = null;
+      }
       this.element.classList.remove("d-none");
     });
   }
diff --git a/bookmarks/frontend/behaviors/dropdown.js b/bookmarks/frontend/behaviors/dropdown.js
index 60a4787..73b03c2 100644
--- a/bookmarks/frontend/behaviors/dropdown.js
+++ b/bookmarks/frontend/behaviors/dropdown.js
@@ -16,6 +16,10 @@ class DropdownBehavior extends Behavior {
     });
   }
 
+  destroy() {
+    this.close();
+  }
+
   open() {
     this.element.classList.add("active");
     document.addEventListener("click", this.onOutsideClick);
diff --git a/bookmarks/frontend/behaviors/global-shortcuts.js b/bookmarks/frontend/behaviors/global-shortcuts.js
index fba6ab1..68e16f7 100644
--- a/bookmarks/frontend/behaviors/global-shortcuts.js
+++ b/bookmarks/frontend/behaviors/global-shortcuts.js
@@ -4,7 +4,12 @@ class GlobalShortcuts extends Behavior {
   constructor(element) {
     super(element);
 
-    document.addEventListener("keydown", this.onKeyDown.bind(this));
+    this.onKeyDown = this.onKeyDown.bind(this);
+    document.addEventListener("keydown", this.onKeyDown);
+  }
+
+  destroy() {
+    document.removeEventListener("keydown", this.onKeyDown);
   }
 
   onKeyDown(event) {
diff --git a/bookmarks/frontend/behaviors/index.js b/bookmarks/frontend/behaviors/index.js
index 3368f7e..0dfe49c 100644
--- a/bookmarks/frontend/behaviors/index.js
+++ b/bookmarks/frontend/behaviors/index.js
@@ -16,7 +16,7 @@ const mutationObserver = new MutationObserver((mutations) => {
   });
 });
 
-window.addEventListener("turbo:load", () => {
+document.addEventListener("turbo:load", () => {
   mutationObserver.observe(document.body, {
     childList: true,
     subtree: true,
@@ -25,6 +25,10 @@ window.addEventListener("turbo:load", () => {
   applyBehaviors(document.body);
 });
 
+document.addEventListener("turbo:before-cache", () => {
+  destroyBehaviors(document.body);
+});
+
 export class Behavior {
   constructor(element) {
     this.element = element;
diff --git a/bookmarks/frontend/behaviors/tag-autocomplete.js b/bookmarks/frontend/behaviors/tag-autocomplete.js
index 58e8e97..755ba3c 100644
--- a/bookmarks/frontend/behaviors/tag-autocomplete.js
+++ b/bookmarks/frontend/behaviors/tag-autocomplete.js
@@ -5,23 +5,35 @@ import { ApiClient } from "../api";
 class TagAutocomplete extends Behavior {
   constructor(element) {
     super(element);
-    const wrapper = document.createElement("div");
+    const input = element.querySelector("input");
+    if (!input) {
+      console.warning("TagAutocomplete: input element not found");
+      return;
+    }
+
+    const container = document.createElement("div");
     const apiBaseUrl = document.documentElement.dataset.apiBaseUrl || "";
     const apiClient = new ApiClient(apiBaseUrl);
 
     new TagAutoCompleteComponent({
-      target: wrapper,
+      target: container,
       props: {
-        id: element.id,
-        name: element.name,
-        value: element.value,
-        placeholder: element.getAttribute("placeholder") || "",
+        id: input.id,
+        name: input.name,
+        value: input.value,
+        placeholder: input.getAttribute("placeholder") || "",
         apiClient: apiClient,
-        variant: element.getAttribute("variant"),
+        variant: input.getAttribute("variant"),
       },
     });
 
-    element.replaceWith(wrapper.firstElementChild);
+    this.input = input;
+    this.autocomplete = container.firstElementChild;
+    input.replaceWith(this.autocomplete);
+  }
+
+  destroy() {
+    this.autocomplete.replaceWith(this.input);
   }
 }
 
diff --git a/bookmarks/templates/bookmarks/bulk_edit/bar.html b/bookmarks/templates/bookmarks/bulk_edit/bar.html
index 847b998..9f5d8a9 100644
--- a/bookmarks/templates/bookmarks/bulk_edit/bar.html
+++ b/bookmarks/templates/bookmarks/bulk_edit/bar.html
@@ -23,9 +23,8 @@
           <option value="bulk_unshare">Unshare</option>
         {% endif %}
       </select>
-      <div class="tag-autocomplete d-none">
-        <input ld-tag-autocomplete variant="small"
-               name="bulk_tag_string" class="form-input input-sm" placeholder="Tag names...">
+      <div class="tag-autocomplete d-none" ld-tag-autocomplete>
+        <input name="bulk_tag_string" class="form-input input-sm" placeholder="Tag names..." variant="small">
       </div>
       <button ld-confirm-button type="submit" name="bulk_execute" class="btn btn-link btn-sm">
         <span>Execute</span>
diff --git a/bookmarks/templates/bookmarks/form.html b/bookmarks/templates/bookmarks/form.html
index bac7cdf..f35b32e 100644
--- a/bookmarks/templates/bookmarks/form.html
+++ b/bookmarks/templates/bookmarks/form.html
@@ -19,9 +19,9 @@
       The form has been pre-filled with the existing bookmark, and saving the form will update the existing bookmark.
     </div>
   </div>
-  <div class="form-group">
+  <div class="form-group" ld-tag-autocomplete>
     <label for="{{ form.tag_string.id_for_label }}" class="form-label">Tags</label>
-    {{ form.tag_string|add_class:"form-input"|attr:"ld-tag-autocomplete"|attr:"autocomplete:off"|attr:"autocapitalize:off" }}
+    {{ form.tag_string|add_class:"form-input"|attr:"autocomplete:off"|attr:"autocapitalize:off" }}
     <div class="form-input-hint">
       Enter any number of tags separated by space and <strong>without</strong> the hash (#).
       If a tag does not exist it will be automatically created.
diff --git a/bookmarks/templates/bookmarks/search.html b/bookmarks/templates/bookmarks/search.html
index c9e5868..266416f 100644
--- a/bookmarks/templates/bookmarks/search.html
+++ b/bookmarks/templates/bookmarks/search.html
@@ -89,9 +89,9 @@
     }
     const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
     const input = document.querySelector('#search input[name="q"]')
-    const wrapper = document.createElement('div')
+    const container = document.createElement('div')
     new linkding.SearchAutoComplete({
-      target: wrapper,
+      target: container,
       props: {
         name: 'q',
         placeholder: 'Search for words or #tags',
@@ -103,6 +103,12 @@
         search,
       }
     })
-    input.replaceWith(wrapper.firstElementChild);
+
+    const autoComplete = container.firstElementChild;
+    input.replaceWith(autoComplete);
+
+    document.addEventListener("turbo:before-cache", () => {
+      autoComplete.replaceWith(input);
+    }, {once: true});
   })();
 </script>
\ No newline at end of file


Test patch:
diff --git a/bookmarks/tests/test_bookmark_edit_view.py b/bookmarks/tests/test_bookmark_edit_view.py
index ee5de9f..68fbafb 100644
--- a/bookmarks/tests/test_bookmark_edit_view.py
+++ b/bookmarks/tests/test_bookmark_edit_view.py
@@ -98,7 +98,7 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
         tag_string = build_tag_string(bookmark.tag_names, " ")
         self.assertInHTML(
             f"""
-            <input ld-tag-autocomplete type="text" name="tag_string" value="{tag_string}" 
+            <input type="text" name="tag_string" value="{tag_string}" 
                     autocomplete="off" autocapitalize="off" class="form-input" id="id_tag_string">
         """,
             html,