Hit / to trigger the search modal, or click on the input from the flyout.
+
Test URL changes
+
+
Quick check to see if the SPA url changes using "pushState" are correctly picked up using our utils (check console).
+
+
+
+
Link Previews
diff --git a/src/events.js b/src/events.js
index 740bed97..02cba032 100644
--- a/src/events.js
+++ b/src/events.js
@@ -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.
*
diff --git a/src/index.js b/src/index.js
index 78781d15..f4aa0daf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,6 +19,7 @@ import {
IS_PRODUCTION,
setupLogging,
getMetadataValue,
+ setupHistoryEvents,
} from "./utils";
import doctoolsStyleSheet from "./doctools.css";
@@ -46,6 +47,7 @@ export function setup() {
domReady
.then(() => {
setupLogging();
+ setupHistoryEvents();
let sendUrlParam = false;
for (const addon of addons) {
diff --git a/src/utils.js b/src/utils.js
index 05fa39f1..ff7b2962 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -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/";
@@ -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.
*