mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Improved build dependency resolution
This commit is contained in:
parent
260effaddb
commit
cb1acef1df
9
Makefile
9
Makefile
@ -117,15 +117,16 @@ material/assets/javascripts/lunr/%.js: ${LUNR_SOURCE}/%.js | $$(@D)/.
|
|||||||
@ echo "+ $@"
|
@ echo "+ $@"
|
||||||
@ cp $< $@
|
@ cp $< $@
|
||||||
|
|
||||||
# All scripts
|
# Scripts
|
||||||
material/assets/javascripts: $$@/lunr
|
material/assets/javascripts: $$@/lunr
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Stylesheets
|
# Stylesheets
|
||||||
STYLESHEETS = $(subst src,material,$(wildcard src/assets/stylesheets/a*.scss))
|
STYLESHEETS = $(subst src,material,$(wildcard src/assets/stylesheets/a*.scss))
|
||||||
|
STYLESHEETS_PARTIALS = $(shell find src -name "_*.scss")
|
||||||
material/assets/stylesheets: $(patsubst %.scss,%.css,${STYLESHEETS})
|
material/assets/stylesheets: $(patsubst %.scss,%.css,${STYLESHEETS})
|
||||||
material/assets/stylesheets/%.css: src/assets/stylesheets/%.scss | $$(@D)/.
|
material/%.css: src/%.scss ${STYLESHEETS_PARTIALS} | $$(@D)/.
|
||||||
@ echo "+ $@"
|
@ echo "+ $@"
|
||||||
@ ${BIN}/node-sass -q \
|
@ ${BIN}/node-sass -q \
|
||||||
--source-map $@.map \
|
--source-map $@.map \
|
||||||
@ -141,13 +142,13 @@ material/assets/stylesheets/%.css: src/assets/stylesheets/%.scss | $$(@D)/.
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# All assets
|
# Assets
|
||||||
material/assets: $$@/fonts $$@/images $$@/javascripts $$@/stylesheets
|
material/assets: $$@/fonts $$@/images $$@/javascripts $$@/stylesheets
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
HTML = $(subst src,material,$(shell find src -type f -name "*.html" ))
|
HTML = $(subst src,material,$(shell find src -name "*.html" ))
|
||||||
material/%.html: src/%.html | $$(@D)/.
|
material/%.html: src/%.html | $$(@D)/.
|
||||||
@ echo "+ $@"
|
@ echo "+ $@"
|
||||||
@ ${BIN}/html-minifier \
|
@ ${BIN}/html-minifier \
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"build": "make build",
|
"build": "make build",
|
||||||
"clean": "make clean",
|
"clean": "make clean",
|
||||||
"lint": "make lint",
|
"lint": "make lint",
|
||||||
"start": "make -j start"
|
"start": "make start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clipboard": "^2.0.0",
|
"clipboard": "^2.0.0",
|
||||||
|
@ -1,537 +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 "../images/icons/bitbucket.svg"
|
|
||||||
import "../images/icons/github.svg"
|
|
||||||
import "../images/icons/gitlab.svg"
|
|
||||||
|
|
||||||
import "../stylesheets/application.scss"
|
|
||||||
import "../stylesheets/application-palette.scss"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Polyfills
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
import "custom-event-polyfill"
|
|
||||||
import "unfetch/polyfill"
|
|
||||||
|
|
||||||
import Promise from "promise-polyfill"
|
|
||||||
window.Promise = window.Promise || Promise
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Dependencies
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
import Clipboard from "clipboard"
|
|
||||||
|
|
||||||
import Material from "./components/Material"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Application
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Material for MkDocs
|
|
||||||
*
|
|
||||||
* @param {Object} config - Configuration
|
|
||||||
*/
|
|
||||||
function initialize(config) { // eslint-disable-line func-style
|
|
||||||
|
|
||||||
/* Initialize Modernizr */
|
|
||||||
new Material.Event.Listener(document, "DOMContentLoaded", () => {
|
|
||||||
if (!(document.body instanceof HTMLElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
|
|
||||||
/* Test for iOS */
|
|
||||||
Modernizr.addTest("ios", () => {
|
|
||||||
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Wrap all data tables for better overflow scrolling */
|
|
||||||
const tables = document.querySelectorAll("table:not([class])") // TODO: this is JSX, we should rename the file
|
|
||||||
Array.prototype.forEach.call(tables, table => {
|
|
||||||
const wrap = (
|
|
||||||
<div class="md-typeset__scrollwrap">
|
|
||||||
<div class="md-typeset__table"></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
if (table.nextSibling) {
|
|
||||||
table.parentNode.insertBefore(wrap, table.nextSibling)
|
|
||||||
} else {
|
|
||||||
table.parentNode.appendChild(wrap)
|
|
||||||
}
|
|
||||||
wrap.children[0].appendChild(table)
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Clipboard integration */
|
|
||||||
if (Clipboard.isSupported()) {
|
|
||||||
const blocks = document.querySelectorAll(".codehilite > pre, pre > code")
|
|
||||||
Array.prototype.forEach.call(blocks, (block, index) => {
|
|
||||||
const id = `__code_${index}`
|
|
||||||
|
|
||||||
/* Create button with message container */
|
|
||||||
const button = (
|
|
||||||
<button class="md-clipboard" title={translate("clipboard.copy")}
|
|
||||||
data-clipboard-target={`#${id} pre, #${id} code`}>
|
|
||||||
<span class="md-clipboard__message"></span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Link to block and insert button */
|
|
||||||
const parent = block.parentNode
|
|
||||||
parent.id = id
|
|
||||||
parent.insertBefore(button, block)
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Initialize Clipboard listener */
|
|
||||||
const copy = new Clipboard(".md-clipboard")
|
|
||||||
|
|
||||||
/* Success handler */
|
|
||||||
copy.on("success", action => {
|
|
||||||
const message = action.trigger.querySelector(".md-clipboard__message")
|
|
||||||
if (!(message instanceof HTMLElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
|
|
||||||
/* Clear selection and reset debounce logic */
|
|
||||||
action.clearSelection()
|
|
||||||
if (message.dataset.mdTimer)
|
|
||||||
clearTimeout(parseInt(message.dataset.mdTimer, 10))
|
|
||||||
|
|
||||||
/* Set message indicating success and show it */
|
|
||||||
message.classList.add("md-clipboard__message--active")
|
|
||||||
message.innerHTML = translate("clipboard.copied")
|
|
||||||
|
|
||||||
/* Hide message after two seconds */
|
|
||||||
message.dataset.mdTimer = setTimeout(() => {
|
|
||||||
message.classList.remove("md-clipboard__message--active")
|
|
||||||
message.dataset.mdTimer = ""
|
|
||||||
}, 2000).toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Polyfill details/summary functionality */
|
|
||||||
if (!Modernizr.details) {
|
|
||||||
const blocks = document.querySelectorAll("details > summary")
|
|
||||||
Array.prototype.forEach.call(blocks, summary => {
|
|
||||||
summary.addEventListener("click", ev => {
|
|
||||||
const details = ev.target.parentNode
|
|
||||||
if (details.hasAttribute("open")) {
|
|
||||||
details.removeAttribute("open")
|
|
||||||
} else {
|
|
||||||
details.setAttribute("open", "")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open details after anchor jump */
|
|
||||||
const details = () => {
|
|
||||||
if (document.location.hash) {
|
|
||||||
const el = document.getElementById(document.location.hash.substring(1))
|
|
||||||
if (!el)
|
|
||||||
return
|
|
||||||
|
|
||||||
/* Walk up as long as we're not in a details tag */
|
|
||||||
let parent = el.parentNode
|
|
||||||
while (parent && !(parent instanceof HTMLDetailsElement))
|
|
||||||
parent = parent.parentNode
|
|
||||||
|
|
||||||
/* If there's a details tag, open it */
|
|
||||||
if (parent && !parent.open) {
|
|
||||||
parent.open = true
|
|
||||||
|
|
||||||
/* Force reload, so the viewport repositions */
|
|
||||||
const loc = location.hash
|
|
||||||
location.hash = " "
|
|
||||||
location.hash = loc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.addEventListener("hashchange", details)
|
|
||||||
details()
|
|
||||||
|
|
||||||
/* Force 1px scroll offset to trigger overflow scrolling */
|
|
||||||
if (Modernizr.ios) {
|
|
||||||
const scrollable = document.querySelectorAll("[data-md-scrollfix]")
|
|
||||||
Array.prototype.forEach.call(scrollable, item => {
|
|
||||||
item.addEventListener("touchstart", () => {
|
|
||||||
const top = item.scrollTop
|
|
||||||
|
|
||||||
/* We're at the top of the container */
|
|
||||||
if (top === 0) {
|
|
||||||
item.scrollTop = 1
|
|
||||||
|
|
||||||
/* We're at the bottom of the container */
|
|
||||||
} else if (top + item.offsetHeight === item.scrollHeight) {
|
|
||||||
item.scrollTop = top - 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Component: header shadow toggle */
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Header.Shadow(
|
|
||||||
"[data-md-component=container]",
|
|
||||||
"[data-md-component=header]")
|
|
||||||
).listen()
|
|
||||||
|
|
||||||
/* Component: header title toggle */
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Header.Title(
|
|
||||||
"[data-md-component=title]",
|
|
||||||
".md-typeset h1")
|
|
||||||
).listen()
|
|
||||||
|
|
||||||
/* Component: hero visibility toggle */
|
|
||||||
if (document.querySelector("[data-md-component=hero]"))
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Tabs.Toggle("[data-md-component=hero]")).listen()
|
|
||||||
|
|
||||||
/* Component: tabs visibility toggle */
|
|
||||||
if (document.querySelector("[data-md-component=tabs]"))
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Tabs.Toggle("[data-md-component=tabs]")).listen()
|
|
||||||
|
|
||||||
/* Component: sidebar with navigation */
|
|
||||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Sidebar.Position(
|
|
||||||
"[data-md-component=navigation]",
|
|
||||||
"[data-md-component=header]")))
|
|
||||||
|
|
||||||
/* Component: sidebar with table of contents (missing on 404 page) */
|
|
||||||
if (document.querySelector("[data-md-component=toc]"))
|
|
||||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
|
||||||
new Material.Event.Listener(window, [
|
|
||||||
"scroll", "resize", "orientationchange"
|
|
||||||
], new Material.Sidebar.Position(
|
|
||||||
"[data-md-component=toc]",
|
|
||||||
"[data-md-component=header]")))
|
|
||||||
|
|
||||||
/* Component: link blurring for table of contents */
|
|
||||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
|
||||||
new Material.Event.Listener(window, "scroll",
|
|
||||||
new Material.Nav.Blur("[data-md-component=toc] .md-nav__link")))
|
|
||||||
|
|
||||||
/* Component: collapsible elements for navigation */
|
|
||||||
const collapsibles =
|
|
||||||
document.querySelectorAll("[data-md-component=collapsible]")
|
|
||||||
Array.prototype.forEach.call(collapsibles, collapse => {
|
|
||||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
|
||||||
new Material.Event.Listener(collapse.previousElementSibling, "click",
|
|
||||||
new Material.Nav.Collapse(collapse)))
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Component: active pane monitor for iOS scrolling fixes */
|
|
||||||
new Material.Event.MatchMedia("(max-width: 1219px)",
|
|
||||||
new Material.Event.Listener(
|
|
||||||
"[data-md-component=navigation] [data-md-toggle]", "change",
|
|
||||||
new Material.Nav.Scrolling("[data-md-component=navigation] nav")))
|
|
||||||
|
|
||||||
/* Initialize search, if available */
|
|
||||||
if (document.querySelector("[data-md-component=search]")) {
|
|
||||||
|
|
||||||
/* Component: search body lock for mobile */
|
|
||||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
|
||||||
new Material.Event.Listener("[data-md-toggle=search]", "change",
|
|
||||||
new Material.Search.Lock("[data-md-toggle=search]")))
|
|
||||||
|
|
||||||
/* Component: search results */
|
|
||||||
new Material.Event.Listener("[data-md-component=query]", [
|
|
||||||
"focus", "keyup", "change"
|
|
||||||
], new Material.Search.Result("[data-md-component=result]", () => {
|
|
||||||
return fetch(`${config.url.base}/search/search_index.json`, {
|
|
||||||
credentials: "same-origin"
|
|
||||||
}).then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
return data.docs.map(doc => {
|
|
||||||
doc.location = `${config.url.base}/${doc.location}`
|
|
||||||
return doc
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})).listen()
|
|
||||||
|
|
||||||
/* Listener: focus input after form reset */
|
|
||||||
new Material.Event.Listener("[data-md-component=reset]", "click", () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const query = document.querySelector("[data-md-component=query]")
|
|
||||||
if (!(query instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
query.focus()
|
|
||||||
}, 10)
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Listener: focus input after opening search */
|
|
||||||
new Material.Event.Listener("[data-md-toggle=search]", "change", ev => {
|
|
||||||
setTimeout(toggle => {
|
|
||||||
if (!(toggle instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
if (toggle.checked) {
|
|
||||||
const query = document.querySelector("[data-md-component=query]")
|
|
||||||
if (!(query instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
query.focus()
|
|
||||||
}
|
|
||||||
}, 400, ev.target)
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Listener: open search on focus */
|
|
||||||
new Material.Event.Listener("[data-md-component=query]", "focus", () => {
|
|
||||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
|
||||||
if (!(toggle instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
if (!toggle.checked) {
|
|
||||||
toggle.checked = true
|
|
||||||
toggle.dispatchEvent(new CustomEvent("change"))
|
|
||||||
}
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Listener: keyboard handlers */ // eslint-disable-next-line complexity
|
|
||||||
new Material.Event.Listener(window, "keydown", ev => { // TODO: split up into component to reduce complexity
|
|
||||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
|
||||||
if (!(toggle instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
const query = document.querySelector("[data-md-component=query]")
|
|
||||||
if (!(query instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
|
|
||||||
/* Skip editable elements */
|
|
||||||
if (document.activeElement instanceof HTMLElement &&
|
|
||||||
document.activeElement.isContentEditable)
|
|
||||||
return
|
|
||||||
|
|
||||||
/* Abort if meta key (macOS) or ctrl key (Windows) is pressed */
|
|
||||||
if (ev.metaKey || ev.ctrlKey)
|
|
||||||
return
|
|
||||||
|
|
||||||
/* Search is open */
|
|
||||||
if (toggle.checked) {
|
|
||||||
|
|
||||||
/* Enter: prevent form submission */
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
if (query === document.activeElement) {
|
|
||||||
ev.preventDefault()
|
|
||||||
|
|
||||||
/* Go to current active/focused link */
|
|
||||||
const focus = document.querySelector(
|
|
||||||
"[data-md-component=search] [href][data-md-state=active]")
|
|
||||||
if (focus instanceof HTMLLinkElement) {
|
|
||||||
window.location = focus.getAttribute("href")
|
|
||||||
|
|
||||||
/* Close search */
|
|
||||||
toggle.checked = false
|
|
||||||
toggle.dispatchEvent(new CustomEvent("change"))
|
|
||||||
query.blur()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Escape or Tab: close search */
|
|
||||||
} else if (ev.keyCode === 9 || ev.keyCode === 27) {
|
|
||||||
toggle.checked = false
|
|
||||||
toggle.dispatchEvent(new CustomEvent("change"))
|
|
||||||
query.blur()
|
|
||||||
|
|
||||||
/* Horizontal arrows and backspace: focus input */
|
|
||||||
} else if ([8, 37, 39].indexOf(ev.keyCode) !== -1) {
|
|
||||||
if (query !== document.activeElement)
|
|
||||||
query.focus()
|
|
||||||
|
|
||||||
/* Vertical arrows: select previous or next search result */
|
|
||||||
} else if ([38, 40].indexOf(ev.keyCode) !== -1) {
|
|
||||||
const key = ev.keyCode
|
|
||||||
|
|
||||||
/* Retrieve all results */
|
|
||||||
const links = Array.prototype.slice.call(
|
|
||||||
document.querySelectorAll(
|
|
||||||
"[data-md-component=query], [data-md-component=search] [href]"))
|
|
||||||
|
|
||||||
/* Retrieve current active/focused result */
|
|
||||||
const focus = links.find(link => {
|
|
||||||
if (!(link instanceof HTMLElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
return link.dataset.mdState === "active"
|
|
||||||
})
|
|
||||||
if (focus)
|
|
||||||
focus.dataset.mdState = ""
|
|
||||||
|
|
||||||
/* Calculate index depending on direction, add length to form ring */
|
|
||||||
const index = Math.max(0, (
|
|
||||||
links.indexOf(focus) + links.length + (key === 38 ? -1 : +1)
|
|
||||||
) % links.length)
|
|
||||||
|
|
||||||
/* Set active state and focus */
|
|
||||||
if (links[index]) {
|
|
||||||
links[index].dataset.mdState = "active"
|
|
||||||
links[index].focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent scrolling of page */
|
|
||||||
ev.preventDefault()
|
|
||||||
ev.stopPropagation()
|
|
||||||
|
|
||||||
/* Return false prevents the cursor position from changing */
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search is closed and we're not inside a form */
|
|
||||||
} else if (document.activeElement && !document.activeElement.form) {
|
|
||||||
|
|
||||||
/* Fixes #1026: search grabs focus for non-form input elements */
|
|
||||||
if (document.activeElement.tagName === "TEXTAREA" ||
|
|
||||||
document.activeElement.tagName === "INPUT")
|
|
||||||
return
|
|
||||||
|
|
||||||
/* F/S: Open search if not in input field */
|
|
||||||
if (ev.keyCode === 70 || ev.keyCode === 83) {
|
|
||||||
query.focus()
|
|
||||||
ev.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Listener: focus query if in search is open and character is typed */
|
|
||||||
new Material.Event.Listener(window, "keypress", () => {
|
|
||||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
|
||||||
if (!(toggle instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
if (toggle.checked) {
|
|
||||||
const query = document.querySelector("[data-md-component=query]")
|
|
||||||
if (!(query instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
if (query !== document.activeElement)
|
|
||||||
query.focus()
|
|
||||||
}
|
|
||||||
}).listen()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Listener: handle tabbing context for better accessibility */
|
|
||||||
new Material.Event.Listener(document.body, "keydown", ev => {
|
|
||||||
if (ev.keyCode === 9) {
|
|
||||||
const labels = document.querySelectorAll(
|
|
||||||
"[data-md-component=navigation] .md-nav__link[for]:not([tabindex])")
|
|
||||||
Array.prototype.forEach.call(labels, label => {
|
|
||||||
if (label.offsetHeight)
|
|
||||||
label.tabIndex = 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
/* Listener: reset tabbing behavior */
|
|
||||||
new Material.Event.Listener(document.body, "mousedown", () => {
|
|
||||||
const labels = document.querySelectorAll(
|
|
||||||
"[data-md-component=navigation] .md-nav__link[tabindex]")
|
|
||||||
Array.prototype.forEach.call(labels, label => {
|
|
||||||
label.removeAttribute("tabIndex")
|
|
||||||
})
|
|
||||||
}).listen()
|
|
||||||
|
|
||||||
document.body.addEventListener("click", () => {
|
|
||||||
if (document.body.dataset.mdState === "tabbing")
|
|
||||||
document.body.dataset.mdState = ""
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Listener: close drawer when anchor links are clicked */
|
|
||||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
|
||||||
new Material.Event.Listener("[data-md-component=navigation] [href^='#']",
|
|
||||||
"click", () => {
|
|
||||||
const toggle = document.querySelector("[data-md-toggle=drawer]")
|
|
||||||
if (!(toggle instanceof HTMLInputElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
if (toggle.checked) {
|
|
||||||
toggle.checked = false
|
|
||||||
toggle.dispatchEvent(new CustomEvent("change"))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
/* Retrieve facts for the given repository type */
|
|
||||||
;(() => {
|
|
||||||
const el = document.querySelector("[data-md-source]")
|
|
||||||
if (!el)
|
|
||||||
return Promise.resolve([])
|
|
||||||
else if (!(el instanceof HTMLAnchorElement))
|
|
||||||
throw new ReferenceError
|
|
||||||
switch (el.dataset.mdSource) {
|
|
||||||
case "github": return new Material.Source.Adapter.GitHub(el).fetch()
|
|
||||||
default: return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Render repository information */
|
|
||||||
})().then(facts => {
|
|
||||||
const sources = document.querySelectorAll("[data-md-source]")
|
|
||||||
Array.prototype.forEach.call(sources, source => {
|
|
||||||
new Material.Source.Repository(source)
|
|
||||||
.initialize(facts)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Before-print hook */
|
|
||||||
const print = () => {
|
|
||||||
const details = document.querySelectorAll("details")
|
|
||||||
Array.prototype.forEach.call(details, detail => {
|
|
||||||
detail.setAttribute("open", "")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open details before printing */
|
|
||||||
new Material.Event.MatchMedia("print", {
|
|
||||||
listen: print, unlisten: () => {}
|
|
||||||
}) // Webkit
|
|
||||||
window.onbeforeprint = print // IE, FF
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Exports
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/* Provide this for downward compatibility for now */
|
|
||||||
const app = {
|
|
||||||
initialize
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
app
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 = ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 = ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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 = ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +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 "../../../.modernizr-autorc"
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["@babel/preset-env", {
|
|
||||||
"loose": true,
|
|
||||||
"targets": " > 1%, last 2 versions"
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
@ -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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Module
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/* eslint-disable no-underscore-dangle */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a native DOM node from JSX's intermediate representation
|
|
||||||
*
|
|
||||||
* @param {string} tag - Tag name
|
|
||||||
* @param {?Object} properties - Properties
|
|
||||||
* @param {Array<string | number | { __html: string } | Array<HTMLElement>>}
|
|
||||||
* children - Child nodes
|
|
||||||
* @return {HTMLElement} Native DOM node
|
|
||||||
*/
|
|
||||||
export function createElement(tag, properties, ...children) {
|
|
||||||
const el = document.createElement(tag)
|
|
||||||
|
|
||||||
/* Set all properties */
|
|
||||||
if (properties)
|
|
||||||
Array.prototype.forEach.call(Object.keys(properties), attr => {
|
|
||||||
el.setAttribute(attr, properties[attr])
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Iterate child nodes */
|
|
||||||
const iterateChildNodes = nodes => {
|
|
||||||
Array.prototype.forEach.call(nodes, node => {
|
|
||||||
|
|
||||||
/* Directly append text content */
|
|
||||||
if (typeof node === "string" ||
|
|
||||||
typeof node === "number") {
|
|
||||||
el.textContent += node
|
|
||||||
|
|
||||||
/* Recurse, if we got an array */
|
|
||||||
} else if (Array.isArray(node)) {
|
|
||||||
iterateChildNodes(node)
|
|
||||||
|
|
||||||
/* Append raw HTML */
|
|
||||||
} else if (typeof node.__html !== "undefined") {
|
|
||||||
el.innerHTML += node.__html
|
|
||||||
|
|
||||||
/* Append regular nodes */
|
|
||||||
} else if (node instanceof Node) {
|
|
||||||
el.appendChild(node)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iterate child nodes and return element */
|
|
||||||
iterateChildNodes(children)
|
|
||||||
return el
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user