mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-07 10:58:25 +02:00
Convert tag modal into drawer (#977)
* change tag modal into drawer * improve scroll handling * teleport all side bar content * improve naming * fix focus trap in filter drawer
This commit is contained in:
@@ -12,13 +12,13 @@ class CollapseSidePanelE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def assertSidePanelIsVisible(self):
|
def assertSidePanelIsVisible(self):
|
||||||
expect(self.page.locator(".bookmarks-page .side-panel")).to_be_visible()
|
expect(self.page.locator(".bookmarks-page .side-panel")).to_be_visible()
|
||||||
expect(
|
expect(
|
||||||
self.page.locator(".bookmarks-page [ld-tag-modal-trigger]")
|
self.page.locator(".bookmarks-page [ld-filter-drawer-trigger]")
|
||||||
).not_to_be_visible()
|
).not_to_be_visible()
|
||||||
|
|
||||||
def assertSidePanelIsHidden(self):
|
def assertSidePanelIsHidden(self):
|
||||||
expect(self.page.locator(".bookmarks-page .side-panel")).not_to_be_visible()
|
expect(self.page.locator(".bookmarks-page .side-panel")).not_to_be_visible()
|
||||||
expect(
|
expect(
|
||||||
self.page.locator(".bookmarks-page [ld-tag-modal-trigger]")
|
self.page.locator(".bookmarks-page [ld-filter-drawer-trigger]")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|
||||||
def test_side_panel_should_be_visible_by_default(self):
|
def test_side_panel_should_be_visible_by_default(self):
|
||||||
|
@@ -4,7 +4,7 @@ from playwright.sync_api import sync_playwright, expect
|
|||||||
from bookmarks.e2e.helpers import LinkdingE2ETestCase
|
from bookmarks.e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
|
|
||||||
class TagCloudModalE2ETestCase(LinkdingE2ETestCase):
|
class FilterDrawerE2ETestCase(LinkdingE2ETestCase):
|
||||||
def test_show_modal_close_modal(self):
|
def test_show_modal_close_modal(self):
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
||||||
@@ -12,31 +12,31 @@ class TagCloudModalE2ETestCase(LinkdingE2ETestCase):
|
|||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
page = self.open(reverse("bookmarks:index"), p)
|
page = self.open(reverse("bookmarks:index"), p)
|
||||||
|
|
||||||
# use smaller viewport to make tags button visible
|
# use smaller viewport to make filter button visible
|
||||||
page.set_viewport_size({"width": 375, "height": 812})
|
page.set_viewport_size({"width": 375, "height": 812})
|
||||||
|
|
||||||
# open tag cloud modal
|
# open drawer
|
||||||
modal_trigger = page.locator(".content-area-header").get_by_role(
|
drawer_trigger = page.locator(".content-area-header").get_by_role(
|
||||||
"button", name="Tags"
|
"button", name="Filters"
|
||||||
)
|
)
|
||||||
modal_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify modal is visible
|
# verify drawer is visible
|
||||||
modal = page.locator(".modal")
|
drawer = page.locator(".modal.drawer.filter-drawer")
|
||||||
expect(modal).to_be_visible()
|
expect(drawer).to_be_visible()
|
||||||
expect(modal.locator("h2")).to_have_text("Tags")
|
expect(drawer.locator("h2")).to_have_text("Filters")
|
||||||
|
|
||||||
# close with close button
|
# close with close button
|
||||||
modal.locator("button.close").click()
|
drawer.locator("button.close").click()
|
||||||
expect(modal).to_be_hidden()
|
expect(drawer).to_be_hidden()
|
||||||
|
|
||||||
# open modal again
|
# open drawer again
|
||||||
modal_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# close with backdrop
|
# close with backdrop
|
||||||
backdrop = modal.locator(".modal-overlay")
|
backdrop = drawer.locator(".modal-overlay")
|
||||||
backdrop.click(position={"x": 0, "y": 0})
|
backdrop.click(position={"x": 0, "y": 0})
|
||||||
expect(modal).to_be_hidden()
|
expect(drawer).to_be_hidden()
|
||||||
|
|
||||||
def test_select_tag(self):
|
def test_select_tag(self):
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
||||||
@@ -45,29 +45,29 @@ class TagCloudModalE2ETestCase(LinkdingE2ETestCase):
|
|||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
page = self.open(reverse("bookmarks:index"), p)
|
page = self.open(reverse("bookmarks:index"), p)
|
||||||
|
|
||||||
# use smaller viewport to make tags button visible
|
# use smaller viewport to make filter button visible
|
||||||
page.set_viewport_size({"width": 375, "height": 812})
|
page.set_viewport_size({"width": 375, "height": 812})
|
||||||
|
|
||||||
# open tag cloud modal
|
# open tag cloud modal
|
||||||
modal_trigger = page.locator(".content-area-header").get_by_role(
|
drawer_trigger = page.locator(".content-area-header").get_by_role(
|
||||||
"button", name="Tags"
|
"button", name="Filters"
|
||||||
)
|
)
|
||||||
modal_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify tags are displayed
|
# verify tags are displayed
|
||||||
modal = page.locator(".modal")
|
drawer = page.locator(".modal.drawer.filter-drawer")
|
||||||
unselected_tags = modal.locator(".unselected-tags")
|
unselected_tags = drawer.locator(".unselected-tags")
|
||||||
expect(unselected_tags.get_by_text("cooking")).to_be_visible()
|
expect(unselected_tags.get_by_text("cooking")).to_be_visible()
|
||||||
expect(unselected_tags.get_by_text("hiking")).to_be_visible()
|
expect(unselected_tags.get_by_text("hiking")).to_be_visible()
|
||||||
|
|
||||||
# select tag
|
# select tag
|
||||||
unselected_tags.get_by_text("cooking").click()
|
unselected_tags.get_by_text("cooking").click()
|
||||||
|
|
||||||
# open modal again
|
# open drawer again
|
||||||
modal_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify tag is selected, other tag is not visible anymore
|
# verify tag is selected, other tag is not visible anymore
|
||||||
selected_tags = modal.locator(".selected-tags")
|
selected_tags = drawer.locator(".selected-tags")
|
||||||
expect(selected_tags.get_by_text("cooking")).to_be_visible()
|
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("cooking")).not_to_be_visible()
|
97
bookmarks/frontend/behaviors/filter-drawer.js
Normal file
97
bookmarks/frontend/behaviors/filter-drawer.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { Behavior, registerBehavior } from "./index";
|
||||||
|
import { ModalBehavior } from "./modal";
|
||||||
|
import { isKeyboardActive } from "./focus-utils";
|
||||||
|
|
||||||
|
class FilterDrawerTriggerBehavior extends Behavior {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
|
||||||
|
this.onClick = this.onClick.bind(this);
|
||||||
|
|
||||||
|
element.addEventListener("click", this.onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.element.removeEventListener("click", this.onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
const modal = document.createElement("div");
|
||||||
|
modal.classList.add("modal", "drawer", "filter-drawer");
|
||||||
|
modal.setAttribute("ld-filter-drawer", "");
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-container" role="dialog" aria-modal="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Filters</h2>
|
||||||
|
<button class="close" aria-label="Close dialog">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" 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="M18 6l-12 12"></path>
|
||||||
|
<path d="M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<section class="content content-area"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.querySelector(".modals").appendChild(modal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterDrawerBehavior extends ModalBehavior {
|
||||||
|
init() {
|
||||||
|
// Teleport content before creating focus trap, otherwise it will not detect
|
||||||
|
// focusable content elements
|
||||||
|
this.teleport();
|
||||||
|
super.init();
|
||||||
|
// Add active class to start slide-in animation
|
||||||
|
this.element.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
super.destroy();
|
||||||
|
// Always close on destroy to restore drawer content to original location
|
||||||
|
// before turbo caches DOM
|
||||||
|
this.doClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mapHeading(container, from, to) {
|
||||||
|
const headings = container.querySelectorAll(from);
|
||||||
|
headings.forEach((heading) => {
|
||||||
|
const newHeading = document.createElement(to);
|
||||||
|
newHeading.textContent = heading.textContent;
|
||||||
|
heading.replaceWith(newHeading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
teleport() {
|
||||||
|
const content = this.element.querySelector(".content");
|
||||||
|
const sidePanel = document.querySelector("section.side-panel");
|
||||||
|
content.append(...sidePanel.children);
|
||||||
|
this.mapHeading(content, "h2", "h3");
|
||||||
|
}
|
||||||
|
|
||||||
|
teleportBack() {
|
||||||
|
const sidePanel = document.querySelector("section.side-panel");
|
||||||
|
const content = this.element.querySelector(".content");
|
||||||
|
sidePanel.append(...content.children);
|
||||||
|
this.mapHeading(sidePanel, "h3", "h2");
|
||||||
|
}
|
||||||
|
|
||||||
|
doClose() {
|
||||||
|
super.doClose();
|
||||||
|
this.teleportBack();
|
||||||
|
|
||||||
|
// Try restore focus to drawer trigger
|
||||||
|
const restoreFocusElement =
|
||||||
|
document.querySelector("[ld-filter-drawer-trigger]") || document.body;
|
||||||
|
restoreFocusElement.focus({ focusVisible: isKeyboardActive() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerBehavior("ld-filter-drawer-trigger", FilterDrawerTriggerBehavior);
|
||||||
|
registerBehavior("ld-filter-drawer", FilterDrawerBehavior);
|
@@ -15,10 +15,7 @@ export class ModalBehavior extends Behavior {
|
|||||||
this.closeButton.addEventListener("click", this.onClose);
|
this.closeButton.addEventListener("click", this.onClose);
|
||||||
document.addEventListener("keydown", this.onKeyDown);
|
document.addEventListener("keydown", this.onKeyDown);
|
||||||
|
|
||||||
this.setupInert();
|
this.init();
|
||||||
this.focusTrap = new FocusTrapController(
|
|
||||||
element.querySelector(".modal-container"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@@ -30,11 +27,20 @@ export class ModalBehavior extends Behavior {
|
|||||||
this.focusTrap.destroy();
|
this.focusTrap.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.setupInert();
|
||||||
|
this.focusTrap = new FocusTrapController(
|
||||||
|
this.element.querySelector(".modal-container"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setupInert() {
|
setupInert() {
|
||||||
// Inert all other elements on the page
|
// Inert all other elements on the page
|
||||||
document
|
document
|
||||||
.querySelectorAll("body > *:not(.modals)")
|
.querySelectorAll("body > *:not(.modals)")
|
||||||
.forEach((el) => el.setAttribute("inert", ""));
|
.forEach((el) => el.setAttribute("inert", ""));
|
||||||
|
// Lock scroll on the body
|
||||||
|
document.body.classList.add("scroll-lock");
|
||||||
}
|
}
|
||||||
|
|
||||||
clearInert() {
|
clearInert() {
|
||||||
@@ -42,6 +48,8 @@ export class ModalBehavior extends Behavior {
|
|||||||
document
|
document
|
||||||
.querySelectorAll("body > *")
|
.querySelectorAll("body > *")
|
||||||
.forEach((el) => el.removeAttribute("inert"));
|
.forEach((el) => el.removeAttribute("inert"));
|
||||||
|
// Remove scroll lock from the body
|
||||||
|
document.body.classList.remove("scroll-lock");
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
|
@@ -1,76 +0,0 @@
|
|||||||
import { Behavior, registerBehavior } from "./index";
|
|
||||||
import { ModalBehavior } from "./modal";
|
|
||||||
import { isKeyboardActive } from "./focus-utils";
|
|
||||||
|
|
||||||
class TagModalTriggerBehavior extends Behavior {
|
|
||||||
constructor(element) {
|
|
||||||
super(element);
|
|
||||||
|
|
||||||
this.onClick = this.onClick.bind(this);
|
|
||||||
|
|
||||||
element.addEventListener("click", this.onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.element.removeEventListener("click", this.onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick() {
|
|
||||||
// Creates a new modal and teleports the tag cloud into it
|
|
||||||
const modal = document.createElement("div");
|
|
||||||
modal.classList.add("modal", "active");
|
|
||||||
modal.setAttribute("ld-tag-modal", "");
|
|
||||||
modal.innerHTML = `
|
|
||||||
<div class="modal-overlay"></div>
|
|
||||||
<div class="modal-container" role="dialog" aria-modal="true">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Tags</h2>
|
|
||||||
<button class="close" aria-label="Close dialog">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" 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="M18 6l-12 12"></path>
|
|
||||||
<path d="M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="content"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
modal.addEventListener("modal:close", this.onClose);
|
|
||||||
|
|
||||||
modal.tagCloud = document.querySelector(".tag-cloud");
|
|
||||||
modal.tagCloudContainer = modal.tagCloud.parentElement;
|
|
||||||
|
|
||||||
const content = modal.querySelector(".content");
|
|
||||||
content.appendChild(modal.tagCloud);
|
|
||||||
|
|
||||||
document.body.querySelector(".modals").appendChild(modal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TagModalBehavior extends ModalBehavior {
|
|
||||||
destroy() {
|
|
||||||
super.destroy();
|
|
||||||
// Always close on destroy to restore tag cloud to original parent before
|
|
||||||
// turbo caches DOM
|
|
||||||
this.doClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
doClose() {
|
|
||||||
super.doClose();
|
|
||||||
|
|
||||||
// Restore tag cloud to original parent
|
|
||||||
this.element.tagCloudContainer.appendChild(this.element.tagCloud);
|
|
||||||
|
|
||||||
// Try restore focus to tag cloud trigger
|
|
||||||
const restoreFocusElement =
|
|
||||||
document.querySelector("[ld-tag-modal-trigger]") || document.body;
|
|
||||||
restoreFocusElement.focus({ focusVisible: isKeyboardActive() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerBehavior("ld-tag-modal-trigger", TagModalTriggerBehavior);
|
|
||||||
registerBehavior("ld-tag-modal", TagModalBehavior);
|
|
@@ -3,13 +3,13 @@ import "./behaviors/bookmark-page";
|
|||||||
import "./behaviors/bulk-edit";
|
import "./behaviors/bulk-edit";
|
||||||
import "./behaviors/clear-button";
|
import "./behaviors/clear-button";
|
||||||
import "./behaviors/confirm-button";
|
import "./behaviors/confirm-button";
|
||||||
import "./behaviors/dropdown";
|
|
||||||
import "./behaviors/form";
|
|
||||||
import "./behaviors/details-modal";
|
import "./behaviors/details-modal";
|
||||||
|
import "./behaviors/dropdown";
|
||||||
|
import "./behaviors/filter-drawer";
|
||||||
|
import "./behaviors/form";
|
||||||
import "./behaviors/global-shortcuts";
|
import "./behaviors/global-shortcuts";
|
||||||
import "./behaviors/search-autocomplete";
|
import "./behaviors/search-autocomplete";
|
||||||
import "./behaviors/tag-autocomplete";
|
import "./behaviors/tag-autocomplete";
|
||||||
import "./behaviors/tag-modal";
|
|
||||||
|
|
||||||
export { default as TagAutoComplete } from "./components/TagAutocomplete.svelte";
|
export { default as TagAutoComplete } from "./components/TagAutocomplete.svelte";
|
||||||
export { default as SearchAutoComplete } from "./components/SearchAutoComplete.svelte";
|
export { default as SearchAutoComplete } from "./components/SearchAutoComplete.svelte";
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
grid-gap: var(--unit-9);
|
grid-gap: var(--unit-9);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ld-tag-modal-trigger] {
|
[ld-filter-drawer-trigger] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ld-tag-modal-trigger] {
|
[ld-filter-drawer-trigger] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ld-tag-modal-trigger] {
|
[ld-filter-drawer-trigger] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
/* Content area component */
|
/* Content area component */
|
||||||
section.content-area {
|
section.content-area {
|
||||||
h2 {
|
h2,
|
||||||
|
h3 {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +15,8 @@ section.content-area {
|
|||||||
padding-bottom: var(--unit-2);
|
padding-bottom: var(--unit-2);
|
||||||
margin-bottom: var(--unit-4);
|
margin-bottom: var(--unit-4);
|
||||||
|
|
||||||
h2 {
|
h2,
|
||||||
|
h3 {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
line-height: var(--unit-9);
|
line-height: var(--unit-9);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@@ -10,7 +10,6 @@ html {
|
|||||||
font-size: var(--html-font-size);
|
font-size: var(--html-font-size);
|
||||||
line-height: var(--html-line-height);
|
line-height: var(--html-line-height);
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
scrollbar-gutter: stable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reserve space for vert. scrollbar to avoid layout shifting when scrollbars are added */
|
/* Reserve space for vert. scrollbar to avoid layout shifting when scrollbars are added */
|
||||||
|
@@ -62,13 +62,14 @@
|
|||||||
gap: var(--unit-4);
|
gap: var(--unit-4);
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
max-width: var(--control-width-md);
|
max-width: var(--control-width-md);
|
||||||
padding: var(--unit-6);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
& .modal-header {
|
& .modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: var(--unit-2);
|
gap: var(--unit-2);
|
||||||
|
padding: var(--unit-6);
|
||||||
|
padding-bottom: 0;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
|
||||||
& h2 {
|
& h2 {
|
||||||
@@ -95,10 +96,53 @@
|
|||||||
|
|
||||||
& .modal-body {
|
& .modal-body {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: relative;
|
padding: 0 var(--unit-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .modal-body:not(:has(+ .modal-footer)) {
|
||||||
|
margin-bottom: var(--unit-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
& .modal-footer {
|
& .modal-footer {
|
||||||
|
padding: var(--unit-6);
|
||||||
|
padding-top: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal.drawer {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& .modal-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
border: none;
|
||||||
|
border-left: solid 1px var(--modal-container-border-color);
|
||||||
|
border-radius: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
animation: fade-in 0.25s ease 1;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
& .modal-container {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active.closing {
|
||||||
|
& .modal-container {
|
||||||
|
animation: fade-out 0.25s ease 1;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-lock {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
@@ -14,8 +14,7 @@
|
|||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
{% bookmark_search bookmark_list.search mode='archived' %}
|
{% bookmark_search bookmark_list.search mode='archived' %}
|
||||||
{% include 'bookmarks/bulk_edit/toggle.html' %}
|
{% include 'bookmarks/bulk_edit/toggle.html' %}
|
||||||
<button ld-tag-modal-trigger class="btn ml-2">Tags
|
<button ld-filter-drawer-trigger class="btn ml-2">Filters</button>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
{% bookmark_search bookmark_list.search %}
|
{% bookmark_search bookmark_list.search %}
|
||||||
{% include 'bookmarks/bulk_edit/toggle.html' %}
|
{% include 'bookmarks/bulk_edit/toggle.html' %}
|
||||||
<button ld-tag-modal-trigger class="btn ml-2">Tags</button>
|
<button ld-filter-drawer-trigger class="btn ml-2">Filters</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -13,8 +13,7 @@
|
|||||||
<h2>Shared bookmarks</h2>
|
<h2>Shared bookmarks</h2>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
{% bookmark_search bookmark_list.search mode='shared' %}
|
{% bookmark_search bookmark_list.search mode='shared' %}
|
||||||
<button ld-tag-modal-trigger class="btn ml-2">Tags
|
<button ld-filter-drawer-trigger class="btn ml-2">Filters</button>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -127,11 +127,11 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.collapse_side_panel.id_for_label }}" class="form-checkbox">
|
<label for="{{ form.collapse_side_panel.id_for_label }}" class="form-checkbox">
|
||||||
{{ form.collapse_side_panel }}
|
{{ form.collapse_side_panel }}
|
||||||
<i class="form-icon"></i> Collapse tags
|
<i class="form-icon"></i> Collapse side panel
|
||||||
</label>
|
</label>
|
||||||
<div class="form-input-hint">
|
<div class="form-input-hint">
|
||||||
When enabled, the tags side panel will be collapsed by default to give more space to the bookmark list.
|
When enabled, the tags side panel will be collapsed by default to give more space to the bookmark list.
|
||||||
Instead, the tags can be shown in a modal dialog by clicking the tags button in the bookmark list header.
|
Instead, the tags are shown in an expandable drawer.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
Reference in New Issue
Block a user