Implemented basic search modal functionality

This commit is contained in:
squidfunk
2016-10-23 10:21:37 +02:00
parent 46b7e0f5fe
commit 95435e7035
56 changed files with 7298 additions and 790 deletions

View File

@@ -71,19 +71,19 @@ document.addEventListener("DOMContentLoaded", () => {
const query = document.getElementById("query")
query.addEventListener("focus", () => {
document.querySelector(".md-search").classList.add("md-js__search--locked")
document.querySelector(".md-search").dataset.mdLocked = ""
})
/* Intercept click on search mode toggle */
let offset = 0
const toggle = document.getElementById("search")
toggle.addEventListener("click", ev => {
const list = document.body.classList
toggle.addEventListener("click", () => { // TODO: click may be the wrong event...
const list = document.body // classList md bla
const lock = !matchMedia("only screen and (min-width: 960px)").matches
/* Exiting search mode */
if (list.contains("md-js__body--locked")) {
list.remove("md-js__body--locked")
if (list.dataset.mdLocked) {
delete list.dataset.mdLocked
/* Scroll to former position, but wait for 100ms to prevent flashes
on iOS. A short timeout seems to do the trick */
@@ -109,17 +109,28 @@ document.addEventListener("DOMContentLoaded", () => {
/* This additional check is necessary to handle fast subsequent clicks
on the toggle and the timeout to lock the body must be cancelled */
if (ev.target.checked) {
if (lock)
list.add("md-js__body--locked")
setTimeout(() => {
document.getElementById("md-search").focus()
}, 200)
}
// if (ev.target.checked) {
if (lock)
list.dataset.mdLocked = ""
setTimeout(() => {
document.getElementById("query").focus()
}, 200)
// }
}, 450)
}
})
// TODO: only do this on MOBILE and TABLET
const toggleSearchClose = document.querySelector(".md-search__icon")
toggleSearchClose.setAttribute("for", "search") // TODO: override query with search, when on mobile!!!
// toggleSearchClose.addEventListener("click", ev => {
// ev.preventDefault()
// // ev.target
//
// const search = document.getElementById("search")
// search.checked = false
// })
// var toc = new Sidebar('.md-sidebar--secondary');
// toc.listen();
@@ -179,6 +190,7 @@ document.addEventListener("DOMContentLoaded", () => {
return response.json()
})
.then(data => {
// console.log(data)
const stars = data.stargazers_count
const forks = data.forks_count
// store in session!!!
@@ -223,7 +235,7 @@ document.addEventListener("DOMContentLoaded", () => {
// console.log(data)
/* Create index */
const index = lunr(() => {
const index = lunr(function() {
/* eslint-disable no-invalid-this, lines-around-comment */
this.field("title", { boost: 10 })
this.field("text")
@@ -290,10 +302,21 @@ document.addEventListener("DOMContentLoaded", () => {
title.innerHTML = article.title
link.appendChild(title)
/* Truncate a string after the given number of characters */
const truncate = function(string, n) {
let i = n
if (string.length > i) {
while (string[i] !== " " && --i > 0);
return `${string.substring(0, i)}…`
}
return string
}
/* Create text element */
const text = document.createElement("p")
text.classList.add("md-search-result__description")
text.innerHTML = article.text // .truncate(140);
text.innerHTML = truncate(article.text) // .truncate(140);
text.innerHTML = truncate(article.text, 140) // .truncate(140);
link.appendChild(text)
container.appendChild(li)
@@ -324,4 +347,13 @@ document.addEventListener("DOMContentLoaded", () => {
})
// }, 1000);
fetch(
"https://api.github.com/repos/squidfunk/mkdocs-material/releases/latest")
.then(response => {
return response.json()
})
// .then(data => {
// // console.log(data)
// })
})

View File

@@ -24,8 +24,7 @@
* Definition
* ------------------------------------------------------------------------- */
export default
class Abstract {
export default class Abstract {
/**
* Dispatch update on next repaint
@@ -33,8 +32,8 @@ class Abstract {
* @constructor
*/
constructor() {
// if (new.target === this.constructor)
// throw new TypeError("Cannot construct abstract instance")
if (this === Abstract)
throw new TypeError("Cannot construct abstract instance")
/* Dispatch update on next repaint */
this.handler_ = ev => {
@@ -48,7 +47,6 @@ class Abstract {
* Update state
*
* @abstract
* @return {void}
*/
update() {
throw new Error("update(): not implemented")
@@ -58,7 +56,6 @@ class Abstract {
* Reset state
*
* @abstract
* @return {void}
*/
reset() {
throw new Error("reset(): not implemented")
@@ -66,8 +63,6 @@ class Abstract {
/**
* Register listener for all relevant events
*
* @return {void}
*/
listen() {
["scroll", "resize", "orientationchange"].forEach(name => {
@@ -80,8 +75,6 @@ class Abstract {
/**
* Unregister listener for all relevant events
*
* @return {void}
*/
unlisten() {
["scroll", "resize", "orientationchange"].forEach(name => {

View File

@@ -26,14 +26,13 @@ import Abstract from "./Abstract"
* Definition
* ------------------------------------------------------------------------- */
export default
class Marker extends Abstract {
export default class Marker extends Abstract {
/**
* Mark anchors within the table of contents above current page y-offset
*
* @constructor
* @param {(string|HTMLCollection)} els - Selector or HTML elements
* @param {(string|NodeList<HTMLElement>)} els - Selector or HTML elements
*/
constructor(els) {
super()
@@ -57,16 +56,16 @@ class Marker extends Abstract {
* Update anchor states
*
* @param {Event} ev - Event (omitted)
* @return {void}
*/
update() {
const offset = window.pageYOffset
/* Scroll direction is down */
if (this.offset_ <= window.pageYOffset) {
if (this.offset_ <= offset) {
for (let i = this.index_ + 1; i < this.els_.length; i++) {
if (this.anchors_[i].offsetTop <= window.pageYOffset) {
if (this.anchors_[i].offsetTop <= offset) {
if (i > 0)
this.els_[i - 1].dataset.mdMarked = true
this.els_[i - 1].dataset.mdMarked = ""
this.index_ = i
} else {
break
@@ -76,7 +75,7 @@ class Marker extends Abstract {
/* Scroll direction is up */
} else {
for (let i = this.index_; i >= 0; i--) {
if (this.anchors_[i].offsetTop > window.pageYOffset) {
if (this.anchors_[i].offsetTop > offset) {
if (i > 0)
delete this.els_[i - 1].dataset.mdMarked
} else {
@@ -87,13 +86,11 @@ class Marker extends Abstract {
}
/* Remember current offset for next iteration */
this.offset_ = window.pageYOffset
this.offset_ = offset
}
/**
* Reset anchor states
*
* @return {void}
*/
reset() {
[].forEach.call(this.els_, el => {

View File

@@ -26,8 +26,7 @@ import Abstract from "./Abstract"
* Definition
* ------------------------------------------------------------------------- */
export default
class Position extends Abstract {
export default class Position extends Abstract {
/**
* Set sidebars to locked state and limit height to parent node
@@ -44,8 +43,15 @@ class Position extends Abstract {
: el
/* Index inner and outer container */
this.inner_ = this.el_.parentNode
this.outer_ = this.el_.parentNode.parentNode
const inner = this.el_.parentNode
const outer = this.el_.parentNode.parentNode
/* Get top and bottom bounds */
this.offset_ = outer.offsetTop
this.bounds_ = {
top: inner.offsetTop,
bottom: inner.offsetTop + inner.offsetHeight
}
/* Initialize current height */
this.height_ = 0
@@ -55,45 +61,34 @@ class Position extends Abstract {
* Update locked state and height
*
* @param {Event} ev - Event (omitted)
* @return {void}
*/
update() {
const bounds = this.inner_.getBoundingClientRect() // TODO: thresholds can be calculated as a function
const parent = this.outer_.offsetTop
/* Determine top and bottom offsets */
const top = bounds.top + window.pageYOffset,
bottom = bounds.bottom + window.pageYOffset
/* Determine current y-offset at top and bottom of window */
const upper = window.pageYOffset,
lower = window.pageYOffset + window.innerHeight
const scroll = window.pageYOffset
const expand = window.innerHeight
/* Calculate new bounds */
const offset = top - upper
const height = window.innerHeight
- Math.max(lower - bottom, 0)
- Math.max(offset, parent)
const offset = this.bounds_.top - scroll
const height = expand
- Math.max(0, scroll + expand - this.bounds_.bottom)
- Math.max(offset, this.offset_)
/* If height changed, update element */
if (height !== this.height_)
this.el_.style.height = `${this.height_ = height}px`
/* Sidebar should be locked, as we're below parent offset */
if (offset < parent) {
if (offset < this.offset_) {
if (!this.el_.dataset.mdLocked)
this.el_.dataset.mdLocked = true
this.el_.dataset.mdLocked = ""
/* Sidebar should be unlocked, if locked */
} else if (this.el_.dataset.mdLocked) {
} else if (typeof this.el_.dataset.mdLocked === "string") {
delete this.el_.dataset.mdLocked
}
}
/**
* Reset locked state and height
*
* @return {void}
*/
reset() {
delete this.el_.dataset.mdLocked

View File

@@ -21,71 +21,18 @@
*/
/* ----------------------------------------------------------------------------
* Navigation expander
* Definition
* ------------------------------------------------------------------------- */
class Expander {
export default
class Abstract {
/**
* Constructor
* Dispatch update on next repaint
*
* @constructor
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
this.el_ = (typeof el === "string")
? document.querySelector(el)
: el
/* Event listener */
this.handler_ = ev => {
this.update(ev)
}
}
/**
* Update state of expandable element
*
* @param {Event} ev - Event
* @return {void}
*/
update() {}
/**
* Reset state of expandable element
*
* @return {void}
*/
reset() {}
/**
* Register listener for all relevant events
*
* @return {void}
*/
listen() {
["click"].forEach(name => {
window.addEventListener(name, this.handler_, false)
})
}
/**
* Unregister listener for all relevant events
*
* @return {void}
*/
unlisten() {
["click"].forEach(name => {
window.removeEventListener(name, this.handler_, false)
})
/* Perform reset */
this.reset()
}
// constructor() {
//
// }
}
/* ----------------------------------------------------------------------------
* Exports
* ------------------------------------------------------------------------- */
export default Expander

View File

@@ -1,131 +0,0 @@
/*
* Copyright (c) 2016 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Sidebar scroll-spy
* ------------------------------------------------------------------------- */
export default
class ScrollSpy {
/**
* Constructor
*
* @constructor
* @param {(string|HTMLCollection)} els - Selector or HTML elements
*/
constructor(els) {
this.els_ = (typeof els === "string")
? document.querySelectorAll(els)
: els
/* Initialize index for currently active element */
this.index_ = 0
this.offset_ = window.pageYOffset
/* Index anchor nodes for fast lookup */
this.anchors_ = [].map.call(this.els_, el => {
return document.querySelector(el.hash)
})
/* Event listener */
this.handler_ = ev => {
this.update(ev)
}
}
/**
* Update state of sidebar
*
* @param {Event} ev - Event (omitted)
* @return {void}
*/
update() {
/* Scroll direction is down */
if (this.offset_ <= window.pageYOffset) {
for (let i = this.index_ + 1; i < this.els_.length; i++) {
if (this.anchors_[i].offsetTop <= window.pageYOffset) {
if (i > 0)
this.els_[i - 1].dataset.mdMarked = true
this.index_ = i
} else {
break
}
}
/* Scroll direction is up */
} else {
for (let i = this.index_; i >= 0; i--) {
if (this.anchors_[i].offsetTop > window.pageYOffset) {
if (i > 0)
delete this.els_[i - 1].dataset.mdMarked
} else {
this.index_ = i
break
}
}
}
/* Remember current offset for next cycle */
this.offset_ = window.pageYOffset
}
/**
* Reset state of sidebar
*
* @return {void}
*/
reset() {
[].forEach.call(this.els_, el => {
delete el.dataset.mdMarked
})
}
/**
* Register listener for all relevant events
*
* @return {void}
*/
listen() {
["scroll", "resize", "orientationchange"].forEach(name => {
window.addEventListener(name, this.handler_, false)
})
/* Initial update */
this.update()
}
/**
* Unregister listener for all relevant events
*
* @return {void}
*/
unlisten() {
["scroll", "resize", "orientationchange"].forEach(name => {
window.removeEventListener(name, this.handler_, false)
})
/* Perform reset */
this.reset()
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2016 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Search
* ------------------------------------------------------------------------- */
class Search {
/**
* Constructor
*
* @constructor
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
this.el_ = (typeof el === "string") ? document.querySelector(el) : el
/* Event listener */
this.handler_ = ev => {
this.update(ev)
}
}
/**
* Update state and height of sidebar
*
* @param {Event} ev - Event
* @return {void}
*/
update() {
}
/**
* Reset state and height of sidebar
*
* @return {void}
*/
reset() {
}
/**
* Register listener for all relevant events
*
* @return {void}
*/
listen() {
// ['scroll', 'resize', 'orientationchange'].forEach(name => {
// window.addEventListener(name, this.handler_, false);
// });
/* Initial update */
this.update()
}
/**
* Unregister listener for all relevant events
*
* @return {void}
*/
unlisten() {
// ['scroll', 'resize', 'orientationchange'].forEach(name => {
// window.removeEventListener(name, this.handler_, false);
// });
/* Perform reset */
this.reset()
}
}
/* ----------------------------------------------------------------------------
* Exports
* ------------------------------------------------------------------------- */
export default Search

View File

@@ -1,142 +0,0 @@
/*
* Copyright (c) 2016 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Sidebar sticky-scroll handler
* ------------------------------------------------------------------------- */
class Sidebar {
/**
* Constructor
*
* @constructor
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
this.el_ = (typeof el === "string")
? document.querySelector(el)
: el
/* Grab inner and outer container */
this.inner_ = this.el_.parentNode
this.outer_ = this.el_.parentNode.parentNode
/* Initialize parameters */
this.height_ = 0
this.locked_ = false
/* Event listener */
this.handler_ = ev => {
this.update(ev)
}
}
/**
* Update state and height of sidebar
*
* @param {Event} ev - Event
* @return {void}
*/
update() {
const bounds = this.inner_.getBoundingClientRect()
const parent = this.outer_.offsetTop
/* Determine top and bottom offsets */
const top = bounds.top + window.pageYOffset,
bottom = bounds.bottom + window.pageYOffset
/* Determine current y-offset at top and bottom of window */
const upper = window.pageYOffset,
lower = window.pageYOffset + window.innerHeight
/* Calculate new bounds */
const offset = top - upper
const height = window.innerHeight - Math.max(lower - bottom, 0)
- Math.max(offset, parent)
/* If height changed, update element */
if (height !== this.height_)
this.el_.style.height = `${this.height_ = height}px`
/* Sidebar should be locked, as we're below parent offset */
if (offset < parent) {
if (!this.locked_) {
this.el_.classList.add("md-js__sidebar--locked")
this.locked_ = true
}
/* Sidebar should be unlocked, if locked */
} else if (this.locked_) {
this.el_.classList.remove("md-js__sidebar--locked")
this.locked_ = false
}
}
/**
* Reset state and height of sidebar
*
* @return {void}
*/
reset() {
this.el_.classList.remove("md-js__sidebar--locked")
this.el_.style.height = ""
/* Reset parameters */
this.height_ = 0
this.locked_ = false
}
/**
* Register listener for all relevant events
*
* @return {void}
*/
listen() {
["scroll", "resize", "orientationchange"].forEach(name => {
window.addEventListener(name, this.handler_, false)
})
/* Initial update */
this.update()
}
/**
* Unregister listener for all relevant events
*
* @return {void}
*/
unlisten() {
["scroll", "resize", "orientationchange"].forEach(name => {
window.removeEventListener(name, this.handler_, false)
})
/* Perform reset */
this.reset()
}
}
/* ----------------------------------------------------------------------------
* Exports
* ------------------------------------------------------------------------- */
export default Sidebar