Bundling JS with ESBuild (#702)
* add npm dependencies used in this theme * implement helper to configure JS and ESBuild * migrate jquery popper.js bootstrap fontawesome to js bundle * refactor main.js into smaller pieces, and moved navbar.js to assets * remove list.js. It adjusts post card height to be the same, but is actually not needed. * refactored notes.js, search.js, single.js into application.js * move ityped to js asset, implement experiences horizontal vertical line in css * align recent post height via css * migrated home.js and refactored into various sections * migrated darkMode feature to js bundle * moved mermaid feature to js bundle * migrate syntax highlight to js bundle * migrate katex ( js portion ) to js bundle * migrate pdf-js to js bundle by delegating to cdn * set explicit comparisions for feature envvars so js can properly optimize * removed goat-counter * more fixes for broken achievements and small bugs * more bug fixes * allow configuration of hightlight.js, fix video-player shortcode * remove jquery all together * add null handling and fix merge conflicts Co-authored-by: Aaron Qian <aaron@yeet.io>
This commit is contained in:
parent
fe14b0fbf5
commit
02db3d3044
491 changed files with 4919 additions and 151344 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules
|
12
.eslintrc.yml
Normal file
12
.eslintrc.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends:
|
||||
- standard
|
||||
- plugin:no-jquery/all
|
||||
- prettier
|
||||
plugins:
|
||||
- no-jquery
|
||||
parserOptions:
|
||||
ecmaVersion: latest
|
||||
sourceType: module
|
5
.prettierrc.yml
Normal file
5
.prettierrc.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
printWidth: 100
|
||||
tabWidth: 2
|
||||
semi: false
|
||||
singleQuote: true
|
||||
trailingComma: "all"
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
nodejs 18.12.1
|
8
assets/scripts/application.js
Normal file
8
assets/scripts/application.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'popper.js'
|
||||
import 'bootstrap'
|
||||
import '@fortawesome/fontawesome-free/js/all'
|
||||
|
||||
import './core'
|
||||
import './features'
|
||||
import './sections'
|
||||
import './pages'
|
36
assets/scripts/core/device.js
Normal file
36
assets/scripts/core/device.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
let deviceState = {
|
||||
isMobile: false,
|
||||
isTablet: false,
|
||||
isLaptop: false
|
||||
}
|
||||
|
||||
function detectDeviceState () {
|
||||
if (window.innerWidth <= 425) {
|
||||
deviceState = {
|
||||
isMobile: true,
|
||||
isTablet: false,
|
||||
isLaptop: false
|
||||
}
|
||||
} else if (window.innerWidth <= 768) {
|
||||
deviceState = {
|
||||
isMobile: false,
|
||||
isTablet: true,
|
||||
isLaptop: false
|
||||
}
|
||||
} else {
|
||||
deviceState = {
|
||||
isMobile: false,
|
||||
isTablet: false,
|
||||
isLaptop: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detectDeviceState()
|
||||
window.addEventListener('resize', detectDeviceState)
|
||||
|
||||
// returns a copy of the device state
|
||||
// so other parts of code can't override this.
|
||||
export function getDeviceState () {
|
||||
return { ...deviceState }
|
||||
}
|
2
assets/scripts/core/index.js
Normal file
2
assets/scripts/core/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './device'
|
||||
export * from './insertScript'
|
14
assets/scripts/core/insertScript.js
Normal file
14
assets/scripts/core/insertScript.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
export const insertScript = (id, src, onload) => {
|
||||
// script is already inserted, do nothing
|
||||
if (document.getElementById(id)) return
|
||||
|
||||
// insert script
|
||||
const firstScriptTag = document.getElementsByTagName('script')[0]
|
||||
const scriptTag = document.createElement('script')
|
||||
scriptTag.id = id
|
||||
scriptTag.onload = onload
|
||||
scriptTag.src = src
|
||||
scriptTag.defer = true
|
||||
scriptTag.async = true
|
||||
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag)
|
||||
}
|
30
assets/scripts/features/darkmode/darkreader.js
Normal file
30
assets/scripts/features/darkmode/darkreader.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { enable, disable, auto, setFetchMethod } from 'darkreader'
|
||||
import * as params from '@params'
|
||||
|
||||
const darkreader = params?.darkmode?.darkreader || {}
|
||||
const defaultColorScheme = darkreader.defaultColorScheme || 'system'
|
||||
const theme = {
|
||||
brightness: 100,
|
||||
contrast: 100,
|
||||
sepia: 0,
|
||||
...(darkreader.theme || {})
|
||||
}
|
||||
const fixes = {
|
||||
invert: ['img[src$=".svg"]'],
|
||||
...(darkreader.fixes || {})
|
||||
}
|
||||
setFetchMethod(window.fetch)
|
||||
|
||||
export function setSchemeDark () {
|
||||
enable(theme, fixes)
|
||||
}
|
||||
|
||||
export function setSchemeLight () {
|
||||
disable()
|
||||
}
|
||||
|
||||
export function setSchemeSystem () {
|
||||
auto(theme, fixes)
|
||||
}
|
||||
|
||||
export { defaultColorScheme }
|
60
assets/scripts/features/darkmode/index.js
Normal file
60
assets/scripts/features/darkmode/index.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const PERSISTENCE_KEY = 'darkmode:color-scheme'
|
||||
|
||||
async function getService () {
|
||||
if (process.env.FEATURE_DARKMODE_DARKREADER === '1') {
|
||||
return await import('./darkreader')
|
||||
}
|
||||
|
||||
throw Error(' No service defined for feature darkMode.')
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
const menu = document.getElementById('themeMenu')
|
||||
const $icon = document.getElementById('navbar-theme-icon-svg')
|
||||
if (menu == null || $icon == null) return
|
||||
|
||||
const btns = menu.getElementsByTagName('a')
|
||||
const iconMap = Array.from(btns).reduce((map, btn) => {
|
||||
const $img = btn.getElementsByTagName('img')[0]
|
||||
map[btn.dataset.scheme] = $img.src
|
||||
return map
|
||||
}, {})
|
||||
|
||||
const {
|
||||
setSchemeDark,
|
||||
setSchemeLight,
|
||||
setSchemeSystem,
|
||||
defaultColorScheme
|
||||
} = await getService()
|
||||
|
||||
function loadScheme () {
|
||||
return localStorage.getItem(PERSISTENCE_KEY) || defaultColorScheme
|
||||
}
|
||||
|
||||
function saveScheme (scheme) {
|
||||
localStorage.setItem(PERSISTENCE_KEY, scheme)
|
||||
}
|
||||
|
||||
function setScheme (newScheme) {
|
||||
$icon.src = iconMap[newScheme]
|
||||
|
||||
if (newScheme === 'dark') {
|
||||
setSchemeDark()
|
||||
} else if (newScheme === 'system') {
|
||||
setSchemeSystem()
|
||||
} else {
|
||||
setSchemeLight()
|
||||
}
|
||||
|
||||
saveScheme(newScheme)
|
||||
}
|
||||
|
||||
setScheme(loadScheme())
|
||||
|
||||
Array.from(menu.getElementsByTagName('a')).forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const { scheme } = btn.dataset
|
||||
setScheme(scheme)
|
||||
})
|
||||
})
|
||||
})
|
165
assets/scripts/features/embedpdf/index.js
Normal file
165
assets/scripts/features/embedpdf/index.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
import { insertScript } from '../../core'
|
||||
|
||||
const PDFJS_BUNDLE = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.0.279/build/pdf.min.js'
|
||||
const WORKER_BUNDLE = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.0.279/build/pdf.worker.min.js'
|
||||
|
||||
class PDFViewer {
|
||||
constructor (el) {
|
||||
const {
|
||||
url,
|
||||
hidePaginator,
|
||||
hideLoader,
|
||||
scale,
|
||||
pageNum
|
||||
} = el.dataset
|
||||
|
||||
if (url == null) {
|
||||
throw new Error('Cannot load PDF! Attribute `data-url` is not set.')
|
||||
}
|
||||
|
||||
// props
|
||||
this.url = url
|
||||
this.hidePaginator = hidePaginator !== 'false'
|
||||
this.hideLoader = hideLoader !== 'false'
|
||||
this.scale = scale || 3
|
||||
|
||||
// initial state
|
||||
this.pageNum = parseInt(pageNum, 10) || 1
|
||||
this.loaded = false
|
||||
this.pageRendering = false
|
||||
this.pageNumPending = null
|
||||
|
||||
// DOM elements
|
||||
this.canvas = el.getElementsByClassName('pdf-canvas')[0]
|
||||
if (this.canvas == null) {
|
||||
throw new Error('canvas element not found!')
|
||||
};
|
||||
this.paginator = el.getElementsByClassName('paginator')[0]
|
||||
this.loadingWrapper = el.getElementsByClassName('loading-wrapper')[0]
|
||||
this.next = el.getElementsByClassName('next')[0]
|
||||
this.prev = el.getElementsByClassName('prev')[0]
|
||||
this.pageNum = el.getElementsByClassName('page-num')[0]
|
||||
this.pageCount = el.getElementsByClassName('page-count')[0]
|
||||
|
||||
// context
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
|
||||
// events
|
||||
this.next.addEventListener('click', this.handleNextPage.bind(this))
|
||||
this.prev.addEventListener('click', this.handlePrevPage.bind(this))
|
||||
|
||||
this.showPaginator()
|
||||
this.showLoader()
|
||||
this.loadPDF()
|
||||
}
|
||||
|
||||
/**
|
||||
* If we haven't disabled the loader, show loader and hide canvas
|
||||
*/
|
||||
showLoader () {
|
||||
if (this.hideLoader) return
|
||||
this.loadingWrapper.style.display = 'flex'
|
||||
this.canvas.style.display = 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* If we haven't disabled the paginator, show paginator
|
||||
*/
|
||||
showPaginator () {
|
||||
if (this.hidePaginator) return
|
||||
this.paginator.style.display = 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides loader and shows canvas
|
||||
*/
|
||||
showContent () {
|
||||
this.loadingWrapper.style.display = 'none'
|
||||
this.canvas.style.display = 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously downloads PDF.
|
||||
*/
|
||||
async loadPDF () {
|
||||
this.pdfDoc = await window.pdfjsLib.getDocument(this.url).promise
|
||||
|
||||
this.pageCount.textContent = this.pdfDoc.numPages
|
||||
|
||||
// If the user passed in a number that is out of range, render the last page.
|
||||
if (this.pageNum > this.pdfDoc.numPages) {
|
||||
this.pageNum = this.pdfDoc.numPages
|
||||
}
|
||||
|
||||
this.renderPage(this.pageNum)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page info from document, resize canvas accordingly, and render page.
|
||||
* @param num Page number.
|
||||
*/
|
||||
async renderPage (num) {
|
||||
this.pageRendering = true
|
||||
|
||||
const page = await this.pdfDoc.getPage(num)
|
||||
const viewport = page.getViewport({ scale: this.scale })
|
||||
this.canvas.height = viewport.height
|
||||
this.canvas.width = viewport.width
|
||||
|
||||
// Wait for rendering to finish
|
||||
await page.render({
|
||||
canvasContext: this.ctx,
|
||||
viewport
|
||||
}).promise
|
||||
|
||||
this.pageRendering = false
|
||||
this.showContent()
|
||||
|
||||
if (this.pageNumPending !== null) {
|
||||
// New page rendering is pending
|
||||
this.renderPage(this.pageNumPending)
|
||||
this.pageNumPending = null
|
||||
}
|
||||
// Update page counters
|
||||
this.pageNum.textContent = num
|
||||
}
|
||||
|
||||
/**
|
||||
* If another page rendering in progress, waits until the rendering is
|
||||
* finished. Otherwise, executes rendering immediately.
|
||||
*/
|
||||
queueRenderPage (num) {
|
||||
if (this.pageRendering) {
|
||||
this.pageNumPending = num
|
||||
} else {
|
||||
this.renderPage(num)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays previous page.
|
||||
*/
|
||||
handlePrevPage () {
|
||||
if (this.pageNum <= 1) {
|
||||
return
|
||||
}
|
||||
this.pageNum--
|
||||
this.queueRenderPage(this.pageNum)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays next page.
|
||||
*/
|
||||
handleNextPage () {
|
||||
if (this.pageNum >= this.pdfDoc.numPages) {
|
||||
return
|
||||
}
|
||||
this.pageNum++
|
||||
this.queueRenderPage(this.pageNum)
|
||||
}
|
||||
}
|
||||
|
||||
insertScript('pdfjs', PDFJS_BUNDLE, () => {
|
||||
window.pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_BUNDLE
|
||||
Array.from(document.getElementsByClassName('pdf-viewer')).forEach(el => new PDFViewer(el))
|
||||
})
|
3
assets/scripts/features/flowchart/index.js
Normal file
3
assets/scripts/features/flowchart/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
if (process.env.FEATURE_FLOWCHART_MERMAID === '1') {
|
||||
import('./mermaid')
|
||||
}
|
7
assets/scripts/features/flowchart/mermaid.js
Normal file
7
assets/scripts/features/flowchart/mermaid.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import mermaid from 'mermaid'
|
||||
import * as params from '@params'
|
||||
|
||||
const mermaidOptions = params.flowchart?.mermaid || {}
|
||||
const options = Object.assign({}, mermaidOptions, { startOnLoad: true })
|
||||
|
||||
mermaid.initialize(options)
|
27
assets/scripts/features/index.js
Normal file
27
assets/scripts/features/index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
if (process.env.FEATURE_VIDEOPLAYER === '1') {
|
||||
import('./videoplayer')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_TOC === '1') {
|
||||
import('./toc')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_DARKMODE === '1') {
|
||||
import('./darkmode')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_FLOWCHART === '1') {
|
||||
import('./flowchart')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_SYNTAXHIGHLIGHT === '1') {
|
||||
import('./syntaxhighlight')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_MATH === '1') {
|
||||
import('./math')
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_EMBEDPDF === '1') {
|
||||
import('./embedpdf')
|
||||
}
|
3
assets/scripts/features/math/index.js
Normal file
3
assets/scripts/features/math/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
if (process.env.FEATURE_MATH_KATEX === '1') {
|
||||
import('./katex')
|
||||
}
|
21
assets/scripts/features/math/katex.js
Normal file
21
assets/scripts/features/math/katex.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import renderMathInElement from 'katex/contrib/auto-render'
|
||||
import * as params from '@params'
|
||||
|
||||
const defaultOptions = {
|
||||
delimiters: [
|
||||
{ left: '$$', right: '$$', display: true },
|
||||
{ left: '\\[', right: '\\]', display: true },
|
||||
{ left: '$', right: '$', display: false },
|
||||
{ left: '\\(', right: '\\)', display: false }
|
||||
]
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
renderMathInElement(
|
||||
document.body,
|
||||
{
|
||||
...defaultOptions,
|
||||
...(params.math?.katex || {})
|
||||
}
|
||||
)
|
||||
})
|
4
assets/scripts/features/syntaxhighlight/hljs.js
Normal file
4
assets/scripts/features/syntaxhighlight/hljs.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import hljs from 'highlight.js'
|
||||
import * as params from '@params'
|
||||
|
||||
hljs.highlightAll(params.syntaxhighlight?.hljs)
|
3
assets/scripts/features/syntaxhighlight/index.js
Normal file
3
assets/scripts/features/syntaxhighlight/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
if (process.env.FEATURE_SYNTAXHIGHLIGHT_HLJS === '1') {
|
||||
import('./hljs')
|
||||
}
|
48
assets/scripts/features/toc/index.js
Normal file
48
assets/scripts/features/toc/index.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { getDeviceState } from '../../core'
|
||||
|
||||
// Toggle Table of Contents on click. Here, class "hide" open the toc
|
||||
function toggleTOC () {
|
||||
const toc = document.getElementById('toc-section')
|
||||
if (toc == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (toc.classList.contains('hide')) {
|
||||
toc.classList.remove('hide')
|
||||
} else {
|
||||
// if sidebar-section is open, then close it first
|
||||
const sidebar = document.getElementById('sidebar-section')
|
||||
if (sidebar != null && sidebar.classList.contains('hide')) {
|
||||
sidebar.classList.remove('hide')
|
||||
}
|
||||
// add "hide" class
|
||||
toc.classList.add('hide')
|
||||
// if it is mobile device. then scroll to top.
|
||||
const { isMobile } = getDeviceState()
|
||||
if (isMobile && toc.classList.contains('hide')) {
|
||||
document.body.scrollTop = 0
|
||||
document.documentElement.scrollTop = 0
|
||||
}
|
||||
}
|
||||
if (document.getElementById('hero-area') != null) {
|
||||
document.getElementById('hero-area').classList.toggle('hide')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// bind click event to #toc-toggle in navbar-2.html
|
||||
const toggle = document.getElementById('toc-toggler')
|
||||
if (toggle) toggle.addEventListener('click', toggleTOC)
|
||||
|
||||
// hide TOC when user clicks on a TOC link.
|
||||
// Only applies if it's mobile.
|
||||
const toc = document.getElementById('TableOfContents')
|
||||
if (toc) {
|
||||
toc.addEventListener('click', (event) => {
|
||||
const { isMobile } = getDeviceState()
|
||||
if (isMobile && event.target.nodeName === 'A') {
|
||||
toggleTOC()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
3
assets/scripts/features/videoplayer/index.js
Normal file
3
assets/scripts/features/videoplayer/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
if (process.env.FEATURE_VIDEOPLAYER_PLYR === '1') {
|
||||
import('./plyr')
|
||||
}
|
5
assets/scripts/features/videoplayer/plyr.js
Normal file
5
assets/scripts/features/videoplayer/plyr.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Plyr from 'plyr'
|
||||
import * as params from '@params'
|
||||
|
||||
const options = params.videoplayer?.plyr
|
||||
window.addEventListener('DOMContentLoaded', () => Plyr.setup('.video-player', options))
|
16
assets/scripts/pages/home.js
Normal file
16
assets/scripts/pages/home.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { init } from 'ityped'
|
||||
|
||||
// =========== Typing Carousel ================
|
||||
// get data from hidden ul and set as typing data
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $ul = document.getElementById('typing-carousel-data')?.children
|
||||
if ($ul == null || $ul.length === 0) return
|
||||
|
||||
const strings = Array.from($ul).map($el => $el.textContent)
|
||||
|
||||
init('#ityped', {
|
||||
strings,
|
||||
startDelay: 200,
|
||||
loop: true
|
||||
})
|
||||
})
|
4
assets/scripts/pages/index.js
Normal file
4
assets/scripts/pages/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import './note'
|
||||
import './search'
|
||||
import './single'
|
||||
import './home'
|
30
assets/scripts/pages/note.js
Normal file
30
assets/scripts/pages/note.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import imagesLoaded from 'imagesloaded'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function resizeGridItem (item) {
|
||||
const grid = document.getElementsByClassName('note-card-holder')[0]
|
||||
const rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'))
|
||||
const rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'))
|
||||
const rowSpan = Math.ceil((item.querySelector('.item').getBoundingClientRect().height + rowGap) / (rowHeight + rowGap))
|
||||
item.style.gridRowEnd = 'span ' + rowSpan
|
||||
}
|
||||
|
||||
function resizeAllGridItems () {
|
||||
const allItems = document.getElementsByClassName('note-card')
|
||||
for (let x = 0; x < allItems.length; x++) {
|
||||
resizeGridItem(allItems[x])
|
||||
}
|
||||
}
|
||||
|
||||
function resizeInstance (instance) {
|
||||
const item = instance.elements[0]
|
||||
resizeGridItem(item)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeAllGridItems)
|
||||
|
||||
const allItems = document.getElementsByClassName('note-card')
|
||||
for (let x = 0; x < allItems.length; x++) {
|
||||
imagesLoaded(allItems[x], resizeInstance)
|
||||
}
|
||||
})
|
133
assets/scripts/pages/search.js
Normal file
133
assets/scripts/pages/search.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
import Fuse from 'fuse.js'
|
||||
import Mark from 'mark.js'
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const summaryInclude = 60
|
||||
|
||||
const fuseOptions = {
|
||||
shouldSort: true,
|
||||
includeMatches: true,
|
||||
threshold: 0.0,
|
||||
tokenize: true,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [
|
||||
{ name: 'title', weight: 0.8 },
|
||||
{ name: 'hero', weight: 0.7 },
|
||||
{ name: 'summary', weight: 0.6 },
|
||||
{ name: 'date', weight: 0.5 },
|
||||
{ name: 'contents', weight: 0.5 },
|
||||
{ name: 'tags', weight: 0.3 },
|
||||
{ name: 'categories', weight: 0.3 }
|
||||
]
|
||||
}
|
||||
|
||||
const searchQuery = param('keyword')
|
||||
if (searchQuery) {
|
||||
document.getElementById('search-query').value = searchQuery
|
||||
executeSearch(searchQuery)
|
||||
} else {
|
||||
const node = document.createElement('p')
|
||||
node.textContent = 'Please enter a word or phrase above'
|
||||
document.getElementById('search-results')?.append(node)
|
||||
}
|
||||
|
||||
function executeSearch (searchQuery) {
|
||||
const url = window.location.href.split('/search/')[0] + '/index.json'
|
||||
|
||||
fetch(url).then(function (data) {
|
||||
const pages = data
|
||||
const fuse = new Fuse(pages, fuseOptions)
|
||||
const results = fuse.search(searchQuery)
|
||||
|
||||
document.getElementById('search-box').value = searchQuery
|
||||
if (results.length > 0) {
|
||||
populateResults(results)
|
||||
} else {
|
||||
const node = document.createElement('p')
|
||||
node.textContent = 'No matches found'
|
||||
document.getElementById('search-results')?.append(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function populateResults (results) {
|
||||
results.forEach(function (value, key) {
|
||||
const contents = value.item.contents
|
||||
let snippet = ''
|
||||
const snippetHighlights = []
|
||||
if (fuseOptions.tokenize) {
|
||||
snippetHighlights.push(searchQuery)
|
||||
} else {
|
||||
value.matches.forEach(function (mvalue) {
|
||||
if (mvalue.key === 'tags' || mvalue.key === 'categories') {
|
||||
snippetHighlights.push(mvalue.value)
|
||||
} else if (mvalue.key === 'contents') {
|
||||
const start = mvalue.indices[0][0] - summaryInclude > 0 ? mvalue.indices[0][0] - summaryInclude : 0
|
||||
const end = mvalue.indices[0][1] + summaryInclude < contents.length ? mvalue.indices[0][1] + summaryInclude : contents.length
|
||||
snippet += contents.substring(start, end)
|
||||
snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (snippet.length < 1) {
|
||||
snippet += contents.substring(0, summaryInclude * 2)
|
||||
}
|
||||
// pull template from hugo template definition
|
||||
const templateDefinition = document.getElementById('search-result-template').innerHTML
|
||||
// replace values
|
||||
const output = render(templateDefinition, {
|
||||
key,
|
||||
title: value.item.title,
|
||||
hero: value.item.hero,
|
||||
date: value.item.date,
|
||||
summary: value.item.summary,
|
||||
link: value.item.permalink,
|
||||
tags: value.item.tags,
|
||||
categories: value.item.categories,
|
||||
snippet
|
||||
})
|
||||
|
||||
const doc = new DOMParser().parseFromString(output, 'text/html')
|
||||
document.getElementById('search-results').append(doc)
|
||||
|
||||
snippetHighlights.forEach(function (snipvalue) {
|
||||
const context = document.getElementById('#summary-' + key)
|
||||
const instance = new Mark(context)
|
||||
instance.mark(snipvalue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function param (name) {
|
||||
return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ')
|
||||
}
|
||||
|
||||
function render (templateString, data) {
|
||||
let conditionalMatches, copy
|
||||
const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g
|
||||
// since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
|
||||
copy = templateString
|
||||
while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
|
||||
if (data[conditionalMatches[1]]) {
|
||||
// valid key, remove conditionals, leave contents.
|
||||
copy = copy.replace(conditionalMatches[0], conditionalMatches[2])
|
||||
} else {
|
||||
// not valid, remove entire section
|
||||
copy = copy.replace(conditionalMatches[0], '')
|
||||
}
|
||||
}
|
||||
templateString = copy
|
||||
// now any conditionals removed we can do simple substitution
|
||||
let key, find, re
|
||||
for (key in data) {
|
||||
find = '\\$\\{\\s*' + key + '\\s*\\}'
|
||||
re = new RegExp(find, 'g')
|
||||
templateString = templateString.replace(re, data[key])
|
||||
}
|
||||
return templateString
|
||||
}
|
||||
})
|
60
assets/scripts/pages/single.js
Normal file
60
assets/scripts/pages/single.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// =========== Add anchor to the headers ================
|
||||
function addAnchor (element) {
|
||||
element.innerHTML = `<a href="#${element.id}" class="header-anchor">${element.innerHTML}<sup><i class="fas fa-link fa-sm"></i></sup></a>`
|
||||
}
|
||||
|
||||
const postContent = document.getElementById('post-content')
|
||||
if (postContent != null) {
|
||||
const headerTypes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
|
||||
for (let i = 0; i < headerTypes.length; i++) {
|
||||
const headers = postContent.querySelectorAll(headerTypes[i])
|
||||
if (headers) {
|
||||
headers.forEach(addAnchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Make TOC Compatible wit Bootstrap Scroll Spy ========
|
||||
// add "navbar" class to the "nav" element
|
||||
const toc = document.getElementById('TableOfContents')
|
||||
if (toc) {
|
||||
toc.classList.add('navbar')
|
||||
// add "nav-pills" class to the "ul" elements
|
||||
let elems = toc.getElementsByTagName('ul')
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
elems[i].classList.add('nav-pills')
|
||||
}
|
||||
// add "nav-item" class to the "li" elements
|
||||
elems = toc.getElementsByTagName('li')
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
elems[i].classList.add('nav-item')
|
||||
}
|
||||
// add "nav-link" class to the "a" elements
|
||||
elems = toc.getElementsByTagName('a')
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
elems[i].classList.add('nav-link')
|
||||
}
|
||||
}
|
||||
|
||||
// add scroll to top button
|
||||
const btn = document.getElementById('scroll-to-top')
|
||||
|
||||
if(btn) {
|
||||
window.addEventListener('scroll', function () {
|
||||
if (window.scrollY > 300) {
|
||||
btn.classList.add('show')
|
||||
} else {
|
||||
btn.classList.remove('show')
|
||||
}
|
||||
})
|
||||
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault()
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
3
assets/scripts/process-shim.js
Normal file
3
assets/scripts/process-shim.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const process = {
|
||||
env: {}
|
||||
}
|
220
assets/scripts/sections/achievements.js
Normal file
220
assets/scripts/sections/achievements.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
import { getDeviceState } from '../core'
|
||||
|
||||
function fourColumRow (gallery, entries, i) {
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-lg-6', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
|
||||
const entry2 = document.createElement('div')
|
||||
entry2.classList.add('col-lg-3', 'm-0', 'p-0')
|
||||
entry2.appendChild(entries[i].cloneNode(true))
|
||||
entry2.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry2)
|
||||
i++
|
||||
|
||||
const entry3 = document.createElement('div')
|
||||
entry3.classList.add('col-lg-3', 'm-0', 'p-0')
|
||||
entry3.appendChild(entries[i].cloneNode(true))
|
||||
entry3.children[0].classList.add('img-type-2')
|
||||
i++
|
||||
entry3.appendChild(entries[i].cloneNode(true))
|
||||
entry3.children[1].classList.add('img-type-2')
|
||||
gallery.appendChild(entry3)
|
||||
i++
|
||||
}
|
||||
|
||||
function fourColumnReversedRow (gallery, entries, i) {
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-lg-3', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-2')
|
||||
i++
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[1].classList.add('img-type-2')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
|
||||
const entry2 = document.createElement('div')
|
||||
entry2.classList.add('col-lg-3', 'm-0', 'p-0')
|
||||
entry2.appendChild(entries[i].cloneNode(true))
|
||||
entry2.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry2)
|
||||
i++
|
||||
|
||||
const entry3 = document.createElement('div')
|
||||
entry3.classList.add('col-lg-6', 'm-0', 'p-0')
|
||||
entry3.appendChild(entries[i].cloneNode(true))
|
||||
entry3.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry3)
|
||||
i++
|
||||
}
|
||||
|
||||
function threeColumnRow (gallery, entries, i) {
|
||||
console.log(i)
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-lg-6', 'col-md-6', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
|
||||
const entry2 = document.createElement('div')
|
||||
entry2.classList.add('col-lg-3', 'col-md-3', 'm-0', 'p-0')
|
||||
entry2.appendChild(entries[i].cloneNode(true))
|
||||
entry2.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry2)
|
||||
i++
|
||||
|
||||
const entry3 = document.createElement('div')
|
||||
entry3.classList.add('col-lg-3', 'col-md-3', 'm-0', 'p-0')
|
||||
entry3.appendChild(entries[i].cloneNode(true))
|
||||
entry3.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry3)
|
||||
i++
|
||||
}
|
||||
|
||||
function threeColumnReversedRow (gallery, entries, i) {
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-lg-3', 'col-md-3', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
|
||||
const entry2 = document.createElement('div')
|
||||
entry2.classList.add('col-lg-3', 'col-md-3', 'm-0', 'p-0')
|
||||
entry2.appendChild(entries[i].cloneNode(true))
|
||||
entry2.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry2)
|
||||
i++
|
||||
|
||||
const entry3 = document.createElement('div')
|
||||
entry3.classList.add('col-lg-6', 'col-md-3', 'm-0', 'p-0')
|
||||
entry3.appendChild(entries[i].cloneNode(true))
|
||||
entry3.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry3)
|
||||
i++
|
||||
}
|
||||
|
||||
function twoColumnRow (gallery, entries, i) {
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-6', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
|
||||
const entry2 = document.createElement('div')
|
||||
entry2.classList.add('col-6', 'm-0', 'p-0')
|
||||
entry2.appendChild(entries[i].cloneNode(true))
|
||||
entry2.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry2)
|
||||
i++
|
||||
}
|
||||
|
||||
function singleColumnRow (gallery, entries, i) {
|
||||
const entry1 = document.createElement('div')
|
||||
entry1.classList.add('col-12', 'm-0', 'p-0')
|
||||
entry1.appendChild(entries[i].cloneNode(true))
|
||||
entry1.children[0].classList.add('img-type-1')
|
||||
gallery.appendChild(entry1)
|
||||
i++
|
||||
}
|
||||
|
||||
function showAchievements () {
|
||||
const { isLaptop, isTablet } = getDeviceState()
|
||||
// show achievements from achievements-holder div
|
||||
const gallery = document.getElementById('gallery')
|
||||
if (gallery == null) {
|
||||
return
|
||||
}
|
||||
gallery.innerHTML = ''
|
||||
const entries = document.getElementById('achievements-holder').children
|
||||
let len = entries.length
|
||||
let i = 0
|
||||
let rowNumber = 1
|
||||
while (i < len) {
|
||||
if (isLaptop) {
|
||||
if (i + 4 <= len) {
|
||||
if (rowNumber % 2) {
|
||||
fourColumRow(gallery, entries, i)
|
||||
} else {
|
||||
fourColumnReversedRow(gallery, entries, i)
|
||||
}
|
||||
i += 4
|
||||
} else if (i + 3 <= len) {
|
||||
if (rowNumber % 2) {
|
||||
threeColumnRow(gallery, entries, i)
|
||||
} else {
|
||||
threeColumnReversedRow(gallery, entries, i)
|
||||
}
|
||||
i += 3
|
||||
} else if (i + 2 <= len) {
|
||||
twoColumnRow(gallery, entries, i)
|
||||
i += 2
|
||||
} else {
|
||||
singleColumnRow(gallery, entries, i)
|
||||
i++
|
||||
}
|
||||
} else if (isTablet) {
|
||||
if (i + 2 <= len) {
|
||||
twoColumnRow(gallery, entries, i)
|
||||
i += 2
|
||||
} else {
|
||||
singleColumnRow(gallery, entries, i)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
singleColumnRow(gallery, entries, i)
|
||||
i++
|
||||
}
|
||||
rowNumber++
|
||||
}
|
||||
|
||||
// show full image on click
|
||||
const elements = document.getElementsByClassName('achievement-entry')
|
||||
len = elements.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
elements[i].onclick = function () {
|
||||
const achievements = document.getElementsByClassName('achievement-entry')
|
||||
const len2 = achievements.length
|
||||
for (let j = 0; j < len2; j++) {
|
||||
achievements[j].classList.toggle('hidden')
|
||||
}
|
||||
this.classList.toggle('achievement-details')
|
||||
this.classList.toggle('hidden')
|
||||
this.parentElement.classList.toggle('col-lg-12')
|
||||
this.parentElement.classList.toggle('col-md-12')
|
||||
this.parentElement.classList.toggle('col-sm-12')
|
||||
if (this.children.SmallImage.hasAttribute('active')) {
|
||||
const mainLogo = this.children.LargeImage.getAttribute('Style')
|
||||
this.children.LargeImage.setAttribute('active', true)
|
||||
this.children.SmallImage.removeAttribute('active')
|
||||
|
||||
this.setAttribute('Style', mainLogo)
|
||||
} else {
|
||||
const mainLogo = this.children.SmallImage.getAttribute('Style')
|
||||
this.children.SmallImage.setAttribute('active', true)
|
||||
this.children.LargeImage.removeAttribute('active')
|
||||
this.setAttribute('Style', mainLogo)
|
||||
}
|
||||
|
||||
if (this.children.caption !== undefined) {
|
||||
this.children.caption.classList.toggle('hidden')
|
||||
}
|
||||
if (this.children['enlarge-icon'] !== undefined) {
|
||||
this.getElementsByClassName('fa-xmark')[0].classList.toggle('hidden')
|
||||
this.getElementsByClassName('fa-magnifying-glass-plus')[0].classList.toggle('hidden')
|
||||
}
|
||||
if (this.children['achievement-title'] !== undefined) {
|
||||
this.children['achievement-title'].classList.toggle('hidden')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
['DOMContentLoaded', 'resize'].forEach((event) =>
|
||||
document.addEventListener(event, showAchievements))
|
33
assets/scripts/sections/education.js
Normal file
33
assets/scripts/sections/education.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Show more rows in the taken courses table
|
||||
function toggleCourseVisibility (elem) {
|
||||
// find the courses
|
||||
const courses = elem.parentNode.getElementsByClassName('course')
|
||||
if (courses == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// toggle hidden-course class from the third elements
|
||||
for (const course of courses) {
|
||||
if (course.classList.contains('hidden-course') || course.classList.contains('toggled-hidden-course')) {
|
||||
course.classList.toggle('hidden-course')
|
||||
course.classList.add('toggled-hidden-course')
|
||||
}
|
||||
}
|
||||
|
||||
// toggle the buttons visibility
|
||||
const buttonsToToggle = elem.parentNode.getElementsByClassName('show-more-btn')
|
||||
for (const buttonToToggle of buttonsToToggle) {
|
||||
buttonToToggle.classList.toggle('hidden')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const els = [
|
||||
document.getElementById('show-more-btn'),
|
||||
document.getElementById('show-less-btn')
|
||||
]
|
||||
|
||||
els.filter((el) => el != null).forEach((el) =>
|
||||
el.addEventListener('click', ({ target }) =>
|
||||
toggleCourseVisibility(target)))
|
||||
})
|
7
assets/scripts/sections/index.js
Normal file
7
assets/scripts/sections/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import './navbar'
|
||||
import './sidebar'
|
||||
|
||||
import './education'
|
||||
import './achievements'
|
||||
import './projects'
|
||||
import './publications'
|
60
assets/scripts/sections/navbar.js
Normal file
60
assets/scripts/sections/navbar.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const updateNavBar = () => {
|
||||
const topNavbar = document.getElementById('top-navbar')
|
||||
const navbarToggler = document.getElementById('navbar-toggler')
|
||||
const themeIcon = document.getElementById('navbar-theme-icon-svg')
|
||||
|
||||
if (window.scrollY > 40) {
|
||||
topNavbar?.classList.remove('initial-navbar')
|
||||
topNavbar?.classList.add('final-navbar', 'shadow')
|
||||
|
||||
navbarToggler?.classList.remove('navbar-dark')
|
||||
navbarToggler?.classList.add('navbar-light')
|
||||
|
||||
// color theme selector a.k.a. dark mode
|
||||
themeIcon?.classList.remove('navbar-icon-svg-dark')
|
||||
|
||||
// get the main logo from hidden img tag
|
||||
const mainLogo = document.getElementById('main-logo')
|
||||
if (mainLogo) {
|
||||
const logoURL = mainLogo.getAttribute('src')
|
||||
document.getElementById('logo')?.setAttribute('src', logoURL)
|
||||
}
|
||||
} else {
|
||||
topNavbar?.classList.remove('final-navbar', 'shadow')
|
||||
topNavbar?.classList.add('initial-navbar')
|
||||
|
||||
navbarToggler?.classList.remove('navbar-light')
|
||||
navbarToggler?.classList.add('navbar-dark')
|
||||
|
||||
// color theme selector a.k.a. dark mode
|
||||
themeIcon?.classList.add('navbar-icon-svg-dark')
|
||||
|
||||
// get the inverted logo from hidden img tag
|
||||
const invertedLogo = document.getElementById('inverted-logo')
|
||||
if (invertedLogo) {
|
||||
const logoURL = invertedLogo.getAttribute('src')
|
||||
document.getElementById('logo')?.setAttribute('src', logoURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// change navbar style on scroll
|
||||
// ==================================================
|
||||
// When the user scrolls down 80px from the top of the document,
|
||||
// resize the navbar's padding and the logo's font size
|
||||
document.addEventListener('scroll', updateNavBar)
|
||||
|
||||
// Creates a click handler to collapse the navigation when
|
||||
// anchors in the mobile nav pop up are clicked
|
||||
const navMain =document.getElementsByClassName('navbar-collapse')
|
||||
Array.from(navMain).forEach(function(el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
if (e.target.tagName === 'A') {
|
||||
el.classList.add('collapse')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
updateNavBar()
|
||||
})
|
19
assets/scripts/sections/projects.js
Normal file
19
assets/scripts/sections/projects.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Filterizr from 'filterizr'
|
||||
import { insertScript } from '../core'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ================== Project cards =====================
|
||||
|
||||
// setup project filter buttons
|
||||
const projectCardHolder = document.getElementById('project-card-holder')
|
||||
if (projectCardHolder != null && projectCardHolder.children.length !== 0) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Filterizr('.filtr-projects', {
|
||||
layout: 'sameWidth',
|
||||
controlsSelector: '.project-filtr-control'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// dynamically insert github buttons script.
|
||||
insertScript('github-buttons', 'https://buttons.github.io/buttons.js')
|
13
assets/scripts/sections/publications.js
Normal file
13
assets/scripts/sections/publications.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Filterizr from 'filterizr'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const publicationCardHolder = document.getElementById('publication-card-holder')
|
||||
if (publicationCardHolder != null && publicationCardHolder.children.length !== 0) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Filterizr('.filtr-publications', {
|
||||
layout: 'sameWidth',
|
||||
gridItemsSelector: '.pub-filtr-item',
|
||||
controlsSelector: '.pub-filtr-control'
|
||||
})
|
||||
}
|
||||
})
|
38
assets/scripts/sections/sidebar.js
Normal file
38
assets/scripts/sections/sidebar.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { getDeviceState } from '../core/device'
|
||||
|
||||
// Toggle sidebar on click. Here, class "hide" open the sidebar
|
||||
function toggleSidebar () {
|
||||
const sidebar = document.getElementById('sidebar-section')
|
||||
if (sidebar == null) {
|
||||
return
|
||||
}
|
||||
if (sidebar.classList.contains('hide')) {
|
||||
sidebar.classList.remove('hide')
|
||||
} else {
|
||||
// if toc-section is open, then close it first
|
||||
const toc = document.getElementById('toc-section')
|
||||
if (toc != null && toc.classList.contains('hide')) {
|
||||
toc.classList.remove('hide')
|
||||
}
|
||||
// add "hide" class
|
||||
sidebar.classList.add('hide')
|
||||
// if it is mobile device. then scroll to top.
|
||||
const { isMobile } = getDeviceState()
|
||||
if (isMobile && sidebar.classList.contains('hide')) {
|
||||
document.body.scrollTop = 0
|
||||
document.documentElement.scrollTop = 0
|
||||
if (document.getElementById('hero-area') != null) {
|
||||
document.getElementById('hero-area').classList.toggle('hide')
|
||||
}
|
||||
}
|
||||
}
|
||||
if (document.getElementById('content-section') != null) {
|
||||
document.getElementById('content-section').classList.toggle('hide')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// bind click event to #sidebar-toggler in navbar-2.html
|
||||
const toggle = document.getElementById('sidebar-toggler')
|
||||
if (toggle) toggle.addEventListener('click', toggleSidebar)
|
||||
})
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -43,7 +43,7 @@ Esto expone los valores en /index.json: por ejemplo, para agregar `categories`
|
|||
\```
|
||||
|
||||
### Editar las opciones de fuse.js para buscar
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -13,7 +13,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -41,7 +41,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -43,7 +43,7 @@ Esto expone los valores en /index.json: por ejemplo, para agregar `categories`
|
|||
\```
|
||||
|
||||
### Editar las opciones de fuse.js para buscar
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -15,7 +15,7 @@ No content shown here is rendered, all content is based in the template layouts/
|
|||
|
||||
Setting a very low sitemap priority will tell search engines this is not important content.
|
||||
|
||||
This implementation uses Fusejs, jquery and mark.js
|
||||
This implementation uses Fusejs and mark.js
|
||||
|
||||
|
||||
## Initial setup
|
||||
|
@ -43,7 +43,7 @@ i.e. add `category`
|
|||
\```
|
||||
|
||||
### Edit fuse.js options to Search
|
||||
`static/js/search.js`
|
||||
`assets/scripts/pages/search.js`
|
||||
\```
|
||||
keys: [
|
||||
"title",
|
||||
|
|
|
@ -55,7 +55,3 @@
|
|||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="{{ "/js/list.js" | relURL }}"></script>
|
||||
{{ end }}
|
||||
|
|
|
@ -66,9 +66,3 @@
|
|||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js"></script>
|
||||
<script src="{{ "/js/search.js" | absURL }}"></script>
|
||||
{{ end }}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<div class="title">
|
||||
<h1>{{ .Page.Title }}</h1>
|
||||
</div>
|
||||
{{ if site.Params.enableTags }}
|
||||
{{ if site.Params.features.tags.enable }}
|
||||
<div class="taxonomy-terms">
|
||||
<ul style="padding-left: 0;">
|
||||
{{ range .Params.tags }}
|
||||
|
@ -158,7 +158,7 @@
|
|||
|
||||
<!----- Add comment support ----->
|
||||
{{ if site.Params.features.comment.enable }}
|
||||
{{ partial "comments.html" site.Params.features.comment }}
|
||||
{{ partial "comments.html" site.Params.features.comment.services }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Keep backward compatibility with old config.yaml -->
|
||||
|
@ -179,7 +179,7 @@
|
|||
|
||||
{{ define "toc" }}
|
||||
<section class="toc-section" id="toc-section">
|
||||
{{ if and site.Params.enableTOC ( .Params.enableTOC | default true ) }}
|
||||
{{ if and site.Params.features.toc.enable ( .Params.enableTOC | default true ) }}
|
||||
<div class="toc-holder">
|
||||
<h5 class="text-center pl-3">{{ i18n "toc_heading" }}</h5>
|
||||
<hr>
|
||||
|
@ -192,20 +192,9 @@
|
|||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
|
||||
<script src="{{ "/js/single.js" | relURL }}"></script>
|
||||
<script>
|
||||
hljs.initHighlightingOnLoad();
|
||||
</script>
|
||||
|
||||
<!-------------- Enable Math support for this page ---------------->
|
||||
{{ if .Params.math }}
|
||||
{{ if site.Params.features.math.enable }}
|
||||
{{ partial "math.html" . }}
|
||||
{{ end }}
|
||||
|
||||
<!-------------- Enable mermaid support for this page ---------------->
|
||||
{{ if .Params.mermaid }}
|
||||
{{ partial "mermaid.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
|
|
|
@ -56,7 +56,3 @@
|
|||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="{{ "/js/list.js" | relURL }}"></script>
|
||||
{{ end }}
|
||||
|
|
|
@ -75,12 +75,6 @@
|
|||
<!--- ADD COMMON SCRIPTS --------------->
|
||||
{{ partial "scripts.html" . }}
|
||||
|
||||
<!--- ADD INDEX PAGE SPECIFIC SCRIPTS -->
|
||||
<script src="{{ "/js/itype.min.js" | relURL }}"></script>
|
||||
<script src="{{ "/js/github-button.js" | relURL }}"></script>
|
||||
<script src="{{ "/js/home.js" | relURL }}"></script>
|
||||
<script src="{{ "/js/jquery.filterizr.min.js" | relURL }}"></script>
|
||||
|
||||
<!------ ADD SUPPORT LINKS -------->
|
||||
{{- partial "misc/support.html" . -}}
|
||||
|
||||
|
|
|
@ -57,13 +57,7 @@
|
|||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
|
||||
<script src="{{ "/js/imagesloaded.pkgd.min.js" | relURL }}"></script>
|
||||
<script src="{{ "/js/note.js" | relURL }}"></script>
|
||||
<script>
|
||||
hljs.initHighlightingOnLoad();
|
||||
</script>
|
||||
{{ if .Params.math }}
|
||||
{{ if site.Params.features.math.enable }}
|
||||
{{ partial "math.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
|
@ -47,13 +47,7 @@
|
|||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
|
||||
<script src="{{ "/js/imagesloaded.pkgd.min.js" | relURL }}"></script>
|
||||
<script src="{{ "/js/note.js" | relURL }}"></script>
|
||||
<script>
|
||||
hljs.initHighlightingOnLoad();
|
||||
</script>
|
||||
{{ if .Params.math }}
|
||||
{{ if site.Params.features.math.enable }}
|
||||
{{ partial "math.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<!-- Add Analytics if enabled in configuration -->
|
||||
{{ with site.Params.features.analytics }}
|
||||
{{ if .enabled }}
|
||||
{{ with .services }}
|
||||
<!-- Google Analytics -->
|
||||
{{ with .google }}
|
||||
{{ $privacyConfig:= dict (slice "Site" "Config" "Privacy" "GoogleAnalytics") $.Site.Config.Privacy.GoogleAnalytics }}
|
||||
|
@ -21,7 +22,7 @@
|
|||
<script
|
||||
data-goatcounter="https://{{ .code }}.goatcounter.com/count"
|
||||
async
|
||||
src="/js/goat-counter.js"
|
||||
src="//gc.zgo.at/count.js"
|
||||
></script>
|
||||
{{ end }}
|
||||
|
||||
|
@ -43,6 +44,7 @@
|
|||
</script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Keep backwards compatibility and consistency with HUGO defaults -->
|
||||
|
|
|
@ -42,8 +42,9 @@
|
|||
{{ end }}
|
||||
<div class="project-btn-holder">
|
||||
{{ if .repo }}
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a
|
||||
class="github-button-inactive project-btn"
|
||||
class="github-button project-btn d-none"
|
||||
href="{{ .repo }}"
|
||||
data-icon="octicon-standard"
|
||||
data-show-count="true"
|
||||
|
|
|
@ -7,17 +7,14 @@
|
|||
<link rel="stylesheet" href="{{ "/css/layouts/main.css" | relURL }}"/>
|
||||
<link rel="stylesheet" href="{{ "/css/navigators/navbar.css" | relURL }}"/>
|
||||
<link rel="stylesheet" href="{{ "/css/plyr.css" | relURL }}"/>
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
<link rel="stylesheet" href="{{ "/css/flag-icon.min.css" | relURL }}"/>
|
||||
{{ end }}
|
||||
<!--=================== fonts ==============================-->
|
||||
<link rel="stylesheet" href="{{ "/google-fonts/Mulish/mulish.css" | relURL }}"/>
|
||||
|
||||
<!--=================== icons ==============================-->
|
||||
<link rel="stylesheet" href="{{ "/fontawesome/css/all.min.css" | relURL }}"/>
|
||||
|
||||
<!--=================== dark mode ==========================-->
|
||||
{{ if site.Params.darkMode.enable }}
|
||||
{{ if site.Params.features.darkMode.enable }}
|
||||
<link rel="stylesheet" href="{{ "/css/colortheme/colortheme.css" | relURL }}"/>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
|
||||
{{/* if the user specify a country code for a language via "params.flagOverwrites" field, then use it. */}}
|
||||
{{ range site.Params.flagOverwrites }}
|
||||
{{ range site.Params.features.flags.flagOverwrites }}
|
||||
{{ if eq $languageCode .languageCode }}
|
||||
{{ $countryCode = .countryCode }}
|
||||
{{ end }}
|
||||
|
|
209
layouts/partials/helpers/get-esbuild-options.html
Normal file
209
layouts/partials/helpers/get-esbuild-options.html
Normal file
|
@ -0,0 +1,209 @@
|
|||
{{/*
|
||||
|
||||
## Overview
|
||||
|
||||
This helper returns options dictionary used to configure ESBuild.
|
||||
The following configurations are set:
|
||||
|
||||
* Enable JS minification.
|
||||
* Enable source map if not building for production.
|
||||
* Prepare `process.env.<ENVIRONMENT VARIABLE>` defines based on enabled features.
|
||||
This allows ESBuild to optimize JS bundle size by removing code related
|
||||
to unused features.
|
||||
* Added `process-shim.js` so `process.env` is defined.
|
||||
This way we don't have to explicitly specify every environment
|
||||
variable via `defines` value.
|
||||
* Prepare `params` for feature and service configs used in JS.
|
||||
|
||||
For more details on ESBuild configuration, see: https://gohugo.io/hugo-pipes/js/
|
||||
|
||||
## Detailed Concepts
|
||||
|
||||
### `feature` and `service`
|
||||
|
||||
Features configured in site wide `config.yml` file under `params.features` section.
|
||||
A **feature** provides a certain functionality to the user via one or more services.
|
||||
A feature be can enabled or disabled.
|
||||
|
||||
A **service** is a 3rd party service, or JS library that implements a feature.
|
||||
|
||||
For example, `analytics` is considered a feature.
|
||||
There are many services that can provide this feature, to name a few:
|
||||
|
||||
* Google Analytics
|
||||
* Counter.Dev
|
||||
* GoatCounter
|
||||
|
||||
To maximize extendibility and therefore usefulness as an open source project,
|
||||
it is important to define a standard interface that is easy to understand,
|
||||
configure, and extend.
|
||||
|
||||
In this file, I took the liberty of standardizing this interface under `params.features`.
|
||||
Please note that this breaks compatibility with previous versions of `config.yaml`.
|
||||
I will provide sample `config.yaml` files with fully documented features, as well as
|
||||
documentation on migrating the `config.yaml` file to the new standard.
|
||||
|
||||
Here is a sample config file for the new `params.features` section. Notice that each `service`
|
||||
is a dictionary with `enable` and `services` key. `services` is a dictionary with each key
|
||||
corresponding to a concrete service, and value as configuration / settings. In the case of
|
||||
services that need no configuration, an empty dictionary is created.
|
||||
|
||||
```yml
|
||||
params:
|
||||
features:
|
||||
|
||||
# This is the `analytics` feature
|
||||
analytics:
|
||||
# This feature is enabled
|
||||
enable: true
|
||||
# List of services to enable
|
||||
services:
|
||||
|
||||
# Google Analytics is enabled
|
||||
google:
|
||||
# settings for Google Analytics
|
||||
id: G-xxxxx
|
||||
|
||||
# # Counter Dev is disabled
|
||||
# counterDev:
|
||||
# id: foo
|
||||
# name: bar
|
||||
|
||||
# The `darkMode` feature
|
||||
darkmode:
|
||||
enable: true
|
||||
services:
|
||||
# darkmode is realized by using DarkReader library
|
||||
darkreader:
|
||||
# options are 'system', 'dark', 'light'
|
||||
defaultColorScheme: system
|
||||
# For all available options, see `interface DynamicThemeFix`:
|
||||
# https://github.com/darkreader/darkreader/blob/main/index.d.ts#L125
|
||||
fixes:
|
||||
invert: ['img[src$=".svg"]'] # inverts svg colors.
|
||||
# For all available options, see `interface Theme` in:
|
||||
# https://github.com/darkreader/darkreader/blob/main/index.d.ts#L45
|
||||
theme:
|
||||
brightness: 100
|
||||
contrast: 100
|
||||
sepia: 0
|
||||
```
|
||||
|
||||
This helper will convert the above config into the following env vars:
|
||||
|
||||
* `FEATURE_ANALYTICS=1`
|
||||
* `FEATURE_ANALYTICS_GOOGLE=1`
|
||||
* `FEATURE_DARKMODE=1`
|
||||
* `FEATURE_DARKMODE_DARKREADER=1`
|
||||
|
||||
In JS, you can use it like this:
|
||||
|
||||
```js
|
||||
import * as params from '@params';
|
||||
|
||||
if (process.env.FEATURE_ANALYTICS) {
|
||||
// Do things to enable this feature here
|
||||
|
||||
if (process.env.FEATURE_ANALYTICS_GOOGLE) {
|
||||
// Do things things to enable google analytics
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also access service configs via params:
|
||||
|
||||
```js
|
||||
import * as params from '@params';
|
||||
|
||||
console.log(params);
|
||||
```
|
||||
|
||||
You will see console output like below. Note, each service configuration is
|
||||
namespaced by their corresponding feature.
|
||||
|
||||
```json
|
||||
{
|
||||
"analytics": {
|
||||
"google": {
|
||||
"id": "G-xxxxx"
|
||||
}
|
||||
},
|
||||
"darkmode": {
|
||||
"darkreader": {
|
||||
"defaultColorScheme": "system",
|
||||
"fixes": {
|
||||
"invert": "['img[src$=\".svg\"]']"
|
||||
},
|
||||
"theme": {
|
||||
"brightness": 100,
|
||||
"contrast": 100,
|
||||
"sepia": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*/}}
|
||||
|
||||
{{/* Holds all the feature flag environment variables for `process.env.*` in JS land */}}
|
||||
{{ $defines := dict }}
|
||||
|
||||
{{/* Holds all the feature configuration variables exposed to JS side */}}
|
||||
{{ $params := dict }}
|
||||
|
||||
{{/* set NODE_ENV depending on if we are building for production use. */}}
|
||||
{{ $defines = $defines | merge (dict
|
||||
"process.env.NODE_ENV" (cond hugo.IsProduction `"production"` `"development"` )
|
||||
)}}
|
||||
|
||||
{{/* Go through each feature defined in our config.yml/config.toml/config.json file. */}}
|
||||
{{ range $feature, $featureDef := site.Params.features }}
|
||||
{{/* Initialize a dictionary that will hold all service configs for this specific feature */}}
|
||||
{{ $featureParams := dict }}
|
||||
|
||||
{{ with $featureDef }}
|
||||
{{/* convert feature name to uppercase and remove '_', e.g. `darkMode` becomes `DARKMODE` */}}
|
||||
{{ $featureName := replace $feature "_" "" | upper }}
|
||||
|
||||
{{/* The feature is enabled if the `enable` key is absent, or is set to `true` */}}
|
||||
{{ $featureEnabled := or (not (isset . "enable")) .enable }}
|
||||
|
||||
{{/* Sets `FEATURE_<FEATURE_NAME>` env var to "1" or "0" depending on if the feature is enabled. */}}
|
||||
{{ $defines = $defines | merge (dict
|
||||
(printf "process.env.FEATURE_%s" $featureName) (cond $featureEnabled `'1'` `'0'`)
|
||||
) }}
|
||||
|
||||
{{ if $featureEnabled }}
|
||||
{{/* Loop through each service under this feature */}}
|
||||
{{ range $service, $config := .services }}
|
||||
{{/*
|
||||
We assume all services are enabled. To disable a service,
|
||||
simply comment it out from `config.yaml`.
|
||||
*/}}
|
||||
|
||||
{{/* Convert name to all uppercase, removing underscore */}}
|
||||
{{ $serviceName := replace $service "_" "" | upper }}
|
||||
|
||||
{{/* let JS side know this service is enabled */}}
|
||||
{{ $defines = $defines | merge (dict
|
||||
(printf "process.env.FEATURE_%s_%s" $featureName $serviceName) `'1'`
|
||||
) }}
|
||||
|
||||
{{/* add service configuration to feature params */}}
|
||||
{{ $featureParams = $featureParams | merge (dict $service $config) }}
|
||||
{{ end }}
|
||||
|
||||
{{/* add feature params to top level params */}}
|
||||
{{ $params = $params | merge (dict $feature $featureParams) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{
|
||||
return dict
|
||||
"defines" $defines
|
||||
"params" $params
|
||||
"minify" true
|
||||
"sourceMap" (cond hugo.IsProduction "" "inline")
|
||||
"inject" "scripts/process-shim.js"
|
||||
}}
|
5
layouts/partials/helpers/script-bundle.html
Normal file
5
layouts/partials/helpers/script-bundle.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{- $options := partial "helpers/get-esbuild-options.html" -}}
|
||||
{{- $options = $options | merge (dict "targetPath" "application.js") -}}
|
||||
{{- $app := resources.Get "scripts/application.js" -}}
|
||||
{{- $bundle := $app | js.Build $options | fingerprint -}}
|
||||
<script src="{{ $bundle.RelPermalink }}" integrity="{{ $bundle.Data.Integrity }}" defer></script>
|
|
@ -1,15 +1 @@
|
|||
<link rel="stylesheet" href="{{ "/katex/katex.min.css" | relURL }}">
|
||||
<script type="text/javascript" defer src="{{ "/katex/katex.min.js" | relURL }}"></script>
|
||||
<script type="text/javascript" defer src="{{ "/katex/auto-render.min.js" | relURL }}" onload="renderMathInElement(document.body);">
|
||||
renderMathInElement(
|
||||
document.body,
|
||||
{
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "\\[", right: "\\]", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false}
|
||||
]
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<script src="{{ "/js/mermaid-8.14.0.min.js" | relURL }}"></script>
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
startOnLoad:true
|
||||
});
|
||||
</script>
|
|
@ -1,5 +1,6 @@
|
|||
{{ with site.Params.features.support }}
|
||||
{{ if .enabled }}
|
||||
{{ if .enable }}
|
||||
{{ with .services }}
|
||||
<!-- Enable Ko-Fi floating button -->
|
||||
{{ with .kofi }}
|
||||
<script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script>
|
||||
|
@ -17,4 +18,5 @@
|
|||
<script data-name="BMC-Widget" data-cfasync="false" src="https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js" data-id="{{ .user }}" data-description="{{ .text }}" data-message="{{ .info }}" data-color="{{ .color }}" data-position="Right" data-x_margin="10" data-y_margin="18"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<div class="dropdown languageSelector">
|
||||
<a class="btn dropdown-toggle" href="#" id="languageSelector" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
@ -14,7 +14,7 @@
|
|||
<div class="dropdown-menu" aria-labelledby="languageSelector">
|
||||
{{ range .Translations }}
|
||||
<a class="dropdown-item nav-link languages-item" href="{{ path.Join "/" (cond (eq .Language.Lang "en") "" .Language.Lang) $pageURL }}">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageSelector" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
@ -14,7 +14,7 @@
|
|||
<div class="dropdown-menu" aria-labelledby="languageSelector">
|
||||
{{ range .Translations }}
|
||||
<a class="dropdown-item nav-link languages-item" href="{{ path.Join "/" (cond (eq .Language.Lang "en") "" .Language.Lang) $pageURL }}">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageSelector" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
@ -9,7 +9,7 @@
|
|||
<div class="dropdown-menu" aria-labelledby="languageSelector">
|
||||
{{ range site.Home.AllTranslations }}
|
||||
<a class="dropdown-item nav-link languages-item" href="{{ .RelPermalink }}">
|
||||
{{ if ne site.Params.showFlags false }}
|
||||
{{ if ne site.Params.features.flags.enable false }}
|
||||
{{ $countryCode := partial "helpers/country-code.html" . }}
|
||||
<span class="flag-icon flag-icon-{{$countryCode}}"></span>
|
||||
{{ end }}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<nav class="navbar navbar-expand-xl top-navbar final-navbar shadow">
|
||||
<div class="container">
|
||||
<button class="navbar-toggler navbar-light" id="sidebar-toggler" type="button" onclick="toggleSidebar()">
|
||||
<button class="navbar-toggler navbar-light" id="sidebar-toggler" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{ site.BaseURL | relLangURL }}">
|
||||
|
@ -38,7 +38,7 @@
|
|||
{{ end }}
|
||||
{{- site.Title -}}
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-light" id="toc-toggler" type="button" onclick="toggleTOC()">
|
||||
<button class="navbar-toggler navbar-light" id="toc-toggler" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
{{ if .IsTranslated }}
|
||||
{{ partial "navigators/lang-selector-2.html" . }}
|
||||
{{ end }}
|
||||
{{ if site.Params.darkMode.enable }}
|
||||
{{ if site.Params.features.darkMode.enable }}
|
||||
{{ partial "navigators/theme-selector.html" . }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
{{ if .IsTranslated }}
|
||||
{{ partial "navigators/lang-selector.html" . }}
|
||||
{{ end }}
|
||||
{{ if site.Params.darkMode.enable }}
|
||||
{{ if site.Params.features.darkMode.enable }}
|
||||
{{ partial "navigators/theme-selector.html" . }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
<li class="nav-item dropdown">
|
||||
<!-- This is for initializing the color scheme selection for new visitors. See /js/darkmode.js -->
|
||||
<div id="theme-initialization" style="display: none;"
|
||||
default-theme="{{ site.Params.darkMode.default }}"></div>
|
||||
<a class="nav-link dropdown-toggle" href="#" id="themeSelector" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img id="navbar-theme-icon-svg" src="{{ "/icons/moon-svgrepo-com.svg" }}" width=20 alt="Dark Theme">
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-icons-only" aria-labelledby="themeSelector">
|
||||
<a class="dropdown-item nav-link" href="#" onclick="enableLightTheme()">
|
||||
<div id="themeMenu" class="dropdown-menu dropdown-menu-icons-only" aria-labelledby="themeSelector">
|
||||
<a class="dropdown-item nav-link" href="#" data-scheme="light">
|
||||
<img class="menu-icon-center" src="{{ "/icons/sun-svgrepo-com.svg" }}" width=20 alt="Light Theme">
|
||||
</a>
|
||||
<a class="dropdown-item nav-link" href="#" onclick="enableDarkTheme()">
|
||||
<a class="dropdown-item nav-link" href="#" data-scheme="dark">
|
||||
<img class="menu-icon-center" src="{{ "/icons/moon-svgrepo-com.svg" }}" width=20 alt="Dark Theme">
|
||||
</a>
|
||||
<a class="dropdown-item nav-link" href="#" onclick="useSystemTheme()">
|
||||
<a class="dropdown-item nav-link" href="#" data-scheme="system">
|
||||
<img class="menu-icon-center" src="{{ "/icons/computer-svgrepo-com.svg" }}" width=20 alt="System Theme">
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1 @@
|
|||
<script type="text/javascript" src="{{ "/js/jquery-3.4.1.min.js" | relURL }}"></script>
|
||||
<script type="text/javascript" src="{{ "/js/popper.min.js" | relURL }}"></script>
|
||||
<script type="text/javascript" src="{{ "/js/bootstrap.min.js" | relURL }}"></script>
|
||||
|
||||
<script type="text/javascript" src="{{ "/js/navbar.js" | relURL }}"></script>
|
||||
<script type="text/javascript" src="{{ "/js/plyr.js" | relURL }}"></script>
|
||||
<script type="text/javascript" src="{{ "/js/main.js" | relURL }}"></script>
|
||||
|
||||
{{ if site.Params.darkMode.enable }}
|
||||
{{ if eq site.Params.darkMode.provider "darkreader" }}
|
||||
<script type="text/javascript" src="{{ "/js/darkreader.js" | relURL }}"></script>
|
||||
<script type="text/javascript" src="{{ "/js/darkmode-darkreader.js" | relURL }}"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ partial "helpers/script-bundle.html" }}
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
class="achievement-entry text-center"
|
||||
style="background-image: url('{{ $achievementImageSm }}');"
|
||||
>
|
||||
<i class="fas fa-search-plus" id="enlarge-icon"></i>
|
||||
<i class="fa-solid fa-xmark hidden"></i>
|
||||
<i class="fa-solid fa-magnifying-glass-plus" id="enlarge-icon"></i>
|
||||
<h4 class="title" id="achievement-title">{{ .title }}</h4>
|
||||
<div class="caption hidden col-lg-6 text-left" id="caption">
|
||||
<h4>{{ .title }}</h4>
|
||||
|
|
|
@ -90,9 +90,9 @@
|
|||
{{ end }}
|
||||
{{ if gt (len .takenCourses.courses) $collapseAfter }}
|
||||
<button type="button" class="btn btn-link show-more-btn pt-0 {{ if .takenCourses.showGrades }}ml-1{{ else }}ml-2{{ end }}"
|
||||
onclick="toggleCourseVisibility(this);" id="show-more-btn" aria-label="{{ i18n "show_more"}}">{{ i18n "show_more"}}</button>
|
||||
id="show-more-btn" aria-label="{{ i18n "show_more"}}">{{ i18n "show_more"}}</button>
|
||||
<button type="button" class="btn btn-link show-more-btn hidden pt-0 {{ if .takenCourses.showGrades }}ml-1{{ else }}ml-2{{ end }}"
|
||||
onclick="toggleCourseVisibility(this);" id="show-less-btn" aria-label="{{ i18n "show_less"}}">{{ i18n "show_less"}}</button>
|
||||
id="show-less-btn" aria-label="{{ i18n "show_less"}}">{{ i18n "show_less"}}</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -90,9 +90,9 @@
|
|||
{{ end }}
|
||||
{{ if gt (len .takenCourses.courses ) $collapseAfter }}
|
||||
<button type="button" class="btn btn-link show-more-btn pt-0 {{ if .takenCourses.showGrades }}ml-1{{ else }}ml-2{{ end }}"
|
||||
onclick="toggleCourseVisibility(this);" id="show-more-btn">{{ i18n "show_more"}}</button>
|
||||
id="show-more-btn">{{ i18n "show_more"}}</button>
|
||||
<button type="button" class="btn btn-link show-more-btn hidden pt-0 {{ if .takenCourses.showGrades }}ml-1{{ else }}ml-2{{ end }}"
|
||||
onclick="toggleCourseVisibility(this);" id="show-less-btn">{{ i18n "show_less"}}</button>
|
||||
id="show-less-btn">{{ i18n "show_less"}}</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,185 +1,32 @@
|
|||
<!--
|
||||
{{ $url := .Get "src" }}
|
||||
{{ $hidePaginator := .Get "hidePaginator" | default "false" }}
|
||||
{{ $hideLoader := .Get "hidePaginator" | default "false" }}
|
||||
{{ $pageNum := .Get "renderPageNum" | default "1"}}
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
This shortcut was originally taken from: https://github.com/anvithks/hugo-embed-pdf-shortcode,
|
||||
where it is available under the MIT License, where it was originally created by https://github.com/anvithks
|
||||
|
||||
Since the project seems discontinued, it has been re-created here
|
||||
-->
|
||||
|
||||
<!-- Load the PDF-JS from the local folder (should be updated over time) -->
|
||||
<script src= '/js/pdf-js/build/pdf.js'></script>
|
||||
|
||||
<!-- Set the navigation menu -->
|
||||
<div id="paginator">
|
||||
<button id="prev">Previous</button>
|
||||
<button id="next">Next</button>
|
||||
|
||||
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
|
||||
</div>
|
||||
|
||||
<!-- And the canvas where the PDF will load -->
|
||||
<div id="embed-pdf-container">
|
||||
<div id="loadingWrapper">
|
||||
<div id="loading"></div>
|
||||
<div
|
||||
class="pdf-viewer"
|
||||
data-url="{{ $url }}"
|
||||
data-hide-paginator="{{ $hidePaginator }}"
|
||||
data-hide-loader="{{ $hideLoader }}"
|
||||
data-page-num="{{ $pageNum }}"
|
||||
>
|
||||
<div class="paginator">
|
||||
<button class="prev">Previous</button>
|
||||
<button class="next">Next</button>
|
||||
<span>Page: <span class="page-num"></span> / <span class="page-count"></span></span>
|
||||
</div>
|
||||
|
||||
<div class="embed-pdf-container">
|
||||
<div class="loading-wrapper">
|
||||
<div class="loading"></div>
|
||||
</div>
|
||||
<canvas class="pdf-canvas"></canvas>
|
||||
</div>
|
||||
<canvas id="the-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- This script gets the PDF, sets it and passes it on to the already-loaded pdf-js -->
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
// If absolute URL from the remote server is provided, configure the CORS
|
||||
// header on that server.
|
||||
var url = "{{.Site.BaseURL}}" + '{{ .Get "src" }}';
|
||||
|
||||
var hidePaginator = "{{ .Get "hidePaginator" }}" === "true";
|
||||
var hideLoader = "{{ .Get "hideLoader" }}" === "true";
|
||||
var selectedPageNum = parseInt("{{ .Get "renderPageNum" }}") || 1;
|
||||
|
||||
// Loaded via <script> tag, create shortcut to access PDF.js exports.
|
||||
var pdfjsLib = window['pdfjs-dist/build/pdf'];
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "{{.Site.BaseURL}}" + '/js/pdf-js/build/pdf.worker.js';
|
||||
|
||||
// Change the Scale value for lower or higher resolution.
|
||||
var pdfDoc = null,
|
||||
pageNum = selectedPageNum,
|
||||
pageRendering = false,
|
||||
pageNumPending = null,
|
||||
scale = 3,
|
||||
canvas = document.getElementById('the-canvas'),
|
||||
ctx = canvas.getContext('2d'),
|
||||
paginator = document.getElementById("paginator"),
|
||||
loadingWrapper = document.getElementById('loadingWrapper');
|
||||
|
||||
|
||||
// Attempt to show paginator and loader if enabled
|
||||
showPaginator();
|
||||
showLoader();
|
||||
|
||||
/**
|
||||
* Get page info from document, resize canvas accordingly, and render page.
|
||||
* @param num Page number.
|
||||
*/
|
||||
function renderPage(num) {
|
||||
pageRendering = true;
|
||||
// Using promise to fetch the page
|
||||
pdfDoc.getPage(num).then(function(page) {
|
||||
var viewport = page.getViewport({scale: scale});
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
// Render PDF page into canvas context
|
||||
var renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
var renderTask = page.render(renderContext);
|
||||
|
||||
// Wait for rendering to finish
|
||||
renderTask.promise.then(function() {
|
||||
pageRendering = false;
|
||||
showContent();
|
||||
|
||||
if (pageNumPending !== null) {
|
||||
// New page rendering is pending
|
||||
renderPage(pageNumPending);
|
||||
pageNumPending = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update page counters
|
||||
document.getElementById('page_num').textContent = num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides loader and shows canvas
|
||||
*/
|
||||
function showContent() {
|
||||
loadingWrapper.style.display = 'none';
|
||||
canvas.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* If we haven't disabled the loader, show loader and hide canvas
|
||||
*/
|
||||
function showLoader() {
|
||||
if(hideLoader) return
|
||||
loadingWrapper.style.display = 'flex';
|
||||
canvas.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* If we haven't disabled the paginator, show paginator
|
||||
*/
|
||||
function showPaginator() {
|
||||
if(hidePaginator) return
|
||||
paginator.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* If another page rendering in progress, waits until the rendering is
|
||||
* finished. Otherwise, executes rendering immediately.
|
||||
*/
|
||||
function queueRenderPage(num) {
|
||||
if (pageRendering) {
|
||||
pageNumPending = num;
|
||||
} else {
|
||||
renderPage(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays previous page.
|
||||
*/
|
||||
function onPrevPage() {
|
||||
if (pageNum <= 1) {
|
||||
return;
|
||||
}
|
||||
pageNum--;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById('prev').addEventListener('click', onPrevPage);
|
||||
|
||||
/**
|
||||
* Displays next page.
|
||||
*/
|
||||
function onNextPage() {
|
||||
if (pageNum >= pdfDoc.numPages) {
|
||||
return;
|
||||
}
|
||||
pageNum++;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById('next').addEventListener('click', onNextPage);
|
||||
|
||||
/**
|
||||
* Asynchronously downloads PDF.
|
||||
*/
|
||||
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
|
||||
pdfDoc = pdfDoc_;
|
||||
var numPages = pdfDoc.numPages;
|
||||
document.getElementById('page_count').textContent = numPages;
|
||||
|
||||
// If the user passed in a number that is out of range, render the last page.
|
||||
if(pageNum > numPages) {
|
||||
pageNum = numPages
|
||||
}
|
||||
|
||||
// Initial/first page rendering
|
||||
renderPage(pageNum);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Finally, make the canvas more beautiful -->
|
||||
<style>
|
||||
#the-canvas {
|
||||
.pdf-viewer canvas {
|
||||
border: 1px solid black;
|
||||
direction: ltr;
|
||||
width: 100%;
|
||||
|
@ -187,13 +34,13 @@ pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#paginator {
|
||||
.pdf-viewer .paginator {
|
||||
display: none;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#loadingWrapper {
|
||||
.pdf-viewer .loading-wrapper {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -201,7 +48,7 @@ pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
|
|||
height: 350px;
|
||||
}
|
||||
|
||||
#loading {
|
||||
.pdf-viewer .loading {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
|
|
@ -56,7 +56,3 @@
|
|||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="{{ "/js/list.js" | relURL }}"></script>
|
||||
{{ end }}
|
||||
|
|
3436
package-lock.json
generated
3436
package-lock.json
generated
File diff suppressed because it is too large
Load diff
19
package.hugo.json
Normal file
19
package.hugo.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@fontsource/mulish": "4.5.13",
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"bootstrap": "^4.6.2",
|
||||
"darkreader": "^4.9.58",
|
||||
"filterizr": "^2.2.4",
|
||||
"flag-icon-css": "^4.1.7",
|
||||
"fuse.js": "^6.6.2",
|
||||
"highlight.js": "^11.6.0",
|
||||
"imagesloaded": "^5.0.0",
|
||||
"ityped": "^1.0.3",
|
||||
"katex": "^0.16.3",
|
||||
"mark.js": "^8.11.1",
|
||||
"mermaid": "^9.2.1",
|
||||
"plyr": "^3.7.2",
|
||||
"popper.js": "^1.16.1"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
"description": "A [Hugo](https://gohugo.io/) theme for a personal portfolio with minimalist design and responsiveness.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"autoprefixer": "postcss static/css/*/*.css --use autoprefixer -r --no-map"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -18,6 +20,13 @@
|
|||
"homepage": "https://github.com/hugo-toha/toha#readme",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.6.0",
|
||||
"eslint-plugin-no-jquery": "^2.7.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"postcss": "^8.4.20",
|
||||
"postcss-cli": "^8.3.1"
|
||||
}
|
||||
|
|
|
@ -13,6 +13,26 @@ Green: #2DCA73
|
|||
Yellow: #FFC212
|
||||
*/
|
||||
|
||||
/*
|
||||
Removed smooth scrolling implementation in main.js in favor of
|
||||
simpler css approach.
|
||||
See: https://css-tricks.com/snippets/jquery/smooth-scrolling/
|
||||
*/
|
||||
*, html {
|
||||
scroll-behavior: smooth !important;
|
||||
}
|
||||
|
||||
/*
|
||||
Fixes anchor overlapping with header.
|
||||
See: https://stackoverflow.com/questions/4086107/fixed-page-header-overlaps-in-page-anchors
|
||||
*/
|
||||
:target::before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 2em; /* fixed header height*/
|
||||
margin: -2em 0 0; /* negative fixed header height */
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f9fafc;
|
||||
font-family: "Muli";
|
||||
|
@ -276,13 +296,13 @@ a.header-anchor {
|
|||
color: #1c2d41;
|
||||
}
|
||||
|
||||
a.header-anchor i {
|
||||
a.header-anchor i, a.header-anchor svg {
|
||||
font-size: 10pt;
|
||||
color: #3c4858;
|
||||
display: none;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
a.header-anchor:hover i {
|
||||
a.header-anchor:hover i, a.header-anchor:hover svg {
|
||||
display: inline-block;
|
||||
}
|
||||
a.header-anchor code {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
.accomplishments-section .card {
|
||||
background: #fff;
|
||||
border-top: 2px solid #248aaa;
|
||||
height: 100%;
|
||||
}
|
||||
.accomplishments-section .card .card-header {
|
||||
background: none;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#gallery i {
|
||||
#gallery .svg-inline--fa {
|
||||
color: #8392a5;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px;
|
||||
|
@ -56,21 +56,21 @@
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
#gallery .achievement-entry:hover i {
|
||||
#gallery .achievement-entry:hover .svg-inline--fa {
|
||||
opacity: 1;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
#gallery .img-type-1 i {
|
||||
#gallery .img-type-1 .svg-inline--fa {
|
||||
margin-top: 135px;
|
||||
}
|
||||
#gallery .img-type-2 i {
|
||||
#gallery .img-type-2 .svg-inline--fa {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#gallery .achievement-details.img-type-1 i,
|
||||
.achievement-details.img-type-2 i {
|
||||
#gallery .achievement-details.img-type-1 .svg-inline--fa,
|
||||
.achievement-details.img-type-2 .svg-inline--fa {
|
||||
margin-top: 0px !important;
|
||||
transition: none !important;
|
||||
float: right;
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
left: 50%;
|
||||
}
|
||||
|
||||
.vertical-line-left-adjustment::after {
|
||||
.timeline .vertical-line:nth-child(even)::after {
|
||||
left: calc(50% - 3px) !important;
|
||||
}
|
||||
|
||||
|
@ -75,26 +75,26 @@
|
|||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
left: -50%;
|
||||
top: -50%;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
.timeline .row:nth-child(2n) div:nth-child(1) .corner {
|
||||
left: 50%;
|
||||
top: -50%;
|
||||
}
|
||||
|
||||
.bottom-left {
|
||||
.timeline .row:nth-child(2n) div:nth-child(3) .corner {
|
||||
left: -50%;
|
||||
top: calc(50% - 3px);
|
||||
}
|
||||
|
||||
.bottom-right {
|
||||
.timeline .row:nth-child(4n) div:nth-child(1) .corner {
|
||||
left: 50%;
|
||||
top: calc(50% - 3px);
|
||||
}
|
||||
|
||||
.timeline .row:nth-child(4n) div:nth-child(3) .corner {
|
||||
left: -50%;
|
||||
top: -50%;
|
||||
}
|
||||
|
||||
/* ============= Device specific fixes ======= */
|
||||
|
||||
/* Large screens such as TV */
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.recent-posts-section .card {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.recent-posts-section .card .card-footer span {
|
||||
font-size: 10pt;
|
||||
color: #6c757d !important;
|
||||
|
|
6
static/fontawesome/css/all.min.css
vendored
6
static/fontawesome/css/all.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue