mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-09-19 15:39:32 +02:00
Use modal dialog for confirming actions (#1168)
* Use modal dialog for confirming actions * cleanup unused state
This commit is contained in:
@@ -1,79 +1,173 @@
|
||||
import { Behavior, registerBehavior } from "./index";
|
||||
import { FocusTrapController, isKeyboardActive } from "./focus-utils";
|
||||
|
||||
let confirmId = 0;
|
||||
|
||||
function nextConfirmId() {
|
||||
return `confirm-${confirmId++}`;
|
||||
}
|
||||
|
||||
class ConfirmButtonBehavior extends Behavior {
|
||||
constructor(element) {
|
||||
super(element);
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
element.addEventListener("click", this.onClick);
|
||||
this.element.addEventListener("click", this.onClick);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.reset();
|
||||
if (this.opened) {
|
||||
this.close();
|
||||
}
|
||||
this.element.removeEventListener("click", this.onClick);
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
event.preventDefault();
|
||||
Behavior.interacting = true;
|
||||
|
||||
const container = document.createElement("span");
|
||||
container.className = "confirmation";
|
||||
|
||||
const icon = this.element.getAttribute("ld-confirm-icon");
|
||||
if (icon) {
|
||||
const iconElement = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg",
|
||||
);
|
||||
iconElement.style.width = "16px";
|
||||
iconElement.style.height = "16px";
|
||||
iconElement.innerHTML = `<use xlink:href="#${icon}"></use>`;
|
||||
container.append(iconElement);
|
||||
if (this.opened) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
|
||||
const question = this.element.getAttribute("ld-confirm-question");
|
||||
if (question) {
|
||||
const questionElement = document.createElement("span");
|
||||
questionElement.innerText = question;
|
||||
container.append(question);
|
||||
}
|
||||
|
||||
const buttonClasses = Array.from(this.element.classList.values())
|
||||
.filter((cls) => cls.startsWith("btn"))
|
||||
.join(" ");
|
||||
|
||||
const cancelButton = document.createElement(this.element.nodeName);
|
||||
cancelButton.type = "button";
|
||||
cancelButton.innerText = question ? "No" : "Cancel";
|
||||
cancelButton.className = `${buttonClasses} mr-1`;
|
||||
cancelButton.addEventListener("click", this.reset.bind(this));
|
||||
|
||||
const confirmButton = document.createElement(this.element.nodeName);
|
||||
confirmButton.type = this.element.type;
|
||||
confirmButton.name = this.element.name;
|
||||
confirmButton.value = this.element.value;
|
||||
confirmButton.innerText = question ? "Yes" : "Confirm";
|
||||
confirmButton.className = buttonClasses;
|
||||
confirmButton.addEventListener("click", this.reset.bind(this));
|
||||
|
||||
container.append(cancelButton, confirmButton);
|
||||
this.container = container;
|
||||
|
||||
this.element.before(container);
|
||||
this.element.classList.add("d-none");
|
||||
}
|
||||
|
||||
reset() {
|
||||
setTimeout(() => {
|
||||
Behavior.interacting = false;
|
||||
if (this.container) {
|
||||
this.container.remove();
|
||||
this.container = null;
|
||||
}
|
||||
this.element.classList.remove("d-none");
|
||||
open() {
|
||||
const dropdown = document.createElement("div");
|
||||
dropdown.className = "dropdown confirm-dropdown active";
|
||||
|
||||
const confirmId = nextConfirmId();
|
||||
const questionId = `${confirmId}-question`;
|
||||
|
||||
const menu = document.createElement("div");
|
||||
menu.className = "menu with-arrow";
|
||||
menu.role = "alertdialog";
|
||||
menu.setAttribute("aria-modal", "true");
|
||||
menu.setAttribute("aria-labelledby", questionId);
|
||||
menu.addEventListener("keydown", this.onMenuKeyDown.bind(this));
|
||||
|
||||
const question = document.createElement("span");
|
||||
question.id = questionId;
|
||||
question.textContent =
|
||||
this.element.getAttribute("ld-confirm-question") || "Are you sure?";
|
||||
question.style.fontWeight = "bold";
|
||||
|
||||
const cancelButton = document.createElement("button");
|
||||
cancelButton.textContent = "Cancel";
|
||||
cancelButton.type = "button";
|
||||
cancelButton.className = "btn";
|
||||
cancelButton.tabIndex = 0;
|
||||
cancelButton.addEventListener("click", () => this.close());
|
||||
|
||||
const confirmButton = document.createElement("button");
|
||||
confirmButton.textContent = "Confirm";
|
||||
confirmButton.type = "submit";
|
||||
confirmButton.name = this.element.name;
|
||||
confirmButton.value = this.element.value;
|
||||
confirmButton.className = "btn btn-error";
|
||||
confirmButton.addEventListener("click", () => this.confirm());
|
||||
|
||||
const arrow = document.createElement("div");
|
||||
arrow.className = "menu-arrow";
|
||||
|
||||
menu.append(question, cancelButton, confirmButton, arrow);
|
||||
dropdown.append(menu);
|
||||
document.body.append(dropdown);
|
||||
|
||||
this.positionController = new AnchorPositionController(this.element, menu);
|
||||
this.focusTrap = new FocusTrapController(menu);
|
||||
this.dropdown = dropdown;
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
onMenuKeyDown(event) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.element.closest("form").requestSubmit(this.element);
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.opened) return;
|
||||
this.positionController.destroy();
|
||||
this.focusTrap.destroy();
|
||||
this.dropdown.remove();
|
||||
this.element.focus({ focusVisible: isKeyboardActive() });
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
class AnchorPositionController {
|
||||
constructor(anchor, overlay) {
|
||||
this.anchor = anchor;
|
||||
this.overlay = overlay;
|
||||
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
window.addEventListener("scroll", this.handleScroll, { capture: true });
|
||||
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
handleScroll() {
|
||||
if (this.debounce) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debounce = true;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.updatePosition();
|
||||
this.debounce = false;
|
||||
});
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
const anchorRect = this.anchor.getBoundingClientRect();
|
||||
const overlayRect = this.overlay.getBoundingClientRect();
|
||||
const bufferX = 10;
|
||||
const bufferY = 30;
|
||||
|
||||
let left = anchorRect.left - (overlayRect.width - anchorRect.width) / 2;
|
||||
const initialLeft = left;
|
||||
const overflowLeft = left < bufferX;
|
||||
const overflowRight =
|
||||
left + overlayRect.width > window.innerWidth - bufferX;
|
||||
|
||||
if (overflowLeft) {
|
||||
left = bufferX;
|
||||
} else if (overflowRight) {
|
||||
left = window.innerWidth - overlayRect.width - bufferX;
|
||||
}
|
||||
|
||||
const delta = initialLeft - left;
|
||||
this.overlay.style.setProperty("--arrow-offset", `${delta}px`);
|
||||
|
||||
let top = anchorRect.bottom;
|
||||
const overflowBottom =
|
||||
top + overlayRect.height > window.innerHeight - bufferY;
|
||||
|
||||
if (overflowBottom) {
|
||||
top = anchorRect.top - overlayRect.height;
|
||||
this.overlay.classList.remove("top-aligned");
|
||||
this.overlay.classList.add("bottom-aligned");
|
||||
} else {
|
||||
this.overlay.classList.remove("bottom-aligned");
|
||||
this.overlay.classList.add("top-aligned");
|
||||
}
|
||||
|
||||
this.overlay.style.left = `${left}px`;
|
||||
this.overlay.style.top = `${top}px`;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener("scroll", this.handleScroll, { capture: true });
|
||||
}
|
||||
}
|
||||
|
||||
registerBehavior("ld-confirm-button", ConfirmButtonBehavior);
|
||||
|
@@ -93,6 +93,12 @@ document.addEventListener("turbo:load", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if there is a modal dialog, which should handle its own focus
|
||||
const modal = document.querySelector("[aria-modal='true']");
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is an explicit focus target for the next page load
|
||||
for (const target of afterPageLoadFocusTarget) {
|
||||
const element = document.querySelector(target);
|
||||
|
@@ -54,8 +54,6 @@ export class Behavior {
|
||||
destroy() {}
|
||||
}
|
||||
|
||||
Behavior.interacting = false;
|
||||
|
||||
export function registerBehavior(name, behavior) {
|
||||
behaviorRegistry[name] = behavior;
|
||||
}
|
||||
|
@@ -23,32 +23,22 @@ export class ModalBehavior extends Behavior {
|
||||
this.closeButton.removeEventListener("click", this.onClose);
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
|
||||
this.clearInert();
|
||||
this.removeScrollLock();
|
||||
this.focusTrap.destroy();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupInert();
|
||||
this.setupScrollLock();
|
||||
this.focusTrap = new FocusTrapController(
|
||||
this.element.querySelector(".modal-container"),
|
||||
);
|
||||
}
|
||||
|
||||
setupInert() {
|
||||
// Inert all other elements on the page
|
||||
document
|
||||
.querySelectorAll("body > *:not(.modals)")
|
||||
.forEach((el) => el.setAttribute("inert", ""));
|
||||
// Lock scroll on the body
|
||||
setupScrollLock() {
|
||||
document.body.classList.add("scroll-lock");
|
||||
}
|
||||
|
||||
clearInert() {
|
||||
// Clear inert attribute from all elements to allow focus outside the modal again
|
||||
document
|
||||
.querySelectorAll("body > *")
|
||||
.forEach((el) => el.removeAttribute("inert"));
|
||||
// Remove scroll lock from the body
|
||||
removeScrollLock() {
|
||||
document.body.classList.remove("scroll-lock");
|
||||
}
|
||||
|
||||
@@ -85,7 +75,7 @@ export class ModalBehavior extends Behavior {
|
||||
|
||||
doClose() {
|
||||
this.element.remove();
|
||||
this.clearInert();
|
||||
this.removeScrollLock();
|
||||
this.element.dispatchEvent(new CustomEvent("modal:close"));
|
||||
}
|
||||
}
|
||||
|
@@ -31,22 +31,17 @@
|
||||
}
|
||||
|
||||
/* Confirm button component */
|
||||
span.confirmation {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--unit-1);
|
||||
color: var(--error-color) !important;
|
||||
.confirm-dropdown.active {
|
||||
position: fixed;
|
||||
z-index: 500;
|
||||
|
||||
svg {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.btn.btn-link {
|
||||
color: var(--error-color) !important;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
& .menu {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
gap: var(--unit-2);
|
||||
padding: var(--unit-2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -87,4 +87,43 @@
|
||||
border-bottom: solid 1px var(--secondary-border-color);
|
||||
margin: var(--unit-2) 0;
|
||||
}
|
||||
|
||||
&.with-arrow {
|
||||
overflow: visible;
|
||||
--arrow-size: 16px;
|
||||
--arrow-offset: 0px;
|
||||
|
||||
.menu-arrow {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset-inline-start: calc(50% + var(--arrow-offset));
|
||||
top: 0;
|
||||
width: var(--arrow-size);
|
||||
height: var(--arrow-size);
|
||||
translate: -50% -50%;
|
||||
rotate: 45deg;
|
||||
background: inherit;
|
||||
border: inherit;
|
||||
clip-path: polygon(0 0, 0 100%, 100% 0);
|
||||
}
|
||||
|
||||
&.top-aligned {
|
||||
transform: translateY(
|
||||
calc(calc(var(--arrow-size) / 2) + var(--layout-spacing-sm))
|
||||
);
|
||||
}
|
||||
|
||||
&.bottom-aligned {
|
||||
transform: translateY(
|
||||
calc(calc(calc(var(--arrow-size) / 2) + var(--layout-spacing-sm)) * -1)
|
||||
);
|
||||
|
||||
.menu-arrow {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
rotate: 225deg;
|
||||
translate: -50% 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -106,7 +106,6 @@
|
||||
& .modal-footer {
|
||||
padding: var(--unit-6);
|
||||
padding-top: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -120,7 +120,7 @@
|
||||
{% if bookmark_item.show_mark_as_read %}
|
||||
<button type="submit" name="mark_as_read" value="{{ bookmark_item.id }}"
|
||||
class="btn btn-link btn-sm btn-icon"
|
||||
ld-confirm-button ld-confirm-icon="ld-icon-read" ld-confirm-question="Mark as read?">
|
||||
ld-confirm-button ld-confirm-question="Mark as read?">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<use xlink:href="#ld-icon-unread"></use>
|
||||
</svg>
|
||||
@@ -130,7 +130,7 @@
|
||||
{% if bookmark_item.show_unshare %}
|
||||
<button type="submit" name="unshare" value="{{ bookmark_item.id }}"
|
||||
class="btn btn-link btn-sm btn-icon"
|
||||
ld-confirm-button ld-confirm-icon="ld-icon-unshare" ld-confirm-question="Unshare?">
|
||||
ld-confirm-button ld-confirm-question="Unshare?">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<use xlink:href="#ld-icon-share"></use>
|
||||
</svg>
|
||||
|
@@ -32,7 +32,7 @@
|
||||
<input type="hidden" name="disable_turbo" value="true">
|
||||
<button ld-confirm-button class="btn btn-error btn-wide"
|
||||
type="submit" name="remove" value="{{ details.bookmark.id }}">
|
||||
Delete...
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@@ -18,18 +18,6 @@
|
||||
<path d="M21 6l0 13"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="ld-icon-read" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 5.899 -1.096"></path>
|
||||
<path d="M3 6a9 9 0 0 1 2.114 -.884m3.8 -.21c1.07 .17 2.116 .534 3.086 1.094a9 9 0 0 1 9 0"></path>
|
||||
<path d="M3 6v13"></path>
|
||||
<path d="M12 6v2m0 4v7"></path>
|
||||
<path d="M21 6v11"></path>
|
||||
<path d="M3 3l18 18"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="ld-icon-share" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -41,18 +29,6 @@
|
||||
<path d="M8.7 13.3l6.6 3.4"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="ld-icon-unshare" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
|
||||
<path d="M18 6m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
|
||||
<path d="M15.861 15.896a3 3 0 0 0 4.265 4.22m.578 -3.417a3.012 3.012 0 0 0 -1.507 -1.45"></path>
|
||||
<path d="M8.7 10.7l1.336 -.688m2.624 -1.352l2.64 -1.36"></path>
|
||||
<path d="M8.7 13.3l6.6 3.4"></path>
|
||||
<path d="M3 3l18 18"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="ld-icon-note" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
|
@@ -501,7 +501,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||
modal = self.get_index_details_modal(bookmark)
|
||||
delete_button = modal.find("button", {"type": "submit", "name": "remove"})
|
||||
self.assertIsNotNone(delete_button)
|
||||
self.assertEqual("Delete...", delete_button.text.strip())
|
||||
self.assertEqual("Delete", delete_button.text.strip())
|
||||
self.assertEqual(str(bookmark.id), delete_button["value"])
|
||||
|
||||
form = delete_button.find_parent("form")
|
||||
|
@@ -231,7 +231,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
f"""
|
||||
<button type="submit" name="unshare" value="{bookmark.id}"
|
||||
class="btn btn-link btn-sm btn-icon"
|
||||
ld-confirm-button ld-confirm-icon="ld-icon-unshare" ld-confirm-question="Unshare?">
|
||||
ld-confirm-button ld-confirm-question="Unshare?">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<use xlink:href="#ld-icon-share"></use>
|
||||
</svg>
|
||||
@@ -247,7 +247,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
f"""
|
||||
<button type="submit" name="mark_as_read" value="{bookmark.id}"
|
||||
class="btn btn-link btn-sm btn-icon"
|
||||
ld-confirm-button ld-confirm-icon="ld-icon-read" ld-confirm-question="Mark as read?">
|
||||
ld-confirm-button ld-confirm-question="Mark as read?">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<use xlink:href="#ld-icon-unread"></use>
|
||||
</svg>
|
||||
|
@@ -140,8 +140,8 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
# Delete bookmark, verify return url
|
||||
with self.page.expect_navigation(url=self.live_server_url + url):
|
||||
details_modal.get_by_text("Delete...").click()
|
||||
details_modal.get_by_text("Confirm").click()
|
||||
details_modal.get_by_text("Delete").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
# verify bookmark is deleted
|
||||
self.locate_bookmark(bookmark.title)
|
||||
@@ -173,7 +173,7 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
# Remove snapshot
|
||||
asset_list.get_by_text("Remove", exact=False).click()
|
||||
asset_list.get_by_text("Confirm", exact=False).click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm", exact=False).click()
|
||||
|
||||
# Snapshot is removed
|
||||
expect(snapshot).not_to_be_visible()
|
||||
|
@@ -46,7 +46,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
|
||||
@@ -84,7 +84,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
|
||||
@@ -122,7 +122,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
|
||||
@@ -160,7 +160,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
|
||||
@@ -291,7 +291,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
# Execute bulk action
|
||||
self.select_bulk_action("Mark as unread")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
@@ -323,7 +323,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||
expect(bookmark_list).not_to_be_visible()
|
||||
|
||||
|
@@ -123,7 +123,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
self.open(reverse("linkding:bookmarks.index"), p)
|
||||
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Remove").click()
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||
@@ -140,7 +140,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread")
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Unread").click()
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Yes").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread")
|
||||
self.assertReloads(0)
|
||||
@@ -156,7 +156,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
|
||||
expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared")
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Shared").click()
|
||||
self.locate_bookmark("Bookmark 2").get_by_text("Yes").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared")
|
||||
self.assertReloads(0)
|
||||
@@ -173,7 +173,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
).click()
|
||||
self.select_bulk_action("Archive")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||
@@ -191,7 +191,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
).click()
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||
@@ -216,7 +216,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
||||
|
||||
self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click()
|
||||
self.locate_bookmark("Archived Bookmark 2").get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||
@@ -234,7 +234,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
).click()
|
||||
self.select_bulk_action("Unarchive")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||
@@ -252,7 +252,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
).click()
|
||||
self.select_bulk_action("Delete")
|
||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||
self.locate_bulk_edit_bar().get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||
@@ -293,7 +293,7 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
||||
self.open(reverse("linkding:bookmarks.shared"), p)
|
||||
|
||||
self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click()
|
||||
self.locate_bookmark("My Bookmark 2").get_by_text("Confirm").click()
|
||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||
|
||||
self.assertVisibleBookmarks(
|
||||
[
|
||||
|
@@ -92,3 +92,6 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
||||
).click()
|
||||
else:
|
||||
self.page.locator("nav").get_by_text(main_menu_item, exact=True).click()
|
||||
|
||||
def locate_confirm_dialog(self):
|
||||
return self.page.locator(".dropdown.confirm-dropdown")
|
||||
|
Reference in New Issue
Block a user