Skip to content

Commit 0540cc8

Browse files
Update and rename app.js to hnlSite.js
1 parent 406edcf commit 0540cc8

2 files changed

Lines changed: 270 additions & 31 deletions

File tree

js/app.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

js/hnlSite.js

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// ==== CONFIG ====
2+
const OWNER = "HumanNeuronLab"; // Username
3+
const REPO = "HumanNeuronLab.github.io"; // Repository name
4+
const BRANCH = "main"; // Branch to display
5+
const SUBFOLDER = "pages"; // Root for all pages to be displayed
6+
const ICONSPATH = `${SUBFOLDER}/icons/`; // Folder containing inline SVG icons (defaults to "unlisted.icon" if none specified). File names are case-insensitive.
7+
const ORDERPATH = `${SUBFOLDER}/order.txt`; // File containing ordering list for the navigation bar. Unlisted items are appended in alphabetical order. Order of display is depth-specific. File names are case-insensitive.
8+
9+
const API_URL = `https://api.github.com/repos/${OWNER}/${REPO}/git/trees/${BRANCH}?recursive=1`;
10+
const RAW_BASE = `https://raw.githubusercontent.com/${OWNER}/${REPO}/${BRANCH}/`;
11+
12+
13+
// ==== EXECUTE ====
14+
main();
15+
16+
17+
// ==== HELPERS ====
18+
function capitalizeName(file, type) {
19+
let base = file;
20+
if (type === "file") {
21+
const dot = base.lastIndexOf(".");
22+
if (dot > 0) base = base.slice(0, dot);
23+
}
24+
return base.charAt(0).toUpperCase() + base.slice(1);
25+
}
26+
27+
function normalizeType(apiType) {
28+
return apiType === "blob" ? "file" : "directory";
29+
}
30+
31+
// recursively build a nested tree from flat paths
32+
function buildTree(flat, basePath = "", depth = 0) {
33+
const tree = [];
34+
35+
const entries = flat
36+
.filter(f => f.path.startsWith(basePath) && f.path !== basePath)
37+
.map(f => {
38+
const relPath = f.path.slice(basePath.length).replace(/^\/+/, "");
39+
const firstPart = relPath.split("/")[0];
40+
return { ...f, relPath, firstPart };
41+
});
42+
43+
const groups = {};
44+
for (const e of entries) {
45+
if (!groups[e.firstPart]) groups[e.firstPart] = [];
46+
groups[e.firstPart].push(e);
47+
}
48+
49+
for (const [part, group] of Object.entries(groups)) {
50+
const fullPath = basePath ? basePath + "/" + part : part;
51+
const first = group[0];
52+
const type = group.some(g => g.relPath.includes("/")) ? "directory" : normalizeType(first.type);
53+
54+
// skip order.txt and icons folder
55+
if (part.toLowerCase() === "order.txt") continue;
56+
if (part.toLowerCase() === "icons") continue;
57+
58+
const node = {
59+
file: part,
60+
path: fullPath,
61+
name: capitalizeName(part, type),
62+
type,
63+
depth,
64+
download_url: type === "file" ? RAW_BASE + fullPath : null,
65+
svg: null,
66+
children: []
67+
};
68+
69+
if (type === "directory") {
70+
node.children = buildTree(group, fullPath, depth + 1);
71+
}
72+
73+
tree.push(node);
74+
}
75+
76+
return tree;
77+
}
78+
79+
// reorder children recursively using order.txt
80+
function reorderTree(tree, orders, icons) {
81+
function reorderChildren(children, orderList) {
82+
const orderLower = orderList.map(x => x.toLowerCase().trim());
83+
const listed = [];
84+
const unlisted = [];
85+
86+
for (const child of children) {
87+
const matchIdx = orderLower.indexOf(child.file.toLowerCase());
88+
if (matchIdx >= 0) {
89+
listed[matchIdx] = child;
90+
} else {
91+
unlisted.push(child);
92+
}
93+
}
94+
95+
const sortedUnlisted = unlisted.sort((a, b) =>
96+
a.file.toLowerCase().localeCompare(b.file.toLowerCase())
97+
);
98+
99+
const reordered = listed.filter(Boolean).concat(sortedUnlisted);
100+
101+
// attach svg icons properly
102+
for (const child of reordered) {
103+
// strip extension if file, keep folder name as-is
104+
let iconKey = (child.type === "file"
105+
? child.file.replace(/\.[^/.]+$/, "")
106+
: child.file).toLowerCase().trim();
107+
108+
if (icons[iconKey]) {
109+
child.svg = icons[iconKey];
110+
} else if (icons["unlisted"]) {
111+
child.svg = icons["unlisted"];
112+
}
113+
114+
if (child.type === "directory") {
115+
child.children = reorderChildren(child.children, orderList);
116+
}
117+
console.log("Looking for icon:", iconKey, "Available:", Object.keys(icons));
118+
}
119+
120+
return reordered;
121+
}
122+
123+
return reorderChildren(tree, orders);
124+
}
125+
126+
function createNavbar(finalTree,elementParent) {
127+
const ul = document.createElement('ul');
128+
elementParent.appendChild(ul);
129+
var subparent = [];
130+
if (elementParent.id === "sidebar") {
131+
const uli = document.createElement('li');
132+
ul.appendChild(uli);
133+
const span = document.createElement('span');
134+
span.className = "logo";
135+
span.innerText = 'Human Neuron Lab';
136+
uli.appendChild(span);
137+
const uliButton = document.createElement('button');
138+
uli.appendChild(uliButton);
139+
uliButton.setAttribute('onclick','toggleSidebar(this)');
140+
uliButton.setAttribute('id','toggle-btn');
141+
uliButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z"/></svg>';
142+
uli.appendChild(uliButton);
143+
subparent = ul;
144+
} else {
145+
ul.className = 'sub-menu';
146+
const div = document.createElement('div');
147+
ul.appendChild(div);
148+
subparent = div;
149+
}
150+
for (const currentEntry of finalTree){
151+
const li = document.createElement('li');
152+
subparent.appendChild(li);
153+
const liButton = document.createElement('button');
154+
if (elementParent.id === "sidebar" && finalTree.indexOf(currentEntry) === 0){
155+
li.className = "active";
156+
}
157+
liButton.innerHTML = currentEntry.svg;
158+
li.appendChild(liButton);
159+
const span = document.createElement('span');
160+
span.innerText = currentEntry.name;
161+
liButton.appendChild(span);
162+
if (currentEntry.type === "file"){
163+
if (elementParent.id === "sidebar" && finalTree.indexOf(currentEntry) === 0){
164+
liButton.setAttribute('id','homePage');
165+
}
166+
liButton.className = 'menu-btn';
167+
liButton.setAttribute('onclick','openSesame(this)');
168+
liButton.setAttribute('HNL-link',currentEntry.download_url);
169+
} else {
170+
liButton.className = 'dropdown-btn';
171+
liButton.setAttribute('onclick','toggleSubMenu(this)');
172+
liButton.innerHTML += '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-361q-8 0-15-2.5t-13-8.5L268-556q-11-11-11-28t11-28q11-11 28-11t28 11l156 156 156-156q11-11 28-11t28 11q11 11 11 28t-11 28L508-372q-6 6-13 8.5t-15 2.5Z"/></svg>';
173+
}
174+
console.log(currentEntry);
175+
const idx = finalTree.indexOf(currentEntry);
176+
console.log(idx);
177+
if (currentEntry.children.length > 0){
178+
createNavbar(currentEntry.children,li);
179+
};
180+
}
181+
}
182+
183+
function toggleSidebar(toggleButton){
184+
const sidebar = document.getElementById("sidebar");
185+
sidebar.classList.toggle('close')
186+
toggleButton.classList.toggle('rotate')
187+
188+
closeAllSubMenus()
189+
}
190+
191+
function toggleSubMenu(button){
192+
193+
if(!button.nextElementSibling.classList.contains('show')){
194+
closeAllSubMenus()
195+
}
196+
197+
button.nextElementSibling.classList.toggle('show')
198+
button.classList.toggle('rotate')
199+
200+
if(sidebar.classList.contains('close')){
201+
sidebar.classList.toggle('close')
202+
toggleButton.classList.toggle('rotate')
203+
}
204+
}
205+
206+
function closeAllSubMenus(){
207+
const sidebar = document.getElementById("sidebar");
208+
Array.from(sidebar.getElementsByClassName('show')).forEach(ul => {
209+
ul.classList.remove('show')
210+
ul.previousElementSibling.classList.remove('rotate')
211+
})
212+
}
213+
214+
async function openSesame(button){
215+
let x = await fetch(button.getAttribute("hnl-link"));
216+
let y = await x.text();
217+
document.getElementById("logger").innerHTML = y;
218+
myList = document.getElementsByTagName("li");
219+
for (i = 0; i < myList.length; i++) {
220+
myList[i].classList.remove("active");
221+
}
222+
button.parentNode.classList.add("active");
223+
}
224+
225+
async function main() {
226+
try {
227+
// 1. Fetch repo tree
228+
const res = await fetch(API_URL);
229+
if (!res.ok) throw new Error("Failed to fetch repo tree");
230+
const repoData = await res.json();
231+
const flat = repoData.tree;
232+
233+
// 2. Fetch all icons
234+
const icons = {};
235+
const iconFiles = flat.filter(f => f.path.startsWith(ICONSPATH) && f.path.endsWith(".icon"));
236+
for (const icon of iconFiles) {
237+
const svgRes = await fetch(RAW_BASE + icon.path);
238+
if (svgRes.ok) {
239+
const svgText = await svgRes.text();
240+
const key = icon.path.split("/").pop().replace(/\.icon$/i, "").toLowerCase();
241+
icons[key] = svgText;
242+
}
243+
}
244+
245+
// 3. Fetch order.txt (only root-level)
246+
let orderList = [];
247+
const orderFile = flat.find(f => f.path.toLowerCase() === ORDERPATH);
248+
if (orderFile) {
249+
const orderRes = await fetch(RAW_BASE + orderFile.path);
250+
orderPath = RAW_BASE + orderFile.path;
251+
if (orderRes.ok) {
252+
const text = await orderRes.text();
253+
orderList = text.split(/\r?\n/).filter(Boolean);
254+
}
255+
}
256+
257+
// 4. Build + reorder
258+
const tree = buildTree(flat, SUBFOLDER, 0);
259+
const finalTree = reorderTree(tree, orderList, icons);
260+
261+
//5. Create and populate the navbar
262+
const sidebar = document.getElementById("sidebar");
263+
createNavbar(finalTree,sidebar);
264+
openSesame(document.getElementById("homePage"));
265+
266+
// document.getElementById("output").textContent = JSON.stringify(finalTree, null, 2);
267+
} catch (err) {
268+
// document.getElementById("output").textContent = "Error: " + err.message;
269+
}
270+
}

0 commit comments

Comments
 (0)