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:
Aaron Qian
2023-01-05 10:42:54 -08:00
committed by GitHub
parent fe14b0fbf5
commit 02db3d3044
491 changed files with 4919 additions and 151344 deletions
+1
View File
@@ -0,0 +1 @@
node_modules
+12
View 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
View File
@@ -0,0 +1,5 @@
printWidth: 100
tabWidth: 2
semi: false
singleQuote: true
trailingComma: "all"
+1
View File
@@ -0,0 +1 @@
nodejs 18.12.1
+8
View 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
View 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
View File
@@ -0,0 +1,2 @@
export * from './device'
export * from './insertScript'
+14
View 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)
}
@@ -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
View 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
View 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))
})
@@ -0,0 +1,3 @@
if (process.env.FEATURE_FLOWCHART_MERMAID === '1') {
import('./mermaid')
}
@@ -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
View 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
View File
@@ -0,0 +1,3 @@
if (process.env.FEATURE_MATH_KATEX === '1') {
import('./katex')
}
+21
View 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 || {})
}
)
})
@@ -0,0 +1,4 @@
import hljs from 'highlight.js'
import * as params from '@params'
hljs.highlightAll(params.syntaxhighlight?.hljs)
@@ -0,0 +1,3 @@
if (process.env.FEATURE_SYNTAXHIGHLIGHT_HLJS === '1') {
import('./hljs')
}
+48
View 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()
}
})
}
})
@@ -0,0 +1,3 @@
if (process.env.FEATURE_VIDEOPLAYER_PLYR === '1') {
import('./plyr')
}
@@ -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
View 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
View File
@@ -0,0 +1,4 @@
import './note'
import './search'
import './single'
import './home'
+30
View 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
View 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
View 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
View File
@@ -0,0 +1,3 @@
export const process = {
env: {}
}
+220
View 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
View 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
View File
@@ -0,0 +1,7 @@
import './navbar'
import './sidebar'
import './education'
import './achievements'
import './projects'
import './publications'
+60
View 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
View 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
View 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
View 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)
})
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+1 -1
View File
@@ -43,7 +43,7 @@ Esto expone los valores en /index.json: por ejemplo, para agregar `categories`
\``` \```
### Editar las opciones de fuse.js para buscar ### Editar las opciones de fuse.js para buscar
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -41,7 +41,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+1 -1
View File
@@ -43,7 +43,7 @@ Esto expone los valores en /index.json: por ejemplo, para agregar `categories`
\``` \```
### Editar las opciones de fuse.js para buscar ### Editar las opciones de fuse.js para buscar
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
+2 -2
View File
@@ -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. 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 ## Initial setup
@@ -43,7 +43,7 @@ i.e. add `category`
\``` \```
### Edit fuse.js options to Search ### Edit fuse.js options to Search
`static/js/search.js` `assets/scripts/pages/search.js`
\``` \```
keys: [ keys: [
"title", "title",
-4
View File
@@ -55,7 +55,3 @@
</div> </div>
</section> </section>
{{ end }} {{ end }}
{{ define "scripts" }}
<script src="{{ "/js/list.js" | relURL }}"></script>
{{ end }}
-6
View File
@@ -66,9 +66,3 @@
</div> </div>
</section> </section>
{{ end }} {{ 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 }}
+4 -15
View File
@@ -54,7 +54,7 @@
<div class="title"> <div class="title">
<h1>{{ .Page.Title }}</h1> <h1>{{ .Page.Title }}</h1>
</div> </div>
{{ if site.Params.enableTags }} {{ if site.Params.features.tags.enable }}
<div class="taxonomy-terms"> <div class="taxonomy-terms">
<ul style="padding-left: 0;"> <ul style="padding-left: 0;">
{{ range .Params.tags }} {{ range .Params.tags }}
@@ -158,7 +158,7 @@
<!----- Add comment support -----> <!----- Add comment support ----->
{{ if site.Params.features.comment.enable }} {{ if site.Params.features.comment.enable }}
{{ partial "comments.html" site.Params.features.comment }} {{ partial "comments.html" site.Params.features.comment.services }}
{{ end }} {{ end }}
<!-- Keep backward compatibility with old config.yaml --> <!-- Keep backward compatibility with old config.yaml -->
@@ -179,7 +179,7 @@
{{ define "toc" }} {{ define "toc" }}
<section class="toc-section" id="toc-section"> <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"> <div class="toc-holder">
<h5 class="text-center pl-3">{{ i18n "toc_heading" }}</h5> <h5 class="text-center pl-3">{{ i18n "toc_heading" }}</h5>
<hr> <hr>
@@ -192,20 +192,9 @@
{{ end }} {{ end }}
{{ define "scripts" }} {{ 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 ----------------> <!-------------- Enable Math support for this page ---------------->
{{ if .Params.math }} {{ if site.Params.features.math.enable }}
{{ partial "math.html" . }} {{ partial "math.html" . }}
{{ end }} {{ end }}
<!-------------- Enable mermaid support for this page ---------------->
{{ if .Params.mermaid }}
{{ partial "mermaid.html" . }}
{{ end }}
{{ end }} {{ end }}
-4
View File
@@ -56,7 +56,3 @@
</div> </div>
</section> </section>
{{ end }} {{ end }}
{{ define "scripts" }}
<script src="{{ "/js/list.js" | relURL }}"></script>
{{ end }}
-6
View File
@@ -75,12 +75,6 @@
<!--- ADD COMMON SCRIPTS ---------------> <!--- ADD COMMON SCRIPTS --------------->
{{ partial "scripts.html" . }} {{ 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 --------> <!------ ADD SUPPORT LINKS -------->
{{- partial "misc/support.html" . -}} {{- partial "misc/support.html" . -}}
+1 -7
View File
@@ -57,13 +57,7 @@
{{ end }} {{ end }}
{{ define "scripts" }} {{ define "scripts" }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script> {{ if site.Params.features.math.enable }}
<script src="{{ "/js/imagesloaded.pkgd.min.js" | relURL }}"></script>
<script src="{{ "/js/note.js" | relURL }}"></script>
<script>
hljs.initHighlightingOnLoad();
</script>
{{ if .Params.math }}
{{ partial "math.html" . }} {{ partial "math.html" . }}
{{ end }} {{ end }}
{{ end }} {{ end }}
+1 -7
View File
@@ -47,13 +47,7 @@
{{ end }} {{ end }}
{{ define "scripts" }} {{ define "scripts" }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script> {{ if site.Params.features.math.enable }}
<script src="{{ "/js/imagesloaded.pkgd.min.js" | relURL }}"></script>
<script src="{{ "/js/note.js" | relURL }}"></script>
<script>
hljs.initHighlightingOnLoad();
</script>
{{ if .Params.math }}
{{ partial "math.html" . }} {{ partial "math.html" . }}
{{ end }} {{ end }}
{{ end }} {{ end }}
+3 -1
View File
@@ -1,6 +1,7 @@
<!-- Add Analytics if enabled in configuration --> <!-- Add Analytics if enabled in configuration -->
{{ with site.Params.features.analytics }} {{ with site.Params.features.analytics }}
{{ if .enabled }} {{ if .enabled }}
{{ with .services }}
<!-- Google Analytics --> <!-- Google Analytics -->
{{ with .google }} {{ with .google }}
{{ $privacyConfig:= dict (slice "Site" "Config" "Privacy" "GoogleAnalytics") $.Site.Config.Privacy.GoogleAnalytics }} {{ $privacyConfig:= dict (slice "Site" "Config" "Privacy" "GoogleAnalytics") $.Site.Config.Privacy.GoogleAnalytics }}
@@ -21,7 +22,7 @@
<script <script
data-goatcounter="https://{{ .code }}.goatcounter.com/count" data-goatcounter="https://{{ .code }}.goatcounter.com/count"
async async
src="/js/goat-counter.js" src="//gc.zgo.at/count.js"
></script> ></script>
{{ end }} {{ end }}
@@ -43,6 +44,7 @@
</script> </script>
{{ end }} {{ end }}
{{ end }} {{ end }}
{{ end }}
{{ end }} {{ end }}
<!-- Keep backwards compatibility and consistency with HUGO defaults --> <!-- Keep backwards compatibility and consistency with HUGO defaults -->
+2 -1
View File
@@ -42,8 +42,9 @@
{{ end }} {{ end }}
<div class="project-btn-holder"> <div class="project-btn-holder">
{{ if .repo }} {{ if .repo }}
<!-- Place this tag where you want the button to render. -->
<a <a
class="github-button-inactive project-btn" class="github-button project-btn d-none"
href="{{ .repo }}" href="{{ .repo }}"
data-icon="octicon-standard" data-icon="octicon-standard"
data-show-count="true" data-show-count="true"
+2 -5
View File
@@ -7,17 +7,14 @@
<link rel="stylesheet" href="{{ "/css/layouts/main.css" | relURL }}"/> <link rel="stylesheet" href="{{ "/css/layouts/main.css" | relURL }}"/>
<link rel="stylesheet" href="{{ "/css/navigators/navbar.css" | relURL }}"/> <link rel="stylesheet" href="{{ "/css/navigators/navbar.css" | relURL }}"/>
<link rel="stylesheet" href="{{ "/css/plyr.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 }}"/> <link rel="stylesheet" href="{{ "/css/flag-icon.min.css" | relURL }}"/>
{{ end }} {{ end }}
<!--=================== fonts ==============================--> <!--=================== fonts ==============================-->
<link rel="stylesheet" href="{{ "/google-fonts/Mulish/mulish.css" | relURL }}"/> <link rel="stylesheet" href="{{ "/google-fonts/Mulish/mulish.css" | relURL }}"/>
<!--=================== icons ==============================-->
<link rel="stylesheet" href="{{ "/fontawesome/css/all.min.css" | relURL }}"/>
<!--=================== dark mode ==========================--> <!--=================== dark mode ==========================-->
{{ if site.Params.darkMode.enable }} {{ if site.Params.features.darkMode.enable }}
<link rel="stylesheet" href="{{ "/css/colortheme/colortheme.css" | relURL }}"/> <link rel="stylesheet" href="{{ "/css/colortheme/colortheme.css" | relURL }}"/>
{{ end }} {{ end }}
+1 -1
View File
@@ -14,7 +14,7 @@
{{/* if the user specify a country code for a language via "params.flagOverwrites" field, then use it. */}} {{/* 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 }} {{ if eq $languageCode .languageCode }}
{{ $countryCode = .countryCode }} {{ $countryCode = .countryCode }}
{{ end }} {{ end }}
@@ -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"
}}
@@ -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>
-14
View File
@@ -1,15 +1 @@
<link rel="stylesheet" href="{{ "/katex/katex.min.css" | relURL }}"> <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>
-6
View File
@@ -1,6 +0,0 @@
<script src="{{ "/js/mermaid-8.14.0.min.js" | relURL }}"></script>
<script>
mermaid.initialize({
startOnLoad:true
});
</script>
+3 -1
View File
@@ -1,5 +1,6 @@
{{ with site.Params.features.support }} {{ with site.Params.features.support }}
{{ if .enabled }} {{ if .enable }}
{{ with .services }}
<!-- Enable Ko-Fi floating button --> <!-- Enable Ko-Fi floating button -->
{{ with .kofi }} {{ with .kofi }}
<script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script> <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> <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 }}
{{ end }}
{{ end }} {{ end }}
@@ -5,7 +5,7 @@
<div class="dropdown languageSelector"> <div class="dropdown languageSelector">
<a class="btn dropdown-toggle" href="#" id="languageSelector" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
@@ -14,7 +14,7 @@
<div class="dropdown-menu" aria-labelledby="languageSelector"> <div class="dropdown-menu" aria-labelledby="languageSelector">
{{ range .Translations }} {{ range .Translations }}
<a class="dropdown-item nav-link languages-item" href="{{ path.Join "/" (cond (eq .Language.Lang "en") "" .Language.Lang) $pageURL }}"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
@@ -5,7 +5,7 @@
<li class="nav-item dropdown"> <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"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
@@ -14,7 +14,7 @@
<div class="dropdown-menu" aria-labelledby="languageSelector"> <div class="dropdown-menu" aria-labelledby="languageSelector">
{{ range .Translations }} {{ range .Translations }}
<a class="dropdown-item nav-link languages-item" href="{{ path.Join "/" (cond (eq .Language.Lang "en") "" .Language.Lang) $pageURL }}"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
@@ -1,6 +1,6 @@
<li class="nav-item dropdown"> <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"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
@@ -9,7 +9,7 @@
<div class="dropdown-menu" aria-labelledby="languageSelector"> <div class="dropdown-menu" aria-labelledby="languageSelector">
{{ range site.Home.AllTranslations }} {{ range site.Home.AllTranslations }}
<a class="dropdown-item nav-link languages-item" href="{{ .RelPermalink }}"> <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" . }} {{ $countryCode := partial "helpers/country-code.html" . }}
<span class="flag-icon flag-icon-{{$countryCode}}"></span> <span class="flag-icon flag-icon-{{$countryCode}}"></span>
{{ end }} {{ end }}
+3 -3
View File
@@ -29,7 +29,7 @@
<nav class="navbar navbar-expand-xl top-navbar final-navbar shadow"> <nav class="navbar navbar-expand-xl top-navbar final-navbar shadow">
<div class="container"> <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> <span class="navbar-toggler-icon"></span>
</button> </button>
<a class="navbar-brand" href="{{ site.BaseURL | relLangURL }}"> <a class="navbar-brand" href="{{ site.BaseURL | relLangURL }}">
@@ -38,7 +38,7 @@
{{ end }} {{ end }}
{{- site.Title -}} {{- site.Title -}}
</a> </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> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -47,7 +47,7 @@
{{ if .IsTranslated }} {{ if .IsTranslated }}
{{ partial "navigators/lang-selector-2.html" . }} {{ partial "navigators/lang-selector-2.html" . }}
{{ end }} {{ end }}
{{ if site.Params.darkMode.enable }} {{ if site.Params.features.darkMode.enable }}
{{ partial "navigators/theme-selector.html" . }} {{ partial "navigators/theme-selector.html" . }}
{{ end }} {{ end }}
</ul> </ul>
+1 -1
View File
@@ -124,7 +124,7 @@
{{ if .IsTranslated }} {{ if .IsTranslated }}
{{ partial "navigators/lang-selector.html" . }} {{ partial "navigators/lang-selector.html" . }}
{{ end }} {{ end }}
{{ if site.Params.darkMode.enable }} {{ if site.Params.features.darkMode.enable }}
{{ partial "navigators/theme-selector.html" . }} {{ partial "navigators/theme-selector.html" . }}
{{ end }} {{ end }}
</ul> </ul>
@@ -1,19 +1,16 @@
<li class="nav-item dropdown"> <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" <a class="nav-link dropdown-toggle" href="#" id="themeSelector" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 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"> <img id="navbar-theme-icon-svg" src="{{ "/icons/moon-svgrepo-com.svg" }}" width=20 alt="Dark Theme">
</a> </a>
<div class="dropdown-menu dropdown-menu-icons-only" aria-labelledby="themeSelector"> <div id="themeMenu" class="dropdown-menu dropdown-menu-icons-only" aria-labelledby="themeSelector">
<a class="dropdown-item nav-link" href="#" onclick="enableLightTheme()"> <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"> <img class="menu-icon-center" src="{{ "/icons/sun-svgrepo-com.svg" }}" width=20 alt="Light Theme">
</a> </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"> <img class="menu-icon-center" src="{{ "/icons/moon-svgrepo-com.svg" }}" width=20 alt="Dark Theme">
</a> </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"> <img class="menu-icon-center" src="{{ "/icons/computer-svgrepo-com.svg" }}" width=20 alt="System Theme">
</a> </a>
</div> </div>
+1 -14
View File
@@ -1,14 +1 @@
<script type="text/javascript" src="{{ "/js/jquery-3.4.1.min.js" | relURL }}"></script> {{ partial "helpers/script-bundle.html" }}
<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 }}
@@ -14,7 +14,8 @@
class="achievement-entry text-center" class="achievement-entry text-center"
style="background-image: url('{{ $achievementImageSm }}');" 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> <h4 class="title" id="achievement-title">{{ .title }}</h4>
<div class="caption hidden col-lg-6 text-left" id="caption"> <div class="caption hidden col-lg-6 text-left" id="caption">
<h4>{{ .title }}</h4> <h4>{{ .title }}</h4>
+2 -2
View File
@@ -90,9 +90,9 @@
{{ end }} {{ end }}
{{ if gt (len .takenCourses.courses) $collapseAfter }} {{ 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 }}" <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 }}" <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 }} {{ end }}
</div> </div>
{{ end }} {{ end }}
+2 -2
View File
@@ -90,9 +90,9 @@
{{ end }} {{ end }}
{{ if gt (len .takenCourses.courses ) $collapseAfter }} {{ 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 }}" <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 }}" <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 }} {{ end }}
</div> </div>
{{ end }} {{ end }}
+26 -179
View File
@@ -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 <div
class="pdf-viewer"
This shortcut was originally taken from: https://github.com/anvithks/hugo-embed-pdf-shortcode, data-url="{{ $url }}"
where it is available under the MIT License, where it was originally created by https://github.com/anvithks data-hide-paginator="{{ $hidePaginator }}"
data-hide-loader="{{ $hideLoader }}"
Since the project seems discontinued, it has been re-created here data-page-num="{{ $pageNum }}"
--> >
<div class="paginator">
<!-- Load the PDF-JS from the local folder (should be updated over time) --> <button class="prev">Previous</button>
<script src= '/js/pdf-js/build/pdf.js'></script> <button class="next">Next</button>
<span>Page: <span class="page-num"></span> / <span class="page-count"></span></span>
<!-- Set the navigation menu --> </div>
<div id="paginator">
<button id="prev">Previous</button> <div class="embed-pdf-container">
<button id="next">Next</button> <div class="loading-wrapper">
&nbsp; &nbsp; <div class="loading"></div>
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span> </div>
</div> <canvas class="pdf-canvas"></canvas>
<!-- And the canvas where the PDF will load -->
<div id="embed-pdf-container">
<div id="loadingWrapper">
<div id="loading"></div>
</div> </div>
<canvas id="the-canvas"></canvas>
</div> </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 --> <!-- Finally, make the canvas more beautiful -->
<style> <style>
#the-canvas { .pdf-viewer canvas {
border: 1px solid black; border: 1px solid black;
direction: ltr; direction: ltr;
width: 100%; width: 100%;
@@ -187,13 +34,13 @@ pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
display: none; display: none;
} }
#paginator { .pdf-viewer .paginator {
display: none; display: none;
text-align: center; text-align: center;
margin-bottom: 10px; margin-bottom: 10px;
} }
#loadingWrapper { .pdf-viewer .loading-wrapper {
display: none; display: none;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -201,7 +48,7 @@ pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
height: 350px; height: 350px;
} }
#loading { .pdf-viewer .loading {
display: inline-block; display: inline-block;
width: 50px; width: 50px;
height: 50px; height: 50px;
-4
View File
@@ -56,7 +56,3 @@
</div> </div>
</section> </section>
{{ end }} {{ end }}
{{ define "scripts" }}
<script src="{{ "/js/list.js" | relURL }}"></script>
{{ end }}
+3424 -12
View File
File diff suppressed because it is too large Load Diff
+19
View 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"
}
}
+9
View File
@@ -4,6 +4,8 @@
"description": "A [Hugo](https://gohugo.io/) theme for a personal portfolio with minimalist design and responsiveness.", "description": "A [Hugo](https://gohugo.io/) theme for a personal portfolio with minimalist design and responsiveness.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"autoprefixer": "postcss static/css/*/*.css --use autoprefixer -r --no-map" "autoprefixer": "postcss static/css/*/*.css --use autoprefixer -r --no-map"
}, },
"repository": { "repository": {
@@ -18,6 +20,13 @@
"homepage": "https://github.com/hugo-toha/toha#readme", "homepage": "https://github.com/hugo-toha/toha#readme",
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.13", "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": "^8.4.20",
"postcss-cli": "^8.3.1" "postcss-cli": "^8.3.1"
} }
+22 -2
View File
@@ -13,6 +13,26 @@ Green: #2DCA73
Yellow: #FFC212 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 { body {
background-color: #f9fafc; background-color: #f9fafc;
font-family: "Muli"; font-family: "Muli";
@@ -276,13 +296,13 @@ a.header-anchor {
color: #1c2d41; color: #1c2d41;
} }
a.header-anchor i { a.header-anchor i, a.header-anchor svg {
font-size: 10pt; font-size: 10pt;
color: #3c4858; color: #3c4858;
display: none; display: none;
margin-left: 0.5rem; margin-left: 0.5rem;
} }
a.header-anchor:hover i { a.header-anchor:hover i, a.header-anchor:hover svg {
display: inline-block; display: inline-block;
} }
a.header-anchor code { a.header-anchor code {
+1
View File
@@ -7,6 +7,7 @@
.accomplishments-section .card { .accomplishments-section .card {
background: #fff; background: #fff;
border-top: 2px solid #248aaa; border-top: 2px solid #248aaa;
height: 100%;
} }
.accomplishments-section .card .card-header { .accomplishments-section .card .card-header {
background: none; background: none;
+6 -6
View File
@@ -48,7 +48,7 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
#gallery i { #gallery .svg-inline--fa {
color: #8392a5; color: #8392a5;
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
padding: 10px; padding: 10px;
@@ -56,21 +56,21 @@
opacity: 0; opacity: 0;
} }
#gallery .achievement-entry:hover i { #gallery .achievement-entry:hover .svg-inline--fa {
opacity: 1; opacity: 1;
font-size: 1rem; font-size: 1rem;
transition: all 0.3s ease-out; transition: all 0.3s ease-out;
} }
#gallery .img-type-1 i { #gallery .img-type-1 .svg-inline--fa {
margin-top: 135px; margin-top: 135px;
} }
#gallery .img-type-2 i { #gallery .img-type-2 .svg-inline--fa {
margin-top: 50px; margin-top: 50px;
} }
#gallery .achievement-details.img-type-1 i, #gallery .achievement-details.img-type-1 .svg-inline--fa,
.achievement-details.img-type-2 i { .achievement-details.img-type-2 .svg-inline--fa {
margin-top: 0px !important; margin-top: 0px !important;
transition: none !important; transition: none !important;
float: right; float: right;
+9 -9
View File
@@ -47,7 +47,7 @@
left: 50%; left: 50%;
} }
.vertical-line-left-adjustment::after { .timeline .vertical-line:nth-child(even)::after {
left: calc(50% - 3px) !important; left: calc(50% - 3px) !important;
} }
@@ -75,26 +75,26 @@
border-radius: 15px; border-radius: 15px;
} }
.top-left { .timeline .row:nth-child(2n) div:nth-child(1) .corner {
left: -50%;
top: -50%;
}
.top-right {
left: 50%; left: 50%;
top: -50%; top: -50%;
} }
.bottom-left { .timeline .row:nth-child(2n) div:nth-child(3) .corner {
left: -50%; left: -50%;
top: calc(50% - 3px); top: calc(50% - 3px);
} }
.bottom-right { .timeline .row:nth-child(4n) div:nth-child(1) .corner {
left: 50%; left: 50%;
top: calc(50% - 3px); top: calc(50% - 3px);
} }
.timeline .row:nth-child(4n) div:nth-child(3) .corner {
left: -50%;
top: -50%;
}
/* ============= Device specific fixes ======= */ /* ============= Device specific fixes ======= */
/* Large screens such as TV */ /* Large screens such as TV */
+5
View File
@@ -8,6 +8,11 @@
display: block; display: block;
} }
.recent-posts-section .card {
height: 100%;
min-height: 100%;
}
.recent-posts-section .card .card-footer span { .recent-posts-section .card .card-footer span {
font-size: 10pt; font-size: 10pt;
color: #6c757d !important; color: #6c757d !important;
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