This commit is contained in:
Jet Hughes 2022-06-10 18:28:33 +12:00
parent 0de90cfb12
commit 20bc87cd0d
14 changed files with 459 additions and 355 deletions

View File

@ -1,37 +1,39 @@
name: Deploy to GitHub Pages name: Deploy to GitHub Pages
on: on:
push: push:
branches: branches:
- hugo - hugo
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
- name: Build Link Index fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
uses: jackyzha0/hugo-obsidian@v2.12
with: - name: Build Link Index
index: true uses: jackyzha0/hugo-obsidian@v2.13
input: content with:
output: assets/indices index: true
root: . input: content
output: assets/indices
- name: Setup Hugo root: .
uses: peaceiris/actions-hugo@v2
with: - name: Setup Hugo
hugo-version: '0.96.0' uses: peaceiris/actions-hugo@v2
extended: true with:
hugo-version: '0.96.0'
- name: Build extended: true
run: hugo --minify
- name: Build
- name: Deploy run: hugo --minify
uses: peaceiris/actions-gh-pages@v3
with: - name: Deploy
github_token: ${{ secrets.GITHUB_TOKEN }} uses: peaceiris/actions-gh-pages@v3
publish_dir: ./public with:
publish_branch: master # deploying branch github_token: ${{ secrets.GITHUB_TOKEN }}
cname: jethughes.github.io publish_dir: ./public
publish_branch: master # deploying branch
cname: quartz.jzhao.xyz

View File

@ -1,12 +1,16 @@
async function drawGraph( async function drawGraph(baseUrl, isHome, pathColors, graphConfig) {
baseUrl,
pathColors, let {
depth, depth,
enableDrag, enableDrag,
enableLegend, enableLegend,
enableZoom enableZoom,
) { opacityScale,
const container = document.getElementById('graph-container') scale,
repelForce,
fontSize} = graphConfig;
const container = document.getElementById("graph-container")
const { index, links, content } = await fetchData const { index, links, content } = await fetchData
// Use .pathname to remove hashes / searchParams / text fragments // Use .pathname to remove hashes / searchParams / text fragments
@ -23,22 +27,19 @@ async function drawGraph(
const copyLinks = JSON.parse(JSON.stringify(links)) const copyLinks = JSON.parse(JSON.stringify(links))
const neighbours = new Set() const neighbours = new Set()
const wl = [curPage || '/', '__SENTINEL'] const wl = [curPage || "/", "__SENTINEL"]
if (depth >= 0) { if (depth >= 0) {
while (depth >= 0 && wl.length > 0) { while (depth >= 0 && wl.length > 0) {
// compute neighbours // compute neighbours
const cur = wl.shift() const cur = wl.shift()
if (cur === '__SENTINEL') { if (cur === "__SENTINEL") {
depth-- depth--
wl.push('__SENTINEL') wl.push("__SENTINEL")
} else { } else {
neighbours.add(cur) neighbours.add(cur)
const outgoing = index.links[cur] || [] const outgoing = index.links[cur] || []
const incoming = index.backlinks[cur] || [] const incoming = index.backlinks[cur] || []
wl.push( wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source))
...outgoing.map((l) => l.target),
...incoming.map((l) => l.source)
)
} }
} }
} else { } else {
@ -47,14 +48,12 @@ async function drawGraph(
const data = { const data = {
nodes: [...neighbours].map((id) => ({ id })), nodes: [...neighbours].map((id) => ({ id })),
links: copyLinks.filter( links: copyLinks.filter((l) => neighbours.has(l.source) && neighbours.has(l.target)),
(l) => neighbours.has(l.source) && neighbours.has(l.target)
),
} }
const color = (d) => { const color = (d) => {
if (d.id === curPage || (d.id === '/' && curPage === '')) { if (d.id === curPage || (d.id === "/" && curPage === "")) {
return 'var(--g-node-active)' return "var(--g-node-active)"
} }
for (const pathColor of pathColors) { for (const pathColor of pathColors) {
@ -65,7 +64,7 @@ async function drawGraph(
} }
} }
return 'var(--g-node)' return "var(--g-node)"
} }
const drag = (simulation) => { const drag = (simulation) => {
@ -86,80 +85,71 @@ async function drawGraph(
d.fy = null d.fy = null
} }
const noop = () => { } const noop = () => {}
return d3 return d3
.drag() .drag()
.on('start', enableDrag ? dragstarted : noop) .on("start", enableDrag ? dragstarted : noop)
.on('drag', enableDrag ? dragged : noop) .on("drag", enableDrag ? dragged : noop)
.on('end', enableDrag ? dragended : noop) .on("end", enableDrag ? dragended : noop)
} }
const height = Math.max(container.offsetHeight, 250) const height = Math.max(container.offsetHeight, isHome ? 500 : 250)
const width = container.offsetWidth const width = container.offsetWidth
const simulation = d3 const simulation = d3
.forceSimulation(data.nodes) .forceSimulation(data.nodes)
.force('charge', d3.forceManyBody().strength(-30)) .force("charge", d3.forceManyBody().strength(-100 * repelForce))
.force( .force(
'link', "link",
d3 d3
.forceLink(data.links) .forceLink(data.links)
.id((d) => d.id) .id((d) => d.id)
.distance(40) .distance(40),
) )
.force('center', d3.forceCenter()) .force("center", d3.forceCenter())
const svg = d3 const svg = d3
.select('#graph-container') .select("#graph-container")
.append('svg') .append("svg")
.attr('width', width) .attr("width", width)
.attr('height', height) .attr("height", height)
.attr('viewBox', [-width / 2, -height / 2, width, height]) .attr('viewBox', [-width / 2 * 1 / scale, -height / 2 * 1 / scale, width * 1 / scale, height * 1 / scale])
if (enableLegend) { if (enableLegend) {
const legend = [ const legend = [{ Current: "var(--g-node-active)" }, { Note: "var(--g-node)" }, ...pathColors]
{ Current: 'var(--g-node-active)' },
{ Note: 'var(--g-node)' },
...pathColors,
]
legend.forEach((legendEntry, i) => { legend.forEach((legendEntry, i) => {
const key = Object.keys(legendEntry)[0] const key = Object.keys(legendEntry)[0]
const colour = legendEntry[key] const colour = legendEntry[key]
svg svg
.append('circle') .append("circle")
.attr('cx', -width / 2 + 20) .attr("cx", -width / 2 + 20)
.attr('cy', height / 2 - 30 * (i + 1)) .attr("cy", height / 2 - 30 * (i + 1))
.attr('r', 6) .attr("r", 6)
.style('fill', colour) .style("fill", colour)
svg svg
.append('text') .append("text")
.attr('x', -width / 2 + 40) .attr("x", -width / 2 + 40)
.attr('y', height / 2 - 30 * (i + 1)) .attr("y", height / 2 - 30 * (i + 1))
.text(key) .text(key)
.style('font-size', '15px') .style("font-size", "15px")
.attr('alignment-baseline', 'middle') .attr("alignment-baseline", "middle")
}) })
} }
// draw links between nodes // draw links between nodes
const link = svg const link = svg
.append('g') .append("g")
.selectAll('line') .selectAll("line")
.data(data.links) .data(data.links)
.join('line') .join("line")
.attr('class', 'link') .attr("class", "link")
.attr('stroke', 'var(--g-link)') .attr("stroke", "var(--g-link)")
.attr('stroke-width', 2) .attr("stroke-width", 2)
.attr('data-source', (d) => d.source.id) .attr("data-source", (d) => d.source.id)
.attr('data-target', (d) => d.target.id) .attr("data-target", (d) => d.target.id)
// svg groups // svg groups
const graphNode = svg const graphNode = svg.append("g").selectAll("g").data(data.nodes).enter().append("g")
.append('g')
.selectAll('g')
.data(data.nodes)
.enter()
.append('g')
// calculate radius // calculate radius
const nodeRadius = (d) => { const nodeRadius = (d) => {
@ -170,82 +160,79 @@ async function drawGraph(
// draw individual nodes // draw individual nodes
const node = graphNode const node = graphNode
.append('circle') .append("circle")
.attr('class', 'node') .attr("class", "node")
.attr('id', (d) => d.id) .attr("id", (d) => d.id)
.attr('r', nodeRadius) .attr("r", nodeRadius)
.attr('fill', color) .attr("fill", color)
.style('cursor', 'pointer') .style("cursor", "pointer")
.on('click', (_, d) => { .on("click", (_, d) => {
// SPA navigation // SPA navigation
window.navigate( window.Million.navigate(new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, "-")}/`), ".singlePage")
new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, '-')}/`),
'.singlePage'
)
}) })
.on('mouseover', function(_, d) { .on("mouseover", function (_, d) {
d3.selectAll('.node') d3.selectAll(".node").transition().duration(100).attr("fill", "var(--g-node-inactive)")
.transition()
.duration(100)
.attr('fill', 'var(--g-node-inactive)')
const neighbours = parseIdsFromLinks([ const neighbours = parseIdsFromLinks([
...(index.links[d.id] || []), ...(index.links[d.id] || []),
...(index.backlinks[d.id] || []), ...(index.backlinks[d.id] || []),
]) ])
const neighbourNodes = d3 const neighbourNodes = d3.selectAll(".node").filter((d) => neighbours.includes(d.id))
.selectAll('.node')
.filter((d) => neighbours.includes(d.id))
const currentId = d.id const currentId = d.id
window.Million.prefetch(new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, "-")}/`))
const linkNodes = d3 const linkNodes = d3
.selectAll('.link') .selectAll(".link")
.filter((d) => d.source.id === currentId || d.target.id === currentId) .filter((d) => d.source.id === currentId || d.target.id === currentId)
// highlight neighbour nodes // highlight neighbour nodes
neighbourNodes.transition().duration(200).attr('fill', color) neighbourNodes.transition().duration(200).attr("fill", color)
// highlight links // highlight links
linkNodes linkNodes.transition().duration(200).attr("stroke", "var(--g-link-active)")
.transition()
.duration(200) const bigFont = fontSize*1.5
.attr('stroke', 'var(--g-link-active)')
// show text for self // show text for self
d3.select(this.parentNode) d3.select(this.parentNode)
.raise() .raise()
.select('text') .select("text")
.transition() .transition()
.duration(200) .duration(200)
.attr('opacityOld', d3.select(this.parentNode).select('text').style("opacity"))
.style('opacity', 1) .style('opacity', 1)
.style('font-size', bigFont+'em')
.attr('dy', d => nodeRadius(d) + 20 + 'px') // radius is in px
}) })
.on('mouseleave', function(_, d) { .on("mouseleave", function (_, d) {
d3.selectAll('.node').transition().duration(200).attr('fill', color) d3.selectAll(".node").transition().duration(200).attr("fill", color)
const currentId = d.id const currentId = d.id
const linkNodes = d3 const linkNodes = d3
.selectAll('.link') .selectAll(".link")
.filter((d) => d.source.id === currentId || d.target.id === currentId) .filter((d) => d.source.id === currentId || d.target.id === currentId)
linkNodes.transition().duration(200).attr('stroke', 'var(--g-link)') linkNodes.transition().duration(200).attr("stroke", "var(--g-link)")
d3.select(this.parentNode) d3.select(this.parentNode)
.select('text') .select("text")
.transition() .transition()
.duration(200) .duration(200)
.style('opacity', 0) .style('opacity', d3.select(this.parentNode).select('text').attr("opacityOld"))
.style('font-size', fontSize+'em')
.attr('dy', d => nodeRadius(d) + 8 + 'px') // radius is in px
}) })
.call(drag(simulation)) .call(drag(simulation))
// draw labels // draw labels
const labels = graphNode const labels = graphNode
.append('text') .append("text")
.attr('dx', 0) .attr("dx", 0)
.attr('dy', (d) => nodeRadius(d) + 8 + 'px') .attr("dy", (d) => nodeRadius(d) + 8 + "px")
.attr('text-anchor', 'middle') .attr("text-anchor", "middle")
.text((d) => content[d.id]?.title || d.id.replace('-', ' ')) .text((d) => content[d.id]?.title || d.id.replace("-", " "))
.style('opacity', 0) .style('opacity', (opacityScale - 1) / 3.75)
.style('pointer-events', 'none') .style("pointer-events", "none")
.style('font-size', '0.4em') .style('font-size', fontSize+'em')
.raise() .raise()
.call(drag(simulation)) .call(drag(simulation))
@ -260,24 +247,24 @@ async function drawGraph(
[width, height], [width, height],
]) ])
.scaleExtent([0.25, 4]) .scaleExtent([0.25, 4])
.on('zoom', ({ transform }) => { .on("zoom", ({ transform }) => {
link.attr('transform', transform) link.attr("transform", transform)
node.attr('transform', transform) node.attr("transform", transform)
const scale = transform.k const scale = transform.k * opacityScale;
const scaledOpacity = Math.max((scale - 1) / 3.75, 0) const scaledOpacity = Math.max((scale - 1) / 3.75, 0)
labels.attr('transform', transform).style('opacity', scaledOpacity) labels.attr("transform", transform).style("opacity", scaledOpacity)
}) }),
) )
} }
// progress the simulation // progress the simulation
simulation.on('tick', () => { simulation.on("tick", () => {
link link
.attr('x1', (d) => d.source.x) .attr("x1", (d) => d.source.x)
.attr('y1', (d) => d.source.y) .attr("y1", (d) => d.source.y)
.attr('x2', (d) => d.target.x) .attr("x2", (d) => d.target.x)
.attr('y2', (d) => d.target.y) .attr("y2", (d) => d.target.y)
node.attr('cx', (d) => d.x).attr('cy', (d) => d.y) node.attr("cx", (d) => d.x).attr("cy", (d) => d.y)
labels.attr('x', (d) => d.x).attr('y', (d) => d.y) labels.attr("x", (d) => d.x).attr("y", (d) => d.y)
}) })
} }

View File

@ -1,12 +1,26 @@
import { router, navigate } from "https://unpkg.com/million@1.8.9-0/dist/router.mjs" import {
apply,
navigate,
prefetch,
router,
} from "https://unpkg.com/million@1.9.8-0/dist/router.mjs"
export const attachSPARouting = (draw) => { export const attachSPARouting = (init, rerender) => {
// SPA navigation for access later // Attach SPA functions to the global Million namespace
window.navigate = navigate window.Million = {
// We only mutate document.title and content within .singlePage element apply,
router(".singlePage") navigate,
// We need on initial load, then subsequent redirs prefetch,
// requestAnimationFrame() delays graph draw until SPA routing is finished router,
window.addEventListener("million:navigate", () => requestAnimationFrame(draw)) }
window.addEventListener("DOMContentLoaded", () => requestAnimationFrame(draw))
const render = () => requestAnimationFrame(rerender)
window.addEventListener("DOMContentLoaded", () => {
apply((doc) => init(doc))
init()
router(".singlePage")
render()
})
window.addEventListener("million:navigate", render)
} }

View File

@ -7,47 +7,44 @@ const removeMarkdown = (
gfm: true, gfm: true,
useImgAltText: false, useImgAltText: false,
preserveLinks: false, preserveLinks: false,
} },
) => { ) => {
let output = markdown || '' let output = markdown || ""
output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '') output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, "")
try { try {
if (options.stripListLeaders) { if (options.stripListLeaders) {
if (options.listUnicodeChar) if (options.listUnicodeChar)
output = output.replace( output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, options.listUnicodeChar + " $1")
/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1")
options.listUnicodeChar + ' $1'
)
else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
} }
if (options.gfm) { if (options.gfm) {
output = output output = output
.replace(/\n={2,}/g, '\n') .replace(/\n={2,}/g, "\n")
.replace(/~{3}.*\n/g, '') .replace(/~{3}.*\n/g, "")
.replace(/~~/g, '') .replace(/~~/g, "")
.replace(/`{3}.*\n/g, '') .replace(/`{3}.*\n/g, "")
} }
if (options.preserveLinks) { if (options.preserveLinks) {
output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, '$1 ($2)') output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, "$1 ($2)")
} }
output = output output = output
.replace(/<[^>]*>/g, '') .replace(/<[^>]*>/g, "")
.replace(/^[=\-]{2,}\s*$/g, '') .replace(/^[=\-]{2,}\s*$/g, "")
.replace(/\[\^.+?\](\: .*?$)?/g, '') .replace(/\[\^.+?\](\: .*?$)?/g, "")
.replace(/(#{1,6})\s+(.+)\1?/g, '<b>$2</b>') .replace(/(#{1,6})\s+(.+)\1?/g, "<b>$2</b>")
.replace(/\s{0,2}\[.*?\]: .*?$/g, '') .replace(/\s{0,2}\[.*?\]: .*?$/g, "")
.replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? '$1' : '') .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? "$1" : "")
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '<a>$1</a>') .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, "<a>$1</a>")
.replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, '<a>$1</a>') .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, "<a>$1</a>")
.replace(/^\s{0,3}>\s?/g, '') .replace(/^\s{0,3}>\s?/g, "")
.replace(/(^|\n)\s{0,3}>\s?/g, '\n\n') .replace(/(^|\n)\s{0,3}>\s?/g, "\n\n")
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') .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(/([\*_]{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(/(`{3,})(.*?)\1/gm, "$2")
.replace(/`(.+?)`/g, '$1') .replace(/`(.+?)`/g, "$1")
.replace(/\n{2,}/g, '\n\n') .replace(/\n{2,}/g, "\n\n")
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return markdown return markdown
@ -64,27 +61,28 @@ const highlight = (content, term) => {
if (directMatchIdx !== -1) { if (directMatchIdx !== -1) {
const h = highlightWindow / 2 const h = highlightWindow / 2
const before = content.substring(0, directMatchIdx).split(" ").slice(-h) const before = content.substring(0, directMatchIdx).split(" ").slice(-h)
const after = content.substring(directMatchIdx + term.length, content.length - 1).split(" ").slice(0, h) const after = content
return (before.length == h ? `...${before.join(" ")}` : before.join(" ")) + `<span class="search-highlight">${term}</span>` + after.join(" ") .substring(directMatchIdx + term.length, content.length - 1)
.split(" ")
.slice(0, h)
return (
(before.length == h ? `...${before.join(" ")}` : before.join(" ")) +
`<span class="search-highlight">${term}</span>` +
after.join(" ")
)
} }
const tokenizedTerm = term.split(/\s+/).filter((t) => t !== '') const tokenizedTerm = term.split(/\s+/).filter((t) => t !== "")
const splitText = content.split(/\s+/).filter((t) => t !== '') const splitText = content.split(/\s+/).filter((t) => t !== "")
const includesCheck = (token) => const includesCheck = (token) =>
tokenizedTerm.some((term) => tokenizedTerm.some((term) => token.toLowerCase().startsWith(term.toLowerCase()))
token.toLowerCase().startsWith(term.toLowerCase())
)
const occurrencesIndices = splitText.map(includesCheck) const occurrencesIndices = splitText.map(includesCheck)
// calculate best index // calculate best index
let bestSum = 0 let bestSum = 0
let bestIndex = 0 let bestIndex = 0
for ( for (let i = 0; i < Math.max(occurrencesIndices.length - highlightWindow, 0); i++) {
let i = 0;
i < Math.max(occurrencesIndices.length - highlightWindow, 0);
i++
) {
const window = occurrencesIndices.slice(i, i + highlightWindow) const window = occurrencesIndices.slice(i, i + highlightWindow)
const windowSum = window.reduce((total, cur) => total + cur, 0) const windowSum = window.reduce((total, cur) => total + cur, 0)
if (windowSum >= bestSum) { if (windowSum >= bestSum) {
@ -94,10 +92,7 @@ const highlight = (content, term) => {
} }
const startIndex = Math.max(bestIndex - highlightWindow, 0) const startIndex = Math.max(bestIndex - highlightWindow, 0)
const endIndex = Math.min( const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length)
startIndex + 2 * highlightWindow,
splitText.length
)
const mappedText = splitText const mappedText = splitText
.slice(startIndex, endIndex) .slice(startIndex, endIndex)
.map((token) => { .map((token) => {
@ -106,27 +101,28 @@ const highlight = (content, term) => {
} }
return token return token
}) })
.join(' ') .join(" ")
.replaceAll('</span> <span class="search-highlight">', ' ') .replaceAll('</span> <span class="search-highlight">', " ")
return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...' return `${startIndex === 0 ? "" : "..."}${mappedText}${
}` endIndex === splitText.length ? "" : "..."
}; }`
}
(async function() { ;(async function () {
const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/) const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/)
const contentIndex = new FlexSearch.Document({ const contentIndex = new FlexSearch.Document({
cache: true, cache: true,
charset: 'latin:extra', charset: "latin:extra",
optimize: true, optimize: true,
index: [ index: [
{ {
field: 'content', field: "content",
tokenize: 'reverse', tokenize: "reverse",
encode: encoder, encode: encoder,
}, },
{ {
field: 'title', field: "title",
tokenize: 'forward', tokenize: "forward",
encode: encoder, encode: encoder,
}, },
], ],
@ -153,11 +149,9 @@ const highlight = (content, term) => {
const redir = (id, term) => { const redir = (id, term) => {
// SPA navigation // SPA navigation
window.navigate( window.Million.navigate(
new URL( new URL(`${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`),
`${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/` ".singlePage",
),
'.singlePage'
) )
closeSearch() closeSearch()
} }
@ -169,24 +163,24 @@ const highlight = (content, term) => {
content: content[id].content, content: content[id].content,
}) })
const source = document.getElementById('search-bar') const source = document.getElementById("search-bar")
const results = document.getElementById('results-container') const results = document.getElementById("results-container")
let term let term
source.addEventListener('keyup', (e) => { source.addEventListener("keyup", (e) => {
if (e.key === 'Enter') { if (e.key === "Enter") {
const anchor = document.getElementsByClassName('result-card')[0] const anchor = document.getElementsByClassName("result-card")[0]
redir(anchor.id, term) redir(anchor.id, term)
} }
}) })
source.addEventListener('input', (e) => { source.addEventListener("input", (e) => {
term = e.target.value term = e.target.value
const searchResults = contentIndex.search(term, [ const searchResults = contentIndex.search(term, [
{ {
field: 'content', field: "content",
limit: 10, limit: 10,
}, },
{ {
field: 'title', field: "title",
limit: 5, limit: 5,
}, },
]) ])
@ -198,7 +192,7 @@ const highlight = (content, term) => {
return [...results[0].result] return [...results[0].result]
} }
} }
const allIds = new Set([...getByField('title'), ...getByField('content')]) const allIds = new Set([...getByField("title"), ...getByField("content")])
const finalResults = [...allIds].map(formatForDisplay) const finalResults = [...allIds].map(formatForDisplay)
// display // display
@ -213,58 +207,55 @@ const highlight = (content, term) => {
resultToHTML({ resultToHTML({
...result, ...result,
term, term,
}) }),
) )
.join('\n') .join("\n")
const anchors = [...document.getElementsByClassName('result-card')] const anchors = [...document.getElementsByClassName("result-card")]
anchors.forEach((anchor) => { anchors.forEach((anchor) => {
anchor.onclick = () => redir(anchor.id, term) anchor.onclick = () => redir(anchor.id, term)
}) })
} }
}) })
const searchContainer = document.getElementById('search-container') const searchContainer = document.getElementById("search-container")
function openSearch() { function openSearch() {
if ( if (searchContainer.style.display === "none" || searchContainer.style.display === "") {
searchContainer.style.display === 'none' || source.value = ""
searchContainer.style.display === '' results.innerHTML = ""
) { searchContainer.style.display = "block"
source.value = ''
results.innerHTML = ''
searchContainer.style.display = 'block'
source.focus() source.focus()
} else { } else {
searchContainer.style.display = 'none' searchContainer.style.display = "none"
} }
} }
function closeSearch() { function closeSearch() {
searchContainer.style.display = 'none' searchContainer.style.display = "none"
} }
document.addEventListener('keydown', (event) => { document.addEventListener("keydown", (event) => {
if (event.key === 'k' && (event.ctrlKey || event.metaKey)) { if (event.key === "k" && (event.ctrlKey || event.metaKey)) {
event.preventDefault() event.preventDefault()
openSearch() openSearch()
} }
if (event.key === 'Escape') { if (event.key === "Escape") {
event.preventDefault() event.preventDefault()
closeSearch() closeSearch()
} }
}) })
const searchButton = document.getElementById('search-icon') const searchButton = document.getElementById("search-icon")
searchButton.addEventListener('click', (evt) => { searchButton.addEventListener("click", (evt) => {
openSearch() openSearch()
}) })
searchButton.addEventListener('keydown', (evt) => { searchButton.addEventListener("keydown", (evt) => {
openSearch() openSearch()
}) })
searchContainer.addEventListener('click', (evt) => { searchContainer.addEventListener("click", (evt) => {
closeSearch() closeSearch()
}) })
document.getElementById('search-space').addEventListener('click', (evt) => { document.getElementById("search-space").addEventListener("click", (evt) => {
evt.stopPropagation() evt.stopPropagation()
}) })
})() })()

View File

@ -171,34 +171,6 @@ article {
opacity: 0.7; 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 { & a {
font-family: Source Sans Pro; font-family: Source Sans Pro;
font-weight: 600; font-weight: 600;
@ -222,6 +194,36 @@ article {
} }
} }
.tags {
list-style: none;
padding-left: 0;
& .meta {
& > h1 {
margin: 0;
}
& > p {
margin: 0;
}
}
& > li {
display: inline-block;
margin: 0.4em 0;
}
& > li > a {
border-radius: 8px;
border: var(--outlinegray) 1px solid;
padding: 0.2em 0.5em;
&::before {
content: "#";
margin-right: 0.3em;
color: var(--outlinegray);
}
}
}
.backlinks a { .backlinks a {
font-weight: 600; font-weight: 600;
font-size: 0.9rem; font-size: 0.9rem;
@ -589,3 +591,5 @@ header {
padding: 0 1em; padding: 0 1em;
} }
} }

View File

@ -1,30 +1,33 @@
baseURL = "https://jethughes.github.io/notes/" baseURL = "https://jethughes.github.io/notes/"
languageCode = "en-us" languageCode = "en-us"
googleAnalytics = "G-XYFD95KB4J" googleAnalytics = "G-XYFD95KB4J"
pygmentsUseClasses = true pygmentsUseClasses = true
relativeURLs = false relativeURLs = false
disablePathToLower = true disablePathToLower = true
ignoreFiles = [ ignoreFiles = [
"/content/templates/*", "/content/templates/*",
"/content/private/*", "/content/private/*",
] ]
summaryLength = 20 summaryLength = 20
paginate = 10 paginate = 10
enableGitInfo = true enableGitInfo = true
[markup] [markup]
[markup.tableOfContents] [markup.tableOfContents]
endLevel = 3 endLevel = 3
ordered = true ordered = true
startLevel = 2 startLevel = 2
[markup.highlight] [markup.highlight]
anchorLineNos = false anchorLineNos = false
codeFences = true codeFences = true
guessSyntax = true guessSyntax = true
hl_Lines = "" hl_Lines = ""
lineAnchors = "" lineAnchors = ""
lineNoStart = 1 lineNoStart = 1
lineNos = true lineNos = true
lineNumbersInTable = true lineNumbersInTable = true
style = "dracula" style = "dracula"
tabWidth = 4 tabWidth = 4
[frontmatter]
lastmod = ["lastmod", ":git", "date", "publishDate"]
publishDate = ["publishDate", "date"]

View File

@ -1,10 +1,16 @@
name: Jet Hughes name: Jet Hughes
enableToc: true enableToc: true
openToc: false openToc: false
enableLinkPreview: true enableLinkPreview: true
enableLatex: true enableLatex: true
description: "My Notes" enableSPA: true
page_title: "Jet Hughes" enableFooter: true
links: enableContextualBacklinks: true
- link_name: Github enableRecentNotes: false
- link: https://github.com/jethughes description:
My notes
page_title:
"Jet's Notes"
links:
- link_name: Github
link: https://github.com/jethughes

View File

@ -1,5 +1,37 @@
enableLegend: false # if true, a Global Graph will be shown on home page with full width, no backlink.
enableDrag: true # A different set of Local Graphs will be shown on sub pages.
enableZoom: true # if false, Local Graph will be default on every page as usual
depth: -1 # set to -1 to show full graph enableGlobalGraph: false
paths:
### Local Graph ###
localGraph:
enableLegend: false
enableDrag: true
enableZoom: true
depth: 1 # set to -1 to show full graph
scale: 1.2
repelForce: 2
centerForce: 1
linkDistance: 1
fontSize: 0.6
opacityScale: 3
### Global Graph ###
globalGraph:
enableLegend: false
enableDrag: true
enableZoom: true
depth: -1 # set to -1 to show full graph
scale: 1.4
repelForce: 1
centerForce: 1
linkDistance: 1
fontSize: 0.5
opacityScale: 3
### For all graphs ###
paths:
- /moc: "#4388cc"

View File

@ -15,8 +15,11 @@
<article> <article>
{{partial "toc.html" .}} {{partial "toc.html" .}}
{{partial "textprocessing.html" . }} {{partial "textprocessing.html" . }}
{{if $.Site.Data.config.enableRecentNotes}}
{{partial "recent.html" . }}
{{end}}
</article> </article>
{{partial "footer.html" .}} {{partial "footerIndex.html" .}}
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,4 +1,8 @@
<hr/> <hr/>
{{if $.Site.Data.config.enableFooter}}
<div class="page-end"> <div class="page-end">
<div class="backlinks-container"> <div class="backlinks-container">
{{partial "backlinks.html" .}} {{partial "backlinks.html" .}}
@ -7,5 +11,6 @@
{{partial "graph.html" .}} {{partial "graph.html" .}}
</div> </div>
</div> </div>
{{end}}
{{partial "contact.html" .}} {{partial "contact.html" .}}

View File

@ -0,0 +1,24 @@
{{if $.Site.Data.config.enableFooter}}
{{if $.Site.Data.graphConfig.enableGlobalGraph}}
<div class="page-end">
<div>
{{partial "graph.html" .}}
</div>
</div>
{{else}}
<hr/>
<div class="page-end">
<div class="backlinks-container">
{{partial "backlinks.html" .}}
</div>
<div>
{{partial "graph.html" .}}
</div>
</div>
{{end}}
{{end}}
{{partial "contact.html" .}}

View File

@ -10,11 +10,7 @@
end }} end }}
</title> </title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link <link rel="shortcut icon" type="image/png" href="{{$.Site.BaseURL}}/icon.png" />
rel="shortcut icon"
type="image/png"
href="{{$.Site.BaseURL}}/icon.png"
/>
<!-- CSS Stylesheets and Fonts --> <!-- CSS Stylesheets and Fonts -->
<link <link
@ -30,8 +26,7 @@
{{$finalCss := $css | resources.Concat "styles.css" | resources.Fingerprint "md5" | resources.Minify }} {{$finalCss := $css | resources.Concat "styles.css" | resources.Fingerprint "md5" | resources.Minify }}
<link href="{{$finalCss.Permalink}}" rel="stylesheet" /> <link href="{{$finalCss.Permalink}}" rel="stylesheet" />
{{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" | {{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" | resources.Minify }}
resources.Minify }}
<script src="{{$darkMode.Permalink}}"></script> <script src="{{$darkMode.Permalink}}"></script>
{{partial "katex.html" .}} {{partial "katex.html" .}}
@ -61,28 +56,31 @@
links, links,
content, content,
})) }))
</script>
{{if $.Site.Data.config.enableSPA}} const render = () => {
{{ $router := resources.Get "js/router.js" | resources.Fingerprint "md5" | // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page, adds event listeners, etc. If you are only dealing with basic DOM replacement, use the init function
resources.Minify }}
<script type="module"> const siteBaseURL = new URL({{$.Site.BaseURL}});
import { attachSPARouting } from '{{$router.Permalink}}'; const pathBase = siteBaseURL.pathname;
// NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page. const pathWindow = window.location.pathname;
const draw = () => { const isHome = pathBase == pathWindow;
{{if $.Site.Data.config.enableFooter}}
const container = document.getElementById("graph-container") const container = document.getElementById("graph-container")
// retry if the graph is not ready // retry if the graph is not ready
if (!container) return requestAnimationFrame(draw) if (!container) return requestAnimationFrame(render)
// clear the graph in case there is anything within it // clear the graph in case there is anything within it
container.textContent = "" container.textContent = ""
const drawGlobal = isHome && {{$.Site.Data.graphConfig.enableGlobalGraph}};
drawGraph( drawGraph(
{{strings.TrimRight "/" .Site.BaseURL}}, {{strings.TrimRight "/" .Site.BaseURL}},
{{$.Site.Data.graphConfig.paths}}, drawGlobal,
{{$.Site.Data.graphConfig.depth}}, {{$.Site.Data.graphConfig.paths}},
{{$.Site.Data.graphConfig.enableDrag}}, drawGlobal ? {{$.Site.Data.graphConfig.globalGraph}} : {{$.Site.Data.graphConfig.localGraph}}
{{$.Site.Data.graphConfig.enableLegend}}, );
{{$.Site.Data.graphConfig.enableZoom}}
); {{end}}
{{if $.Site.Data.config.enableLinkPreview}} {{if $.Site.Data.config.enableLinkPreview}}
initPopover( initPopover(
@ -91,8 +89,12 @@
{{$.Site.Data.config.enableLatex}} {{$.Site.Data.config.enableLatex}}
) )
{{end}} {{end}}
}
const init = (doc = document) => {
// NOTE: everything within this callback will be executed for initial page navigation. This is a good place to put JavaScript that only replaces DOM nodes.
{{if $.Site.Data.config.enableLatex}} {{if $.Site.Data.config.enableLatex}}
renderMathInElement(document.body, { renderMathInElement(doc.body, {
delimiters: [ delimiters: [
{left: '$$', right: '$$', display: true}, {left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}, {left: '$', right: '$', display: false},
@ -101,10 +103,23 @@
}); });
{{end}} {{end}}
}; };
attachSPARouting(draw); </script>
{{if $.Site.Data.config.enableSPA}}
{{ $router := resources.Get "js/router.js" | resources.Fingerprint "md5" |
resources.Minify }}
<script type="module">
import { attachSPARouting } from "{{$router.Permalink}}"
attachSPARouting(init, render)
</script> </script>
{{else}} {{else}}
<script>window.navigate = (url) => window.location.href = url</script> <script>
window.Million = {
navigate: (url) => (window.location.href = url),
prefetch: () => {},
}
init()
render()
</script>
{{end}} {{end}}
</head> </head>
{{ template "_internal/google_analytics.html" . }} {{ template "_internal/google_analytics.html" . }}

View File

@ -4,11 +4,17 @@
<div class="section"> <div class="section">
<div class="desc"> <div class="desc">
<h3><a href="{{ .Permalink }}">{{- .Title -}}</a></h3> <h3><a href="{{ .Permalink }}">{{- .Title -}}</a></h3>
<ul class="tags">
{{ range (.GetTerms "tags") }}
<li><a href="{{ .Permalink }}">{{ .LinkTitle | title}}</a></li>
{{ end }}
</ul>
<p>{{- .Summary -}}{{if .Truncated}}...{{end}}</p> <p>{{- .Summary -}}{{if .Truncated}}...{{end}}</p>
</div> </div>
<p class="meta"> <p class="meta">
{{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}} {{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
</p> </p>
</div> </div>
</li> </li>
{{- end -}} {{- end -}}

View File

@ -0,0 +1,12 @@
<div class="content-list">
<h2>Recent Notes</h2>
<!--
You can also configure this to find related pages!
All you need to pass into the "page-list.html" partial
is a collection of pages.
https://gohugo.io/content-management/related/
-->
{{$notes := .Site.RegularPages}}
{{partial "page-list.html" (first 3 $notes)}}
</div>