mkdocs-material/src/assets/javascripts/application.js

341 lines
12 KiB
JavaScript
Raw Normal View History

2016-01-29 01:27:15 +03:00
/*
2017-01-06 21:11:18 +03:00
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
2016-01-29 01:27:15 +03:00
*
* 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 FastClick from "fastclick"
import Material from "./components/Material"
2016-01-29 01:27:15 +03:00
/* ----------------------------------------------------------------------------
2016-08-07 19:01:56 +03:00
* Application
2016-01-29 01:27:15 +03:00
* ------------------------------------------------------------------------- */
/**
* Initialize Material for MkDocs
*
* @param {Object} config - Configuration
*/
function initialize(config) { // eslint-disable-line func-style
2017-02-11 18:00:45 +03:00
/* Initialize Modernizr and FastClick */
new Material.Event.Listener(document, "DOMContentLoaded", () => {
if (!(document.body instanceof HTMLElement))
throw new ReferenceError
2017-02-11 18:00:45 +03:00
/* Attach FastClick to mitigate 300ms delay on touch devices */
FastClick.attach(document.body)
2017-02-11 18:00:45 +03:00
/* Test for iOS */
Modernizr.addTest("ios", () => {
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
})
2016-11-02 21:21:14 +03:00
2017-02-11 18:00:45 +03:00
/* Wrap all data tables for better overflow scrolling */
const tables = document.querySelectorAll("table:not([class])")
Array.prototype.forEach.call(tables, table => {
2017-02-25 13:29:15 +03:00
const wrap = (
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table"></div>
</div>
)
2017-02-11 18:00:45 +03:00
if (table.nextSibling) {
table.parentNode.insertBefore(wrap, table.nextSibling)
} else {
table.parentNode.appendChild(wrap)
2016-12-29 16:19:26 +03:00
}
2017-02-25 13:29:15 +03:00
wrap.children[0].appendChild(table)
2017-02-11 18:00:45 +03:00
})
2016-12-29 16:19:26 +03:00
2017-02-11 18:00:45 +03:00
/* 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
2016-12-29 16:19:26 +03:00
2017-02-11 18:00:45 +03:00
/* We're at the top of the container */
if (top === 0) {
item.scrollTop = 1
2016-12-29 16:19:26 +03:00
/* We're at the bottom of the container */
2017-02-11 18:00:45 +03:00
} else if (top + item.offsetHeight === item.scrollHeight) {
item.scrollTop = top - 1
2016-12-15 17:55:40 +03:00
}
2016-12-29 16:19:26 +03:00
})
2017-02-11 18:00:45 +03:00
})
}
}).listen()
/* Component: header shadow toggle */
new Material.Event.MatchMedia("(min-width: 1220px)",
new Material.Event.Listener(window, [
"scroll", "resize", "orientationchange"
], new Material.Header.Shadow(
"[data-md-component=container]",
"[data-md-component=header]")))
/* Component: tabs visibility toggle */
2017-02-26 17:30:48 +03:00
if (document.querySelector("[data-md-component=tabs]"))
new Material.Event.Listener(window, [
"scroll", "resize", "orientationchange"
], new Material.Tabs.Toggle("[data-md-component=tabs]")).listen()
2016-12-15 17:55:40 +03:00
2017-02-11 18:00:45 +03:00
/* 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]")))
2016-12-15 17:55:40 +03:00
2017-04-06 13:49:37 +03:00
/* 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]")))
2017-02-11 18:00:45 +03:00
/* 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] [href]")))
2016-12-15 17:55:40 +03:00
2017-02-11 18:00:45 +03:00
/* 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")))
/* 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]", [
2017-03-19 21:49:19 +03:00
"focus", "keyup", "change"
2017-02-11 18:00:45 +03:00
], new Material.Search.Result("[data-md-component=result]", () => {
return fetch(`${config.url.base}/mkdocs/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
2016-12-15 17:55:40 +03:00
})
2017-02-11 18:00:45 +03:00
})
})).listen()
/* 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
2016-12-15 17:55:40 +03:00
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
2016-12-15 17:55:40 +03:00
}
}))
2017-03-21 17:27:45 +03:00
/* 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()
2017-02-11 18:00:45 +03:00
/* 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
2017-02-11 18:00:45 +03:00
query.focus()
}
2017-02-11 18:00:45 +03:00
}, 400, ev.target)
}).listen()
/* Listener: open search on focus */
new Material.Event.MatchMedia("(min-width: 960px)",
new Material.Event.Listener("[data-md-component=query]", "focus", () => {
2017-02-11 18:00:45 +03:00
const toggle = document.querySelector("[data-md-toggle=search]")
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
2017-02-11 18:00:45 +03:00
if (!toggle.checked) {
toggle.checked = true
toggle.dispatchEvent(new CustomEvent("change"))
2016-12-28 19:32:56 +03:00
}
2017-02-11 18:00:45 +03:00
}))
/* Listener: keyboard handlers */
2017-03-24 00:41:08 +03:00
new Material.Event.Listener(window, "keydown", ev => {
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
2017-04-20 19:28:28 +03:00
/* Abort if meta key (macOS) or ctrl key (Windows) is pressed */
if (ev.metaKey || ev.ctrlKey)
return
2017-03-24 00:41:08 +03:00
/* Search is open */
if (toggle.checked) {
/* Enter: prevent form submission */
if (ev.keyCode === 13) {
if (query === document.activeElement)
ev.preventDefault()
2017-03-24 00:41:08 +03:00
/* Escape: close search */
} else if (ev.keyCode === 27) {
2017-03-24 00:41:08 +03:00
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) {
2017-03-24 00:41:08 +03:00
if (query !== document.activeElement)
query.focus()
/* Vertical arrows and tab: select previous or next search result */
} else if ([9, 38, 40].indexOf(ev.keyCode) !== -1) {
const map = ev.shiftKey ? 38 : 40
const key = ev.keyCode === 9 ? map : ev.keyCode
2017-03-24 00:41:08 +03:00
/* Retrieve all results */
const links = Array.prototype.slice.call(
document.querySelectorAll("[data-md-component=search] [href]"))
2017-03-24 00:41:08 +03:00
if (!links.length)
return
/* 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)
2017-03-24 00:41:08 +03:00
) % links.length)
/* Set active state and focus */
if (!(links[index] instanceof HTMLElement))
throw new ReferenceError
links[index].dataset.mdState = "active"
links[index].focus()
2017-03-24 00:41:08 +03:00
/* Prevent scrolling of page */
ev.preventDefault()
ev.stopPropagation()
/* Return false prevents the cursor position from changing */
return false
}
2017-03-24 00:41:08 +03:00
/* Search is closed */
} else {
/* F/S: Open search if not in input field */
if (ev.keyCode === 70 || ev.keyCode === 83) {
2017-03-24 00:41:08 +03:00
query.focus()
ev.preventDefault()
2017-02-11 18:00:45 +03:00
}
}
}).listen()
2017-03-24 00:41:08 +03:00
/* 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()
2017-02-11 18:00:45 +03:00
/* Listener: fix unclickable toggle due to blur handler */
new Material.Event.MatchMedia("(min-width: 960px)",
new Material.Event.Listener("[data-md-toggle=search]", "click",
ev => ev.stopPropagation()))
/* Listener: prevent search from closing when clicking */
new Material.Event.MatchMedia("(min-width: 960px)",
new Material.Event.Listener("[data-md-component=search]", "click",
ev => ev.stopPropagation()))
/* 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
2017-02-11 18:00:45 +03:00
switch (el.dataset.mdSource) {
case "github": return new Material.Source.Adapter.GitHub(el).fetch()
default: return Promise.resolve([])
}
/* Render repository information */
2017-02-11 18:00:45 +03:00
})().then(facts => {
const sources = document.querySelectorAll("[data-md-source]")
Array.prototype.forEach.call(sources, source => {
new Material.Source.Repository(source)
.initialize(facts)
2016-12-15 17:55:40 +03:00
})
2017-02-11 18:00:45 +03:00
})
}
/* ----------------------------------------------------------------------------
* Exports
* ------------------------------------------------------------------------- */
export {
initialize
}