mirror of
https://github.com/davegallant/davegallant.github.io.git
synced 2025-08-13 11:50:19 +00:00
Switch themes to minimo
This commit is contained in:
33
themes/minimo/src/scripts/comments.js
Normal file
33
themes/minimo/src/scripts/comments.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const commentList = document.querySelector('.comment-list')
|
||||
const respondBlock = document.querySelector('#respond')
|
||||
const commentForm = respondBlock.querySelector('form')
|
||||
const cancelReplyLink = respondBlock.querySelector('#cancel-comment-reply-link')
|
||||
const parentIdInput = respondBlock.querySelector('[name="fields[parent_id]"]')
|
||||
|
||||
const moveRespondBlock = commentId => {
|
||||
if (!commentId) return
|
||||
|
||||
const comment = commentList.querySelector(`#comment-${commentId} article`)
|
||||
|
||||
parentIdInput.value = commentId
|
||||
comment.parentNode.insertBefore(respondBlock, comment.nextSibling)
|
||||
cancelReplyLink.style.display = ''
|
||||
|
||||
commentForm.querySelector('textarea').focus()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const initComments = () => {
|
||||
cancelReplyLink.style.display = 'none'
|
||||
|
||||
cancelReplyLink.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
|
||||
parentIdInput.value = ''
|
||||
commentList.parentNode.appendChild(respondBlock)
|
||||
cancelReplyLink.style.display = 'none'
|
||||
})
|
||||
|
||||
window.moveRespondBlock = moveRespondBlock
|
||||
}
|
36
themes/minimo/src/scripts/details-polyfill.js
Normal file
36
themes/minimo/src/scripts/details-polyfill.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const body = document.body
|
||||
|
||||
const detailsTagSupported = () => {
|
||||
let el = document.createElement('details')
|
||||
if (!('open' in el)) return false
|
||||
|
||||
el.innerHTML = '<summary>a</summary>b'
|
||||
body.appendChild(el)
|
||||
|
||||
let diff = el.offsetHeight
|
||||
el.open = true
|
||||
let result = diff != el.offsetHeight
|
||||
|
||||
body.removeChild(el)
|
||||
return result
|
||||
}
|
||||
|
||||
export const detailsPolyfill = detailsElements => {
|
||||
if (!detailsTagSupported()) {
|
||||
body.classList.add('no-details')
|
||||
|
||||
detailsElements.forEach(detailsElement => {
|
||||
let summaryElement = detailsElement.querySelector('summary')
|
||||
|
||||
summaryElement.addEventListener('click', () => {
|
||||
if (detailsElement.getAttribute('open')) {
|
||||
detailsElement.open = false
|
||||
detailsElement.removeAttribute('open')
|
||||
} else {
|
||||
detailsElement.open = true
|
||||
detailsElement.setAttribute('open', 'open')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
17
themes/minimo/src/scripts/helpers.js
Normal file
17
themes/minimo/src/scripts/helpers.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export const shuffle = array => {
|
||||
let shuffled = [...array],
|
||||
currIndex = array.length,
|
||||
tempValue,
|
||||
randIndex
|
||||
|
||||
while (currIndex) {
|
||||
randIndex = Math.floor(Math.random() * currIndex)
|
||||
currIndex--
|
||||
|
||||
tempValue = shuffled[currIndex]
|
||||
shuffled[currIndex] = shuffled[randIndex]
|
||||
shuffled[randIndex] = tempValue
|
||||
}
|
||||
|
||||
return shuffled
|
||||
}
|
50
themes/minimo/src/scripts/main.js
Normal file
50
themes/minimo/src/scripts/main.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import './webpack-public-path'
|
||||
|
||||
import '../stylesheets/style'
|
||||
|
||||
import docReady from 'es6-docready'
|
||||
|
||||
import { shuffle } from './helpers'
|
||||
|
||||
docReady(() => {
|
||||
const body = document.body
|
||||
|
||||
const taxonomyClouds = body.querySelectorAll(
|
||||
'.taxonomy-cloud:not(.no-shuffle)'
|
||||
)
|
||||
if (taxonomyClouds.length) {
|
||||
taxonomyClouds.forEach(taxonomyCloud => {
|
||||
let terms = taxonomyCloud.querySelectorAll('li')
|
||||
shuffle(terms).forEach(term => term.parentElement.appendChild(term))
|
||||
})
|
||||
}
|
||||
|
||||
const detailsElements = body.querySelectorAll('details')
|
||||
if (detailsElements.length) {
|
||||
import(/* webpackChunkName: "details-polyfill" */ './details-polyfill').then(
|
||||
({ detailsPolyfill }) => detailsPolyfill(detailsElements)
|
||||
)
|
||||
}
|
||||
|
||||
let hasEmoji = body.classList.contains('has-emoji')
|
||||
if (hasEmoji) {
|
||||
let entry = body.querySelector('.entry')
|
||||
import(/* webpackChunkName: "twemoji" */ 'twemoji').then(twemoji =>
|
||||
twemoji.parse(entry)
|
||||
)
|
||||
}
|
||||
|
||||
let hasSidebar = body.classList.contains('has-sidebar')
|
||||
if (hasSidebar) {
|
||||
import(/* webpackChunkName: "sidebar" */ './sidebar').then(
|
||||
({ initSidebar }) => initSidebar()
|
||||
)
|
||||
}
|
||||
|
||||
let hasComments = body.querySelector('#comment-form')
|
||||
if (hasComments) {
|
||||
import(/* webpackChunkName: "comments" */ './comments').then(
|
||||
({ initComments }) => initComments()
|
||||
)
|
||||
}
|
||||
})
|
47
themes/minimo/src/scripts/search/algolia.js
Normal file
47
themes/minimo/src/scripts/search/algolia.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
|
||||
import {
|
||||
appendResults,
|
||||
getUrlSearchParam,
|
||||
setSearchingIndicator
|
||||
} from './helpers'
|
||||
|
||||
const { appId, indexName, searchApiKey } = window.algolia
|
||||
|
||||
const client = algoliasearch(appId, searchApiKey)
|
||||
|
||||
const index = client.initIndex(
|
||||
`${indexName}${window.location.pathname.replace('/search/', '')}`
|
||||
)
|
||||
|
||||
const doSearch = (term, resultsBlock) => {
|
||||
setSearchingIndicator(resultsBlock)
|
||||
|
||||
if (!term) {
|
||||
appendResults([], resultsBlock)
|
||||
} else {
|
||||
index.search(
|
||||
term,
|
||||
{ attributesToRetrieve: ['title', 'href'], hitsPerPage: 10 },
|
||||
(err, content) => {
|
||||
if (err) console.error(err)
|
||||
else appendResults(content.hits, resultsBlock)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const searchForm = document.getElementById('search-form')
|
||||
const searchInputBox = document.getElementById('search-term')
|
||||
const resultsBlock = document.getElementById('search-results')
|
||||
|
||||
let term = getUrlSearchParam('q')
|
||||
searchInputBox.value = term
|
||||
searchInputBox.focus()
|
||||
doSearch(term, resultsBlock)
|
||||
|
||||
searchForm.addEventListener('submit', e => {
|
||||
e.preventDefault()
|
||||
|
||||
doSearch(searchInputBox.value, resultsBlock)
|
||||
})
|
54
themes/minimo/src/scripts/search/fuse.js
Normal file
54
themes/minimo/src/scripts/search/fuse.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
import {
|
||||
appendResults,
|
||||
getJSON,
|
||||
getUrlSearchParam,
|
||||
setSearchingIndicator
|
||||
} from './helpers'
|
||||
|
||||
const doSearch = (term, fuse, resultsBlock) => {
|
||||
setSearchingIndicator(resultsBlock)
|
||||
|
||||
let results = term
|
||||
? fuse
|
||||
.search(term)
|
||||
.map(result => ({ href: result.href, title: result.title }))
|
||||
: []
|
||||
|
||||
appendResults(results, resultsBlock)
|
||||
}
|
||||
|
||||
const options = {
|
||||
shouldSort: true,
|
||||
threshold: 0.5,
|
||||
location: 0,
|
||||
distance: 500,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [{ name: 'title', weight: 0.7 }, { name: 'content', weight: 0.3 }]
|
||||
}
|
||||
|
||||
const searchInputBox = document.getElementById('search-term')
|
||||
const resultsBlock = document.getElementById('search-results')
|
||||
|
||||
let term = getUrlSearchParam('q')
|
||||
searchInputBox.value = term
|
||||
searchInputBox.focus()
|
||||
|
||||
setSearchingIndicator(resultsBlock)
|
||||
|
||||
getJSON(`${window.location.pathname}index.json`, (err, list) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return
|
||||
}
|
||||
|
||||
let fuse = new Fuse(list, options)
|
||||
|
||||
doSearch(term, fuse, resultsBlock)
|
||||
|
||||
searchInputBox.addEventListener('input', e => {
|
||||
doSearch(e.target.value, fuse, resultsBlock)
|
||||
})
|
||||
})
|
52
themes/minimo/src/scripts/search/helpers.js
Normal file
52
themes/minimo/src/scripts/search/helpers.js
Normal file
@@ -0,0 +1,52 @@
|
||||
export const appendResults = (results, resultsBlock) => {
|
||||
if (results.length === 0) {
|
||||
resultsBlock.innerHTML = `<li class='results-empty'>
|
||||
<a href='#search-term'>${resultsBlock.dataset.resultsEmpty}</a>
|
||||
</li>`
|
||||
} else {
|
||||
resultsBlock.innerHTML = results.reduce((prevItem, { href, title }) => {
|
||||
return `${prevItem}<li><a href='${href}'>${title}</a></li>`
|
||||
}, '')
|
||||
}
|
||||
}
|
||||
|
||||
export const setSearchingIndicator = resultsBlock => {
|
||||
resultsBlock.innerHTML = `<li class='searching'>
|
||||
<a href='#search-results'>${resultsBlock.dataset.searching}…</a>
|
||||
</li>`
|
||||
}
|
||||
|
||||
export const getUrlSearchParam = name => {
|
||||
if ('URLSearchParams' in window) {
|
||||
let urlParams = new URLSearchParams(window.location.search)
|
||||
return urlParams.get(name)
|
||||
} else {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
|
||||
let regex = new RegExp('[\\?&]' + name + '=([^&#]*)')
|
||||
let results = regex.exec(location.search)
|
||||
return results === null
|
||||
? ''
|
||||
: decodeURIComponent(results[1].replace(/\+/g, ' '))
|
||||
}
|
||||
}
|
||||
|
||||
export const getJSON = (url, callback) => {
|
||||
let request = new XMLHttpRequest()
|
||||
|
||||
request.open('GET', url, true)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
let data = JSON.parse(request.responseText)
|
||||
callback(null, data)
|
||||
} else {
|
||||
callback(new Error(request.statusText))
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
callback(new Error(`Failed to get JSON! ${request.statusText}`))
|
||||
}
|
||||
|
||||
request.send()
|
||||
}
|
34
themes/minimo/src/scripts/search/lunr.js
Normal file
34
themes/minimo/src/scripts/search/lunr.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import lunr from 'lunr'
|
||||
|
||||
import {
|
||||
appendResults,
|
||||
getUrlSearchParam,
|
||||
setSearchingIndicator
|
||||
} from './helpers'
|
||||
|
||||
const doSearch = (term, idx, pageTitles, resultsBlock) => {
|
||||
setSearchingIndicator(resultsBlock)
|
||||
|
||||
let results = term
|
||||
? idx
|
||||
.search(term)
|
||||
.map(result => ({ href: result.ref, title: pageTitles[result.ref] }))
|
||||
: []
|
||||
|
||||
appendResults(results, resultsBlock)
|
||||
}
|
||||
|
||||
const idx = lunr.Index.load(window.lunr_idx)
|
||||
const pageTitles = window.page_titles
|
||||
|
||||
const searchInputBox = document.getElementById('search-term')
|
||||
const resultsBlock = document.getElementById('search-results')
|
||||
|
||||
let term = getUrlSearchParam('q')
|
||||
searchInputBox.value = term
|
||||
searchInputBox.focus()
|
||||
doSearch(term, idx, pageTitles, resultsBlock)
|
||||
|
||||
searchInputBox.addEventListener('input', e => {
|
||||
doSearch(e.target.value, idx, pageTitles, resultsBlock)
|
||||
})
|
127
themes/minimo/src/scripts/sidebar.js
Normal file
127
themes/minimo/src/scripts/sidebar.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const body = document.body
|
||||
|
||||
const sidebar = body.querySelector('#sidebar')
|
||||
const expandButton = body.querySelector('#sidebar-toggler')
|
||||
const overlay = body.querySelector('.sidebar-overlay')
|
||||
const sidebarMenu = body.querySelector('#sidebar-menu')
|
||||
|
||||
const collapseButton = expandButton.cloneNode(true)
|
||||
collapseButton.setAttribute('id', '#sidebar-collapse')
|
||||
|
||||
const setAriaExpanded = (items, value) => {
|
||||
items.forEach(item => item.setAttribute('aria-expanded', value))
|
||||
}
|
||||
|
||||
const hideSidebar = () => {
|
||||
sidebar.classList.remove('toggled')
|
||||
setAriaExpanded([sidebar, expandButton, collapseButton], false)
|
||||
}
|
||||
|
||||
const showSidebar = () => {
|
||||
sidebar.classList.add('toggled')
|
||||
setAriaExpanded([sidebar, expandButton, collapseButton], true)
|
||||
sidebar.focus()
|
||||
}
|
||||
|
||||
let windowWidth,
|
||||
windowHeight,
|
||||
bodyHeight,
|
||||
sidebarHeight,
|
||||
windowPos,
|
||||
lastWindowPos = 0,
|
||||
top = false,
|
||||
bottom = false,
|
||||
topOffset = 0,
|
||||
sidebarOffsetTop,
|
||||
resizeTimer
|
||||
|
||||
const resizeHandler = () => {
|
||||
windowWidth = window.innerWidth
|
||||
windowHeight = window.innerHeight
|
||||
}
|
||||
|
||||
const scrollHandler = () => {
|
||||
windowPos = window.scrollY
|
||||
bodyHeight = body.offsetHeight
|
||||
sidebarHeight = sidebar.offsetHeight
|
||||
sidebarOffsetTop = Math.round(windowPos + sidebar.getBoundingClientRect().top)
|
||||
|
||||
if (sidebarHeight > windowHeight) {
|
||||
if (windowPos > lastWindowPos) {
|
||||
if (top) {
|
||||
top = false
|
||||
topOffset = sidebarOffsetTop > 0 ? sidebarOffsetTop : 0
|
||||
sidebar.setAttribute('style', `top: ${topOffset}px;`)
|
||||
} else if (
|
||||
!bottom &&
|
||||
windowPos + windowHeight > sidebarHeight + sidebarOffsetTop &&
|
||||
sidebarHeight < bodyHeight
|
||||
) {
|
||||
bottom = true
|
||||
sidebar.setAttribute('style', 'position: fixed; bottom: 0;')
|
||||
}
|
||||
} else if (windowPos < lastWindowPos) {
|
||||
if (bottom) {
|
||||
bottom = false
|
||||
topOffset = sidebarOffsetTop > 0 ? sidebarOffsetTop : 0
|
||||
sidebar.setAttribute('style', `top: ${topOffset}px;`)
|
||||
} else if (!top && windowPos < sidebarOffsetTop) {
|
||||
top = true
|
||||
sidebar.setAttribute('style', 'position: fixed;')
|
||||
}
|
||||
} else {
|
||||
top = bottom = false
|
||||
topOffset = sidebarOffsetTop ? sidebarOffsetTop : 0
|
||||
sidebar.setAttribute('style', `top: ${topOffset}px;`)
|
||||
}
|
||||
} else if (!top) {
|
||||
top = true
|
||||
sidebar.setAttribute('style', 'position: fixed;')
|
||||
}
|
||||
|
||||
lastWindowPos = windowPos
|
||||
}
|
||||
|
||||
const resizeAndScrollHandler = () => {
|
||||
resizeHandler()
|
||||
scrollHandler()
|
||||
}
|
||||
|
||||
const initSidebarMenu = () => {
|
||||
let itemsWithSubmenu = sidebarMenu.querySelectorAll('.item.has-children')
|
||||
|
||||
itemsWithSubmenu.forEach(item => {
|
||||
let toggler = item.querySelector('button')
|
||||
let submenu = item.querySelector('.sub-menu')
|
||||
|
||||
setAriaExpanded([submenu, toggler], false)
|
||||
|
||||
toggler.addEventListener('click', () => {
|
||||
let toggled = item.classList.contains('toggled')
|
||||
|
||||
item.classList[toggled ? 'remove' : 'add']('toggled')
|
||||
setAriaExpanded([submenu, toggler], !toggled)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const initSidebar = () => {
|
||||
sidebar.setAttribute('tabindex', '-1')
|
||||
sidebar.insertBefore(collapseButton, sidebar.children[1])
|
||||
|
||||
setAriaExpanded([sidebar, expandButton, collapseButton], false)
|
||||
|
||||
expandButton.addEventListener('click', showSidebar)
|
||||
collapseButton.addEventListener('click', hideSidebar)
|
||||
overlay.addEventListener('click', hideSidebar)
|
||||
|
||||
window.addEventListener('scroll', scrollHandler)
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(resizeAndScrollHandler, 500)
|
||||
})
|
||||
|
||||
resizeAndScrollHandler()
|
||||
|
||||
if (sidebarMenu) initSidebarMenu()
|
||||
}
|
1
themes/minimo/src/scripts/webpack-public-path.js
Normal file
1
themes/minimo/src/scripts/webpack-public-path.js
Normal file
@@ -0,0 +1 @@
|
||||
__webpack_public_path__ = window.__assets_js_src
|
Reference in New Issue
Block a user