diff --git a/bookmarks/e2e/e2e_test_bookmark_details_modal.py b/bookmarks/e2e/e2e_test_bookmark_details_modal.py index d3eb183..beef9bd 100644 --- a/bookmarks/e2e/e2e_test_bookmark_details_modal.py +++ b/bookmarks/e2e/e2e_test_bookmark_details_modal.py @@ -1,3 +1,4 @@ +from django.test import override_settings from django.urls import reverse from playwright.sync_api import sync_playwright, expect @@ -44,14 +45,17 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): details_modal = self.open_details_modal(bookmark) details_modal.get_by_text("Archived", exact=False).click() expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() + self.assertReloads(0) # unarchive url = reverse("bookmarks:archived") self.page.goto(self.live_server_url + url) + self.resetReloads() details_modal = self.open_details_modal(bookmark) details_modal.get_by_text("Archived", exact=False).click() expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() + self.assertReloads(0) def test_toggle_unread(self): bookmark = self.setup_bookmark() @@ -66,11 +70,13 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): details_modal.get_by_text("Unread").click() bookmark_item = self.locate_bookmark(bookmark.title) expect(bookmark_item.get_by_text("Unread")).to_be_visible() + self.assertReloads(0) # mark as read details_modal.get_by_text("Unread").click() bookmark_item = self.locate_bookmark(bookmark.title) expect(bookmark_item.get_by_text("Unread")).not_to_be_visible() + self.assertReloads(0) def test_toggle_shared(self): profile = self.get_or_create_test_user().profile @@ -89,11 +95,13 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): details_modal.get_by_text("Shared").click() bookmark_item = self.locate_bookmark(bookmark.title) expect(bookmark_item.get_by_text("Shared")).to_be_visible() + self.assertReloads(0) # unshare bookmark details_modal.get_by_text("Shared").click() bookmark_item = self.locate_bookmark(bookmark.title) expect(bookmark_item.get_by_text("Shared")).not_to_be_visible() + self.assertReloads(0) def test_edit_return_url(self): bookmark = self.setup_bookmark() @@ -131,3 +139,33 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() self.assertEqual(Bookmark.objects.count(), 0) + + @override_settings(LD_ENABLE_SNAPSHOTS=True) + def test_create_snapshot_remove_snapshot(self): + bookmark = self.setup_bookmark() + + with sync_playwright() as p: + url = reverse("bookmarks:index") + f"?q={bookmark.title}" + self.open(url, p) + + details_modal = self.open_details_modal(bookmark) + asset_list = details_modal.locator(".assets") + + # No snapshots initially + snapshot = asset_list.get_by_text("HTML snapshot from", exact=False) + expect(snapshot).not_to_be_visible() + + # Create snapshot + details_modal.get_by_text("Create HTML snapshot", exact=False).click() + self.assertReloads(0) + + # Has new snapshots + expect(snapshot).to_be_visible() + + # Create snapshot + asset_list.get_by_text("Remove", exact=False).click() + asset_list.get_by_text("Confirm", exact=False).click() + + # Snapshot is removed + expect(snapshot).not_to_be_visible() + self.assertReloads(0) diff --git a/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py b/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py index ac20326..91918fd 100644 --- a/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py +++ b/bookmarks/e2e/e2e_test_bookmark_page_bulk_edit.py @@ -194,7 +194,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_toggle().click() - checkboxes = page.locator("label[ld-bulk-edit-checkbox] input") + checkboxes = page.locator("label.bulk-edit-checkbox input") self.assertEqual(6, checkboxes.count()) for i in range(checkboxes.count()): expect(checkboxes.nth(i)).not_to_be_checked() @@ -264,13 +264,13 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): # Hide select across by toggling a single bookmark self.locate_bookmark("Bookmark 1").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() expect(self.locate_bulk_edit_select_across()).not_to_be_visible() # Show select across again, verify it is unchecked self.locate_bookmark("Bookmark 1").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() expect(self.locate_bulk_edit_select_across()).not_to_be_checked() @@ -297,7 +297,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): expect(bookmark_list).not_to_be_visible() # Verify bulk edit checkboxes are reset - checkboxes = page.locator("label[ld-bulk-edit-checkbox] input") + checkboxes = page.locator("label.bulk-edit-checkbox input") self.assertEqual(31, checkboxes.count()) for i in range(checkboxes.count()): expect(checkboxes.nth(i)).not_to_be_checked() diff --git a/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py b/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py index 2d77283..a2e9d62 100644 --- a/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py +++ b/bookmarks/e2e/e2e_test_bookmark_page_partial_updates.py @@ -169,7 +169,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_toggle().click() self.locate_bookmark("Bookmark 2").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() self.select_bulk_action("Archive") self.locate_bulk_edit_bar().get_by_text("Execute").click() @@ -187,7 +187,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_toggle().click() self.locate_bookmark("Bookmark 2").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() self.select_bulk_action("Delete") self.locate_bulk_edit_bar().get_by_text("Execute").click() @@ -230,7 +230,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_toggle().click() self.locate_bookmark("Archived Bookmark 2").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() self.select_bulk_action("Unarchive") self.locate_bulk_edit_bar().get_by_text("Execute").click() @@ -248,7 +248,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.locate_bulk_edit_toggle().click() self.locate_bookmark("Archived Bookmark 2").locator( - "label[ld-bulk-edit-checkbox]" + "label.bulk-edit-checkbox" ).click() self.select_bulk_action("Delete") self.locate_bulk_edit_bar().get_by_text("Execute").click() diff --git a/bookmarks/e2e/e2e_test_tag_cloud_modal.py b/bookmarks/e2e/e2e_test_tag_cloud_modal.py new file mode 100644 index 0000000..19793eb --- /dev/null +++ b/bookmarks/e2e/e2e_test_tag_cloud_modal.py @@ -0,0 +1,76 @@ +from django.test import override_settings +from django.urls import reverse +from playwright.sync_api import sync_playwright, expect, Locator + +from bookmarks.e2e.helpers import LinkdingE2ETestCase +from bookmarks.models import Bookmark + + +class TagCloudModalE2ETestCase(LinkdingE2ETestCase): + def test_show_modal_close_modal(self): + self.setup_bookmark(tags=[self.setup_tag(name="cooking")]) + self.setup_bookmark(tags=[self.setup_tag(name="hiking")]) + + with sync_playwright() as p: + page = self.open(reverse("bookmarks:index"), p) + + # use smaller viewport to make tags button visible + page.set_viewport_size({"width": 375, "height": 812}) + + # open tag cloud modal + modal_trigger = page.locator(".content-area-header").get_by_role( + "button", name="Tags" + ) + modal_trigger.click() + + # verify modal is visible + modal = page.locator(".modal") + expect(modal).to_be_visible() + expect(modal.locator(".modal-title")).to_have_text("Tags") + + # close with close button + modal.locator("button.close").click() + expect(modal).to_be_hidden() + + # open modal again + modal_trigger.click() + + # close with backdrop + backdrop = modal.locator(".modal-overlay") + backdrop.click(position={"x": 0, "y": 0}) + expect(modal).to_be_hidden() + + def test_select_tag(self): + self.setup_bookmark(tags=[self.setup_tag(name="cooking")]) + self.setup_bookmark(tags=[self.setup_tag(name="hiking")]) + + with sync_playwright() as p: + page = self.open(reverse("bookmarks:index"), p) + + # use smaller viewport to make tags button visible + page.set_viewport_size({"width": 375, "height": 812}) + + # open tag cloud modal + modal_trigger = page.locator(".content-area-header").get_by_role( + "button", name="Tags" + ) + modal_trigger.click() + + # verify tags are displayed + modal = page.locator(".modal") + unselected_tags = modal.locator(".unselected-tags") + expect(unselected_tags.get_by_text("cooking")).to_be_visible() + expect(unselected_tags.get_by_text("hiking")).to_be_visible() + + # select tag + unselected_tags.get_by_text("cooking").click() + + # open modal again + modal_trigger.click() + + # verify tag is selected, other tag is not visible anymore + selected_tags = modal.locator(".selected-tags") + expect(selected_tags.get_by_text("cooking")).to_be_visible() + + expect(unselected_tags.get_by_text("cooking")).not_to_be_visible() + expect(unselected_tags.get_by_text("hiking")).not_to_be_visible() diff --git a/bookmarks/e2e/helpers.py b/bookmarks/e2e/helpers.py index 99c4fed..c19852e 100644 --- a/bookmarks/e2e/helpers.py +++ b/bookmarks/e2e/helpers.py @@ -39,6 +39,9 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): def assertReloads(self, count: int): self.assertEqual(self.num_loads, count) + def resetReloads(self): + self.num_loads = 0 + def locate_bookmark_list(self): return self.page.locator("ul[ld-bookmark-list]") @@ -62,7 +65,7 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): return self.page.locator(".bulk-edit-bar") def locate_bulk_edit_select_all(self): - return self.locate_bulk_edit_bar().locator("label[ld-bulk-edit-checkbox][all]") + return self.locate_bulk_edit_bar().locator("label.bulk-edit-checkbox.all") def locate_bulk_edit_select_across(self): return self.locate_bulk_edit_bar().locator("label.select-across") diff --git a/bookmarks/frontend/behaviors/bookmark-page.js b/bookmarks/frontend/behaviors/bookmark-page.js index e60d82e..304d8ce 100644 --- a/bookmarks/frontend/behaviors/bookmark-page.js +++ b/bookmarks/frontend/behaviors/bookmark-page.js @@ -1,63 +1,4 @@ -import { registerBehavior, swapContent } from "./index"; - -class BookmarkPage { - constructor(element) { - this.element = element; - this.form = element.querySelector("form.bookmark-actions"); - this.form.addEventListener("submit", this.onFormSubmit.bind(this)); - - this.bookmarkList = element.querySelector(".bookmark-list-container"); - this.tagCloud = element.querySelector(".tag-cloud-container"); - - document.addEventListener("bookmark-page-refresh", () => { - this.refresh(); - }); - } - - async onFormSubmit(event) { - event.preventDefault(); - - const url = this.form.action; - const formData = new FormData(this.form); - formData.append(event.submitter.name, event.submitter.value); - - await fetch(url, { - method: "POST", - body: formData, - redirect: "manual", // ignore redirect - }); - await this.refresh(); - } - - async refresh() { - const query = window.location.search; - const bookmarksUrl = this.element.getAttribute("bookmarks-url"); - const tagsUrl = this.element.getAttribute("tags-url"); - Promise.all([ - fetch(`${bookmarksUrl}${query}`).then((response) => response.text()), - fetch(`${tagsUrl}${query}`).then((response) => response.text()), - ]).then(([bookmarkListHtml, tagCloudHtml]) => { - swapContent(this.bookmarkList, bookmarkListHtml); - swapContent(this.tagCloud, tagCloudHtml); - - // Dispatch list updated event - const listElement = this.bookmarkList.querySelector( - "ul[data-bookmarks-total]", - ); - const bookmarksTotal = - (listElement && listElement.dataset.bookmarksTotal) || 0; - - this.bookmarkList.dispatchEvent( - new CustomEvent("bookmark-list-updated", { - bubbles: true, - detail: { bookmarksTotal }, - }), - ); - }); - } -} - -registerBehavior("ld-bookmark-page", BookmarkPage); +import { registerBehavior } from "./index"; class BookmarkItem { constructor(element) { diff --git a/bookmarks/frontend/behaviors/bulk-edit.js b/bookmarks/frontend/behaviors/bulk-edit.js index f0927c6..57af913 100644 --- a/bookmarks/frontend/behaviors/bulk-edit.js +++ b/bookmarks/frontend/behaviors/bulk-edit.js @@ -4,43 +4,56 @@ class BulkEdit { constructor(element) { this.element = element; this.active = false; - this.actionSelect = element.querySelector("select[name='bulk_action']"); - this.tagAutoComplete = element.querySelector(".tag-autocomplete"); - this.selectAcross = element.querySelector("label.select-across"); - element.addEventListener( - "bulk-edit-toggle-active", - this.onToggleActive.bind(this), - ); - element.addEventListener( - "bulk-edit-toggle-all", - this.onToggleAll.bind(this), - ); - element.addEventListener( - "bulk-edit-toggle-bookmark", - this.onToggleBookmark.bind(this), - ); - element.addEventListener( - "bookmark-list-updated", - this.onListUpdated.bind(this), - ); + this.onToggleActive = this.onToggleActive.bind(this); + this.onToggleAll = this.onToggleAll.bind(this); + this.onToggleBookmark = this.onToggleBookmark.bind(this); + this.onActionSelected = this.onActionSelected.bind(this); - this.actionSelect.addEventListener( - "change", - this.onActionSelected.bind(this), - ); + this.init(); + // Reset when bookmarks are refreshed + document.addEventListener("refresh-bookmark-list-done", () => this.init()); } - get allCheckbox() { - return this.element.querySelector("[ld-bulk-edit-checkbox][all] input"); - } + init() { + // Update elements + this.activeToggle = this.element.querySelector(".bulk-edit-active-toggle"); + this.actionSelect = this.element.querySelector( + "select[name='bulk_action']", + ); + this.tagAutoComplete = this.element.querySelector(".tag-autocomplete"); + this.selectAcross = this.element.querySelector("label.select-across"); + this.allCheckbox = this.element.querySelector( + ".bulk-edit-checkbox.all input", + ); + this.bookmarkCheckboxes = Array.from( + this.element.querySelectorAll(".bulk-edit-checkbox:not(.all) input"), + ); - get bookmarkCheckboxes() { - return [ - ...this.element.querySelectorAll( - "[ld-bulk-edit-checkbox]:not([all]) input", - ), - ]; + // Remove previous listeners if elements are the same + this.activeToggle.removeEventListener("click", this.onToggleActive); + this.actionSelect.removeEventListener("change", this.onActionSelected); + this.allCheckbox.removeEventListener("change", this.onToggleAll); + this.bookmarkCheckboxes.forEach((checkbox) => { + checkbox.removeEventListener("change", this.onToggleBookmark); + }); + + // Reset checkbox states + this.reset(); + + // Update total number of bookmarks + const totalHolder = this.element.querySelector("[data-bookmarks-total]"); + const total = totalHolder?.dataset.bookmarksTotal || 0; + const totalSpan = this.selectAcross.querySelector("span.total"); + totalSpan.textContent = total; + + // Add new listeners + this.activeToggle.addEventListener("click", this.onToggleActive); + this.actionSelect.addEventListener("change", this.onActionSelected); + this.allCheckbox.addEventListener("change", this.onToggleAll); + this.bookmarkCheckboxes.forEach((checkbox) => { + checkbox.addEventListener("change", this.onToggleBookmark); + }); } onToggleActive() { @@ -81,16 +94,6 @@ class BulkEdit { } } - onListUpdated(event) { - // Reset checkbox states - this.reset(); - - // Update total number of bookmarks - const total = event.detail.bookmarksTotal; - const totalSpan = this.selectAcross.querySelector("span.total"); - totalSpan.textContent = total; - } - updateSelectAcross(allChecked) { if (allChecked) { this.selectAcross.classList.remove("d-none"); @@ -109,33 +112,4 @@ class BulkEdit { } } -class BulkEditActiveToggle { - constructor(element) { - this.element = element; - element.addEventListener("click", this.onClick.bind(this)); - } - - onClick() { - this.element.dispatchEvent( - new CustomEvent("bulk-edit-toggle-active", { bubbles: true }), - ); - } -} - -class BulkEditCheckbox { - constructor(element) { - this.element = element; - element.addEventListener("change", this.onChange.bind(this)); - } - - onChange() { - const type = this.element.hasAttribute("all") ? "all" : "bookmark"; - this.element.dispatchEvent( - new CustomEvent(`bulk-edit-toggle-${type}`, { bubbles: true }), - ); - } -} - registerBehavior("ld-bulk-edit", BulkEdit); -registerBehavior("ld-bulk-edit-active-toggle", BulkEditActiveToggle); -registerBehavior("ld-bulk-edit-checkbox", BulkEditCheckbox); diff --git a/bookmarks/frontend/behaviors/confirm-button.js b/bookmarks/frontend/behaviors/confirm-button.js index fbea591..7bf0b1a 100644 --- a/bookmarks/frontend/behaviors/confirm-button.js +++ b/bookmarks/frontend/behaviors/confirm-button.js @@ -19,7 +19,7 @@ class ConfirmButtonBehavior { const container = document.createElement("span"); container.className = "confirmation"; - const icon = this.button.getAttribute("confirm-icon"); + const icon = this.button.getAttribute("ld-confirm-icon"); if (icon) { const iconElement = document.createElementNS( "http://www.w3.org/2000/svg", @@ -31,7 +31,7 @@ class ConfirmButtonBehavior { container.append(iconElement); } - const question = this.button.getAttribute("confirm-question"); + const question = this.button.getAttribute("ld-confirm-question"); if (question) { const questionElement = document.createElement("span"); questionElement.innerText = question; diff --git a/bookmarks/frontend/behaviors/fetch.js b/bookmarks/frontend/behaviors/fetch.js new file mode 100644 index 0000000..3a65511 --- /dev/null +++ b/bookmarks/frontend/behaviors/fetch.js @@ -0,0 +1,25 @@ +import { fireEvents, registerBehavior, swap } from "./index"; + +class FetchBehavior { + constructor(element) { + this.element = element; + const eventName = element.getAttribute("ld-on"); + + element.addEventListener(eventName, this.onFetch.bind(this)); + } + + async onFetch(event) { + event.preventDefault(); + const url = this.element.getAttribute("ld-fetch"); + const html = await fetch(url).then((response) => response.text()); + + const target = this.element.getAttribute("ld-target"); + const select = this.element.getAttribute("ld-select"); + swap(this.element, html, { target, select }); + + const events = this.element.getAttribute("ld-fire"); + fireEvents(events); + } +} + +registerBehavior("ld-fetch", FetchBehavior); diff --git a/bookmarks/frontend/behaviors/form.js b/bookmarks/frontend/behaviors/form.js index 5b78c31..7a30f4c 100644 --- a/bookmarks/frontend/behaviors/form.js +++ b/bookmarks/frontend/behaviors/form.js @@ -1,12 +1,12 @@ -import { registerBehavior, swap } from "./index"; +import { fireEvents, registerBehavior } from "./index"; class FormBehavior { constructor(element) { this.element = element; - element.addEventListener("submit", this.onFormSubmit.bind(this)); + element.addEventListener("submit", this.onSubmit.bind(this)); } - async onFormSubmit(event) { + async onSubmit(event) { event.preventDefault(); const url = this.element.action; @@ -21,34 +21,21 @@ class FormBehavior { redirect: "manual", // ignore redirect }); - // Dispatch refresh events - const refreshEvents = this.element.getAttribute("refresh-events"); - if (refreshEvents) { - refreshEvents.split(",").forEach((eventName) => { - document.dispatchEvent(new CustomEvent(eventName)); - }); + const events = this.element.getAttribute("ld-fire"); + if (fireEvents) { + fireEvents(events); } - - // Refresh form - await this.refresh(); - } - - async refresh() { - const refreshUrl = this.element.getAttribute("refresh-url"); - const html = await fetch(refreshUrl).then((response) => response.text()); - swap(this.element, html); } } -class FormAutoSubmitBehavior { +class AutoSubmitBehavior { constructor(element) { - this.element = element; - this.element.addEventListener("change", () => { - const form = this.element.closest("form"); + element.addEventListener("change", () => { + const form = element.closest("form"); form.dispatchEvent(new Event("submit", { cancelable: true })); }); } } registerBehavior("ld-form", FormBehavior); -registerBehavior("ld-form-auto-submit", FormAutoSubmitBehavior); +registerBehavior("ld-auto-submit", AutoSubmitBehavior); diff --git a/bookmarks/frontend/behaviors/index.js b/bookmarks/frontend/behaviors/index.js index d7b79e8..6a87653 100644 --- a/bookmarks/frontend/behaviors/index.js +++ b/bookmarks/frontend/behaviors/index.js @@ -37,14 +37,49 @@ export function applyBehaviors(container, behaviorNames = null) { }); } -export function swap(element, html) { +export function swap(element, html, options) { const dom = new DOMParser().parseFromString(html, "text/html"); - const newElement = dom.body.firstChild; - element.replaceWith(newElement); - applyBehaviors(newElement); + + let targetElement = element; + let strategy = "innerHTML"; + if (options.target) { + const parts = options.target.split("|"); + targetElement = + parts[0] === "self" ? element : document.querySelector(parts[0]); + strategy = parts[1] || "innerHTML"; + } + + let contents = Array.from(dom.body.children); + if (options.select) { + contents = Array.from(dom.querySelectorAll(options.select)); + } + + switch (strategy) { + case "append": + targetElement.append(...contents); + break; + case "outerHTML": + targetElement.parentElement.replaceChild(contents[0], targetElement); + break; + case "innerHTML": + default: + targetElement.innerHTML = ""; + targetElement.append(...contents); + } + contents.forEach((content) => applyBehaviors(content)); } -export function swapContent(element, html) { - element.innerHTML = html; - applyBehaviors(element); +export function fireEvents(events) { + if (!events) { + return; + } + events.split(",").forEach((eventName) => { + const targets = Array.from( + document.querySelectorAll(`[ld-on='${eventName}']`), + ); + targets.push(document); + targets.forEach((target) => { + target.dispatchEvent(new CustomEvent(eventName)); + }); + }); } diff --git a/bookmarks/frontend/behaviors/modal.js b/bookmarks/frontend/behaviors/modal.js index 592fde7..b83bb0e 100644 --- a/bookmarks/frontend/behaviors/modal.js +++ b/bookmarks/frontend/behaviors/modal.js @@ -1,97 +1,20 @@ -import { applyBehaviors, registerBehavior } from "./index"; +import { registerBehavior } from "./index"; class ModalBehavior { constructor(element) { - const toggle = element; - toggle.addEventListener("click", this.onToggleClick.bind(this)); - this.toggle = toggle; - } + this.element = element; - async onToggleClick(event) { - // Ignore Ctrl + click - if (event.ctrlKey || event.metaKey) { - return; - } - event.preventDefault(); - event.stopPropagation(); - - // Create modal either by teleporting existing content or fetching from URL - const modal = this.toggle.hasAttribute("modal-content") - ? this.createFromContent() - : await this.createFromUrl(); - - if (!modal) { - return; - } - - // Register close handlers - const modalOverlay = modal.querySelector(".modal-overlay"); - const closeButton = modal.querySelector("button.close"); + const modalOverlay = element.querySelector(".modal-overlay"); + const closeButton = element.querySelector("button.close"); modalOverlay.addEventListener("click", this.onClose.bind(this)); closeButton.addEventListener("click", this.onClose.bind(this)); - - document.body.append(modal); - applyBehaviors(document.body); - this.modal = modal; - } - - async createFromUrl() { - const url = this.toggle.getAttribute("modal-url"); - const modalHtml = await fetch(url).then((response) => response.text()); - const parser = new DOMParser(); - const doc = parser.parseFromString(modalHtml, "text/html"); - return doc.querySelector(".modal"); - } - - createFromContent() { - const contentSelector = this.toggle.getAttribute("modal-content"); - const content = document.querySelector(contentSelector); - if (!content) { - return; - } - - // Todo: make title configurable, only used for tag cloud for now - const modal = document.createElement("div"); - modal.classList.add("modal", "active"); - modal.innerHTML = ` -
-