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

View File

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

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

View File

@ -171,34 +171,6 @@ article {
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;
@ -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 {
font-weight: 600;
font-size: 0.9rem;
@ -589,3 +591,5 @@ header {
padding: 0 1em;
}
}

View File

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

View File

@ -1,10 +1,16 @@
name: Jet Hughes
enableToc: true
openToc: false
enableLinkPreview: true
enableLatex: true
description: "My Notes"
page_title: "Jet Hughes"
links:
- link_name: Github
- link: https://github.com/jethughes
name: Jet Hughes
enableToc: true
openToc: false
enableLinkPreview: true
enableLatex: true
enableSPA: true
enableFooter: true
enableContextualBacklinks: true
enableRecentNotes: false
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
enableDrag: true
enableZoom: true
depth: -1 # set to -1 to show full graph
paths:
# if true, a Global Graph will be shown on home page with full width, no backlink.
# A different set of Local Graphs will be shown on sub pages.
# if false, Local Graph will be default on every page as usual
enableGlobalGraph: false
### 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>
{{partial "toc.html" .}}
{{partial "textprocessing.html" . }}
{{if $.Site.Data.config.enableRecentNotes}}
{{partial "recent.html" . }}
{{end}}
</article>
{{partial "footer.html" .}}
{{partial "footerIndex.html" .}}
</div>
</body>
</html>

View File

@ -1,4 +1,8 @@
<hr/>
{{if $.Site.Data.config.enableFooter}}
<div class="page-end">
<div class="backlinks-container">
{{partial "backlinks.html" .}}
@ -7,5 +11,6 @@
{{partial "graph.html" .}}
</div>
</div>
{{end}}
{{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 }}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="shortcut icon"
type="image/png"
href="{{$.Site.BaseURL}}/icon.png"
/>
<link rel="shortcut icon" type="image/png" href="{{$.Site.BaseURL}}/icon.png" />
<!-- CSS Stylesheets and Fonts -->
<link
@ -30,8 +26,7 @@
{{$finalCss := $css | resources.Concat "styles.css" | resources.Fingerprint "md5" | resources.Minify }}
<link href="{{$finalCss.Permalink}}" rel="stylesheet" />
{{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" |
resources.Minify }}
{{ $darkMode := resources.Get "js/darkmode.js" | resources.Fingerprint "md5" | resources.Minify }}
<script src="{{$darkMode.Permalink}}"></script>
{{partial "katex.html" .}}
@ -61,28 +56,31 @@
links,
content,
}))
</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}}';
// 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 draw = () => {
const render = () => {
// 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
const siteBaseURL = new URL({{$.Site.BaseURL}});
const pathBase = siteBaseURL.pathname;
const pathWindow = window.location.pathname;
const isHome = pathBase == pathWindow;
{{if $.Site.Data.config.enableFooter}}
const container = document.getElementById("graph-container")
// 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
container.textContent = ""
const drawGlobal = isHome && {{$.Site.Data.graphConfig.enableGlobalGraph}};
drawGraph(
{{strings.TrimRight "/" .Site.BaseURL}},
{{$.Site.Data.graphConfig.paths}},
{{$.Site.Data.graphConfig.depth}},
{{$.Site.Data.graphConfig.enableDrag}},
{{$.Site.Data.graphConfig.enableLegend}},
{{$.Site.Data.graphConfig.enableZoom}}
);
{{strings.TrimRight "/" .Site.BaseURL}},
drawGlobal,
{{$.Site.Data.graphConfig.paths}},
drawGlobal ? {{$.Site.Data.graphConfig.globalGraph}} : {{$.Site.Data.graphConfig.localGraph}}
);
{{end}}
{{if $.Site.Data.config.enableLinkPreview}}
initPopover(
@ -91,8 +89,12 @@
{{$.Site.Data.config.enableLatex}}
)
{{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}}
renderMathInElement(document.body, {
renderMathInElement(doc.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
@ -101,10 +103,23 @@
});
{{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>
{{else}}
<script>window.navigate = (url) => window.location.href = url</script>
<script>
window.Million = {
navigate: (url) => (window.location.href = url),
prefetch: () => {},
}
init()
render()
</script>
{{end}}
</head>
{{ template "_internal/google_analytics.html" . }}

View File

@ -4,11 +4,17 @@
<div class="section">
<div class="desc">
<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>
</div>
<p class="meta">
{{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
</p>
</div>
</li>
{{- 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>