2020-03-10 21:08:11 +01:00
|
|
|
/**
|
2020-03-11 08:13:53 +01:00
|
|
|
* Reads and writes the URL based on reveal.js' current state.
|
2020-03-10 21:08:11 +01:00
|
|
|
*/
|
|
|
|
export default class Location {
|
2023-05-23 19:50:14 +02:00
|
|
|
// The minimum number of milliseconds that must pass between
|
|
|
|
// calls to history.replaceState
|
|
|
|
MAX_REPLACE_STATE_FREQUENCY = 1000;
|
|
|
|
|
|
|
|
constructor(Reveal) {
|
|
|
|
this.Reveal = Reveal;
|
|
|
|
|
|
|
|
// Delays updates to the URL due to a Chrome thumbnailer bug
|
|
|
|
this.writeURLTimeout = 0;
|
|
|
|
|
|
|
|
this.replaceStateTimestamp = 0;
|
|
|
|
|
|
|
|
this.onWindowHashChange = this.onWindowHashChange.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
bind() {
|
|
|
|
window.addEventListener("hashchange", this.onWindowHashChange, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
unbind() {
|
|
|
|
window.removeEventListener("hashchange", this.onWindowHashChange, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the slide indices for the given hash link.
|
|
|
|
*
|
|
|
|
* @param {string} [hash] the hash string that we want to
|
|
|
|
* find the indices for
|
|
|
|
*
|
|
|
|
* @returns slide indices or null
|
|
|
|
*/
|
|
|
|
getIndicesFromHash(hash = window.location.hash, options = {}) {
|
|
|
|
// Attempt to parse the hash as either an index or name
|
|
|
|
let name = hash.replace(/^#\/?/, "");
|
|
|
|
let bits = name.split("/");
|
|
|
|
|
|
|
|
// If the first bit is not fully numeric and there is a name we
|
|
|
|
// can assume that this is a named link
|
|
|
|
if (!/^[0-9]*$/.test(bits[0]) && name.length) {
|
|
|
|
let element;
|
|
|
|
|
|
|
|
let f;
|
|
|
|
|
|
|
|
// Parse named links with fragments (#/named-link/2)
|
|
|
|
if (/\/[-\d]+$/g.test(name)) {
|
|
|
|
f = parseInt(name.split("/").pop(), 10);
|
|
|
|
f = isNaN(f) ? undefined : f;
|
|
|
|
name = name.split("/").shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the named link is a valid HTML ID attribute
|
|
|
|
try {
|
|
|
|
element = document.getElementById(decodeURIComponent(name));
|
|
|
|
} catch (error) {}
|
|
|
|
|
|
|
|
if (element) {
|
|
|
|
return { ...this.Reveal.getIndices(element), f };
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const config = this.Reveal.getConfig();
|
|
|
|
let hashIndexBase =
|
|
|
|
config.hashOneBasedIndex || options.oneBasedIndex ? 1 : 0;
|
|
|
|
|
|
|
|
// Read the index components of the hash
|
|
|
|
let h = parseInt(bits[0], 10) - hashIndexBase || 0,
|
|
|
|
v = parseInt(bits[1], 10) - hashIndexBase || 0,
|
|
|
|
f;
|
|
|
|
|
|
|
|
if (config.fragmentInURL) {
|
|
|
|
f = parseInt(bits[2], 10);
|
|
|
|
if (isNaN(f)) {
|
|
|
|
f = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { h, v, f };
|
|
|
|
}
|
|
|
|
|
|
|
|
// The hash couldn't be parsed or no matching named link was found
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the current URL (hash) and navigates accordingly.
|
|
|
|
*/
|
|
|
|
readURL() {
|
|
|
|
const currentIndices = this.Reveal.getIndices();
|
|
|
|
const newIndices = this.getIndicesFromHash();
|
|
|
|
|
|
|
|
if (newIndices) {
|
|
|
|
if (
|
|
|
|
newIndices.h !== currentIndices.h ||
|
|
|
|
newIndices.v !== currentIndices.v ||
|
|
|
|
newIndices.f !== undefined
|
|
|
|
) {
|
|
|
|
this.Reveal.slide(newIndices.h, newIndices.v, newIndices.f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If no new indices are available, we're trying to navigate to
|
|
|
|
// a slide hash that does not exist
|
|
|
|
else {
|
|
|
|
this.Reveal.slide(currentIndices.h || 0, currentIndices.v || 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the page URL (hash) to reflect the current
|
|
|
|
* state.
|
|
|
|
*
|
|
|
|
* @param {number} delay The time in ms to wait before
|
|
|
|
* writing the hash
|
|
|
|
*/
|
|
|
|
writeURL(delay) {
|
|
|
|
let config = this.Reveal.getConfig();
|
|
|
|
let currentSlide = this.Reveal.getCurrentSlide();
|
|
|
|
|
|
|
|
// Make sure there's never more than one timeout running
|
|
|
|
clearTimeout(this.writeURLTimeout);
|
|
|
|
|
|
|
|
// If a delay is specified, timeout this call
|
|
|
|
if (typeof delay === "number") {
|
|
|
|
this.writeURLTimeout = setTimeout(this.writeURL, delay);
|
|
|
|
} else if (currentSlide) {
|
|
|
|
let hash = this.getHash();
|
|
|
|
|
|
|
|
// If we're configured to push to history OR the history
|
|
|
|
// API is not available.
|
|
|
|
if (config.history) {
|
|
|
|
window.location.hash = hash;
|
|
|
|
}
|
|
|
|
// If we're configured to reflect the current slide in the
|
|
|
|
// URL without pushing to history.
|
|
|
|
else if (config.hash) {
|
|
|
|
// If the hash is empty, don't add it to the URL
|
|
|
|
if (hash === "/") {
|
|
|
|
this.debouncedReplaceState(
|
|
|
|
window.location.pathname + window.location.search
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.debouncedReplaceState("#" + hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// UPDATE: The below nuking of all hash changes breaks
|
|
|
|
// anchors on pages where reveal.js is running. Removed
|
|
|
|
// in 4.0. Why was it here in the first place? ¯\_(ツ)_/¯
|
|
|
|
//
|
|
|
|
// If history and hash are both disabled, a hash may still
|
|
|
|
// be added to the URL by clicking on a href with a hash
|
|
|
|
// target. Counter this by always removing the hash.
|
|
|
|
// else {
|
|
|
|
// window.history.replaceState( null, null, window.location.pathname + window.location.search );
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
replaceState(url) {
|
|
|
|
window.history.replaceState(null, null, url);
|
|
|
|
this.replaceStateTimestamp = Date.now();
|
|
|
|
}
|
|
|
|
|
|
|
|
debouncedReplaceState(url) {
|
|
|
|
clearTimeout(this.replaceStateTimeout);
|
|
|
|
|
|
|
|
if (
|
|
|
|
Date.now() - this.replaceStateTimestamp >
|
|
|
|
this.MAX_REPLACE_STATE_FREQUENCY
|
|
|
|
) {
|
|
|
|
this.replaceState(url);
|
|
|
|
} else {
|
|
|
|
this.replaceStateTimeout = setTimeout(
|
|
|
|
() => this.replaceState(url),
|
|
|
|
this.MAX_REPLACE_STATE_FREQUENCY
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a hash URL that will resolve to the given slide location.
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} [slide=currentSlide] The slide to link to
|
|
|
|
*/
|
|
|
|
getHash(slide) {
|
|
|
|
let url = "/";
|
|
|
|
|
|
|
|
// Attempt to create a named link based on the slide's ID
|
|
|
|
let s = slide || this.Reveal.getCurrentSlide();
|
|
|
|
let id = s ? s.getAttribute("id") : null;
|
|
|
|
if (id) {
|
|
|
|
id = encodeURIComponent(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
let index = this.Reveal.getIndices(slide);
|
|
|
|
if (!this.Reveal.getConfig().fragmentInURL) {
|
|
|
|
index.f = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the current slide has an ID, use that as a named link,
|
|
|
|
// but we don't support named links with a fragment index
|
|
|
|
if (typeof id === "string" && id.length) {
|
|
|
|
url = "/" + id;
|
|
|
|
|
|
|
|
// If there is also a fragment, append that at the end
|
|
|
|
// of the named link, like: #/named-link/2
|
|
|
|
if (index.f >= 0) url += "/" + index.f;
|
|
|
|
}
|
|
|
|
// Otherwise use the /h/v index
|
|
|
|
else {
|
|
|
|
let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;
|
|
|
|
if (index.h > 0 || index.v > 0 || index.f >= 0)
|
|
|
|
url += index.h + hashIndexBase;
|
|
|
|
if (index.v > 0 || index.f >= 0) url += "/" + (index.v + hashIndexBase);
|
|
|
|
if (index.f >= 0) url += "/" + index.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the window level 'hashchange' event.
|
|
|
|
*
|
|
|
|
* @param {object} [event]
|
|
|
|
*/
|
|
|
|
onWindowHashChange(event) {
|
|
|
|
this.readURL();
|
|
|
|
}
|
|
|
|
}
|