Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ <h2 id="search">Search</h2>
<p>Hit <code>/</code> to trigger the search modal, or click on the input from the flyout.</p>
<readthedocs-search></readthedocs-search>

<h2>Test URL changes</h2>

<p>Quick check to see if the SPA url changes using "pushState" are correctly picked up using our utils (check console).</p>
<button id="change-url-button" type="button">Change URL</button>

<script>
const button = document.getElementById("change-url-button");
button.addEventListener("click", () => {
const randomHash = (Math.random() + 1).toString(36).substring(7);
history.pushState({}, "", `#url-${randomHash}`);
});

window.addEventListener("readthedocs-url-changed", (ev) => {console.log("URL Change detected!", ev)});
</script>

<h2>Link Previews</h2>
<div role="main">
<ul>
Expand Down
5 changes: 5 additions & 0 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const EVENT_READTHEDOCS_DOCDIFF_HIDE = "readthedocs-docdiff-hide";
export const EVENT_READTHEDOCS_FLYOUT_SHOW = "readthedocs-flyout-show";
export const EVENT_READTHEDOCS_FLYOUT_HIDE = "readthedocs-flyout-hide";

/**
* Event triggered when the URL has changed dynamically (modifying the history object)
*/
export const EVENT_READTHEDOCS_URL_CHANGED = "readthedocs-url-changed";

/**
* Event triggered when the Read the Docs data is ready to be consumed.
*
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
IS_PRODUCTION,
setupLogging,
getMetadataValue,
setupHistoryEvents,
} from "./utils";

import doctoolsStyleSheet from "./doctools.css";
Expand Down Expand Up @@ -46,6 +47,7 @@ export function setup() {
domReady
.then(() => {
setupLogging();
setupHistoryEvents();

let sendUrlParam = false;
for (const addon of addons) {
Expand Down
39 changes: 39 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ANTORA,
DOCSIFY,
} from "./constants";
import { EVENT_READTHEDOCS_URL_CHANGED } from "./events";

export const ADDONS_API_VERSION = "1";
export const ADDONS_API_ENDPOINT = "/_/addons/";
Expand Down Expand Up @@ -160,6 +161,44 @@ export class AddonBase {
}
}

/**
* Setup events firing on history `pushState` and `replaceState`
*
* This is needed when addons are used in SPA. A lot of addons rely
* on the current URL. However in the SPA, the pages are not reloaded, so
* the addons never get notified of the changes in the URL.
*
* While History API does have `popstate` event, the only way to listen to
* changes via `pushState` and `replaceState` is using monkey-patching, which is
* what this function does. (See https://stackoverflow.com/a/4585031)
* It will fire a `READTHEDOCS_URL_CHANGED` event, on `pushState` and `replaceState`.
*
*/
export function setupHistoryEvents() {
// Let's ensure that the history will be patched only once, so we create a Symbol to check by
const patchKey = Symbol.for("addons_history");

if (
typeof history !== "undefined" &&
typeof window[patchKey] === "undefined"
) {
for (const methodName of ["pushState", "replaceState"]) {
const originalMethod = history[methodName];
history[methodName] = function () {
const result = originalMethod.apply(this, arguments);
const event = new Event(EVENT_READTHEDOCS_URL_CHANGED);
event.arguments = arguments;

dispatchEvent(event);
return result;
};
}

// Let's leave a flag, so we know that history has been patched
Object.defineProperty(window, patchKey, { value: true });
}
}

/**
* Debounce a function.
*
Expand Down