Format JS
This commit is contained in:
		
							parent
							
								
									6f9283e95b
								
							
						
					
					
						commit
						978d5ca1ae
					
				
					 3 changed files with 361 additions and 362 deletions
				
			
		|  | @ -1,221 +1,220 @@ | |||
| async function drawGraph(url, baseUrl, pathColors, depth, enableDrag, enableLegend, enableZoom) { | ||||
|     const { index, links, content } = await fetchData | ||||
|     const curPage = url.replace(baseUrl, "") | ||||
|    | ||||
|     const parseIdsFromLinks = (links) => [...(new Set(links.flatMap(link => ([link.source, link.target]))))] | ||||
|    | ||||
|     const neighbours = new Set() | ||||
|     const wl = [curPage || "/", "__SENTINEL"] | ||||
|     if (depth >= 0) { | ||||
|       while (depth >= 0 && wl.length > 0) { | ||||
|         // compute neighbours
 | ||||
|         const cur = wl.shift() | ||||
|         if (cur === "__SENTINEL") { | ||||
|           depth-- | ||||
|           wl.push("__SENTINEL") | ||||
|         } else { | ||||
|           neighbours.add(cur) | ||||
|           const outgoing = index.links[cur] || [] | ||||
|           const incoming = index.backlinks[cur] || [] | ||||
|           wl.push(...outgoing.map(l => l.target), ...incoming.map(l => l.source)) | ||||
|         } | ||||
|   const { index, links, content } = await fetchData | ||||
|   const curPage = url.replace(baseUrl, "") | ||||
| 
 | ||||
|   const parseIdsFromLinks = (links) => [...(new Set(links.flatMap(link => ([link.source, link.target]))))] | ||||
| 
 | ||||
|   const neighbours = new Set() | ||||
|   const wl = [curPage || "/", "__SENTINEL"] | ||||
|   if (depth >= 0) { | ||||
|     while (depth >= 0 && wl.length > 0) { | ||||
|       // compute neighbours
 | ||||
|       const cur = wl.shift() | ||||
|       if (cur === "__SENTINEL") { | ||||
|         depth-- | ||||
|         wl.push("__SENTINEL") | ||||
|       } else { | ||||
|         neighbours.add(cur) | ||||
|         const outgoing = index.links[cur] || [] | ||||
|         const incoming = index.backlinks[cur] || [] | ||||
|         wl.push(...outgoing.map(l => l.target), ...incoming.map(l => l.source)) | ||||
|       } | ||||
|     } else { | ||||
|       parseIdsFromLinks(links).forEach(id => neighbours.add(id)) | ||||
|     } | ||||
|    | ||||
|     const data = { | ||||
|       nodes: [...neighbours].map(id => ({id})), | ||||
|       links: links.filter(l => neighbours.has(l.source) && neighbours.has(l.target)), | ||||
|     } | ||||
|    | ||||
|     const color = (d) => { | ||||
|       if (d.id === curPage || (d.id === "/" && curPage === "")) { | ||||
|         return "var(--g-node-active)" | ||||
|       } | ||||
|    | ||||
|       for (const pathColor of pathColors) { | ||||
|         const path = Object.keys(pathColor)[0] | ||||
|         const colour = pathColor[path] | ||||
|         if (d.id.startsWith(path)) { | ||||
|           return colour | ||||
|         } | ||||
|       } | ||||
|    | ||||
|       return "var(--g-node)" | ||||
|     } | ||||
|    | ||||
|     const drag = simulation => { | ||||
|       function dragstarted(event, d) { | ||||
|         if (!event.active) simulation.alphaTarget(1).restart(); | ||||
|         d.fx = d.x; | ||||
|         d.fy = d.y; | ||||
|       } | ||||
|    | ||||
|       function dragged(event,d) { | ||||
|         d.fx = event.x; | ||||
|         d.fy = event.y; | ||||
|       } | ||||
|    | ||||
|       function dragended(event,d) { | ||||
|         if (!event.active) simulation.alphaTarget(0); | ||||
|         d.fx = null; | ||||
|         d.fy = null; | ||||
|       } | ||||
|    | ||||
|       const noop = () => {} | ||||
|       return d3.drag() | ||||
|         .on("start", enableDrag ? dragstarted : noop) | ||||
|         .on("drag", enableDrag ? dragged : noop) | ||||
|         .on("end", enableDrag ? dragended : noop); | ||||
|     } | ||||
|    | ||||
|     const height = 250 | ||||
|     const width = document.getElementById("graph-container").offsetWidth | ||||
|    | ||||
|     const simulation = d3.forceSimulation(data.nodes) | ||||
|       .force("charge", d3.forceManyBody().strength(-30)) | ||||
|       .force("link", d3.forceLink(data.links).id(d => d.id)) | ||||
|       .force("center", d3.forceCenter()); | ||||
|    | ||||
|     const svg = d3.select('#graph-container') | ||||
|       .append('svg') | ||||
|       .attr('width', width) | ||||
|       .attr('height', height) | ||||
|       .attr("viewBox", [-width / 2, -height / 2, width, height]); | ||||
|    | ||||
|     if (enableLegend) { | ||||
|       const legend = [ | ||||
|         {"Current": "var(--g-node-active)"}, | ||||
|         {"Note": "var(--g-node)"}, | ||||
|         ...pathColors | ||||
|       ] | ||||
|       legend.forEach((legendEntry, i) => { | ||||
|         const key = Object.keys(legendEntry)[0] | ||||
|         const colour = legendEntry[key] | ||||
|         svg.append("circle").attr("cx", -width/2 + 20).attr("cy", height/2 - 30 * (i+1)).attr("r", 6).style("fill", colour) | ||||
|         svg.append("text").attr("x", -width/2 + 40).attr("y", height/2 - 30 * (i+1)).text(key).style("font-size", "15px").attr("alignment-baseline","middle") | ||||
|       }) | ||||
|     } | ||||
|    | ||||
|     // draw links between nodes
 | ||||
|     const link = svg.append("g") | ||||
|       .selectAll("line") | ||||
|       .data(data.links) | ||||
|       .join("line") | ||||
|       .attr("class", "link") | ||||
|       .attr("stroke", "var(--g-link)") | ||||
|       .attr("stroke-width", 2) | ||||
|       .attr("data-source", d => d.source.id) | ||||
|       .attr("data-target", d => d.target.id) | ||||
|    | ||||
|     // svg groups
 | ||||
|     const graphNode = svg.append("g") | ||||
|       .selectAll("g") | ||||
|       .data(data.nodes) | ||||
|       .enter().append("g") | ||||
|    | ||||
|     // draw individual nodes
 | ||||
|     const node = graphNode.append("circle") | ||||
|       .attr("class", "node") | ||||
|       .attr("id", (d) => d.id) | ||||
|       .attr("r", (d) => { | ||||
|         const numOut = index.links[d.id]?.length || 0 | ||||
|         const numIn = index.backlinks[d.id]?.length || 0 | ||||
|         return 3 + (numOut + numIn) / 4 | ||||
|       }) | ||||
|       .attr("fill", color) | ||||
|       .style("cursor", "pointer") | ||||
|       .on("click", (_, d) => { | ||||
|         window.location.href = baseUrl + '/' + decodeURI(d.id).replace(/\s+/g, '-') | ||||
|       }) | ||||
|       .on("mouseover", function (_, d) { | ||||
|         d3.selectAll(".node") | ||||
|           .transition() | ||||
|           .duration(100) | ||||
|           .attr("fill", "var(--g-node-inactive)") | ||||
|    | ||||
|         const neighbours = parseIdsFromLinks([...(index.links[d.id] || []), ...(index.backlinks[d.id] || [])]) | ||||
|         const neighbourNodes = d3.selectAll(".node").filter(d => neighbours.includes(d.id)) | ||||
|         const currentId = d.id | ||||
|         const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId) | ||||
|    | ||||
|         // highlight neighbour nodes
 | ||||
|         neighbourNodes | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .attr("fill", color) | ||||
|    | ||||
|         // highlight links
 | ||||
|         linkNodes | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .attr("stroke", "var(--g-link-active)") | ||||
|    | ||||
|         // show text for self
 | ||||
|         d3.select(this.parentNode) | ||||
|           .select("text") | ||||
|           .raise() | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .style("opacity", 1) | ||||
|       }).on("mouseleave", function (_,d) { | ||||
|         d3.selectAll(".node") | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .attr("fill", color) | ||||
|    | ||||
|         const currentId = d.id | ||||
|         const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId) | ||||
|    | ||||
|         linkNodes | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .attr("stroke", "var(--g-link)") | ||||
|    | ||||
|         d3.select(this.parentNode) | ||||
|           .select("text") | ||||
|           .transition() | ||||
|           .duration(200) | ||||
|           .style("opacity", 0) | ||||
|       }) | ||||
|       .call(drag(simulation)); | ||||
|    | ||||
|     // draw labels
 | ||||
|     const labels = graphNode.append("text") | ||||
|       .attr("dx", 12) | ||||
|       .attr("dy", ".35em") | ||||
|       .text((d) => content[decodeURI(d.id).replace(/\s+/g, '-')]?.title || "Untitled") | ||||
|       .style("opacity", 0) | ||||
|       .style("pointer-events", "none") | ||||
|       .call(drag(simulation)); | ||||
|    | ||||
|     // set panning
 | ||||
|    | ||||
|     if (enableZoom) { | ||||
|       svg.call(d3.zoom() | ||||
|         .extent([[0, 0], [width, height]]) | ||||
|         .scaleExtent([0.25, 4]) | ||||
|         .on("zoom", ({transform}) => { | ||||
|           link.attr("transform", transform); | ||||
|           node.attr("transform", transform); | ||||
|           labels.attr("transform", transform); | ||||
|         })); | ||||
|     } | ||||
|    | ||||
|     // progress the simulation
 | ||||
|     simulation.on("tick", () => { | ||||
|       link | ||||
|         .attr("x1", d => d.source.x) | ||||
|         .attr("y1", d => d.source.y) | ||||
|         .attr("x2", d => d.target.x) | ||||
|         .attr("y2", d => d.target.y) | ||||
|       node | ||||
|         .attr("cx", d => d.x) | ||||
|         .attr("cy", d => d.y) | ||||
|       labels | ||||
|         .attr("x", d => d.x) | ||||
|         .attr("y", d => d.y) | ||||
|     }); | ||||
|   } else { | ||||
|     parseIdsFromLinks(links).forEach(id => neighbours.add(id)) | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   const data = { | ||||
|     nodes: [...neighbours].map(id => ({ id })), | ||||
|     links: links.filter(l => neighbours.has(l.source) && neighbours.has(l.target)), | ||||
|   } | ||||
| 
 | ||||
|   const color = (d) => { | ||||
|     if (d.id === curPage || (d.id === "/" && curPage === "")) { | ||||
|       return "var(--g-node-active)" | ||||
|     } | ||||
| 
 | ||||
|     for (const pathColor of pathColors) { | ||||
|       const path = Object.keys(pathColor)[0] | ||||
|       const colour = pathColor[path] | ||||
|       if (d.id.startsWith(path)) { | ||||
|         return colour | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return "var(--g-node)" | ||||
|   } | ||||
| 
 | ||||
|   const drag = simulation => { | ||||
|     function dragstarted(event, d) { | ||||
|       if (!event.active) simulation.alphaTarget(1).restart(); | ||||
|       d.fx = d.x; | ||||
|       d.fy = d.y; | ||||
|     } | ||||
| 
 | ||||
|     function dragged(event, d) { | ||||
|       d.fx = event.x; | ||||
|       d.fy = event.y; | ||||
|     } | ||||
| 
 | ||||
|     function dragended(event, d) { | ||||
|       if (!event.active) simulation.alphaTarget(0); | ||||
|       d.fx = null; | ||||
|       d.fy = null; | ||||
|     } | ||||
| 
 | ||||
|     const noop = () => { } | ||||
|     return d3.drag() | ||||
|       .on("start", enableDrag ? dragstarted : noop) | ||||
|       .on("drag", enableDrag ? dragged : noop) | ||||
|       .on("end", enableDrag ? dragended : noop); | ||||
|   } | ||||
| 
 | ||||
|   const height = 250 | ||||
|   const width = document.getElementById("graph-container").offsetWidth | ||||
| 
 | ||||
|   const simulation = d3.forceSimulation(data.nodes) | ||||
|     .force("charge", d3.forceManyBody().strength(-30)) | ||||
|     .force("link", d3.forceLink(data.links).id(d => d.id)) | ||||
|     .force("center", d3.forceCenter()); | ||||
| 
 | ||||
|   const svg = d3.select('#graph-container') | ||||
|     .append('svg') | ||||
|     .attr('width', width) | ||||
|     .attr('height', height) | ||||
|     .attr("viewBox", [-width / 2, -height / 2, width, height]); | ||||
| 
 | ||||
|   if (enableLegend) { | ||||
|     const legend = [ | ||||
|       { "Current": "var(--g-node-active)" }, | ||||
|       { "Note": "var(--g-node)" }, | ||||
|       ...pathColors | ||||
|     ] | ||||
|     legend.forEach((legendEntry, i) => { | ||||
|       const key = Object.keys(legendEntry)[0] | ||||
|       const colour = legendEntry[key] | ||||
|       svg.append("circle").attr("cx", -width / 2 + 20).attr("cy", height / 2 - 30 * (i + 1)).attr("r", 6).style("fill", colour) | ||||
|       svg.append("text").attr("x", -width / 2 + 40).attr("y", height / 2 - 30 * (i + 1)).text(key).style("font-size", "15px").attr("alignment-baseline", "middle") | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   // draw links between nodes
 | ||||
|   const link = svg.append("g") | ||||
|     .selectAll("line") | ||||
|     .data(data.links) | ||||
|     .join("line") | ||||
|     .attr("class", "link") | ||||
|     .attr("stroke", "var(--g-link)") | ||||
|     .attr("stroke-width", 2) | ||||
|     .attr("data-source", d => d.source.id) | ||||
|     .attr("data-target", d => d.target.id) | ||||
| 
 | ||||
|   // svg groups
 | ||||
|   const graphNode = svg.append("g") | ||||
|     .selectAll("g") | ||||
|     .data(data.nodes) | ||||
|     .enter().append("g") | ||||
| 
 | ||||
|   // draw individual nodes
 | ||||
|   const node = graphNode.append("circle") | ||||
|     .attr("class", "node") | ||||
|     .attr("id", (d) => d.id) | ||||
|     .attr("r", (d) => { | ||||
|       const numOut = index.links[d.id]?.length || 0 | ||||
|       const numIn = index.backlinks[d.id]?.length || 0 | ||||
|       return 3 + (numOut + numIn) / 4 | ||||
|     }) | ||||
|     .attr("fill", color) | ||||
|     .style("cursor", "pointer") | ||||
|     .on("click", (_, d) => { | ||||
|       window.location.href = baseUrl + '/' + decodeURI(d.id).replace(/\s+/g, '-') | ||||
|     }) | ||||
|     .on("mouseover", function (_, d) { | ||||
|       d3.selectAll(".node") | ||||
|         .transition() | ||||
|         .duration(100) | ||||
|         .attr("fill", "var(--g-node-inactive)") | ||||
| 
 | ||||
|       const neighbours = parseIdsFromLinks([...(index.links[d.id] || []), ...(index.backlinks[d.id] || [])]) | ||||
|       const neighbourNodes = d3.selectAll(".node").filter(d => neighbours.includes(d.id)) | ||||
|       const currentId = d.id | ||||
|       const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId) | ||||
| 
 | ||||
|       // highlight neighbour nodes
 | ||||
|       neighbourNodes | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .attr("fill", color) | ||||
| 
 | ||||
|       // highlight links
 | ||||
|       linkNodes | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .attr("stroke", "var(--g-link-active)") | ||||
| 
 | ||||
|       // show text for self
 | ||||
|       d3.select(this.parentNode) | ||||
|         .select("text") | ||||
|         .raise() | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .style("opacity", 1) | ||||
|     }).on("mouseleave", function (_, d) { | ||||
|       d3.selectAll(".node") | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .attr("fill", color) | ||||
| 
 | ||||
|       const currentId = d.id | ||||
|       const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId) | ||||
| 
 | ||||
|       linkNodes | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .attr("stroke", "var(--g-link)") | ||||
| 
 | ||||
|       d3.select(this.parentNode) | ||||
|         .select("text") | ||||
|         .transition() | ||||
|         .duration(200) | ||||
|         .style("opacity", 0) | ||||
|     }) | ||||
|     .call(drag(simulation)); | ||||
| 
 | ||||
|   // draw labels
 | ||||
|   const labels = graphNode.append("text") | ||||
|     .attr("dx", 12) | ||||
|     .attr("dy", ".35em") | ||||
|     .text((d) => content[decodeURI(d.id).replace(/\s+/g, '-')]?.title || "Untitled") | ||||
|     .style("opacity", 0) | ||||
|     .style("pointer-events", "none") | ||||
|     .call(drag(simulation)); | ||||
| 
 | ||||
|   // set panning
 | ||||
| 
 | ||||
|   if (enableZoom) { | ||||
|     svg.call(d3.zoom() | ||||
|       .extent([[0, 0], [width, height]]) | ||||
|       .scaleExtent([0.25, 4]) | ||||
|       .on("zoom", ({ transform }) => { | ||||
|         link.attr("transform", transform); | ||||
|         node.attr("transform", transform); | ||||
|         labels.attr("transform", transform); | ||||
|       })); | ||||
|   } | ||||
| 
 | ||||
|   // progress the simulation
 | ||||
|   simulation.on("tick", () => { | ||||
|     link | ||||
|       .attr("x1", d => d.source.x) | ||||
|       .attr("y1", d => d.source.y) | ||||
|       .attr("x2", d => d.target.x) | ||||
|       .attr("y2", d => d.target.y) | ||||
|     node | ||||
|       .attr("cx", d => d.x) | ||||
|       .attr("cy", d => d.y) | ||||
|     labels | ||||
|       .attr("x", d => d.x) | ||||
|       .attr("y", d => d.y) | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -5,29 +5,29 @@ function htmlToElement(html) { | |||
|     return template.content.firstChild | ||||
| } | ||||
| 
 | ||||
| function initPopover(base) { | ||||
|     const baseUrl = base.replace(window.location.origin, "") // is this useless?
 | ||||
| function initPopover(baseURL) { | ||||
|     const basePath = baseURL.replace(window.location.origin, "") | ||||
|     document.addEventListener("DOMContentLoaded", () => { | ||||
|         fetchData.then(({content}) => { | ||||
|         const links = [...document.getElementsByClassName("internal-link")] | ||||
|         links.forEach(li => { | ||||
|             const linkDest = content[li.dataset.src.replace(baseUrl, "")] | ||||
|             // const linkDest = content[li.dataset.src]
 | ||||
|             if (linkDest) { | ||||
|                 const popoverElement = `<div class="popover">
 | ||||
|         fetchData.then(({ content }) => { | ||||
|             const links = [...document.getElementsByClassName("internal-link")] | ||||
|             links.forEach(li => { | ||||
|                 const linkDest = content[li.dataset.src.replace(basePath, "")] | ||||
|                 // const linkDest = content[li.dataset.src]
 | ||||
|                 if (linkDest) { | ||||
|                     const popoverElement = `<div class="popover">
 | ||||
|     <h3>${linkDest.title}</h3> | ||||
|     <p>${removeMarkdown(linkDest.content).split(" ", 20).join(" ")}...</p> | ||||
|     <p class="meta">${new Date(linkDest.lastmodified).toLocaleDateString()}</p> | ||||
| </div>` | ||||
|                 const el = htmlToElement(popoverElement) | ||||
|                 li.appendChild(el) | ||||
|                 li.addEventListener("mouseover", () => { | ||||
|                 el.classList.add("visible") | ||||
|                 }) | ||||
|                 li.addEventListener("mouseout", () => { | ||||
|                 el.classList.remove("visible") | ||||
|                 }) | ||||
|             } | ||||
|                     const el = htmlToElement(popoverElement) | ||||
|                     li.appendChild(el) | ||||
|                     li.addEventListener("mouseover", () => { | ||||
|                         el.classList.add("visible") | ||||
|                     }) | ||||
|                     li.addEventListener("mouseout", () => { | ||||
|                         el.classList.remove("visible") | ||||
|                     }) | ||||
|                 } | ||||
|             }) | ||||
|         }) | ||||
|     }) | ||||
|  |  | |||
|  | @ -58,190 +58,190 @@ const removeMarkdown = ( | |||
| }; | ||||
| // -----
 | ||||
| 
 | ||||
| (async function() { | ||||
| (async function () { | ||||
|     const contentIndex = new FlexSearch.Document({ | ||||
|     cache: true, | ||||
|     charset: "latin:extra", | ||||
|     optimize: true, | ||||
|     worker: true, | ||||
|     document: { | ||||
|     index: [{ | ||||
|         field: "content", | ||||
|         tokenize: "strict", | ||||
|         context: { | ||||
|         resolution: 5, | ||||
|         depth: 3, | ||||
|         bidirectional: true | ||||
|         }, | ||||
|         suggest: true, | ||||
|     }, { | ||||
|         field: "title", | ||||
|         tokenize: "forward", | ||||
|     }] | ||||
|     } | ||||
|         cache: true, | ||||
|         charset: "latin:extra", | ||||
|         optimize: true, | ||||
|         worker: true, | ||||
|         document: { | ||||
|             index: [{ | ||||
|                 field: "content", | ||||
|                 tokenize: "strict", | ||||
|                 context: { | ||||
|                     resolution: 5, | ||||
|                     depth: 3, | ||||
|                     bidirectional: true | ||||
|                 }, | ||||
|                 suggest: true, | ||||
|             }, { | ||||
|                 field: "title", | ||||
|                 tokenize: "forward", | ||||
|             }] | ||||
|         } | ||||
|     }) | ||||
| 
 | ||||
|     const { content } = await fetchData | ||||
|     for (const [key, value] of Object.entries(content)) { | ||||
|     contentIndex.add({ | ||||
|     id: key, | ||||
|     title: value.title, | ||||
|     content: removeMarkdown(value.content), | ||||
|     }) | ||||
|         contentIndex.add({ | ||||
|             id: key, | ||||
|             title: value.title, | ||||
|             content: removeMarkdown(value.content), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     const highlight = (content, term) => { | ||||
|     const highlightWindow = 20 | ||||
|     const tokenizedTerm = term.split(/\s+/).filter(t => t !== "") | ||||
|     const splitText = content.split(/\s+/).filter(t => t !== "") | ||||
|     const includesCheck = (token) => tokenizedTerm.some(term => token.toLowerCase().startsWith(term.toLowerCase())) | ||||
|         const highlightWindow = 20 | ||||
|         const tokenizedTerm = term.split(/\s+/).filter(t => t !== "") | ||||
|         const splitText = content.split(/\s+/).filter(t => t !== "") | ||||
|         const includesCheck = (token) => tokenizedTerm.some(term => token.toLowerCase().startsWith(term.toLowerCase())) | ||||
| 
 | ||||
|     const occurrencesIndices = splitText | ||||
|     .map(includesCheck) | ||||
|         const occurrencesIndices = splitText | ||||
|             .map(includesCheck) | ||||
| 
 | ||||
|     // calculate best index
 | ||||
|     let bestSum = 0 | ||||
|     let bestIndex = 0 | ||||
|     for (let i = 0; i < Math.max(occurrencesIndices.length - highlightWindow, 0); i++) { | ||||
|     const window = occurrencesIndices.slice(i, i + highlightWindow) | ||||
|     const windowSum = window.reduce((total, cur) => total + cur, 0) | ||||
|     if (windowSum >= bestSum) { | ||||
|         bestSum = windowSum | ||||
|         bestIndex = i | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     const startIndex = Math.max(bestIndex - highlightWindow, 0) | ||||
|     const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length) | ||||
|     const mappedText = splitText | ||||
|     .slice(startIndex, endIndex) | ||||
|     .map(token => { | ||||
|         if (includesCheck(token)) { | ||||
|         return `<span class="search-highlight">${token}</span>` | ||||
|         // calculate best index
 | ||||
|         let bestSum = 0 | ||||
|         let bestIndex = 0 | ||||
|         for (let i = 0; i < Math.max(occurrencesIndices.length - highlightWindow, 0); i++) { | ||||
|             const window = occurrencesIndices.slice(i, i + highlightWindow) | ||||
|             const windowSum = window.reduce((total, cur) => total + cur, 0) | ||||
|             if (windowSum >= bestSum) { | ||||
|                 bestSum = windowSum | ||||
|                 bestIndex = i | ||||
|             } | ||||
|         } | ||||
|         return token | ||||
|     }) | ||||
|     .join(" ") | ||||
|     .replaceAll('</span> <span class="search-highlight">', " ") | ||||
|     return `${startIndex === 0 ? "" : "..."}${mappedText}${endIndex === splitText.length ? "" : "..."}` | ||||
| 
 | ||||
|         const startIndex = Math.max(bestIndex - highlightWindow, 0) | ||||
|         const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length) | ||||
|         const mappedText = splitText | ||||
|             .slice(startIndex, endIndex) | ||||
|             .map(token => { | ||||
|                 if (includesCheck(token)) { | ||||
|                     return `<span class="search-highlight">${token}</span>` | ||||
|                 } | ||||
|                 return token | ||||
|             }) | ||||
|             .join(" ") | ||||
|             .replaceAll('</span> <span class="search-highlight">', " ") | ||||
|         return `${startIndex === 0 ? "" : "..."}${mappedText}${endIndex === splitText.length ? "" : "..."}` | ||||
|     } | ||||
| 
 | ||||
|     const resultToHTML = ({url, title, content, term}) => { | ||||
|     const text = removeMarkdown(content) | ||||
|     const resultTitle = highlight(title, term) | ||||
|     const resultText = highlight(text, term) | ||||
|     return `<button class="result-card" id="${url}">
 | ||||
|     const resultToHTML = ({ url, title, content, term }) => { | ||||
|         const text = removeMarkdown(content) | ||||
|         const resultTitle = highlight(title, term) | ||||
|         const resultText = highlight(text, term) | ||||
|         return `<button class="result-card" id="${url}">
 | ||||
|         <h3>${resultTitle}</h3> | ||||
|         <p>${resultText}</p> | ||||
|     </button>` | ||||
|     } | ||||
| 
 | ||||
|     const redir = (id, term) => { | ||||
|     window.location.href = BASE_URL + `${id}#:~:text=${encodeURIComponent(term)}` | ||||
|         window.location.href = BASE_URL + `${id}#:~:text=${encodeURIComponent(term)}` | ||||
|     } | ||||
| 
 | ||||
|     const formatForDisplay = id => ({ | ||||
|     id, | ||||
|     url: id, | ||||
|     title: content[id].title, | ||||
|     content: content[id].content | ||||
|         id, | ||||
|         url: id, | ||||
|         title: content[id].title, | ||||
|         content: content[id].content | ||||
|     }) | ||||
| 
 | ||||
|     const source = document.getElementById('search-bar') | ||||
|     const results = document.getElementById("results-container") | ||||
|     let term | ||||
|     source.addEventListener("keyup", (e) => { | ||||
|     if (e.key === "Enter") { | ||||
|     const anchor = document.getElementsByClassName("result-card")[0] | ||||
|     redir(anchor.id, term) | ||||
|     } | ||||
|         if (e.key === "Enter") { | ||||
|             const anchor = document.getElementsByClassName("result-card")[0] | ||||
|             redir(anchor.id, term) | ||||
|         } | ||||
|     }) | ||||
|     source.addEventListener('input', (e) => { | ||||
|     term = e.target.value | ||||
|     contentIndex.search(term, [ | ||||
|     { | ||||
|         field: "content", | ||||
|         limit: 10, | ||||
|         suggest: true, | ||||
|     }, | ||||
|     { | ||||
|         field: "title", | ||||
|         limit: 5, | ||||
|     } | ||||
|     ]).then(searchResults => { | ||||
|     const getByField = field => { | ||||
|         const results = searchResults.filter(x => x.field === field) | ||||
|         if (results.length === 0) { | ||||
|         return [] | ||||
|         } else { | ||||
|         return [...results[0].result] | ||||
|         } | ||||
|     } | ||||
|     const allIds = new Set([...getByField('title'), ...getByField('content')]) | ||||
|     const finalResults = [...allIds].map(formatForDisplay) | ||||
|         term = e.target.value | ||||
|         contentIndex.search(term, [ | ||||
|             { | ||||
|                 field: "content", | ||||
|                 limit: 10, | ||||
|                 suggest: true, | ||||
|             }, | ||||
|             { | ||||
|                 field: "title", | ||||
|                 limit: 5, | ||||
|             } | ||||
|         ]).then(searchResults => { | ||||
|             const getByField = field => { | ||||
|                 const results = searchResults.filter(x => x.field === field) | ||||
|                 if (results.length === 0) { | ||||
|                     return [] | ||||
|                 } else { | ||||
|                     return [...results[0].result] | ||||
|                 } | ||||
|             } | ||||
|             const allIds = new Set([...getByField('title'), ...getByField('content')]) | ||||
|             const finalResults = [...allIds].map(formatForDisplay) | ||||
| 
 | ||||
|     // display
 | ||||
|     if (finalResults.length === 0) { | ||||
|         results.innerHTML = `<button class="result-card">
 | ||||
|             // display
 | ||||
|             if (finalResults.length === 0) { | ||||
|                 results.innerHTML = `<button class="result-card">
 | ||||
|                     <h3>No results.</h3> | ||||
|                     <p>Try another search term?</p> | ||||
|                 </button>` | ||||
|     } else { | ||||
|         results.innerHTML = finalResults | ||||
|         .map(result => resultToHTML({ | ||||
|             ...result, | ||||
|             term, | ||||
|         })) | ||||
|         .join("\n") | ||||
|         const anchors = document.getElementsByClassName("result-card"); | ||||
|         [...anchors].forEach(anchor => { | ||||
|         anchor.onclick = () => redir(anchor.id, term) | ||||
|             } else { | ||||
|                 results.innerHTML = finalResults | ||||
|                     .map(result => resultToHTML({ | ||||
|                         ...result, | ||||
|                         term, | ||||
|                     })) | ||||
|                     .join("\n") | ||||
|                 const anchors = document.getElementsByClassName("result-card"); | ||||
|                 [...anchors].forEach(anchor => { | ||||
|                     anchor.onclick = () => redir(anchor.id, term) | ||||
|                 }) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|     }) | ||||
|     }) | ||||
| 
 | ||||
| 
 | ||||
|     const searchContainer = document.getElementById("search-container") | ||||
| 
 | ||||
|     function openSearch() { | ||||
|     if (searchContainer.style.display === "none" || searchContainer.style.display === "") { | ||||
|     source.value = "" | ||||
|     results.innerHTML = "" | ||||
|     searchContainer.style.display = "block" | ||||
|     source.focus() | ||||
|     } else { | ||||
|     searchContainer.style.display = "none" | ||||
|     } | ||||
|         if (searchContainer.style.display === "none" || searchContainer.style.display === "") { | ||||
|             source.value = "" | ||||
|             results.innerHTML = "" | ||||
|             searchContainer.style.display = "block" | ||||
|             source.focus() | ||||
|         } else { | ||||
|             searchContainer.style.display = "none" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function closeSearch() { | ||||
|     searchContainer.style.display = "none" | ||||
|         searchContainer.style.display = "none" | ||||
|     } | ||||
| 
 | ||||
|     document.addEventListener('keydown', (event) => { | ||||
|     if (event.key === "/") { | ||||
|     event.preventDefault() | ||||
|     openSearch() | ||||
|     } | ||||
|     if (event.key === "Escape") { | ||||
|     event.preventDefault() | ||||
|     closeSearch() | ||||
|     } | ||||
|         if (event.key === "/") { | ||||
|             event.preventDefault() | ||||
|             openSearch() | ||||
|         } | ||||
|         if (event.key === "Escape") { | ||||
|             event.preventDefault() | ||||
|             closeSearch() | ||||
|         } | ||||
|     }) | ||||
| 
 | ||||
|     const searchButton = document.getElementById("search-icon") | ||||
|     searchButton.addEventListener('click', (evt) => { | ||||
|     openSearch() | ||||
|         openSearch() | ||||
|     }) | ||||
|     searchButton.addEventListener('keydown', (evt) => { | ||||
|     openSearch() | ||||
|         openSearch() | ||||
|     }) | ||||
|     searchContainer.addEventListener('click', (evt) => { | ||||
|     closeSearch() | ||||
|         closeSearch() | ||||
|     }) | ||||
|     document.getElementById("search-space").addEventListener('click', (evt) => { | ||||
|     evt.stopPropagation() | ||||
|         evt.stopPropagation() | ||||
|     }) | ||||
| })() | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue