Skip to content
Closed
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: 11 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
}
},
"dependencies": {
"@floating-ui/dom": "^1.6.13"
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.3"
}
}
1 change: 1 addition & 0 deletions public/docdiff.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<html>
<head>
<meta name="readthedocs-addons-api-version" content="1" />
<title>DocDiff Addons - Read the Docs</title>
<meta name="readthedocs-project-slug" content="test-builds" />
<meta name="readthedocs-version-slug" content="latest" />
Expand Down
3 changes: 3 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ <h1 id="documentation-addons">Documentation Addons</h1>
<h2 id="docdiff">CustomEvent</h2>
<p>Project slug using <em>CustomEvent</em>: <span id="custom-event-project-slug"></span></p>

<h2 id="flyout">Flyout</h2>
<readthedocs-flyout></readthedocs-flyout>

<h2 id="docdiff">DocDiff</h2>
<p>Visit <a href="docdiff.html">this page</a> to take a look at it.</p>

Expand Down
76 changes: 56 additions & 20 deletions src/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,40 @@ import { default as fetch } from "unfetch";
import { ajv } from "./data-validation";
import { AddonBase } from "./utils";
import { CLIENT_VERSION } from "./utils";
import { ContextConsumer } from "@lit/context";
import { configContext } from "./context.js";
import { nothing, render, LitElement } from "lit";

export const API_ENDPOINT = "/_/api/v2/analytics/";

/**
* Analytics addon
*
* Register page views that can be seen from the project's dashboard.
* Besides, it injects the Global Read the Docs analytics.
*
* Read more at:
* - https://docs.readthedocs.io/en/stable/reference/analytics.html
* - https://docs.readthedocs.io/en/stable/advertising/advertising-details.html#analytics
*
* @param {Object} config - Addon configuration object
*/
export class AnalyticsAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.analytics.json";
static addonEnabledPath = "addons.analytics.enabled";
static addonName = "Analytics";
static enabledOnHttpStatus = [200, 404];
export class AnalyticsElement extends LitElement {
static elementName = "readthedocs-analytics";

// `_config` is the context we are going to consume when it's updated.
_config = new ContextConsumer(this, {
context: configContext,
subscribe: true,
});

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

render() {
// Validate the context (`this._config.value`) on each update and return
// nothing if it's invalid. ``nothing`` is a special Lit response type.
if (!AnalyticsAddon.isEnabled(this._config.value)) {
return nothing;
}

this.config = this._config.value;

// Only register pageviews on non-external versions
if (this.config.versions.current.type !== "external") {
this.registerPageView();
}
return nothing;
}

registerPageView() {
Expand All @@ -59,3 +63,35 @@ export class AnalyticsAddon extends AddonBase {
});
}
}

/**
* Analytics addon
*
* Register page views that can be seen from the project's dashboard.
* Besides, it injects the Global Read the Docs analytics.
*
* Read more at:
* - https://docs.readthedocs.io/en/stable/reference/analytics.html
* - https://docs.readthedocs.io/en/stable/advertising/advertising-details.html#analytics
*
* @param {Object} config - Addon configuration object
*/
export class AnalyticsAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.analytics.json";
static addonEnabledPath = "addons.analytics.enabled";
static addonName = "Analytics";
static enabledOnHttpStatus = [200, 404];

constructor() {
super();

// If there are no elements found, inject one
let elems = document.querySelectorAll("readthedocs-analytics");
if (!elems.length) {
render(new AnalyticsElement(), document.body);
}
}
}

customElements.define("readthedocs-analytics", AnalyticsElement);
26 changes: 26 additions & 0 deletions src/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
ContextProvider,
ContextRoot,
createContext,
} from "@lit/context";
import { EVENT_READTHEDOCS_ADDONS_DATA_READY } from "./events";

export const contextRoot = new ContextRoot().attach(document.body);
export const configContext = createContext(Symbol("readthedocs-config"));

/**
* Because `config` provider is not attached to a ReactiveElement, and is
* instead connected to `document.html`, we have to call `hostConnected()`
* manually. See:
*
* https://github.com/lit/lit/blob/935697d47e62ed75e3157423400163a8371c62fc/packages/context/src/lib/controllers/context-provider.ts#L55-L58
**/
const config = new ContextProvider(document.documentElement, {
context: configContext,
});
config.hostConnected();

document.addEventListener(EVENT_READTHEDOCS_ADDONS_DATA_READY, (event) => {
console.log("Event:", EVENT_READTHEDOCS_ADDONS_DATA_READY);
config.setValue(event.detail.data());
});
62 changes: 49 additions & 13 deletions src/customscript.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { default as objectPath } from "object-path";
import { AddonBase } from "./utils";
import { ContextConsumer } from "@lit/context";
import { configContext } from "./context.js";
import { nothing, render, LitElement } from "lit";

const SCRIPT_ID = "readthedocs-addons-custom-script";

/**
* User JavaScript file.
*
* Allow a user to inject a custom JavaScript file in all the pages.
*/
export class CustomScriptAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.customscript.json";
static addonEnabledPath = "addons.customscript.enabled";
static addonName = "CustomScript";
static enabledOnHttpStatus = [200, 403, 404, 500];
export class CustomScriptElement extends LitElement {
static elementName = "readthedocs-customscript";

// `_config` is the context we are going to consume when it's updated.
_config = new ContextConsumer(this, {
context: configContext,
subscribe: true,
});

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

render() {
// Validate the context (`this._config.value`) on each update and return
// nothing if it's invalid. ``nothing`` is a special Lit response type.
if (!CustomScriptAddon.isEnabled(this._config.value)) {
return nothing;
}

this.config = this._config.value;

if (objectPath.get(this.config, "addons.customscript.src")) {
this.injectJavaScriptFile();
}
return nothing;
}

injectJavaScriptFile() {
Expand All @@ -33,3 +44,28 @@ export class CustomScriptAddon extends AddonBase {
document.body.appendChild(script);
}
}

/**
* User JavaScript file.
*
* Allow a user to inject a custom JavaScript file in all the pages.
*/
export class CustomScriptAddon extends AddonBase {
static jsonValidationURI =
"http://v1.schemas.readthedocs.org/addons.customscript.json";
static addonEnabledPath = "addons.customscript.enabled";
static addonName = "CustomScript";
static enabledOnHttpStatus = [200, 403, 404, 500];

constructor() {
super();

// If there are no elements found, inject one
let elems = document.querySelectorAll("readthedocs-customscript");
if (!elems.length) {
render(new CustomScriptElement(), document.body);
}
}
}

customElements.define("readthedocs-customscript", CustomScriptElement);
55 changes: 21 additions & 34 deletions src/docdiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
EVENT_READTHEDOCS_DOCDIFF_HIDE,
EVENT_READTHEDOCS_ROOT_DOM_CHANGED,
} from "./events";
import { nothing, LitElement } from "lit";
import { render, nothing, LitElement } from "lit";
import { default as objectPath } from "object-path";
import { hasQueryParam, docTool } from "./utils";
import { ContextConsumer } from "@lit/context";
import { configContext } from "./context.js";

export const DOCDIFF_URL_PARAM = "readthedocs-diff";

Expand Down Expand Up @@ -75,6 +77,12 @@ export class DocDiffElement extends LitElement {

static styles = styleSheet;

// `_config` is the context we are going to consume when it's updated.
_config = new ContextConsumer(this, {
context: configContext,
subscribe: true,
});

constructor() {
super();

Expand All @@ -87,11 +95,7 @@ export class DocDiffElement extends LitElement {
this.cachedRemoteContent = null;
}

loadConfig(config) {
if (!DocDiffAddon.isEnabled(config)) {
return;
}
this.config = config;
firstUpdated() {
this.rootSelector =
objectPath.get(this.config, "options.root_selector") ||
docTool.getRootSelector();
Expand All @@ -101,39 +105,28 @@ export class DocDiffElement extends LitElement {
if (this.injectStyles) {
document.adoptedStyleSheets.push(docdiffGeneralStyleSheet);
}
}

render() {
// Validate the context (`this._config.value`) on each update and return
// nothing if it's invalid. ``nothing`` is a special Lit response type.
if (!DocDiffAddon.isEnabled(this._config.value)) {
return nothing;
}
console.log("DocDiff:", this._config.value);

this.config = this._config.value;
// Enable DocDiff if the URL parameter is present
if (hasQueryParam(DOCDIFF_URL_PARAM)) {
const event = new CustomEvent(
EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW,
);
document.dispatchEvent(event);
}
}

render() {
return nothing;
// TODO: render a checkbox once we are settled on the UI.
// For now, we are only enabling/disabling via a hotkey.
//
// return html`
// <label class="switch">
// <input @click="${this.handleClick}" type="checkbox" />
// <span class="slider round"></span>
// </label>
// `;
}

// This code isn't used until we show a UI,
// and even then we'll want to trigger events to match state?
// handleClick(e) {
// if (e.target.checked) {
// this.enableDocDiff();
// } else {
// this.disableDocDiff();
// }
// }

compare() {
let promiseData;

Expand Down Expand Up @@ -264,13 +257,7 @@ export class DocDiffAddon extends AddonBase {
customElements.define("readthedocs-docdiff", DocDiffElement);
let elems = document.querySelectorAll("readthedocs-docdiff");
if (!elems.length) {
elems = [new DocDiffElement()];
document.body.append(elems[0]);
elems[0].requestUpdate();
}

for (const elem of elems) {
elem.loadConfig(config);
render(new DocDiffElement(), document.body);
}
}

Expand Down
Loading