diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 2824084fb..40b2d4a3a 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1 @@
-github: [jackyzha0]
+github: [jackyzha0]
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 5c8616f42..189648d9c 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,32 +1,32 @@
----
-name: Bug report
-about: Something about Quartz isn't working the way you expect
-title: ''
-labels: bug
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Desktop (please complete the following information):**
- - Device: [e.g. iPhone6]
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
-
-**Additional context**
-Add any other context about the problem here.
+---
+name: Bug report
+about: Something about Quartz isn't working the way you expect
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 80e19eff6..2c9c226d9 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,20 +1,20 @@
----
-name: Feature request
-about: Suggest an idea or improvement for Quartz
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
+---
+name: Feature request
+about: Suggest an idea or improvement for Quartz
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/Makefile b/Makefile
index 4310e9478..0d29bdacf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,18 +1,20 @@
-.DEFAULT_GOAL := serve
-
-help: ## Show all Makefile targets
- @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
-
-update: ## Update Quartz to the latest version on Github
- @git remote show upstream || (echo "remote 'upstream' not present, setting 'upstream'" && git remote add upstream https://github.com/jackyzha0/quartz.git)
- git fetch upstream
- git log --oneline --decorate --graph ..upstream/hugo
- git checkout -p upstream/hugo -- layouts .github Makefile assets/js assets/styles/base.scss assets/styles/darkmode.scss config.toml data
-
-update-force: ## Forcefully pull all changes and don't ask to patch
- @git remote show upstream || (echo "remote 'upstream' not present, setting 'upstream'" && git remote add upstream https://github.com/jackyzha0/quartz.git)
- git fetch upstream
- git checkout upstream/hugo -- layouts .github Makefile assets/js assets/styles/base.scss assets/styles/darkmode.scss config.toml data
-
-serve: ## Serve Quartz locally
- hugo-obsidian -input=content -output=assets/indices -index -root=. && hugo server --enableGitInfo
+.DEFAULT_GOAL := serve
+
+help: ## Show all Makefile targets
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+update: ## Update Quartz to the latest version on Github
+ go install github.com/jackyzha0/hugo-obsidian@latest
+ @git remote show upstream || (echo "remote 'upstream' not present, setting 'upstream'" && git remote add upstream https://github.com/jackyzha0/quartz.git)
+ git fetch upstream
+ git log --oneline --decorate --graph ..upstream/hugo
+ git checkout -p upstream/hugo -- layouts .github Makefile assets/js assets/styles/base.scss assets/styles/darkmode.scss config.toml data
+
+update-force: ## Forcefully pull all changes and don't ask to patch
+ go install github.com/jackyzha0/hugo-obsidian@latest
+ @git remote show upstream || (echo "remote 'upstream' not present, setting 'upstream'" && git remote add upstream https://github.com/jackyzha0/quartz.git)
+ git fetch upstream
+ git checkout upstream/hugo -- layouts .github Makefile assets/js assets/styles/base.scss assets/styles/darkmode.scss config.toml data
+
+serve: ## Serve Quartz locally
+ hugo-obsidian -input=content -output=assets/indices -index -root=. && hugo server --enableGitInfo
diff --git a/assets/js/darkmode.js b/assets/js/darkmode.js
index a9f170f30..d95a281ac 100644
--- a/assets/js/darkmode.js
+++ b/assets/js/darkmode.js
@@ -1,29 +1,29 @@
-const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
-const currentTheme = localStorage.getItem('theme') ?? userPref
-
-if (currentTheme) {
- document.documentElement.setAttribute('saved-theme', currentTheme);
-}
-
-const switchTheme = (e) => {
- if (e.target.checked) {
- document.documentElement.setAttribute('saved-theme', 'dark')
- localStorage.setItem('theme', 'dark')
- }
- else {
- document.documentElement.setAttribute('saved-theme', 'light')
- localStorage.setItem('theme', 'light')
- }
-}
-
-window.addEventListener('DOMContentLoaded', () => {
- // Darkmode toggle
- const toggleSwitch = document.querySelector('#darkmode-toggle')
-
- // listen for toggle
- toggleSwitch.addEventListener('change', switchTheme, false)
-
- if (currentTheme === 'dark') {
- toggleSwitch.checked = true
- }
-})
+const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
+const currentTheme = localStorage.getItem('theme') ?? userPref
+
+if (currentTheme) {
+ document.documentElement.setAttribute('saved-theme', currentTheme);
+}
+
+const switchTheme = (e) => {
+ if (e.target.checked) {
+ document.documentElement.setAttribute('saved-theme', 'dark')
+ localStorage.setItem('theme', 'dark')
+ }
+ else {
+ document.documentElement.setAttribute('saved-theme', 'light')
+ localStorage.setItem('theme', 'light')
+ }
+}
+
+window.addEventListener('DOMContentLoaded', () => {
+ // Darkmode toggle
+ const toggleSwitch = document.querySelector('#darkmode-toggle')
+
+ // listen for toggle
+ toggleSwitch.addEventListener('change', switchTheme, false)
+
+ if (currentTheme === 'dark') {
+ toggleSwitch.checked = true
+ }
+})
diff --git a/assets/js/graph.js b/assets/js/graph.js
index e5b42cd43..f71e44d37 100644
--- a/assets/js/graph.js
+++ b/assets/js/graph.js
@@ -1,220 +1,283 @@
-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))
- }
- }
- } 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[d.id]?.title || d.id.replace("-", " "))
- .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)
- });
-}
+async function drawGraph(
+ baseUrl,
+ pathColors,
+ depth,
+ enableDrag,
+ enableLegend,
+ enableZoom
+) {
+ const container = document.getElementById('graph-container')
+ const { index, links, content } = await fetchData
+
+ // Use .pathname to remove hashes / searchParams / text fragments
+ const cleanUrl = window.location.origin + window.location.pathname
+
+ const curPage = cleanUrl.replace(/\/$/g, "").replace(baseUrl, "")
+
+ const parseIdsFromLinks = (links) => [
+ ...new Set(links.flatMap((link) => [link.source, link.target])),
+ ]
+
+ // Links is mutated by d3. We want to use links later on, so we make a copy and pass that one to d3
+ // Note: shallow cloning does not work because it copies over references from the original array
+ const copyLinks = JSON.parse(JSON.stringify(links))
+
+ 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(copyLinks).forEach((id) => neighbours.add(id))
+ }
+
+ const data = {
+ nodes: [...neighbours].map((id) => ({ id })),
+ links: copyLinks.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 = Math.max(container.offsetHeight, 250)
+ const width = container.offsetWidth
+
+ const simulation = d3
+ .forceSimulation(data.nodes)
+ .force('charge', d3.forceManyBody().strength(-30))
+ .force(
+ 'link',
+ d3
+ .forceLink(data.links)
+ .id((d) => d.id)
+ .distance(40)
+ )
+ .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')
+
+ // calculate radius
+ const nodeRadius = (d) => {
+ const numOut = index.links[d.id]?.length || 0
+ const numIn = index.backlinks[d.id]?.length || 0
+ return 3 + (numOut + numIn) / 4
+ }
+
+ // draw individual nodes
+ const node = graphNode
+ .append('circle')
+ .attr('class', 'node')
+ .attr('id', (d) => d.id)
+ .attr('r', nodeRadius)
+ .attr('fill', color)
+ .style('cursor', 'pointer')
+ .on('click', (_, d) => {
+ // SPA navigation
+ window.navigate(
+ new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, '-')}/`),
+ '.singlePage'
+ )
+ })
+ .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)
+ .raise()
+ .select('text')
+ .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', 0)
+ .attr('dy', (d) => nodeRadius(d) + 8 + 'px')
+ .attr('text-anchor', 'middle')
+ .text((d) => content[d.id]?.title || d.id.replace('-', ' '))
+ .style('opacity', 0)
+ .style('pointer-events', 'none')
+ .style('font-size', '0.4em')
+ .raise()
+ .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)
+ const scale = transform.k
+ const scaledOpacity = Math.max((scale - 1) / 3.75, 0)
+ labels.attr('transform', transform).style('opacity', scaledOpacity)
+ })
+ )
+ }
+
+ // 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)
+ })
+}
diff --git a/assets/js/popover.js b/assets/js/popover.js
index 93ae12275..494cd84e1 100644
--- a/assets/js/popover.js
+++ b/assets/js/popover.js
@@ -1,35 +1,58 @@
-function htmlToElement(html) {
- const template = document.createElement('template')
- html = html.trim()
- template.innerHTML = html
- return template.content.firstChild
-}
-
-function initPopover(baseURL) {
- const basePath = baseURL.replace(window.location.origin, "")
- document.addEventListener("DOMContentLoaded", () => {
- fetchData.then(({ content }) => {
- const links = [...document.getElementsByClassName("internal-link")]
- links
- .filter(li => li.dataset.src)
- .forEach(li => {
- const linkDest = content[li.dataset.src.replace(basePath, "")]
- if (linkDest) {
- const popoverElement = `
-
${linkDest.title}
-
${removeMarkdown(linkDest.content).split(" ", 20).join(" ")}...
-
${new Date(linkDest.lastmodified).toLocaleDateString()}
-
`
- const el = htmlToElement(popoverElement)
- li.appendChild(el)
- li.addEventListener("mouseover", () => {
- el.classList.add("visible")
- })
- li.addEventListener("mouseout", () => {
- el.classList.remove("visible")
- })
- }
- })
- })
- })
-}
+function htmlToElement(html) {
+ const template = document.createElement("template")
+ html = html.trim()
+ template.innerHTML = html
+ return template.content.firstChild
+}
+
+function initPopover(baseURL, useContextualBacklinks, renderLatex) {
+ const basePath = baseURL.replace(window.location.origin, "")
+ fetchData.then(({ content }) => {
+ const links = [...document.getElementsByClassName("internal-link")]
+ links
+ .filter(li => li.dataset.src || (li.dataset.idx && useContextualBacklinks))
+ .forEach(li => {
+ var el
+ if (li.dataset.ctx) {
+ const linkDest = content[li.dataset.src]
+ const popoverElement = `
+
${linkDest.title}
+
${highlight(removeMarkdown(linkDest.content), li.dataset.ctx)}...
+
${new Date(linkDest.lastmodified).toLocaleDateString()}
+
`
+ el = htmlToElement(popoverElement)
+ } else {
+ const linkDest = content[li.dataset.src.replace(/\/$/g, "").replace(basePath, "")]
+ if (linkDest) {
+ const popoverElement = `
+
${linkDest.title}
+
${removeMarkdown(linkDest.content).split(" ", 20).join(" ")}...
+
${new Date(linkDest.lastmodified).toLocaleDateString()}
+
`
+ el = htmlToElement(popoverElement)
+ }
+ }
+
+ if (el) {
+ li.appendChild(el)
+ if (renderLatex) {
+ renderMathInElement(el, {
+ delimiters: [
+ { left: '$$', right: '$$', display: false },
+ { left: '$', right: '$', display: false },
+ { left: '\\(', right: '\\)', display: false },
+ { left: '\\[', right: '\\]', display: false }
+ ],
+ throwOnError: false
+ })
+ }
+ li.addEventListener("mouseover", () => {
+ el.classList.add("visible")
+ })
+ li.addEventListener("mouseout", () => {
+ el.classList.remove("visible")
+ })
+ }
+ })
+ })
+}
diff --git a/assets/js/router.js b/assets/js/router.js
new file mode 100644
index 000000000..81c25ac1c
--- /dev/null
+++ b/assets/js/router.js
@@ -0,0 +1,12 @@
+import { router, navigate } from "https://unpkg.com/million@1.8.9-0/dist/router.mjs"
+
+export const attachSPARouting = (draw) => {
+ // SPA navigation for access later
+ window.navigate = navigate
+ // We only mutate document.title and content within .singlePage element
+ router(".singlePage")
+ // We need on initial load, then subsequent redirs
+ // requestAnimationFrame() delays graph draw until SPA routing is finished
+ window.addEventListener("million:navigate", () => requestAnimationFrame(draw))
+ window.addEventListener("DOMContentLoaded", () => requestAnimationFrame(draw))
+}
diff --git a/assets/js/search.js b/assets/js/search.js
index f155590b9..bb94cd30b 100644
--- a/assets/js/search.js
+++ b/assets/js/search.js
@@ -1,239 +1,270 @@
-// code from https://github.com/danestves/markdown-to-text
-const removeMarkdown = (
- markdown,
- options = {
- listUnicodeChar: false,
- stripListLeaders: true,
- gfm: true,
- useImgAltText: false,
- preserveLinks: false,
- }
-) => {
- let output = markdown || "";
- output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, "");
-
- try {
- if (options.stripListLeaders) {
- if (options.listUnicodeChar)
- output = output.replace(
- /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm,
- options.listUnicodeChar + " $1"
- );
- else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1");
- }
- if (options.gfm) {
- output = output
- .replace(/\n={2,}/g, "\n")
- .replace(/~{3}.*\n/g, "")
- .replace(/~~/g, "")
- .replace(/`{3}.*\n/g, "");
- }
- if (options.preserveLinks) {
- output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, "$1 ($2)")
- }
- output = output
- .replace(/<[^>]*>/g, "")
- .replace(/^[=\-]{2,}\s*$/g, "")
- .replace(/\[\^.+?\](\: .*?$)?/g, "")
- .replace(/\s{0,2}\[.*?\]: .*?$/g, "")
- .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? "$1" : "")
- .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, "$1")
- .replace(/^\s{0,3}>\s?/g, "")
- .replace(/(^|\n)\s{0,3}>\s?/g, "\n\n")
- .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, "")
- .replace(
- /^(\n)?\s{0,}#{1,6}\s+| {0,}(\n)?\s{0,}#{0,} {0,}(\n)?\s{0,}$/gm,
- "$1$2$3"
- )
- .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
- .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
- .replace(/(`{3,})(.*?)\1/gm, "$2")
- .replace(/`(.+?)`/g, "$1")
- .replace(/\n{2,}/g, "\n\n");
- } catch (e) {
- console.error(e);
- return markdown;
- }
- return output;
-};
-// -----
-
-(async function() {
- const encoder = str => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/)
- const contentIndex = new FlexSearch.Document({
- cache: true,
- charset: "latin:extra",
- optimize: true,
- index: [{
- field: "content",
- tokenize: "reverse",
- encode: encoder,
- }, {
- field: "title",
- tokenize: "forward",
- encode: encoder,
- }]
- })
-
- const { content } = await fetchData
- for (const [key, value] of Object.entries(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 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 `${token} `
- }
- return token
- })
- .join(" ")
- .replaceAll(' ', " ")
- 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 `
- ${resultTitle}
- ${resultText}
- `
- }
-
- const redir = (id, term) => {
- window.location.href = BASE_URL + `${id}#:~:text=${encodeURIComponent(term)}`
- }
-
- const formatForDisplay = id => ({
- 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)
- }
- })
- source.addEventListener('input', (e) => {
- term = e.target.value
- const searchResults = contentIndex.search(term, [
- {
- field: "content",
- limit: 10,
- },
- {
- field: "title",
- limit: 5,
- }
- ])
- 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 = `
- No results.
- Try another search 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"
- }
- }
-
- function closeSearch() {
- searchContainer.style.display = "none"
- }
-
- document.addEventListener('keydown', (event) => {
- if (event.key === "k" && (event.ctrlKey || event.metaKey)) {
- event.preventDefault()
- openSearch()
- }
- if (event.key === "Escape") {
- event.preventDefault()
- closeSearch()
- }
- })
-
- const searchButton = document.getElementById("search-icon")
- searchButton.addEventListener('click', (evt) => {
- openSearch()
- })
- searchButton.addEventListener('keydown', (evt) => {
- openSearch()
- })
- searchContainer.addEventListener('click', (evt) => {
- closeSearch()
- })
- document.getElementById("search-space").addEventListener('click', (evt) => {
- evt.stopPropagation()
- })
-})()
-
+// code from https://github.com/danestves/markdown-to-text
+const removeMarkdown = (
+ markdown,
+ options = {
+ listUnicodeChar: false,
+ stripListLeaders: true,
+ gfm: true,
+ useImgAltText: false,
+ preserveLinks: false,
+ }
+) => {
+ let output = markdown || ''
+ output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '')
+
+ try {
+ if (options.stripListLeaders) {
+ if (options.listUnicodeChar)
+ output = output.replace(
+ /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm,
+ options.listUnicodeChar + ' $1'
+ )
+ else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
+ }
+ if (options.gfm) {
+ output = output
+ .replace(/\n={2,}/g, '\n')
+ .replace(/~{3}.*\n/g, '')
+ .replace(/~~/g, '')
+ .replace(/`{3}.*\n/g, '')
+ }
+ if (options.preserveLinks) {
+ output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, '$1 ($2)')
+ }
+ output = output
+ .replace(/<[^>]*>/g, '')
+ .replace(/^[=\-]{2,}\s*$/g, '')
+ .replace(/\[\^.+?\](\: .*?$)?/g, '')
+ .replace(/(#{1,6})\s+(.+)\1?/g, '$2 ')
+ .replace(/\s{0,2}\[.*?\]: .*?$/g, '')
+ .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? '$1' : '')
+ .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1 ')
+ .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, '$1 ')
+ .replace(/^\s{0,3}>\s?/g, '')
+ .replace(/(^|\n)\s{0,3}>\s?/g, '\n\n')
+ .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
+ .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2')
+ .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2')
+ .replace(/(`{3,})(.*?)\1/gm, '$2')
+ .replace(/`(.+?)`/g, '$1')
+ .replace(/\n{2,}/g, '\n\n')
+ } catch (e) {
+ console.error(e)
+ return markdown
+ }
+ return output
+}
+// -----
+
+const highlight = (content, term) => {
+ const highlightWindow = 20
+
+ // try to find direct match first
+ const directMatchIdx = content.indexOf(term)
+ if (directMatchIdx !== -1) {
+ const h = highlightWindow / 2
+ const before = content.substring(0, directMatchIdx).split(" ").slice(-h)
+ const after = content.substring(directMatchIdx + term.length, content.length - 1).split(" ").slice(0, h)
+ return (before.length == h ? `...${before.join(" ")}` : before.join(" ")) + `${term} ` + after.join(" ")
+ }
+
+ 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)
+
+ // 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 `${token} `
+ }
+ return token
+ })
+ .join(' ')
+ .replaceAll(' ', ' ')
+ return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...'
+ }`
+};
+
+(async function() {
+ const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/)
+ const contentIndex = new FlexSearch.Document({
+ cache: true,
+ charset: 'latin:extra',
+ optimize: true,
+ index: [
+ {
+ field: 'content',
+ tokenize: 'reverse',
+ encode: encoder,
+ },
+ {
+ field: 'title',
+ tokenize: 'forward',
+ encode: encoder,
+ },
+ ],
+ })
+
+ const { content } = await fetchData
+ for (const [key, value] of Object.entries(content)) {
+ contentIndex.add({
+ id: key,
+ title: value.title,
+ content: removeMarkdown(value.content),
+ })
+ }
+
+ const resultToHTML = ({ url, title, content, term }) => {
+ const text = removeMarkdown(content)
+ const resultTitle = highlight(title, term)
+ const resultText = highlight(text, term)
+ return `
+ ${resultTitle}
+ ${resultText}
+ `
+ }
+
+ const redir = (id, term) => {
+ // SPA navigation
+ window.navigate(
+ new URL(
+ `${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`
+ ),
+ '.singlePage'
+ )
+ closeSearch()
+ }
+
+ const formatForDisplay = (id) => ({
+ 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)
+ }
+ })
+ source.addEventListener('input', (e) => {
+ term = e.target.value
+ const searchResults = contentIndex.search(term, [
+ {
+ field: 'content',
+ limit: 10,
+ },
+ {
+ field: 'title',
+ limit: 5,
+ },
+ ])
+ 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 = `
+ No results.
+ Try another search 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'
+ }
+ }
+
+ function closeSearch() {
+ searchContainer.style.display = 'none'
+ }
+
+ document.addEventListener('keydown', (event) => {
+ if (event.key === 'k' && (event.ctrlKey || event.metaKey)) {
+ event.preventDefault()
+ openSearch()
+ }
+ if (event.key === 'Escape') {
+ event.preventDefault()
+ closeSearch()
+ }
+ })
+
+ const searchButton = document.getElementById('search-icon')
+ searchButton.addEventListener('click', (evt) => {
+ openSearch()
+ })
+ searchButton.addEventListener('keydown', (evt) => {
+ openSearch()
+ })
+ searchContainer.addEventListener('click', (evt) => {
+ closeSearch()
+ })
+ document.getElementById('search-space').addEventListener('click', (evt) => {
+ evt.stopPropagation()
+ })
+})()
diff --git a/assets/styles/base.scss b/assets/styles/base.scss
index 68cbb213a..1b9b936e3 100644
--- a/assets/styles/base.scss
+++ b/assets/styles/base.scss
@@ -1,571 +1,591 @@
-:root {
- --lt-colours-light: var(--light) !important;
- --lt-colours-lightgray: var(--lightgray) !important;
- --lt-colours-dark: var(--secondary) !important;
- --lt-colours-secondary: var(--tertiary) !important;
- --lt-colours-gray: var(--outlinegray) !important;
-}
-
-h1, h2, h3, h4, h5, h6, ol, ul, thead {
- font-family: Inter;
- color: var(--dark);
- font-weight: revert;
- margin: revert;
- padding: revert;
-}
-
-p, ul, text {
- font-family: 'Source Sans Pro', sans-serif;
- color: var(--gray);
- fill: var(--gray);
- font-weight: revert;
- margin: revert;
- padding: revert;
-}
-
-.mainTOC {
- background: var(--lightgray);
- border-radius: 5px;
- padding: 0.75em 1em;
-}
-
-.mainTOC details summary {
- cursor: zoom-in;
- font-family: Inter;
- color: var(--dark);
- font-weight: 700;
-}
-
-.mainTOC details[open] summary {
- cursor: zoom-out;
-}
-
-#TableOfContents > ol {
- counter-reset: section;
- margin-left: 0em;
- padding-left: 1.5em;
- & > li {
- counter-increment: section;
- & > ol {
- counter-reset: subsection;
- & > li {
- counter-increment: subsection;
- &::marker {
- content: counter(section) "." counter(subsection) " ";
- }
- }
- }
- }
-
- & > li::marker {
- content: counter(section) " ";
- }
-
- & > li::marker, & > li > ol > li::marker {
- font-family: Source Sans Pro;
- font-weight: 700;
- }
-}
-
-table {
- width: 100%;
-}
-
-img {
- width: 100%;
- border-radius: 3px;
- margin: 1em 0;
-}
-
-p>img+em {
- display: block;
- transform: translateY(-1em);
-}
-
-sup {
- line-height: 0
-}
-
-p, tbody, li {
- font-family: Source Sans Pro;
- color: var(--gray);
- line-height: 1.5em;
-}
-
-blockquote {
- margin-left: 0em;
- border-left: 3px solid var(--secondary);
- padding-left: 1em;
- transition: border-color 0.2s ease;
-
- &:hover {
- border-color: var(--tertiary);
- }
-}
-
-table {
- padding: 1.5em;
-}
-
-td, th {
- padding: 0.1em 0.5em;
-}
-
-.footnotes p {
- margin: 0.5em 0;
-}
-
-.pagination {
- list-style: none;
- padding-left: 0;
- display: flex;
- margin-top: 2em;
- gap: 1.5em;
- justify-content: center;
-
- .disabled {
- opacity: 0.2;
- }
-
- & > li {
- text-align: center;
- display: inline-block;
-
- & a {
- background-color: transparent !important;
- }
-
- & a[href$="#"] {
- opacity: 0.2;
- }
- }
-}
-
-.section {
- & h3 > a {
- font-weight: 700;
- font-family: Inter;
- margin: 0;
- }
- & p {
- margin-top: 0;
- }
-}
-
-article {
- & > .meta {
- margin: -1.5em 0 1em 0;
- opacity: 0.7;
- }
-
- & > .tags {
- list-style: none;
- padding-left: 0;
-
- & .meta {
- & > h1 {
- margin: 0;
- }
- & > p {
- margin: 0;
- }
- }
-
- & > li {
- display: inline-block;
- }
- & > li > a {
- border-radius: 8px;
- border: var(--outlinegray) 1px solid;
- padding: 0.2em 0.5em;
- &::before {
- content: "#";
- margin-right: 0.3em;
- color: var(--outlinegray);
- }
- }
- }
-
- & a {
- font-family: Source Sans Pro;
- font-weight: 600;
-
- &.internal-link {
- text-decoration: none;
- background-color: transparentize(#8f9fa9, 0.85);
- padding: 0 0.1em;
- margin: auto -0.1em;
- border-radius: 3px;
-
- &.broken {
- opacity: 0.5;
- background-color: transparent;
- }
- }
- }
-
- & p {
- overflow-wrap: anywhere;
- }
-}
-
-.backlinks a {
- font-weight: 600;
- font-size: 0.9rem;
-}
-
-sup > a {
- text-decoration: none;
- padding: 0 0.1em 0 0.2em;
-}
-
-a {
- font-family: Inter, sans-serif;
- font-size: 1em;
- font-weight: 700;
- text-decoration: none;
- transition: all 0.2s ease;
- color: var(--secondary);
-
- &:hover {
- color: var(--tertiary) !important;
- }
-}
-
-pre {
- font-family: 'Fira Code';
- padding: 0.75em;
- border-radius: 3px;
- overflow-x: scroll;
-}
-
-code {
- font-family: 'Fira Code';
- font-size: 0.85em;
- padding: 0.15em 0.3em;
- border-radius: 5px;
- background: var(--lightgray);
-}
-
-html {
- scroll-behavior: smooth;
-
- &:lang(ar) {
- & p, & h1, & h2, & h3, article {
- direction: rtl;
- text-align: right;
- }
- }
-}
-
-body {
- margin: 0;
- height: 100vh;
- width: 100vw;
- //overflow-x: hidden;
- max-width: 100%;
- box-sizing: border-box;
- background-color: var(--light);
-}
-
-@keyframes fadeIn {
- 0% {opacity:0;}
- 100% {opacity:1;}
-}
-
-footer {
- margin-top: 4em;
- text-align: center;
- & ul {
- padding-left: 0;
- }
-}
-
-hr {
- width: 25%;
- margin: 4em auto;
- height: 2px;
- border-radius: 1px;
- border-width: 0;
- color: var(--dark);
- background-color: var(--dark);
-}
-
-.singlePage {
- padding: 4em 30vw;
-
- @media all and (max-width: 1200px) {
- padding: 25px 5vw;
- }
-}
-
-.page-end {
- display: flex;
- flex-direction: row;
- gap: 2em;
-
- @media all and (max-width: 780px) {
- flex-direction: column;
- }
-
- & > * {
- flex: 1 0 0;
- }
-
- & > .backlinks-container {
- & > ul {
- list-style: none;
- padding-left: 0;
-
- & > li {
- margin: 0.5em 0;
- padding: 0.25em 1em;
- border: var(--outlinegray) 1px solid;
- border-radius: 5px
- }
- }
- }
-
- & #graph-container {
- border: var(--outlinegray) 1px solid;
- border-radius: 5px;
- }
-}
-
-.centered {
- margin-top: 30vh;
-}
-
-article > h1 {
- font-size: 2em;
-}
-
-header {
- display: flex;
- flex-direction: row;
- align-items: center;
-
- & > h1 {
- font-size: 2em;
- }
-
- & > nav {
- @media all and (max-width: 600px) {
- display: none;
- }
- }
-
- & > .spacer {
- flex: 1 1 auto;
- }
-
- & > svg {
- cursor: pointer;
- width: 18px;
- min-width: 18px;
- margin: 0 1em;
-
- &:hover .search-path {
- stroke: var(--tertiary);
- }
-
- .search-path {
- stroke: var(--gray);
- stroke-width: 2px;
- transition: stroke 0.5s ease;
- }
- }
-}
-
-#search-container {
- position: fixed;
- z-index: 9999;
- left: 0;
- top: 0;
- width: 100vw;
- height: 100%;
- overflow: scroll;
- display: none;
- backdrop-filter: blur(4px);
- -webkit-backdrop-filter: blur(4px);
-
- & > div {
- width: 50%;
- margin-top: 15vh;
- margin-left: auto;
- margin-right: auto;
-
- @media all and (max-width: 1200px) {
- width: 90%;
- }
-
- & > * {
- width: 100%;
- border-radius: 4px;
- background: var(--light);
- box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16);
- margin-bottom: 2em;
- }
-
- & > input {
- box-sizing: border-box;
- padding: 0.5em 1em;
- font-family: Inter, sans-serif;
- color: var(--dark);
- font-size: 1.1em;
- border: 1px solid var(--outlinegray);
-
- &:focus {
- outline: none;
- }
- }
-
- & > #results-container {
- & > .result-card {
- padding: 1em;
- cursor: pointer;
- transition: background 0.2s ease;
- border: 1px solid var(--outlinegray);
- border-bottom: none;
- width: 100%;
-
- // normalize button props
- font-family: inherit;
- font-size: 100%;
- line-height: 1.15;
- margin: 0;
- overflow: visible;
- text-transform: none;
- text-align: left;
- background: var(--light);
- outline: none;
-
- &:hover, &:focus {
- background: rgba(180, 180, 180, 0.15);
- }
-
- &:first-of-type {
- border-top-left-radius: 5px;
- border-top-right-radius: 5px;
- }
-
- &:last-of-type {
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- border-bottom: 1px solid var(--outlinegray);
- }
-
- & > h3, & > p {
- margin: 0;
- }
-
- & .search-highlight {
- background-color: #afbfc966;
- padding: 0.05em 0.2em;
- border-radius: 3px;
- }
- }
- }
- }
-}
-
-.section-ul {
- list-style: none;
- padding-left: 0;
-
- & > li {
- border: 1px solid var(--outlinegray);
- border-radius: 5px;
- padding: 0 1em;
- margin-bottom: 1em;
-
- & h3 {
- opacity: 1;
- font-weight: 700;
- margin-bottom: 0em;
- }
-
- & .meta {
- opacity: 0.6;
- }
- }
-}
-
-@keyframes dropin {
- 0% {
- display: none;
- opacity: 0;
- visibility: hidden;
- }
- 1% {
- display: inline-block;
- opacity: 0;
- transform: translate(-50%, 40%);
- }
- 100% {
- opacity: 1;
- visibility: visible;
- transform: translate(-50%, 20%);
- }
-}
-
-.popover {
- z-index: 999;
- position: absolute;
- width: 20em;
- display: none;
- background-color: var(--light);
- padding: 1em;
- border: 1px solid var(--outlinegray);
- border-radius: 5px;
- transform: translate(-50%, 40%);
- pointer-events: none;
- transition: opacity 0.2s ease, transform 0.2s ease;
- user-select: none;
- overflow-wrap: anywhere;
- box-shadow: 6px 6px 36px 0px rgba(0,0,0,0.25);
-
- @media all and (max-width: 600px) {
- display: none;
- }
-
- &.visible {
- opacity: 1;
- visibility: visible;
- transform: translate(-50%, 20%);
- display: inline-block;
- animation: dropin 0.2s ease;
- }
-
- & > h3 {
- font-size: 1rem;
- margin: 0.25em 0;
- }
-
- & > .meta {
- margin-top: 0.25em;
- opacity: 0.5;
- font-family: "JetBrains Mono", monospace;
- font-size: 0.8rem;
- }
-
- & > p {
- margin: 0;
- font-weight: 400;
- user-select: none;
- }
-}
-
-
-
-#contact_buttons ul {
- list-style-type: none;
-
- li {
- display: inline-block;
- }
-
- li a {
- padding: 0 1em;
- }
-}
+:root {
+ --lt-colours-light: var(--light) !important;
+ --lt-colours-lightgray: var(--lightgray) !important;
+ --lt-colours-dark: var(--secondary) !important;
+ --lt-colours-secondary: var(--tertiary) !important;
+ --lt-colours-gray: var(--outlinegray) !important;
+}
+
+h1, h2, h3, h4, h5, h6, ol, ul, thead {
+ font-family: Inter;
+ color: var(--dark);
+ font-weight: revert;
+ margin: revert;
+ padding: revert;
+
+ &:hover > .hanchor {
+ opacity: 1;
+ }
+}
+
+.hanchor {
+ font-family: Inter;
+ margin-left: -1em;
+ opacity: 0.3;
+ transition: opacity 0.3s ease;
+ color: var(--secondary);
+
+}
+
+p, ul, text {
+ font-family: 'Source Sans Pro', sans-serif;
+ color: var(--gray);
+ fill: var(--gray);
+ font-weight: revert;
+ margin: revert;
+ padding: revert;
+}
+
+.mainTOC {
+ background: var(--lightgray);
+ border-radius: 5px;
+ padding: 0.75em 1em;
+}
+
+.mainTOC details summary {
+ cursor: zoom-in;
+ font-family: Inter;
+ color: var(--dark);
+ font-weight: 700;
+}
+
+.mainTOC details[open] summary {
+ cursor: zoom-out;
+}
+
+#TableOfContents > ol {
+ counter-reset: section;
+ margin-left: 0em;
+ padding-left: 1.5em;
+ & > li {
+ counter-increment: section;
+ & > ol {
+ counter-reset: subsection;
+ & > li {
+ counter-increment: subsection;
+ &::marker {
+ content: counter(section) "." counter(subsection) " ";
+ }
+ }
+ }
+ }
+
+ & > li::marker {
+ content: counter(section) " ";
+ }
+
+ & > li::marker, & > li > ol > li::marker {
+ font-family: Source Sans Pro;
+ font-weight: 700;
+ }
+}
+
+table {
+ width: 100%;
+}
+
+img {
+ width: 100%;
+ border-radius: 3px;
+ margin: 1em 0;
+}
+
+p>img+em {
+ display: block;
+ transform: translateY(-1em);
+}
+
+sup {
+ line-height: 0
+}
+
+p, tbody, li {
+ font-family: Source Sans Pro;
+ color: var(--gray);
+ line-height: 1.5em;
+}
+
+blockquote {
+ margin-left: 0em;
+ border-left: 3px solid var(--secondary);
+ padding-left: 1em;
+ transition: border-color 0.2s ease;
+
+ &:hover {
+ border-color: var(--tertiary);
+ }
+}
+
+table {
+ padding: 1.5em;
+}
+
+td, th {
+ padding: 0.1em 0.5em;
+}
+
+.footnotes p {
+ margin: 0.5em 0;
+}
+
+.pagination {
+ list-style: none;
+ padding-left: 0;
+ display: flex;
+ margin-top: 2em;
+ gap: 1.5em;
+ justify-content: center;
+
+ .disabled {
+ opacity: 0.2;
+ }
+
+ & > li {
+ text-align: center;
+ display: inline-block;
+
+ & a {
+ background-color: transparent !important;
+ }
+
+ & a[href$="#"] {
+ opacity: 0.2;
+ }
+ }
+}
+
+.section {
+ & h3 > a {
+ font-weight: 700;
+ font-family: Inter;
+ margin: 0;
+ }
+ & p {
+ margin-top: 0;
+ }
+}
+
+article {
+ & > .meta {
+ margin: -1.5em 0 1em 0;
+ opacity: 0.7;
+ }
+
+ & > .tags {
+ list-style: none;
+ padding-left: 0;
+
+ & .meta {
+ & > h1 {
+ margin: 0;
+ }
+ & > p {
+ margin: 0;
+ }
+ }
+
+ & > li {
+ display: inline-block;
+ }
+ & > li > a {
+ border-radius: 8px;
+ border: var(--outlinegray) 1px solid;
+ padding: 0.2em 0.5em;
+ &::before {
+ content: "#";
+ margin-right: 0.3em;
+ color: var(--outlinegray);
+ }
+ }
+ }
+
+ & a {
+ font-family: Source Sans Pro;
+ font-weight: 600;
+
+ &.internal-link {
+ text-decoration: none;
+ background-color: transparentize(#8f9fa9, 0.85);
+ padding: 0 0.1em;
+ margin: auto -0.1em;
+ border-radius: 3px;
+
+ &.broken {
+ opacity: 0.5;
+ background-color: transparent;
+ }
+ }
+ }
+
+ & p {
+ overflow-wrap: anywhere;
+ }
+}
+
+.backlinks a {
+ font-weight: 600;
+ font-size: 0.9rem;
+}
+
+sup > a {
+ text-decoration: none;
+ padding: 0 0.1em 0 0.2em;
+}
+
+a {
+ font-family: Inter, sans-serif;
+ font-size: 1em;
+ font-weight: 700;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ color: var(--secondary);
+
+ &:hover {
+ color: var(--tertiary) !important;
+ }
+}
+
+pre {
+ font-family: 'Fira Code';
+ padding: 0.75em;
+ border-radius: 3px;
+ overflow-x: scroll;
+}
+
+code {
+ font-family: 'Fira Code';
+ font-size: 0.85em;
+ padding: 0.15em 0.3em;
+ border-radius: 5px;
+ background: var(--lightgray);
+}
+
+html {
+ scroll-behavior: smooth;
+
+ &:lang(ar) {
+ & p, & h1, & h2, & h3, article {
+ direction: rtl;
+ text-align: right;
+ }
+ }
+}
+
+body {
+ margin: 0;
+ height: 100vh;
+ width: 100vw;
+ //overflow-x: hidden;
+ max-width: 100%;
+ box-sizing: border-box;
+ background-color: var(--light);
+}
+
+@keyframes fadeIn {
+ 0% {opacity:0;}
+ 100% {opacity:1;}
+}
+
+footer {
+ margin-top: 4em;
+ text-align: center;
+ & ul {
+ padding-left: 0;
+ }
+}
+
+hr {
+ width: 25%;
+ margin: 4em auto;
+ height: 2px;
+ border-radius: 1px;
+ border-width: 0;
+ color: var(--dark);
+ background-color: var(--dark);
+}
+
+.singlePage {
+ padding: 4em 30vw;
+
+ @media all and (max-width: 1200px) {
+ padding: 25px 5vw;
+ }
+}
+
+.page-end {
+ display: flex;
+ flex-direction: row;
+ gap: 2em;
+
+ @media all and (max-width: 780px) {
+ flex-direction: column;
+ }
+
+ & > * {
+ flex: 1 0 0;
+ }
+
+ & > .backlinks-container {
+ & > ul {
+ list-style: none;
+ padding-left: 0;
+
+ & > li {
+ margin: 0.5em 0;
+ padding: 0.25em 1em;
+ border: var(--outlinegray) 1px solid;
+ border-radius: 5px
+ }
+ }
+ }
+
+ & #graph-container {
+ border: var(--outlinegray) 1px solid;
+ border-radius: 5px;
+ box-sizing: border-box;
+ min-height: 250px;
+
+ & > svg {
+ margin-bottom: -5px;
+
+ }
+ }
+}
+
+.centered {
+ margin-top: 30vh;
+}
+
+article > h1 {
+ font-size: 2em;
+}
+
+header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ & > h1 {
+ font-size: 2em;
+ }
+
+ & > nav {
+ @media all and (max-width: 600px) {
+ display: none;
+ }
+ }
+
+ & > .spacer {
+ flex: 1 1 auto;
+ }
+
+ & > svg {
+ cursor: pointer;
+ width: 18px;
+ min-width: 18px;
+ margin: 0 1em;
+
+ &:hover .search-path {
+ stroke: var(--tertiary);
+ }
+
+ .search-path {
+ stroke: var(--gray);
+ stroke-width: 2px;
+ transition: stroke 0.5s ease;
+ }
+ }
+}
+
+#search-container {
+ position: fixed;
+ z-index: 9999;
+ left: 0;
+ top: 0;
+ width: 100vw;
+ height: 100%;
+ overflow: scroll;
+ display: none;
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
+
+ & > div {
+ width: 50%;
+ margin-top: 15vh;
+ margin-left: auto;
+ margin-right: auto;
+
+ @media all and (max-width: 1200px) {
+ width: 90%;
+ }
+
+ & > * {
+ width: 100%;
+ border-radius: 4px;
+ background: var(--light);
+ box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16);
+ margin-bottom: 2em;
+ }
+
+ & > input {
+ box-sizing: border-box;
+ padding: 0.5em 1em;
+ font-family: Inter, sans-serif;
+ color: var(--dark);
+ font-size: 1.1em;
+ border: 1px solid var(--outlinegray);
+
+ &:focus {
+ outline: none;
+ }
+ }
+
+ & > #results-container {
+ & .result-card {
+ padding: 1em;
+ cursor: pointer;
+ transition: background 0.2s ease;
+ border: 1px solid var(--outlinegray);
+ border-bottom: none;
+ width: 100%;
+
+ // normalize button props
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.15;
+ margin: 0;
+ overflow: visible;
+ text-transform: none;
+ text-align: left;
+ background: var(--light);
+ outline: none;
+
+ &:hover, &:focus {
+ background: rgba(180, 180, 180, 0.15);
+ }
+
+ &:first-of-type {
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ }
+
+ &:last-of-type {
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ border-bottom: 1px solid var(--outlinegray);
+ }
+
+ & > h3, & > p {
+ margin: 0;
+ }
+ }
+ }
+ }
+}
+
+.search-highlight {
+ background-color: #afbfc966;
+ padding: 0.05em 0.2em;
+ border-radius: 3px;
+}
+
+.section-ul {
+ list-style: none;
+ padding-left: 0;
+
+ & > li {
+ border: 1px solid var(--outlinegray);
+ border-radius: 5px;
+ padding: 0 1em;
+ margin-bottom: 1em;
+
+ & h3 {
+ opacity: 1;
+ font-weight: 700;
+ margin-bottom: 0em;
+ }
+
+ & .meta {
+ opacity: 0.6;
+ }
+ }
+}
+
+@keyframes dropin {
+ 0% {
+ display: none;
+ opacity: 0;
+ visibility: hidden;
+ }
+ 1% {
+ display: inline-block;
+ opacity: 0;
+ transform: translate(-50%, 40%);
+ }
+ 100% {
+ opacity: 1;
+ visibility: visible;
+ transform: translate(-50%, 20%);
+ }
+}
+
+.popover {
+ z-index: 999;
+ position: absolute;
+ width: 20em;
+ display: none;
+ background-color: var(--light);
+ padding: 1em;
+ border: 1px solid var(--outlinegray);
+ border-radius: 5px;
+ transform: translate(-50%, 40%);
+ pointer-events: none;
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ user-select: none;
+ overflow-wrap: anywhere;
+ box-shadow: 6px 6px 36px 0px rgba(0,0,0,0.25);
+
+ @media all and (max-width: 600px) {
+ display: none !important;
+ }
+
+ &.visible {
+ opacity: 1;
+ visibility: visible;
+ transform: translate(-50%, 20%);
+ display: inline-block;
+ animation: dropin 0.2s ease;
+ }
+
+ & > h3 {
+ font-size: 1rem;
+ margin: 0.25em 0;
+ }
+
+ & > .meta {
+ margin-top: 0.25em;
+ opacity: 0.5;
+ font-family: "JetBrains Mono", monospace;
+ font-size: 0.8rem;
+ }
+
+ & > p, & > a {
+ margin: 0;
+ font-weight: 400;
+ user-select: none;
+ }
+}
+
+
+
+#contact_buttons ul {
+ list-style-type: none;
+
+ li {
+ display: inline-block;
+ }
+
+ li a {
+ padding: 0 1em;
+ }
+}
diff --git a/assets/styles/darkmode.scss b/assets/styles/darkmode.scss
index dc293b66c..61967d797 100644
--- a/assets/styles/darkmode.scss
+++ b/assets/styles/darkmode.scss
@@ -1,44 +1,44 @@
-.darkmode {
- float: right;
- padding: 1em;
- min-width: 30px;
- position: relative;
-
- @media all and (max-width: 450px) {
- padding: 1em;
- }
-
- & > .toggle {
- display: none;
- box-sizing: border-box;
- }
-
- & svg {
- opacity: 0;
- position: absolute;
- width: 20px;
- height: 20px;
- top: calc(50% - 10px);
- margin: 0 7px;
- fill: var(--gray);
- transition: opacity 0.1s ease;
- }
-}
-
-.toggle:checked ~ label {
- & > #dayIcon {
- opacity: 0;
- }
- & > #nightIcon {
- opacity: 1;
- }
-}
-
-.toggle:not(:checked) ~ label {
- & > #dayIcon {
- opacity: 1;
- }
- & > #nightIcon {
- opacity: 0;
- }
+.darkmode {
+ float: right;
+ padding: 1em;
+ min-width: 30px;
+ position: relative;
+
+ @media all and (max-width: 450px) {
+ padding: 1em;
+ }
+
+ & > .toggle {
+ display: none;
+ box-sizing: border-box;
+ }
+
+ & svg {
+ opacity: 0;
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ top: calc(50% - 10px);
+ margin: 0 7px;
+ fill: var(--gray);
+ transition: opacity 0.1s ease;
+ }
+}
+
+.toggle:checked ~ label {
+ & > #dayIcon {
+ opacity: 0;
+ }
+ & > #nightIcon {
+ opacity: 1;
+ }
+}
+
+.toggle:not(:checked) ~ label {
+ & > #dayIcon {
+ opacity: 1;
+ }
+ & > #nightIcon {
+ opacity: 0;
+ }
}
\ No newline at end of file
diff --git a/data/graphConfig.yaml b/data/graphConfig.yaml
index 7ab89bf80..3f8d3fb6b 100644
--- a/data/graphConfig.yaml
+++ b/data/graphConfig.yaml
@@ -1,6 +1,6 @@
-enableLegend: false
-enableDrag: true
-enableZoom: true
-depth: -1 # set to -1 to show full graph
-paths:
+enableLegend: false
+enableDrag: true
+enableZoom: true
+depth: -1 # set to -1 to show full graph
+paths:
- /moc: "#4388cc"
\ No newline at end of file
diff --git a/layouts/404.html b/layouts/404.html
index 27f2ab857..424839502 100644
--- a/layouts/404.html
+++ b/layouts/404.html
@@ -1,16 +1,16 @@
-
-
-{{ partial "head.html" . }}
-
-
-
- {{partial "darkmode.html" .}}
-
-
404.
-
Hey! You look a little lost. This page doesn't exist (or may be private).
-
↳ Let's get you home.
-
-
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+
+ {{partial "darkmode.html" .}}
+
+
404.
+
Hey! You look a little lost. This page doesn't exist (or may be private).
+
↳ Let's get you home.
+
+
+
+
+
diff --git a/layouts/_default/_markup/render-image.html b/layouts/_default/_markup/render-image.html
index 1ae86075c..ff4e8b39c 100644
--- a/layouts/_default/_markup/render-image.html
+++ b/layouts/_default/_markup/render-image.html
@@ -1,8 +1,8 @@
-{{$src := .Destination | safeURL }}
-{{$external := strings.HasPrefix $src "http" }}
-{{- if $external -}}
-
-{{- else -}}
-{{$fixedUrl := (cond (hasPrefix $src "/") $src (print "/" $src)) | urlize}}
-
-{{- end -}}
+{{$src := .Destination | safeURL }}
+{{$external := strings.HasPrefix $src "http" }}
+{{- if $external -}}
+
+{{- else -}}
+{{$fixedUrl := (cond (hasPrefix $src "/") $src (print "/" $src)) | urlize}}
+
+{{- end -}}
diff --git a/layouts/_default/_markup/render-link.html b/layouts/_default/_markup/render-link.html
index 0cb588371..4757b7284 100644
--- a/layouts/_default/_markup/render-link.html
+++ b/layouts/_default/_markup/render-link.html
@@ -1,16 +1,16 @@
-{{$trimmed := strings.TrimSuffix ".md" (.Destination | safeURL)}}
-{{$dashedurl := replace $trimmed "%20" "-" }}
-{{$external := strings.HasPrefix $dashedurl "http" }}
-{{- if $external -}}
-{{ .Text | safeHTML }}
-{{- else -}}
-{{$spacedurl := replace $trimmed "%20" " " }}
-{{$fixedUrl := (cond (hasPrefix $spacedurl "/") $spacedurl (print "/" $spacedurl)) | urlize}}
-{{$nonexistent := eq (.Page.GetPage $spacedurl).RelPermalink ""}}
-{{$rooted := default $spacedurl (strings.TrimRight "/" (.Page.GetPage $spacedurl).RelPermalink) }}
-{{- .Text | safeHTML -}}
-
-{{- end -}}
+{{$trimmed := strings.TrimSuffix ".md" (.Destination | safeURL)}}
+{{$dashedurl := replace $trimmed "%20" "-" }}
+{{$external := strings.HasPrefix $dashedurl "http" }}
+{{- if $external -}}
+{{ .Text | safeHTML }}
+{{- else -}}
+{{$spacedurl := replace $trimmed "%20" " " }}
+{{$fixedUrl := (cond (hasPrefix $spacedurl "/") $spacedurl (print "/" $spacedurl)) | urlize}}
+{{$nonexistent := eq (.Page.GetPage $spacedurl).RelPermalink ""}}
+{{$rooted := default $spacedurl ((.Page.GetPage $spacedurl).RelPermalink) }}
+{{- .Text | safeHTML -}}
+
+{{- end -}}
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index 90d44cda6..ccb3b93ac 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -1,10 +1,10 @@
-
-
-{{ block "head" . }}
-{{ end }}
-
-
-{{ block "main" . }}
-{{ end }}
-
+
+
+{{ block "head" . }}
+{{ end }}
+
+
+{{ block "main" . }}
+{{ end }}
+
\ No newline at end of file
diff --git a/layouts/_default/section.html b/layouts/_default/section.html
index 783aa2332..abdf0b05c 100644
--- a/layouts/_default/section.html
+++ b/layouts/_default/section.html
@@ -1,25 +1,24 @@
-
-
-{{ partial "head.html" . }}
-
-
-{{partial "search.html" .}}
-
-
-
-
- Search Icon Icon to open search
-
- {{partial "darkmode.html" .}}
-
-
- All {{.Title}}
- {{partial "page-list.html" .Paginator.Pages.ByLastmod.Reverse }}
- {{ template "_internal/pagination.html" .}}
-
- {{partial "contact.html" .}}
-
-{{partial "popover.html" .}}
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+{{partial "search.html" .}}
+
+
+
+
+ Search Icon Icon to open search
+
+ {{partial "darkmode.html" .}}
+
+
+ All {{.Title}}
+ {{partial "page-list.html" .Paginator.Pages.ByLastmod.Reverse }}
+ {{ template "_internal/pagination.html" .}}
+
+ {{partial "contact.html" .}}
+
+
+
+
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index cfaea7100..91eda290b 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -1,33 +1,32 @@
-
-
-{{ partial "head.html" . }}
-
-
-{{partial "search.html" .}}
-
-
-
-
- Search Icon Icon to open search
-
- {{partial "darkmode.html" .}}
-
-
- {{if .Title}}{{ .Title }} {{end}}
-
- Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
-
-
- {{partial "toc.html" .}}
- {{partial "textprocessing.html" . }}
-
- {{partial "footer.html" .}}
- {{partial "popover.html" .}}
-
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+{{partial "search.html" .}}
+
+
+
+
+ Search Icon Icon to open search
+
+ {{partial "darkmode.html" .}}
+
+
+ {{if .Title}}{{ .Title }} {{end}}
+
+ Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
+
+
+ {{partial "toc.html" .}}
+ {{partial "textprocessing.html" . }}
+
+ {{partial "footer.html" .}}
+
+
+
+
diff --git a/layouts/_default/taxonomy.html b/layouts/_default/taxonomy.html
index be7532237..e0a1e876c 100644
--- a/layouts/_default/taxonomy.html
+++ b/layouts/_default/taxonomy.html
@@ -1,34 +1,33 @@
-
-
-{{ partial "head.html" . }}
-
-
-{{partial "search.html" .}}
-
-
-
-
- Search Icon Icon to open search
-
- {{partial "darkmode.html" .}}
-
-
- All {{.Title}}
-
-
- {{partial "contact.html" .}}
-
-{{partial "popover.html" .}}
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+{{partial "search.html" .}}
+
+
+
+
+ Search Icon Icon to open search
+
+ {{partial "darkmode.html" .}}
+
+
+ All {{.Title}}
+
+
+ {{partial "contact.html" .}}
+
+
+
+
diff --git a/layouts/_default/term.html b/layouts/_default/term.html
index 7b897ec34..58f024bcc 100644
--- a/layouts/_default/term.html
+++ b/layouts/_default/term.html
@@ -1,25 +1,24 @@
-
-
-{{ partial "head.html" . }}
-
-
-{{partial "search.html" .}}
-
-
-
-
- Search Icon Icon to open search
-
- {{partial "darkmode.html" .}}
-
-
- Tag: {{.Title | humanize}}
- {{partial "page-list.html" .Paginator.Pages}}
- {{ template "_internal/pagination.html" . }}
-
- {{partial "contact.html" .}}
-
-{{partial "popover.html" .}}
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+{{partial "search.html" .}}
+
+
+
+
+ Search Icon Icon to open search
+
+ {{partial "darkmode.html" .}}
+
+
+ Tag: {{.Title | humanize}}
+ {{partial "page-list.html" .Paginator.Pages}}
+ {{ template "_internal/pagination.html" . }}
+
+ {{partial "contact.html" .}}
+
+
+
+
diff --git a/layouts/index.html b/layouts/index.html
index cffe3b21c..224c99784 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -1,24 +1,22 @@
-
-
-{{ partial "head.html" . }}
-
-
-{{partial "search.html" .}}
-
-
-
- {{if .Title}}{{ .Title }}{{else}}Untitled{{end}}
- Search Icon Icon to open search
-
- {{partial "darkmode.html" .}}
-
-
- {{partial "toc.html" .}}
- {{partial "textprocessing.html" . }}
-
- {{partial "footer.html" .}}
- {{partial "popover.html" .}}
-
-
-
-
+
+
+{{ partial "head.html" . }}
+
+
+{{partial "search.html" .}}
+
+
+
+ {{if .Title}}{{ .Title }}{{else}}Untitled{{end}}
+ Search Icon Icon to open search
+
+ {{partial "darkmode.html" .}}
+
+
+ {{partial "toc.html" .}}
+ {{partial "textprocessing.html" . }}
+
+ {{partial "footer.html" .}}
+
+
+
diff --git a/layouts/partials/backlinks.html b/layouts/partials/backlinks.html
index 45187b575..23c9091a5 100644
--- a/layouts/partials/backlinks.html
+++ b/layouts/partials/backlinks.html
@@ -1,24 +1,30 @@
-Backlinks
-
- {{$url := urls.Parse .Site.BaseURL }}
- {{$host := strings.TrimRight "/" $url.Path }}
- {{$curPage := strings.TrimPrefix $host (strings.TrimRight "/" .Page.RelPermalink)}}
- {{$linkIndex := getJSON "/assets/indices/linkIndex.json"}}
- {{$inbound := index $linkIndex.index.backlinks $curPage}}
- {{$contentTable := getJSON "/assets/indices/contentIndex.json"}}
- {{if $inbound}}
- {{$cleanedInbound := apply (apply $inbound "index" "." "source") "replace" "." " " "-"}}
- {{- range $cleanedInbound | uniq -}}
- {{$l := printf "%s%s" $host .}}
- {{with (index $contentTable .)}}
-
- {{index (index . "title")}}
-
- {{end}}
- {{- end -}}
- {{else}}
-
- No backlinks found
-
- {{end}}
-
+Backlinks
+
+ {{$url := urls.Parse .Site.BaseURL }}
+ {{$host := strings.TrimRight "/" $url.Path }}
+ {{$curPage := strings.TrimPrefix $host (strings.TrimRight "/" .Page.RelPermalink)}}
+ {{$linkIndex := getJSON "/assets/indices/linkIndex.json"}}
+ {{$inbound := index $linkIndex.index.backlinks $curPage}}
+ {{$contentTable := getJSON "/assets/indices/contentIndex.json"}}
+ {{if $inbound}}
+ {{$backlinks := dict "SENTINEL" "SENTINEL"}}
+ {{range $k, $v := $inbound}}
+ {{$cleanedInbound := replace $v.source " " "-"}}
+ {{$ctx := $v.text}}
+ {{$backlinks = merge $backlinks (dict $cleanedInbound $ctx)}}
+ {{end}}
+ {{- range $lnk, $ctx := $backlinks -}}
+ {{$l := printf "%s%s/" $host $lnk}}
+ {{$l = cond (eq $l "//") "/" $l}}
+ {{with (index $contentTable $lnk)}}
+
+ {{index (index . "title")}}
+
+ {{end}}
+ {{- end -}}
+ {{else}}
+
+ No backlinks found
+
+ {{end}}
+
diff --git a/layouts/partials/contact.html b/layouts/partials/contact.html
index 9eaa87faa..7fb991e9d 100644
--- a/layouts/partials/contact.html
+++ b/layouts/partials/contact.html
@@ -1,14 +1,14 @@
-
-
+
+
diff --git a/layouts/partials/darkmode.html b/layouts/partials/darkmode.html
index 3dec38f1e..d7540c22a 100644
--- a/layouts/partials/darkmode.html
+++ b/layouts/partials/darkmode.html
@@ -1,15 +1,15 @@
-
-
-
-
- Light Mode
-
-
-
-
-
- Dark Mode
-
-
-
+
+
+
+
+ Light Mode
+
+
+
+
+
+ Dark Mode
+
+
+
\ No newline at end of file
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index cc891362c..6d4ef17b9 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -1,11 +1,11 @@
-
-
-
- {{partial "backlinks.html" .}}
-
-
- {{partial "graph.html" .}}
-
-
-
+
+
+
+ {{partial "backlinks.html" .}}
+
+
+ {{partial "graph.html" .}}
+
+
+
{{partial "contact.html" .}}
\ No newline at end of file
diff --git a/layouts/partials/graph.html b/layouts/partials/graph.html
index 6be8c0651..b9f79763c 100644
--- a/layouts/partials/graph.html
+++ b/layouts/partials/graph.html
@@ -1,25 +1,18 @@
-
-
Interactive Graph
-
-
-{{ $js := resources.Get "js/graph.js" | resources.Fingerprint "md5" }}
-
-
+
+
Interactive Graph
+
+
+{{ $js := resources.Get "js/graph.js" | resources.Fingerprint "md5" }}
+
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index d3be2a606..a5f23da30 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -1,46 +1,110 @@
-
-
-
-
-
{{ if .Title }}{{ .Title }}{{ else }}{{ $.Site.Data.config.page_title }}{{ end }}
-
-
-
-
-
- {{$sass := resources.Match "styles/[!_]*.scss" }}
- {{$css := slice }}
- {{range $sass}}
- {{$scss := . | resources.ToCSS (dict "outputStyle" "compressed") }}
- {{$css = $css | append $scss}}
- {{end}}
- {{$finalCss := $css | resources.Concat "styles.css" | resources.Fingerprint "md5" | resources.Minify }}
-
-
- {{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" | resources.Minify }}
-
- {{partial "katex.html" .}}
-
-
- {{$linkIndex := resources.Get "indices/linkIndex.json" | resources.Fingerprint "md5" | resources.Minify | }}
- {{$contentIndex := resources.Get "indices/contentIndex.json" | resources.Fingerprint "md5" | resources.Minify }}
-
-
-{{ template "_internal/google_analytics.html" . }}
+
+
+
+
+
+ {{ if .Title }}{{ .Title }}{{ else }}{{ $.Site.Data.config.page_title }}{{
+ end }}
+
+
+
+
+
+
+ {{$sass := resources.Match "styles/[!_]*.scss" }}
+ {{$css := slice }}
+ {{range $sass}}
+ {{$scss := . | resources.ToCSS (dict "outputStyle" "compressed") }}
+ {{$css = $css | append $scss}}
+ {{end}}
+ {{$finalCss := $css | resources.Concat "styles.css" | resources.Fingerprint "md5" | resources.Minify }}
+
+
+ {{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" |
+ resources.Minify }}
+
+ {{partial "katex.html" .}}
+
+ {{ $popover := resources.Get "js/popover.js" | resources.Fingerprint "md5" |
+ resources.Minify }}
+
+
+
+ {{$linkIndex := resources.Get "indices/linkIndex.json" | resources.Fingerprint
+ "md5" | resources.Minify | }} {{$contentIndex := resources.Get
+ "indices/contentIndex.json" | resources.Fingerprint "md5" | resources.Minify
+ }}
+
+ {{if $.Site.Data.config.enableSPA}}
+ {{ $router := resources.Get "js/router.js" | resources.Fingerprint "md5" |
+ resources.Minify }}
+
+ {{else}}
+
+ {{end}}
+
+{{ template "_internal/google_analytics.html" . }}
diff --git a/layouts/partials/katex.html b/layouts/partials/katex.html
index 06ef1c969..756ef779e 100644
--- a/layouts/partials/katex.html
+++ b/layouts/partials/katex.html
@@ -1,16 +1,5 @@
-{{if $.Site.Data.config.enableLatex}}
-
-
-
-
-{{end}}
+{{if $.Site.Data.config.enableLatex}}
+
+
+
+{{end}}
diff --git a/layouts/partials/page-list.html b/layouts/partials/page-list.html
index e2c01e315..6c2249baf 100644
--- a/layouts/partials/page-list.html
+++ b/layouts/partials/page-list.html
@@ -1,15 +1,15 @@
-
- {{- range . -}}
-
-
-
-
-
{{- .Summary -}}{{if .Truncated}}...{{end}}
-
-
- {{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
-
-
-
- {{- end -}}
-
\ No newline at end of file
+
+ {{- range . -}}
+
+
+
+
+
{{- .Summary -}}{{if .Truncated}}...{{end}}
+
+
+ {{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
+
+
+
+ {{- end -}}
+
diff --git a/layouts/partials/popover.html b/layouts/partials/popover.html
deleted file mode 100644
index 4d35c0f38..000000000
--- a/layouts/partials/popover.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{{if $.Site.Data.config.enableLinkPreview}}
-{{ $js := resources.Get "js/popover.js" | resources.Fingerprint "md5" | resources.Minify }}
-
-
-{{end}}
\ No newline at end of file
diff --git a/layouts/partials/search.html b/layouts/partials/search.html
index bee0ba687..f727184a6 100644
--- a/layouts/partials/search.html
+++ b/layouts/partials/search.html
@@ -1,10 +1,10 @@
-
-
-{{ $js := resources.Get "js/search.js" | resources.Fingerprint "md5" | resources.Minify }}
-
+
+
+{{ $js := resources.Get "js/search.js" | resources.Fingerprint "md5" | resources.Minify }}
+
diff --git a/layouts/partials/textprocessing.html b/layouts/partials/textprocessing.html
index 868d626ac..8e8c99917 100644
--- a/layouts/partials/textprocessing.html
+++ b/layouts/partials/textprocessing.html
@@ -1,56 +1,60 @@
-{{ $content := .Content }}
-{{ $raw := .RawContent }}
-{{ $page := .Page }}
-
-{{/* Escape slashes for Latex to fix line breaks */}}
-{{$latex := findRE "\\$\\$([^\\$]+)\\$\\$" $content}}
-{{range $latex}}
- {{$fixed := replaceRE "\\\\(?: +|\\n)" "\\\\" .}}
- {{$content = replace $content . $fixed}}
-{{end}}
-
-{{/* Wikilinks */}}
-{{$wikilinks := $content | findRE "!?\\[\\[\\S[^\\[\\]\\|]*(?:\\|[^\\[\\]]*)?\\S\\]\\]" }}
-{{$codefences := $raw | findRE "\\x60[^\\x60\\n]+\\x60"}}
-{{$codeblocks := $raw | findRE "\\x60{3}[^\\x60]+\\x60{3}"}}
-{{$code := union $codefences $codeblocks}}
-{{range $wikilinks}}
- {{$cur := .}}
- {{$incode := false}}
- {{range $code}}
- {{if (in . $cur)}}
- {{$incode = true}}
- {{end}}
- {{end}}
- {{if not $incode}}
- {{if (hasPrefix . "!")}}
- {{$inner := . | strings.TrimPrefix "![[" | strings.TrimSuffix "]]" }}
- {{$split := split $inner "|"}}
- {{$path := index $split 0 | relURL}}
- {{$reference := split $path "#"}}
- {{$title := index $reference 0}}
- {{$display := default $title (index $split 1)}}
- {{$img := printf "
" $path $display}}
- {{$content = replace $content . $img}}
- {{else}}
- {{$inner := . | strings.TrimPrefix "[[" | strings.TrimSuffix "]]" }}
- {{$split := split $inner "|"}}
- {{$path := index $split 0}}
- {{$reference := split $path "#"}}
- {{$title := index $reference 0}}
- {{$block := default "" (index $reference 1)}}
- {{$block = strings.TrimRight "/" (cond (eq $block "") $block (printf "#%s" $block))}}
- {{$href := strings.TrimRight "/" ($page.GetPage $title).RelPermalink}}
- {{$display := default $title (index $split 1)}}
- {{if not $href}}
- {{$link := printf "
%s " $display}}
- {{$content = replace $content . $link}}
- {{else}}
- {{$fullhref := printf "%s%s" $href $block }}
- {{$link := printf "
%s " $fullhref $href $display}}
- {{$content = replace $content . $link}}
- {{end}}
- {{end}}
- {{end}}
-{{end}}
-{{ $content | safeHTML }}
+{{ $content := .Content }}
+{{ $raw := .RawContent }}
+{{ $page := .Page }}
+
+{{/* Escape slashes for Latex to fix line breaks */}}
+{{$latex := findRE "\\$\\$([^\\$]+)\\$\\$" $content}}
+{{range $latex}}
+ {{$fixed := replaceRE "\\\\(?: +|\\n)" "\\\\" .}}
+ {{$content = replace $content . $fixed}}
+{{end}}
+
+{{/* Wikilinks */}}
+{{$wikilinks := $content | findRE "!?\\[\\[\\S[^\\[\\]\\|]*(?:\\|[^\\[\\]]*)?\\S\\]\\]" }}
+{{$codefences := $raw | findRE "\\x60[^\\x60\\n]+\\x60"}}
+{{$codeblocks := $raw | findRE "\\x60{3}[^\\x60]+\\x60{3}"}}
+{{$code := union $codefences $codeblocks}}
+{{range $wikilinks}}
+ {{$cur := .}}
+ {{$incode := false}}
+ {{range $code}}
+ {{if (in . $cur)}}
+ {{$incode = true}}
+ {{end}}
+ {{end}}
+ {{if not $incode}}
+ {{if (hasPrefix . "!")}}
+ {{$inner := . | strings.TrimPrefix "![[" | strings.TrimSuffix "]]" }}
+ {{$split := split $inner "|"}}
+ {{$path := index $split 0 | relURL}}
+ {{$reference := split $path "#"}}
+ {{$title := index $reference 0}}
+ {{$display := default $title (index $split 1)}}
+ {{$img := printf "
" $path $display}}
+ {{$content = replace $content . $img}}
+ {{else}}
+ {{$inner := . | strings.TrimPrefix "[[" | strings.TrimSuffix "]]" }}
+ {{$split := split $inner "|"}}
+ {{$path := index $split 0}}
+ {{$reference := split $path "#"}}
+ {{$title := index $reference 0}}
+ {{$block := default "" (index $reference 1)}}
+ {{$block = strings.TrimRight "/" (cond (eq $block "") $block (printf "#%s" $block))}}
+ {{$href := strings.TrimRight "/" ($page.GetPage $title).RelPermalink}}
+ {{$display := default $title (index $split 1)}}
+ {{if not $href}}
+ {{$link := printf "
%s " $display}}
+ {{$content = replace $content . $link}}
+ {{else}}
+ {{$fullhref := printf "%s%s" $href $block }}
+ {{$link := printf "
%s " $fullhref $href $display}}
+ {{$content = replace $content . $link}}
+ {{end}}
+ {{end}}
+ {{end}}
+{{end}}
+
+{{/* Add copyable anchors */}}
+{{ $content = $content | replaceRE "(
)(.+)( )" `
${1}# ${3}${4} ` }}
+
+{{ $content | safeHTML }}
diff --git a/layouts/partials/toc.html b/layouts/partials/toc.html
index a3b1ec0ec..acda23d6d 100644
--- a/layouts/partials/toc.html
+++ b/layouts/partials/toc.html
@@ -1,8 +1,8 @@
-{{ if (and $.Site.Data.config.enableToc (ne .Params.enableToc false) (gt .WordCount 250)) }}
-
-
- Table of Contents
- {{ .TableOfContents }}
-
-
-{{end}}
+{{ if (and $.Site.Data.config.enableToc (ne .Params.enableToc false) (gt .WordCount 250)) }}
+
+
+ Table of Contents
+ {{ .TableOfContents }}
+
+
+{{end}}