Created
February 21, 2025 15:03
-
-
Save pdcmoreira/fcee0c3d5e005a5d40e557a3aa58d73b to your computer and use it in GitHub Desktop.
Wiki.js arbitrary menu levels (experiment)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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