integreat_cms/static/src/js/pages/toggle-subpages.ts
/*
* The functionality to toggle subpages
*/
import { createIconsAt } from "../utils/create-icons";
import { restorePageTreeLayout, storeExpandedState } from "./persistent_page_tree";
/**
* This function iterates over all direct children of a page and
* toggles their "hidden" class if they are not yet collapsed.
* This enables to "save" the collapsed-state of all subpages, so when showing the subpages again,
* all previously collapsed subpages will remain collapsed.
*
* @param childrenIds Nodes to be toggled
*/
const toggleSubpagesRecursive = (childrenIds: Array<number>) => {
// Foreach child: toggle class "hidden" and proceed for all children which are not explicitly hidden themselves
childrenIds.forEach((childId) => {
// Get child table row
const child = document.getElementById(`page-${childId}`);
// Hide/show table row
child.classList.toggle("hidden");
// Remove the left sibling from possible drop targets while it is collapsed
document.getElementById(`page-${childId}-drop-left`)?.classList.toggle("drop-between");
// Find out whether this page has children itself
const collapseSpan = child.querySelector(".toggle-subpages") as HTMLElement;
if (collapseSpan) {
// The icon will be null if the page is a leaf node
const icon = collapseSpan.querySelector("svg");
if (icon.classList.contains("lucide-chevron-down")) {
// This means the children are not yet collapsed and have to be hidden as well
const grandChildren: number[] = JSON.parse(collapseSpan.getAttribute("data-page-children"));
toggleSubpagesRecursive(grandChildren);
}
}
});
};
/**
* Sets the collapsed state for the given span element and
* updates the icon.
* Updates the stored collapse state, too.
*
* @param element The element to update
* @param expanded whether the element should be collapsed or expanded
*/
const setExpandedState = (element: HTMLElement, expanded: boolean) => {
const id: number = JSON.parse(element.getAttribute("data-page-id"));
if (expanded) {
/* eslint-disable-next-line no-param-reassign */
element.innerHTML = '<i icon-name="chevron-down"></i>';
/* eslint-disable-next-line no-param-reassign */
element.title = element.getAttribute("data-collapse-title");
storeExpandedState(id, true);
} else {
/* eslint-disable-next-line no-param-reassign */
element.innerHTML = '<i icon-name="chevron-right"></i>';
/* eslint-disable-next-line no-param-reassign */
element.title = element.getAttribute("data-expand-title");
storeExpandedState(id, false);
}
createIconsAt(element);
};
/**
* This function toggles all subpages of the given element and changes the icon
*
* @param collapseSpan The page which should be toggled
*/
export const toggleSubpagesForElement = (collapseSpan: HTMLSpanElement) => {
const children: number[] = JSON.parse(collapseSpan.getAttribute("data-page-children"));
// Toggle subpages
toggleSubpagesRecursive(children);
// Change icon and title
const icon = collapseSpan.querySelector("svg");
if (icon.classList.contains("lucide-chevron-down")) {
setExpandedState(collapseSpan, false);
} else {
setExpandedState(collapseSpan, true);
}
};
/**
* This function toggles all subpages of the clicked page and changes the icon
*
* @param event Collapse/Expand button clicked
*/
const collapseAllPages = async () => {
(<HTMLElement[]>Array.from(document.querySelectorAll(".page-row"))).forEach((page: HTMLElement) => {
// Hide table row of it's not a root page
if (!page.classList.contains("level-1")) {
page.classList.add("hidden");
}
// Remove the left sibling from possible drop targets
document.getElementById(`${page.id}-drop-left`)?.classList.remove("drop-between");
// Find out whether this page has children itself
const span = page.querySelector(".toggle-subpages") as HTMLElement;
if (span) {
setExpandedState(span, false);
}
});
};
/**
* Expand all pages
*/
const expandAllPages = async () => {
(<HTMLElement[]>Array.from(document.querySelectorAll(".page-row"))).forEach((page: HTMLElement) => {
// Show table row
page.classList.remove("hidden");
// Add the left sibling to possible drop targets
document.getElementById(`${page.id}-drop-left`)?.classList.add("drop-between");
// Find out whether this page has children itself
const span = page.querySelector(".toggle-subpages") as HTMLElement;
if (span) {
setExpandedState(span, true);
}
});
};
/**
* This function toggles all subpages of the clicked page and changes the icon
*
* @param event Collapse/Expand button clicked
*/
export const toggleSubpages = (event: Event) => {
event.preventDefault();
toggleSubpagesForElement((event.target as HTMLElement).closest("span"));
};
/**
* Set all event handlers
*/
export const setToggleSubpagesEventListeners = () => {
if (!document.querySelector(".toggle-subpages")) {
return;
}
console.debug("Set event handlers for collapsing/expanding subpages");
// event handler to hide and show subpages
document.querySelectorAll<HTMLElement>(".toggle-subpages").forEach((element: HTMLElement) => {
element.addEventListener("click", toggleSubpages);
element.classList.remove("cursor-wait");
element.classList.add("cursor-pointer", "hover:scale-125");
/* eslint-disable-next-line no-param-reassign */
element.title = element.getAttribute("data-expand-title");
});
// Button to expand all pages at once
const expandAllPagesButton = document.getElementById("expand-all-pages");
expandAllPagesButton?.addEventListener("click", expandAllPages);
expandAllPagesButton?.classList.remove("!cursor-wait");
expandAllPagesButton?.classList.add("hover:underline", "group");
// Button to collapse all pages at once
const collapseAllPagesButton = document.getElementById("collapse-all-pages");
collapseAllPagesButton?.addEventListener("click", collapseAllPages);
collapseAllPagesButton?.classList.remove("!cursor-wait");
collapseAllPagesButton?.classList.add("hover:underline", "group");
};
window.addEventListener("icon-load", () => {
// On the page tree, the event listeners are set after all subpages have been loaded
if (!document.querySelector("[data-delay-event-handlers]")) {
setToggleSubpagesEventListeners();
restorePageTreeLayout();
}
});