Skip to content

Commit

Permalink
Visual diff: trigger DOM_CHANGED event after enable/disable (#465)
Browse files Browse the repository at this point in the history
A new event `READTHEDOCS_ROOT_DOM_CHANGED` is triggered when the "Visual
diff" is enabled/disabled after the DOM was modified.

This allows other addons to subscribe to this event to re-initialize if
required. I used this event from links preview to call `setupTooltips`
again to re-install tooltips on these links.

I migrated links preview to a `LitElement` to be able to make usage of
this pattern and follow what we already had.

We will be able to do something similar on #157

Closes #460
  • Loading branch information
humitos authored Dec 10, 2024
1 parent dc3edc6 commit a89136b
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 14 deletions.
3 changes: 3 additions & 0 deletions public/_/readthedocs-addons.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@
"enabled": true,
"code": "UA-12345"
},
"linkpreviews": {
"enabled": true
},
"notifications": {
"enabled": true,
"show_on_latest": true,
Expand Down
12 changes: 11 additions & 1 deletion src/docdiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { AddonBase } from "./utils";
import {
EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW,
EVENT_READTHEDOCS_DOCDIFF_HIDE,
EVENT_READTHEDOCS_ROOT_DOM_CHANGED,
} from "./events";
import { nothing, LitElement } from "lit";
import { default as objectPath } from "object-path";
Expand Down Expand Up @@ -103,7 +104,9 @@ export class DocDiffElement extends LitElement {

// Enable DocDiff if the URL parameter is present
if (hasQueryParam(DOCDIFF_URL_PARAM)) {
event = new CustomEvent(EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW);
const event = new CustomEvent(
EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW,
);
document.dispatchEvent(event);
}
}
Expand Down Expand Up @@ -152,6 +155,10 @@ export class DocDiffElement extends LitElement {
this.cachedRemoteContent = text;
this.performDiff(text);
})
.finally(() => {
const event = new CustomEvent(EVENT_READTHEDOCS_ROOT_DOM_CHANGED);
document.dispatchEvent(event);
})
.catch((error) => {
console.error(error);
});
Expand Down Expand Up @@ -204,6 +211,9 @@ export class DocDiffElement extends LitElement {

this.enabled = false;
document.querySelector(this.rootSelector).replaceWith(this.originalBody);

const event = new CustomEvent(EVENT_READTHEDOCS_ROOT_DOM_CHANGED);
document.dispatchEvent(event);
}

_handleShowDocDiff = (e) => {
Expand Down
17 changes: 17 additions & 0 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,26 @@ export const EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW =
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 Read the Docs data is ready to be consumed.
*
* This is the event users subscribe to to make usage of Read the Docs data.
* The object received is `ReadTheDocsEventData`.
*/
export const EVENT_READTHEDOCS_ADDONS_DATA_READY =
"readthedocs-addons-data-ready";

/**
* Event triggered when any addons modifies the root DOM.
*
* As an example, DocDiff triggers it when injecting the visual diferences.
* Addons subscribe to this event to re-initialize them in case they perform
* something specific on DOM elements from inside the root.
*/
export const EVENT_READTHEDOCS_ROOT_DOM_CHANGED =
"readthedocs-root-dom-changed";

/**
* Object to pass to user subscribing to `EVENT_READTHEDOCS_ADDONS_DATA_READY`.
*
Expand Down
93 changes: 80 additions & 13 deletions src/linkpreviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IS_TESTING,
docTool,
} from "./utils";
import { EVENT_READTHEDOCS_ROOT_DOM_CHANGED } from "./events";
import {
computePosition,
autoPlacement,
Expand Down Expand Up @@ -200,26 +201,28 @@ function setupTooltip(el, doctoolname, doctoolversion, selector) {
}
}

/**
* LinkPreviews addon
*
* @param {Object} config - Addon configuration object
*/
export class LinkPreviewsAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.linkpreviews.json";
static addonEnabledPath = "addons.linkpreviews.enabled";
static addonName = "LinkPreviews";
export class LinkPreviewsElement extends LitElement {
static elementName = "readthedocs-linkpreviews";

constructor(config) {
static properties = {
config: {
state: true,
},
};

constructor() {
super();
this.config = config;

if (!IS_TESTING) {
// Include CSS into the DOM so they can be read.
// We can't include these CSS in the LitElement, because we need them to be globally available.
document.adoptedStyleSheets.push(styleSheet);
}

this.config = null;
}

setupTooltips() {
// Autodetect if the page is built with Sphinx and send the `doctool=` attribute in that case.
const doctoolName = docTool.getDocumentationTool();
const rootSelector =
Expand All @@ -242,7 +245,7 @@ export class LinkPreviewsAddon extends AddonBase {
let elementHostname = elementUrl.hostname;
const pointToSamePage =
window.location.pathname.replace("/index.html", "") ==
elementUrl.pathname.replace("/index.html");
elementUrl.pathname.replace("/index.html", "");
if (elementHostname === window.location.hostname && !pointToSamePage) {
element.classList.add("link-preview");
setupTooltip(element, doctoolName, null, rootSelector);
Expand All @@ -254,4 +257,68 @@ export class LinkPreviewsAddon extends AddonBase {
}
}
}

render() {
return nothing;
}

loadConfig(config) {
if (!LinkPreviewsAddon.isEnabled(config)) {
return;
}

this.config = config;
this.setupTooltips();
}

_handleRootDOMChanged = (e) => {
// Trigger the setup again since the DOM has changed
this.setupTooltips();
};

connectedCallback() {
super.connectedCallback();
document.addEventListener(
EVENT_READTHEDOCS_ROOT_DOM_CHANGED,
this._handleRootDOMChanged,
);
}

disconnectedCallback() {
document.removeEventListener(
EVENT_READTHEDOCS_ROOT_DOM_CHANGED,
this._handleRootDOMChanged,
);
super.disconnectedCallback();
}
}

/**
* LinkPreviews addon
*
* @param {Object} config - Addon configuration object
*/
export class LinkPreviewsAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.linkpreviews.json";
static addonEnabledPath = "addons.linkpreviews.enabled";
static addonName = "LinkPreviews";

constructor(config) {
super();

// If there are no elements found, inject one
let elems = document.querySelectorAll("readthedocs-linkpreviews");
if (!elems.length) {
elems = [new LinkPreviewsElement()];
document.body.append(elems[0]);
elems[0].requestUpdate();
}

for (const elem of elems) {
elem.loadConfig(config);
}
}
}

customElements.define("readthedocs-linkpreviews", LinkPreviewsElement);

0 comments on commit a89136b

Please sign in to comment.