Skip to content

Instantly share code, notes, and snippets.

@pdcmoreira
Created February 21, 2025 15:03
Show Gist options
  • Save pdcmoreira/fcee0c3d5e005a5d40e557a3aa58d73b to your computer and use it in GitHub Desktop.
Save pdcmoreira/fcee0c3d5e005a5d40e557a3aa58d73b to your computer and use it in GitHub Desktop.
Wiki.js arbitrary menu levels (experiment)
<script>
window.boot.register("page-ready", () => {
const pathSeparator = "/";
const maxGroupsSafeGuard = 100;
const getNodeTitleElement = (node) =>
node.querySelector(".v-list-item__title");
const getNodeTitle = (node) => getNodeTitleElement(node).textContent;
const setNodeTitle = (node, title) => {
getNodeTitleElement(node).textContent = title;
};
const getNodePath = (node) => getNodeTitle(node).split(pathSeparator);
const nodeIsDivider = (node) =>
["v-subheader", "v-divider"].some((className) =>
node.classList.contains(className)
);
const appendChild = (parent, child) => {
let childrenContainer = parent.querySelector("&>.children");
if (!childrenContainer) {
const itemInfoContainer = document.createElement("div");
itemInfoContainer.classList.add("d-flex");
itemInfoContainer.style.width = "100%";
itemInfoContainer.append(...parent.childNodes);
parent.append(itemInfoContainer);
parent.classList.add("d-flex", "flex-column", "align-start");
childrenContainer = document.createElement("div");
childrenContainer.classList.add("children", "v-list", "pa-0");
childrenContainer.style.width = "100%";
parent.append(childrenContainer);
}
childrenContainer.append(child);
};
const compositeTitle = (path) => [
path.slice(0, 2).join(pathSeparator),
...path.slice(2),
];
const nodes = Array.from(
document.querySelectorAll(".v-navigation-drawer .v-list>*")
);
let currentGroupRange = null;
const findNextGroupRange = (searchStart) => {
let start = nodes
.slice(searchStart)
.findIndex((item) => !nodeIsDivider(item));
if (start < 0) {
return null;
}
start += searchStart;
let end = nodes.slice(start).findIndex((item) => nodeIsDivider(item));
end = end >= 0 ? end + start - 1 : nodes.length - 1;
return { start, end };
};
const moveToNextGroupRange = () => {
const searchStart = currentGroupRange ? currentGroupRange.end + 1 : 0;
currentGroupRange = findNextGroupRange(searchStart);
};
let safeGuardLoopCounter = 0;
const groups = [];
moveToNextGroupRange();
while (currentGroupRange) {
if (safeGuardLoopCounter >= maxGroupsSafeGuard) {
throw new Error("Something went wrong when resolving menu hierarchy.");
}
const { start, end } = currentGroupRange;
// Order by depth, to ensure that parents are processed before their children
const groupNodes = nodes
.slice(start, end + 1)
.sort((a, b) => getNodePath(a).length - getNodePath(b).length);
groups.push(groupNodes);
safeGuardLoopCounter++;
moveToNextGroupRange();
}
groups.forEach((groupNodes) => {
const findParent = (path, ancestor = null) => {
if (!path || !path.length) {
return null;
}
if (path.length === 1) {
return {
parent: ancestor,
title: path[0],
};
}
if (!ancestor) {
// Find the root ancestor
ancestor = groupNodes.find(
(groupNode) => getNodeTitle(groupNode) === path[0]
);
if (!ancestor) {
// If no root ancestor was found, try again with a composite title, by joining 2 parts as
// a single one
return findParent(compositeTitle(path));
}
// Now that we have an ancestor, go again with the next part of the path
return findParent(path.slice(1), ancestor);
}
// Get child nodes - may or may not have a children container already
const childNodes = Array.from(
ancestor.querySelector("&>.children")
? ancestor.querySelectorAll("&>.children>*")
: ancestor.childNodes
);
if (!childNodes.length) {
return {
parent: ancestor,
title: path.join(pathSeparator),
};
}
let matchingChild = childNodes.find(
(childNode) => getNodeTitle(childNode) === path[0]
);
if (!matchingChild) {
// If no matching child was found, try again with a composite title, by joining 2 parts as a
// single one
return findParent(compositeTitle(path), ancestor);
}
// Continue digging
return findParent(path.slice(1), matchingChild);
};
groupNodes.forEach((node) => {
const { parent, title } = findParent(getNodePath(node));
setNodeTitle(node, title);
if (parent) {
appendChild(parent, node);
}
});
});
// Add some CSS hacks
const styleNode = document.createElement("style");
const cssString = `
.v-navigation-drawer .v-list-item:after {
display: none;
}
`;
styleNode.textContent = cssString;
document.head.append(styleNode);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment