Skip to content

Commit

Permalink
POC Monkey-patch history pushState and replaceState to listen to loca…
Browse files Browse the repository at this point in the history
…tion changes (#455)

Closes #157

---------

Co-authored-by: Manuel Kaufmann <[email protected]>
  • Loading branch information
zanderle and humitos authored Jan 22, 2025
1 parent 9560221 commit 4599e33
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 0 deletions.
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

0 comments on commit 4599e33

Please sign in to comment.