Improved build dependency resolution

This commit is contained in:
squidfunk
2019-09-28 19:55:08 +02:00
parent 260effaddb
commit cb1acef1df
29 changed files with 6 additions and 2737 deletions

View File

@@ -1,43 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Event from "./Material/Event"
import Header from "./Material/Header"
import Nav from "./Material/Nav"
import Search from "./Material/Search"
import Sidebar from "./Material/Sidebar"
import Source from "./Material/Source"
import Tabs from "./Material/Tabs"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Event,
Header,
Nav,
Search,
Sidebar,
Source,
Tabs
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Listener from "./Event/Listener"
import MatchMedia from "./Event/MatchMedia"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Listener,
MatchMedia
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Listener {
/**
* Generic event listener
*
* @constructor
*
* @property {(Array<EventTarget>)} els_ - Event targets
* @property {Object} handler_- Event handlers
* @property {Array<string>} events_ - Event names
* @property {Function} update_ - Update handler
*
* @param {?(string|EventTarget|NodeList<EventTarget>)} els -
* Selector or Event targets
* @param {(string|Array<string>)} events - Event names
* @param {(Object|Function)} handler - Handler to be invoked
*/
constructor(els, events, handler) {
this.els_ = Array.prototype.slice.call(
(typeof els === "string")
? document.querySelectorAll(els)
: [].concat(els))
/* Set handler as function or directly as object */
this.handler_ = typeof handler === "function"
? { update: handler }
: handler
/* Initialize event names and update handler */
this.events_ = [].concat(events)
this.update_ = ev => this.handler_.update(ev)
}
/**
* Register listener for all relevant events
*/
listen() {
this.els_.forEach(el => {
this.events_.forEach(event => {
el.addEventListener(event, this.update_, false)
})
})
/* Execute setup handler, if implemented */
if (typeof this.handler_.setup === "function")
this.handler_.setup()
}
/**
* Unregister listener for all relevant events
*/
unlisten() {
this.els_.forEach(el => {
this.events_.forEach(event => {
el.removeEventListener(event, this.update_)
})
})
/* Execute reset handler, if implemented */
if (typeof this.handler_.reset === "function")
this.handler_.reset()
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Listener from "./Listener" // eslint-disable-line no-unused-vars
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class MatchMedia {
/**
* Media query listener
*
* This class listens for state changes of media queries and automatically
* switches the given listeners on or off.
*
* @constructor
*
* @property {Function} handler_ - Media query event handler
*
* @param {string} query - Media query to test for
* @param {Listener} listener - Event listener
*/
constructor(query, listener) {
this.handler_ = mq => {
if (mq.matches)
listener.listen()
else
listener.unlisten()
}
/* Initialize media query listener */
const media = window.matchMedia(query)
media.addListener(this.handler_)
/* Always check at initialization */
this.handler_(media)
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Shadow from "./Header/Shadow"
import Title from "./Header/Title"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Shadow,
Title
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Shadow {
/**
* Show or hide header shadow depending on page y-offset
*
* @constructor
*
* @property {HTMLElement} el_ - Content container
* @property {HTMLElement} header_ - Header
* @property {number} height_ - Offset height of previous nodes
* @property {boolean} active_ - Header shadow state
*
* @param {(string|HTMLElement)} el - Selector or HTML element
* @param {(string|HTMLElement)} header - Selector or HTML element
*/
constructor(el, header) {
let ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement) ||
!(ref.parentNode instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref.parentNode
/* Retrieve header */
ref = (typeof header === "string")
? document.querySelector(header)
: header
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.header_ = ref
/* Initialize height and state */
this.height_ = 0
this.active_ = false
}
/**
* Calculate total height of previous nodes
*/
setup() {
let current = this.el_
while ((current = current.previousElementSibling)) {
if (!(current instanceof HTMLElement))
throw new ReferenceError
this.height_ += current.offsetHeight
}
this.update()
}
/**
* Update shadow state
*
* @param {Event} ev - Event
*/
update(ev) {
if (ev && (ev.type === "resize" || ev.type === "orientationchange")) {
this.height_ = 0
this.setup()
} else {
const active = window.pageYOffset >= this.height_
if (active !== this.active_)
this.header_.dataset.mdState = (this.active_ = active) ? "shadow" : ""
}
}
/**
* Reset shadow state
*/
reset() {
this.header_.dataset.mdState = ""
this.height_ = 0
this.active_ = false
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Title {
/**
* Swap header title topics when header is scrolled past
*
* @constructor
*
* @property {HTMLElement} el_ - Element
* @property {HTMLElement} header_ - Header
* @property {boolean} active_ - Title state
*
* @param {(string|HTMLElement)} el - Selector or HTML element
* @param {(string|HTMLHeadingElement)} header - Selector or HTML element
*/
constructor(el, header) {
let ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
/* Retrieve header */
ref = (typeof header === "string")
? document.querySelector(header)
: header
if (!(ref instanceof HTMLHeadingElement))
throw new ReferenceError
this.header_ = ref
/* Initialize state */
this.active_ = false
}
/**
* Setup title state
*/
setup() {
Array.prototype.forEach.call(this.el_.children, node => { // TODO: use childNodes here for IE?
node.style.width = `${this.el_.offsetWidth - 20}px`
})
}
/**
* Update title state
*
* @param {Event} ev - Event
*/
update(ev) {
const active = window.pageYOffset >= this.header_.offsetTop
if (active !== this.active_)
this.el_.dataset.mdState = (this.active_ = active) ? "active" : ""
/* Hack: induce ellipsis on topics */
if (ev.type === "resize" || ev.type === "orientationchange") {
Array.prototype.forEach.call(this.el_.children, node => {
node.style.width = `${this.el_.offsetWidth - 20}px`
})
}
}
/**
* Reset title state
*/
reset() {
this.el_.dataset.mdState = ""
this.el_.style.width = ""
this.active_ = false
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Blur from "./Nav/Blur"
import Collapse from "./Nav/Collapse"
import Scrolling from "./Nav/Scrolling"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Blur,
Collapse,
Scrolling
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Blur {
/**
* Blur links within the table of contents above current page y-offset
*
* @constructor
*
* @property {NodeList<HTMLElement>} els_ - Table of contents links
* @property {Array<HTMLElement>} anchors_ - Referenced anchor nodes
* @property {number} index_ - Current link index
* @property {number} offset_ - Current page y-offset
* @property {boolean} dir_ - Scroll direction change
*
* @param {(string|NodeList<HTMLElement>)} els - Selector or HTML elements
*/
constructor(els) {
this.els_ = (typeof els === "string")
? document.querySelectorAll(els)
: els
/* Initialize index and page y-offset */
this.index_ = 0
this.offset_ = window.pageYOffset
/* Necessary state to correctly reset the index */
this.dir_ = false
/* Index anchor node offsets for fast lookup */
this.anchors_ = [].reduce.call(this.els_, (anchors, el) => {
const hash = decodeURIComponent(el.hash)
return anchors.concat(
document.getElementById(hash.substring(1)) || [])
}, [])
}
/**
* Initialize blur states
*/
setup() {
this.update()
}
/**
* Update blur states
*
* Deduct the static offset of the header (56px) and sidebar offset (24px),
* see _permalinks.scss for more information.
*/
update() {
const offset = window.pageYOffset
const dir = this.offset_ - offset < 0
/* Hack: reset index if direction changed to catch very fast scrolling,
because otherwise we would have to register a timer and that sucks */
if (this.dir_ !== dir)
this.index_ = dir
? this.index_ = 0
: this.index_ = this.els_.length - 1
/* Exit when there are no anchors */
if (this.anchors_.length === 0)
return
/* Scroll direction is down */
if (this.offset_ <= offset) {
for (let i = this.index_ + 1; i < this.els_.length; i++) {
if (this.anchors_[i].offsetTop - (56 + 24) <= offset) {
if (i > 0)
this.els_[i - 1].dataset.mdState = "blur"
this.index_ = i
} else {
break
}
}
/* Scroll direction is up */
} else {
for (let i = this.index_; i >= 0; i--) {
if (this.anchors_[i].offsetTop - (56 + 24) > offset) {
if (i > 0)
this.els_[i - 1].dataset.mdState = ""
} else {
this.index_ = i
break
}
}
}
/* Remember current offset and direction for next iteration */
this.offset_ = offset
this.dir_ = dir
}
/**
* Reset blur states
*/
reset() {
Array.prototype.forEach.call(this.els_, el => {
el.dataset.mdState = ""
})
/* Reset index and page y-offset */
this.index_ = 0
this.offset_ = window.pageYOffset
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Collapse {
/**
* Expand or collapse navigation on toggle
*
* @constructor
*
* @property {HTMLElement} el_ - Navigation list
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
}
/**
* Initialize overflow and display for accessibility
*/
setup() {
const current = this.el_.getBoundingClientRect().height
/* Hidden links should not be focusable, so hide them when the navigation
is collapsed and set overflow so the outline is not cut off */
this.el_.style.display = current ? "block" : "none"
this.el_.style.overflow = current ? "visible" : "hidden"
}
/**
* Animate expand and collapse smoothly
*
* Internet Explorer 11 is very slow at recognizing changes on the dataset
* which results in the menu not expanding or collapsing properly. THerefore,
* for reasons of compatibility, the attribute accessors are used.
*/
update() {
const current = this.el_.getBoundingClientRect().height
/* Reset overflow to CSS defaults */
this.el_.style.display = "block"
this.el_.style.overflow = ""
/* Hack: read value directly from input field */
const expanded = this.el_
.previousElementSibling
.previousElementSibling
.checked
/* Expanded, so collapse */
if (expanded) {
this.el_.style.maxHeight = `${current}px`
requestAnimationFrame(() => {
this.el_.setAttribute("data-md-state", "animate")
this.el_.style.maxHeight = "0px"
})
/* Collapsed, so expand */
} else {
this.el_.setAttribute("data-md-state", "expand")
this.el_.style.maxHeight = ""
/* Read height and unset pseudo-toggled state */
const height = this.el_.getBoundingClientRect().height
this.el_.removeAttribute("data-md-state")
/* Set initial state and animate */
this.el_.style.maxHeight = "0px"
requestAnimationFrame(() => {
this.el_.setAttribute("data-md-state", "animate")
this.el_.style.maxHeight = `${height}px`
})
}
/* Remove state on end of transition */
const end = ev => {
const target = ev.target
if (!(target instanceof HTMLElement))
throw new ReferenceError
/* Reset height and state */
target.removeAttribute("data-md-state")
target.style.maxHeight = ""
/* Hidden links should not be focusable, so hide them when the navigation
is collapsed and set overflow so the outline is not cut off */
target.style.display = expanded ? "none" : "block"
target.style.overflow = expanded ? "hidden" : "visible"
/* Only fire once, so directly remove event listener */
target.removeEventListener("transitionend", end)
}
this.el_.addEventListener("transitionend", end, false)
}
/**
* Reset height and pseudo-toggled state
*/
reset() {
this.el_.dataset.mdState = ""
this.el_.style.maxHeight = ""
this.el_.style.display = ""
this.el_.style.overflow = ""
}
}

View File

@@ -1,176 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Scrolling {
/**
* Set overflow scrolling on the current active pane (for iOS)
*
* @constructor
*
* @property {HTMLElement} el_ - Primary navigation
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
}
/**
* Setup panes
*/
setup() {
/* Initially set overflow scrolling on main pane */
const main = this.el_.children[this.el_.children.length - 1]
main.style.webkitOverflowScrolling = "touch"
/* Find all toggles and check which one is active */
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
Array.prototype.forEach.call(toggles, toggle => {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
/* Find corresponding navigational pane */
let pane = toggle.nextElementSibling
if (!(pane instanceof HTMLElement))
throw new ReferenceError
while (pane.tagName !== "NAV" && pane.nextElementSibling)
pane = pane.nextElementSibling
/* Check references */
if (!(toggle.parentNode instanceof HTMLElement) ||
!(toggle.parentNode.parentNode instanceof HTMLElement))
throw new ReferenceError
/* Find current and parent list elements */
const parent = toggle.parentNode.parentNode
const target = pane.children[pane.children.length - 1]
/* Always reset all lists when transitioning */
parent.style.webkitOverflowScrolling = ""
target.style.webkitOverflowScrolling = "touch"
}
})
}
/**
* Update active panes
*
* @param {Event} ev - Change event
*/
update(ev) {
const target = ev.target
if (!(target instanceof HTMLElement))
throw new ReferenceError
/* Find corresponding navigational pane */
let pane = target.nextElementSibling
if (!(pane instanceof HTMLElement))
throw new ReferenceError
while (pane.tagName !== "NAV" && pane.nextElementSibling)
pane = pane.nextElementSibling
/* Check references */
if (!(target.parentNode instanceof HTMLElement) ||
!(target.parentNode.parentNode instanceof HTMLElement))
throw new ReferenceError
/* Find parent and active panes */
const parent = target.parentNode.parentNode
const active = pane.children[pane.children.length - 1]
/* Always reset all lists when transitioning */
parent.style.webkitOverflowScrolling = ""
active.style.webkitOverflowScrolling = ""
/* Set overflow scrolling on parent pane */
if (!target.checked) {
const end = () => {
if (pane instanceof HTMLElement) {
parent.style.webkitOverflowScrolling = "touch"
pane.removeEventListener("transitionend", end)
}
}
pane.addEventListener("transitionend", end, false)
}
/* Set overflow scrolling on active pane */
if (target.checked) {
const end = () => {
if (pane instanceof HTMLElement) {
active.style.webkitOverflowScrolling = "touch"
pane.removeEventListener("transitionend", end)
}
}
pane.addEventListener("transitionend", end, false)
}
}
/**
* Reset panes
*/
reset() {
/* Reset overflow scrolling on main pane */
this.el_.children[1].style.webkitOverflowScrolling = ""
/* Find all toggles and check which one is active */
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
Array.prototype.forEach.call(toggles, toggle => {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
/* Find corresponding navigational pane */
let pane = toggle.nextElementSibling
if (!(pane instanceof HTMLElement))
throw new ReferenceError
while (pane.tagName !== "NAV" && pane.nextElementSibling)
pane = pane.nextElementSibling
/* Check references */
if (!(toggle.parentNode instanceof HTMLElement) ||
!(toggle.parentNode.parentNode instanceof HTMLElement))
throw new ReferenceError
/* Find parent and active panes */
const parent = toggle.parentNode.parentNode
const active = pane.children[pane.children.length - 1]
/* Always reset all lists when transitioning */
parent.style.webkitOverflowScrolling = ""
active.style.webkitOverflowScrolling = ""
}
})
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Lock from "./Search/Lock"
import Result from "./Search/Result"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Lock,
Result
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Lock {
/**
* Lock body for full-screen search modal
*
* @constructor
*
* @property {HTMLInputElement} el_ - Lock toggle
* @property {HTMLElement} lock_ - Element to lock (document body)
* @property {number} offset_ - Current page y-offset
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLInputElement))
throw new ReferenceError
this.el_ = ref
/* Retrieve element to lock (= body) */
if (!document.body)
throw new ReferenceError
this.lock_ = document.body
}
/**
* Setup locked state
*/
setup() {
this.update()
}
/**
* Update locked state
*/
update() {
/* Entering search mode */
if (this.el_.checked) {
this.offset_ = window.pageYOffset
/* Scroll to top after transition, to omit flickering */
setTimeout(() => {
window.scrollTo(0, 0)
/* Lock body after finishing transition */
if (this.el_.checked) {
this.lock_.dataset.mdState = "lock"
}
}, 400)
/* Exiting search mode */
} else {
this.lock_.dataset.mdState = ""
/* Scroll to former position, but wait for 100ms to prevent flashes on
iOS. A short timeout seems to do the trick */
setTimeout(() => {
if (typeof this.offset_ !== "undefined")
window.scrollTo(0, this.offset_)
}, 100)
}
}
/**
* Reset locked state and page y-offset
*/
reset() {
if (this.lock_.dataset.mdState === "lock")
window.scrollTo(0, this.offset_)
this.lock_.dataset.mdState = ""
}
}

View File

@@ -1,414 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import lunr from "expose-loader?lunr!lunr"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Escape a regular expression string
*
* Taken from the package `escape-string-regexp`
*
* @param regex - Regular expresison string
*
* @return
*/
const escapeRegex = regex => {
return regex.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&');
};
/**
* Escape HTML strings
*
* Documentation may contain code JavaScript code snippets which would get
* executed when inserted into the DOM as plain HTML.
*
* See https://github.com/squidfunk/mkdocs-material/issues/906
*
* @param {string} html - HTML string
*
* @return {string} Escaped HTML string
*/
const escapeHTML = html => {
var text = document.createTextNode(html);
var p = document.createElement('p');
p.appendChild(text);
return p.innerHTML;
}
/**
* Truncate a string after the given number of character
*
* This is not a reasonable approach, since the summaries kind of suck. It
* would be better to create something more intelligent, highlighting the
* search occurrences and making a better summary out of it.
*
* @param {string} string - String to be truncated
* @param {number} n - Number of characters
* @return {string} Truncated string
*/
const truncate = (string, n) => {
let i = n
if (string.length > i) {
while (string[i] !== " " && --i > 0);
return `${string.substring(0, i)}...`
}
return string
}
/**
* Return the meta tag value for the given key
*
* @param {string} key - Meta name
*
* @return {string} Meta content value
*/
const translate = key => {
const meta = document.getElementsByName(`lang:${key}`)[0]
if (!(meta instanceof HTMLMetaElement))
throw new ReferenceError
return meta.content
}
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Result {
/**
* Perform search and update results on keyboard events
*
* @constructor
*
* @property {HTMLElement} el_ - Search result container
* @property {(Array<Object>|Function)} data_ - Raw document data
* @property {Object} docs_ - Indexed documents
* @property {HTMLElement} meta_ - Search meta information
* @property {HTMLElement} list_ - Search result list
* @property {Array<string>} lang_ - Search languages
* @property {Object} message_ - Search result messages
* @property {Object} index_ - Search index
* @property {Array<Function>} stack_ - Search result stack
* @property {string} value_ - Last input value
*
* @param {(string|HTMLElement)} el - Selector or HTML element
* @param {(Array<Object>|Function)} data - Function providing data or array
*/
constructor(el, data) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
/* Retrieve metadata and list element */
const [meta, list] = Array.prototype.slice.call(this.el_.children)
/* Set data, metadata and list elements */
this.data_ = data
this.meta_ = meta
this.list_ = list
/* Load messages for metadata display */
this.message_ = {
placeholder: this.meta_.textContent,
none: translate("search.result.none"),
one: translate("search.result.one"),
other: translate("search.result.other")
}
/* Override tokenizer separator, if given */
const tokenizer = translate("search.tokenizer")
if (tokenizer.length)
lunr.tokenizer.separator = tokenizer
/* Load search languages */
this.lang_ = translate("search.language").split(",")
.filter(Boolean)
.map(lang => lang.trim())
}
/**
* Update search results
*
* @param {Event} ev - Input or focus event
*/
update(ev) {
/* Initialize index, if this has not be done yet */
if (ev.type === "focus" && !this.index_) {
/* Initialize index */
const init = data => {
/* Preprocess and index sections and documents */
this.docs_ = data.reduce((docs, doc) => {
const [path, hash] = doc.location.split("#")
/* Escape HTML */
doc.text = escapeHTML(doc.text)
/* Associate section with parent document */
if (hash) {
doc.parent = docs.get(path)
/* Override page title with document title if first section */
if (doc.parent && !doc.parent.done) {
doc.parent.title = doc.title
doc.parent.text = doc.text
doc.parent.done = true
}
}
/* Some cleanup on the text */
doc.text = doc.text
.replace(/\n/g, " ") /* Remove newlines */
.replace(/\s+/g, " ") /* Compact whitespace */
.replace(/\s+([,.:;!?])/g, /* Correct punctuation */
(_, char) => char)
/* Index sections and documents, but skip top-level headline */
if (!doc.parent || doc.parent.title !== doc.title)
docs.set(doc.location, doc)
return docs
}, new Map)
/* eslint-disable no-invalid-this */
const docs = this.docs_,
lang = this.lang_
/* Create stack and index */
this.stack_ = []
this.index_ = lunr(function() {
const filters = {
"search.pipeline.trimmer": lunr.trimmer,
"search.pipeline.stopwords": lunr.stopWordFilter
}
/* Disable stop words filter and trimmer, if desired */
const pipeline = Object.keys(filters).reduce((result, name) => {
if (!translate(name).match(/^false$/i))
result.push(filters[name])
return result
}, [])
/* Remove stemmer, as it cripples search experience */
this.pipeline.reset()
if (pipeline)
this.pipeline.add(...pipeline)
/* Set up alternate search languages */
if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) {
this.use(lunr[lang[0]])
} else if (lang.length > 1) {
this.use(lunr.multiLanguage(...lang))
}
/* Index fields */
this.field("title", { boost: 10 })
this.field("text")
this.ref("location")
/* Index documents */
docs.forEach(doc => this.add(doc))
})
/* Register event handler for lazy rendering */
const container = this.el_.parentNode
if (!(container instanceof HTMLElement))
throw new ReferenceError
container.addEventListener("scroll", () => {
while (this.stack_.length && container.scrollTop +
container.offsetHeight >= container.scrollHeight - 16)
this.stack_.splice(0, 10).forEach(render => render())
})
}
/* eslint-enable no-invalid-this */
/* Initialize index after short timeout to account for transition */
setTimeout(() => {
return typeof this.data_ === "function"
? this.data_().then(init)
: init(this.data_)
}, 250)
/* Execute search on new input event */
} else if (ev.type === "focus" || ev.type === "keyup") {
const target = ev.target
if (!(target instanceof HTMLInputElement))
throw new ReferenceError
/* Abort early, if index is not build or input hasn't changed */
if (!this.index_ || target.value === this.value_)
return
/* Clear current list */
while (this.list_.firstChild)
this.list_.removeChild(this.list_.firstChild)
/* Abort early, if search input is empty */
this.value_ = target.value
if (this.value_.length === 0) {
this.meta_.textContent = this.message_.placeholder
return
}
/* Perform search on index and group sections by document */
const result = this.index_
/* Append trailing wildcard to all terms for prefix querying */
.query(query => {
this.value_.toLowerCase().split(" ")
.filter(Boolean)
.forEach(term => {
query.term(term, { wildcard: lunr.Query.wildcard.TRAILING })
})
})
/* Process query results */
.reduce((items, item) => {
const doc = this.docs_.get(item.ref)
if (doc.parent) {
const ref = doc.parent.location
items.set(ref, (items.get(ref) || []).concat(item))
} else {
const ref = doc.location
items.set(ref, (items.get(ref) || []))
}
return items
}, new Map)
/* Assemble regular expressions for matching */
const query = escapeRegex(this.value_.trim()).replace(
new RegExp(lunr.tokenizer.separator, "img"), "|")
const match =
new RegExp(`(^|${lunr.tokenizer.separator})(${query})`, "img")
const highlight = (_, separator, token) =>
`${separator}<em>${token}</em>`
/* Reset stack and render results */
this.stack_ = []
result.forEach((items, ref) => {
const doc = this.docs_.get(ref)
/* Render article */
const article = (
<li class="md-search-result__item">
<a href={doc.location} title={doc.title}
class="md-search-result__link" tabindex="-1">
<article class="md-search-result__article
md-search-result__article--document">
<h1 class="md-search-result__title">
{{ __html: doc.title.replace(match, highlight) }}
</h1>
{doc.text.length ?
<p class="md-search-result__teaser">
{{ __html: doc.text.replace(match, highlight) }}
</p> : {}}
</article>
</a>
</li>
)
/* Render sections for article */
const sections = items.map(item => {
return () => {
const section = this.docs_.get(item.ref)
article.appendChild(
<a href={section.location} title={section.title}
class="md-search-result__link" data-md-rel="anchor"
tabindex="-1">
<article class="md-search-result__article">
<h1 class="md-search-result__title">
{{ __html: section.title.replace(match, highlight) }}
</h1>
{section.text.length ?
<p class="md-search-result__teaser">
{{ __html: truncate(
section.text.replace(match, highlight), 400)
}}
</p> : {}}
</article>
</a>
)
}
})
/* Push articles and section renderers onto stack */
this.stack_.push(() => this.list_.appendChild(article), ...sections)
})
/* Gradually add results as long as the height of the container grows */
const container = this.el_.parentNode
if (!(container instanceof HTMLElement))
throw new ReferenceError
while (this.stack_.length &&
container.offsetHeight >= container.scrollHeight - 16)
(this.stack_.shift())()
/* Bind click handlers for anchors */
const anchors = this.list_.querySelectorAll("[data-md-rel=anchor]")
Array.prototype.forEach.call(anchors, anchor => {
["click", "keydown"].forEach(action => {
anchor.addEventListener(action, ev2 => {
if (action === "keydown" && ev2.keyCode !== 13)
return
/* Close search */
const toggle = document.querySelector("[data-md-toggle=search]")
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
}
/* Hack: prevent default, as the navigation needs to be delayed due
to the search body lock on mobile */
ev2.preventDefault()
setTimeout(() => {
document.location.href = anchor.href
}, 100)
})
})
})
/* Update search metadata */
switch (result.size) {
case 0:
this.meta_.textContent = this.message_.none
break
case 1:
this.meta_.textContent = this.message_.one
break
default:
this.meta_.textContent =
this.message_.other.replace("#", result.size)
}
}
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Position from "./Sidebar/Position"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Position
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Position {
/**
* Set sidebars to locked state and limit height to parent node
*
* @constructor
*
* @property {HTMLElement} el_ - Sidebar
* @property {HTMLElement} parent_ - Sidebar container
* @property {HTMLElement} header_ - Header
* @property {number} height_ - Current sidebar height
* @property {number} offset_ - Current page y-offset
* @property {boolean} pad_ - Pad when header is fixed
*
* @param {(string|HTMLElement)} el - Selector or HTML element
* @param {(string|HTMLElement)} header - Selector or HTML element
*/
constructor(el, header) {
let ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement) ||
!(ref.parentNode instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
this.parent_ = ref.parentNode
/* Retrieve header */
ref = (typeof header === "string")
? document.querySelector(header)
: header
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.header_ = ref
/* Initialize current height and test whether header is fixed */
this.height_ = 0
this.pad_ = window.getComputedStyle(this.header_).position === "fixed"
}
/**
* Initialize sidebar state
*/
setup() {
const top = Array.prototype.reduce.call(
this.parent_.children, (offset, child) => {
return Math.max(offset, child.offsetTop)
}, 0)
/* Set lock offset for element with largest top offset */
this.offset_ = top - (this.pad_ ? this.header_.offsetHeight : 0)
this.update()
}
/**
* Update locked state and height
*
* The inner height of the window (= the visible area) is the maximum
* possible height for the stretching sidebar. This height must be deducted
* by the height of the fixed header (56px). Depending on the page y-offset,
* the top offset of the sidebar must be taken into account, as well as the
* case where the window is scrolled beyond the sidebar container.
*
* @param {Event?} ev - Event
*/
update(ev) {
const offset = window.pageYOffset
const visible = window.innerHeight
/* Update offset, in case window is resized */
if (ev && ev.type === "resize")
this.setup()
/* Set bounds of sidebar container - must be calculated on every run, as
the height of the content might change due to loading images etc. */
const bounds = {
top: this.pad_ ? this.header_.offsetHeight : 0,
bottom: this.parent_.offsetTop + this.parent_.offsetHeight
}
/* Calculate new offset and height */
const height = visible - bounds.top
- Math.max(0, this.offset_ - offset)
- Math.max(0, offset + visible - bounds.bottom)
/* 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 >= this.offset_) {
if (this.el_.dataset.mdState !== "lock")
this.el_.dataset.mdState = "lock"
/* Sidebar should be unlocked, if locked */
} else if (this.el_.dataset.mdState === "lock") {
this.el_.dataset.mdState = ""
}
}
/**
* Reset locked state and height
*/
reset() {
this.el_.dataset.mdState = ""
this.el_.style.height = ""
this.height_ = 0
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Adapter from "./Source/Adapter"
import Repository from "./Source/Repository"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Adapter,
Repository
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import GitHub from "./Adapter/GitHub"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
GitHub
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Cookies from "js-cookie"
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Abstract {
/**
* Retrieve repository information
*
* @constructor
*
* @property {HTMLAnchorElement} el_ - Link to repository
* @property {string} base_ - API base URL
* @property {number} salt_ - Unique identifier
*
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLAnchorElement))
throw new ReferenceError
this.el_ = ref
/* Retrieve base URL */
this.base_ = this.el_.href
this.salt_ = this.hash_(this.base_)
}
/**
* Retrieve data from Cookie or fetch from respective API
*
* @return {Promise<Array<string>>} Promise that returns an array of facts
*/
fetch() {
return new Promise(resolve => {
const cached = Cookies.getJSON(`${this.salt_}.cache-source`)
if (typeof cached !== "undefined") {
resolve(cached)
/* If the data is not cached in a cookie, invoke fetch and set
a cookie that automatically expires in 15 minutes */
} else {
this.fetch_().then(data => {
Cookies.set(`${this.salt_}.cache-source`, data, { expires: 1 / 96 })
resolve(data)
})
}
})
}
/**
* Abstract private function that fetches relevant repository information
*
* @abstract
*/
fetch_() {
throw new Error("fetch_(): Not implemented")
}
/**
* Format a number with suffix
*
* @param {number} number - Number to format
* @return {string} Formatted number
*/
format_(number) {
if (number > 10000)
return `${(number / 1000).toFixed(0)}k`
else if (number > 1000)
return `${(number / 1000).toFixed(1)}k`
return `${number}`
}
/**
* Simple hash function
*
* Taken from http://stackoverflow.com/a/7616484/1065584
*
* @param {string} str - Input string
* @return {number} Hashed string
*/
hash_(str) {
let hash = 0
if (str.length === 0) return hash
for (let i = 0, len = str.length; i < len; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i)
hash |= 0 // Convert to 32bit integer
}
return hash
}
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Abstract from "./Abstract"
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class GitHub extends Abstract {
/**
* Retrieve repository information from GitHub
*
* @constructor
*
* @property {string} name_ - Name of the repository
*
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
*/
constructor(el) {
super(el)
/* Extract user (and repository name) from URL, as we have to query for all
repositories, to omit 404 errors for private repositories */
const matches = /^.+github\.com\/([^/]+)\/?([^/]+)?.*$/
.exec(this.base_)
if (matches && matches.length === 3) {
const [, user, name] = matches
/* Initialize base URL and repository name */
this.base_ = `https://api.github.com/users/${user}/repos`
this.name_ = name
}
}
/**
* Fetch relevant repository information from GitHub
*
* @return {Promise<Array<string>>} Promise returning an array of facts
*/
fetch_() {
const paginate = (page = 0) => {
return fetch(`${this.base_}?per_page=30&page=${page}`)
.then(response => response.json())
.then(data => {
if (!(data instanceof Array))
throw new TypeError
/* Display number of stars and forks, if repository is given */
if (this.name_) {
const repo = data.find(item => item.name === this.name_)
if (!repo && data.length === 30)
return paginate(page + 1)
/* If we found a repo, extract the facts */
return repo
? [
`${this.format_(repo.stargazers_count)} Stars`,
`${this.format_(repo.forks_count)} Forks`
]
: []
/* Display number of repositories, otherwise */
} else {
return [
`${data.length} Repositories`
]
}
})
}
/* Paginate through repos */
return paginate()
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Repository {
/**
* Render repository information
*
* @constructor
*
* @property {HTMLElement} el_ - Repository information
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof HTMLElement))
throw new ReferenceError
this.el_ = ref
}
/**
* Initialize the repository
*
* @param {Array<string>} facts - Facts to be rendered
*/
initialize(facts) {
if (facts.length && this.el_.children.length)
this.el_.children[this.el_.children.length - 1].appendChild(
<ul class="md-source__facts">
{facts.map(fact => <li class="md-source__fact">{fact}</li>)}
</ul>
)
/* Finish rendering with animation */
this.el_.dataset.mdState = "done"
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
import Toggle from "./Tabs/Toggle"
/* ----------------------------------------------------------------------------
* Module
* ------------------------------------------------------------------------- */
export default {
Toggle
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (c) 2016-2019 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.
*/
/* ----------------------------------------------------------------------------
* Class
* ------------------------------------------------------------------------- */
export default class Toggle {
/**
* Toggle tabs visibility depending on page y-offset
*
* @constructor
*
* @property {HTMLElement} el_ - Content container
* @property {number} height_ - Header height
* @property {number} offset_ - Toggle page-y offset
* @property {boolean} active_ - Tabs visibility
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
constructor(el) {
const ref = (typeof el === "string")
? document.querySelector(el)
: el
if (!(ref instanceof Node))
throw new ReferenceError
this.el_ = ref
/* Obtain header */
const header = document.querySelector("[data-md-component=header]")
/* Initialize height and state */
this.height_ = header.offsetHeight
this.active_ = false
}
/**
* Update visibility
*/
update() {
const active = window.pageYOffset >=
this.el_.children[0].offsetTop + (5 - this.height_)
if (active !== this.active_)
this.el_.dataset.mdState = (this.active_ = active) ? "hidden" : ""
}
/**
* Reset visibility
*/
reset() {
this.el_.dataset.mdState = ""
this.active_ = false
}
}