// src/index.ts async function githubTree(name, ref) { return { id: 0, path: "", filename: name, kind: { type: "dir", children: await githubListRecursive(name, ref) } }; } async function githubListRecursive(name, ref, path = "", nextId = [1]) { const fileNodes = await fetch( `https://api.github.com/repos/${name}/contents${path}?ref=${ref}` ).then((res) => res.json()); return Promise.all( fileNodes.map(async (node) => { const id = nextId[0]++; if (node.type === "dir") { return await githubListRecursive( name, ref, `${path}/${node.name}`, nextId ).then((children) => ({ id, filename: node.name, path, kind: { type: "dir", children } })); } else if (node.type === "file") { return { id, filename: node.name, path, kind: { type: "file", url: node.download_url } }; } throw new Error(); }) ); } function generateTreeHtml(node) { return ``; } function generateNodeHtml(node) { if (node.kind.type === "dir") { const children = node.kind.children.map((node2) => generateNodeHtml(node2)).join(""); return `
  • ${node.filename}/
  • `; } else if (node.kind.type === "file") { return `
  • ${node.filename}
  • `; } throw new Error(); } function queryHtmlINodes(tree) { const nodes = /* @__PURE__ */ new Map(); queryHtmlINodesRecursive(nodes, tree); return nodes; } function queryHtmlINodesRecursive(nodes, node, parentId) { const checkbox = document.querySelector(`#checkbox-${node.id}`); if (node.kind.type === "dir") { nodes.set(node.id, { node, parentId, checkbox }); for (const child of node.kind.children) { queryHtmlINodesRecursive(nodes, child, node.id); } } else if (node.kind.type === "file") { nodes.set(node.id, { node, parentId, checkbox }); } } function hydrateHtmlTree(tree) { for (const node of tree.values()) { node.checkbox.addEventListener("change", () => { if (node.node.kind.type === "dir") { setCheckChildrenRecursively( tree, node.node.id, node.checkbox.checked ); } checkParentDirCheckRecursively(tree, node.parentId); }); } } function setCheckChildrenRecursively(tree, id, state) { const node = tree.get(id); if (node.node.kind.type !== "dir") { throw new Error(); } for (const { id: childId } of node.node.kind.children) { const child = tree.get(childId); child.checkbox.checked = state; if (child.node.kind.type === "dir") { setCheckChildrenRecursively(tree, childId, state); } } } function checkParentDirCheckRecursively(tree, parentId) { if (parentId === void 0) { return; } const parent = tree.get(parentId); if (parent === void 0) { throw new Error(); } if (parent.node.kind.type !== "dir") { return; } const checked = parent.node.kind.children.some(({ id }) => tree.get(id).checkbox.checked); parent.checkbox.checked = checked; checkParentDirCheckRecursively(tree, parent.parentId); } async function* searchTree(tree, pattern) { for (const node of tree.values()) { if (node.node.kind.type !== "file") { continue; } if (!node.checkbox.checked) { continue; } const text = await fetch(node.node.kind.url).then((res) => res.text()); const lines = text.split("\n").map((v, i) => [v, i]); for (const [linetext, i] of lines) { if (!pattern.test(linetext)) { continue; } const { filename, path } = node.node; const linenr = i + 1; yield { filename, path, linetext, linenr }; } } } var repoNameInput = document.querySelector("#repo-name"); var repoRefInput = document.querySelector("#repo-ref"); var gitProviderSelect = document.querySelector("#git-provider"); var loadRepoButton = document.querySelector("#load-repo"); var fileTreeDiv = document.querySelector("#file-tree"); var searchPatternInput = document.querySelector("#search-pattern"); var searchButton = document.querySelector("#search"); var searchStatusSpan = document.querySelector("#search-status"); var searchResultsDiv = document.querySelector("#search-results"); loadRepoButton.onclick = async () => { const name = repoNameInput.value; const ref = repoRefInput.value; const provider = gitProviderSelect.value; if (provider === "github") { const tree = await githubTree(name, ref); fileTreeDiv.innerHTML = generateTreeHtml(tree); const htmlTree = queryHtmlINodes(tree); hydrateHtmlTree(htmlTree); searchStatusSpan.innerText = ``; searchButton.onclick = async () => { const patternText = searchPatternInput.value; const pattern = new RegExp(patternText); searchStatusSpan.innerText = "Searching ..."; searchResultsDiv.innerHTML = ""; let matches = 0; for await (const match of searchTree(htmlTree, pattern)) { matches += 1; searchResultsDiv.innerHTML += `

    ${match.path}/${match.filename} line ${match.linenr}

    ${match.linetext}
    `; } if (matches === 0) { searchStatusSpan.innerText = "** Nothing found **"; } else { searchStatusSpan.innerText = `\u22C6\u02D9\u27E1 Done, ${matches} matches \u27E1\u02D9\u22C6`; } }; } }; searchButton.onclick = () => { searchStatusSpan.innerText = `No repository loaded :(`; };