diff --git a/src/docdiff.js b/src/docdiff.js index ac2beef4..666950e6 100644 --- a/src/docdiff.js +++ b/src/docdiff.js @@ -198,6 +198,11 @@ export class DocDiffElement extends LitElement { return null; } + // Update URL to include the diff parameter + const url = new URL(window.location.href); + url.searchParams.set(DOCDIFF_URL_PARAM, "true"); + window.history.replaceState({}, "", url); + this.enabled = true; this.originalBody = document.querySelector(this.rootSelector); return this.compare(); @@ -209,6 +214,11 @@ export class DocDiffElement extends LitElement { return null; } + // Remove diff parameter from URL + const url = new URL(window.location.href); + url.searchParams.delete(DOCDIFF_URL_PARAM); + window.history.replaceState({}, "", url); + this.enabled = false; document.querySelector(this.rootSelector).replaceWith(this.originalBody); @@ -238,6 +248,7 @@ export class DocDiffElement extends LitElement { this._handleHideDocDiff, ); } + disconnectedCallback() { document.removeEventListener( EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW, diff --git a/src/filetreediff.css b/src/filetreediff.css index a177f6e8..d20f895e 100644 --- a/src/filetreediff.css +++ b/src/filetreediff.css @@ -1,86 +1,39 @@ -:host > div { - margin: 1rem 0rem; - padding-top: 1rem; - padding-bottom: 1rem; - overflow: auto; - border-radius: 0.5rem; - font-family: "Lato", "proxima-nova", "Helvetica Neue", "Arial", "sans-serif"; - font-size: 1rem; - color: rgb(64, 64, 64); - background-color: rgb(234, 234, 234); -} - -:host > div > div.content > ul { - margin: 0; - padding-left: 20px; -} - -:host(.toast) > div { +.file-dropdown { position: fixed; - padding-top: calc(1rem * 0.75); - padding-bottom: calc(1rem * 0.75); - margin: 0.75rem 0rem; - top: 7rem; - right: 2rem; - z-index: 1750; - font-size: calc(1rem * 0.85); - width: 35rem; - max-width: calc(100vw - 4rem); -} - -@media (max-width: 640px) { - :host(.toast) > div { - right: 0.5rem; - } -} - -:host > div > svg.header.icon { - height: calc(1rem * 2); - padding: 0.5rem 1.5rem; - float: left; -} - -:host(.toast) > div > svg.header.icon { - height: calc(1rem * 1.5); -} - -:host > div a { - color: rgb(8, 140, 219); - text-decoration: none; -} - -:host > div > .title { - padding: 0.25rem 1rem; - margin-bottom: 0.25rem; - line-height: 1rem; - font-weight: bold; + top: 0; + right: 1rem; + background-color: #f8f9fa; + padding: 0.25rem 0.75rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 2000; + border-radius: 0 0 6px 6px; font-size: 0.85rem; } -:host > div > div.content { - line-height: 1rem; - font-size: calc(1rem * 0.85); - padding-left: 66px; -} -:host(.toast) > div > .title { - padding: 0rem 1rem; +.diff-controls { + display: flex; + gap: 0.75rem; + align-items: center; } -:host > div > .title > .right { - float: right; +.diff-controls select { + flex: 1; + padding: 0.25rem 0.5rem; + border: 1px solid #ddd; + border-radius: 4px; + appearance: revert; } -:host > div > .title > .right > svg { - display: inline-block; - height: 1rem; - vertical-align: middle; +.diff-checkbox { + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 0.85rem; + color: #333; + white-space: nowrap; cursor: pointer; - color: rgba(96, 96, 96); - font-weight: normal; } -:host(.raised) > div { - box-shadow: - 0 2px 4px 0 rgba(34, 36, 38, 0.12), - 0 2px 10px 0 rgba(34, 36, 38, 0.15); +.diff-checkbox input { + margin: 0; } diff --git a/src/filetreediff.js b/src/filetreediff.js index 4e285258..cfa0b1a8 100644 --- a/src/filetreediff.js +++ b/src/filetreediff.js @@ -1,10 +1,12 @@ -import { library, icon } from "@fortawesome/fontawesome-svg-core"; -import { faCircleXmark, faFile } from "@fortawesome/free-solid-svg-icons"; -import { html, nothing, render, LitElement } from "lit"; -import { repeat } from "lit/directives/repeat.js"; +import { html, nothing, LitElement } from "lit"; import { default as objectPath } from "object-path"; import styleSheet from "./filetreediff.css"; import { DOCDIFF_URL_PARAM } from "./docdiff.js"; +import { + EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW, + EVENT_READTHEDOCS_DOCDIFF_HIDE, +} from "./events"; +import { hasQueryParam } from "./utils"; import { AddonBase } from "./utils"; @@ -13,7 +15,7 @@ export class FileTreeDiffElement extends LitElement { static properties = { config: { state: true }, - dismissed: { state: true }, + docDiffEnabled: { state: true }, }; static styles = styleSheet; @@ -21,95 +23,128 @@ export class FileTreeDiffElement extends LitElement { constructor() { super(); this.config = null; - this.dismissed = false; - } - - firstUpdated() { - // Add CSS classes to the element on ``firstUpdated`` because we need the - // HTML element to exist in the DOM before being able to add tag attributes. - this.className = this.className || "raised toast"; + this.docDiffEnabled = false; } loadConfig(config) { if (!FileTreeDiffAddon.isEnabled(config)) { return; } - this.config = config; } - render() { - if (this.dismissed) { - return nothing; + handleFileChange(event) { + const fileUrl = event.target.value; + if (fileUrl) { + const url = new URL(fileUrl); + // Only add the diff parameter if diff is currently enabled + if (this.docDiffEnabled) { + url.searchParams.set(DOCDIFF_URL_PARAM, "true"); + } + window.location.href = url.toString(); } + } - library.add(faFile); - const iconFile = icon(faFile, { - title: "This version is a pull request version", - classes: ["header", "icon"], - }); - - const generateDiffList = (diffArray, label) => { - return diffArray.length - ? html` - ${label} - - ` - : nothing; - }; + handleToggleDiff(event) { + if (event.target.checked) { + document.dispatchEvent( + new CustomEvent(EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW), + ); + } else { + document.dispatchEvent(new CustomEvent(EVENT_READTHEDOCS_DOCDIFF_HIDE)); + } + } + + getCurrentPageUrl() { + // Remove any query parameters to match against file URLs + const currentPath = window.location.pathname; + const currentOrigin = window.location.origin; + return `${currentOrigin}${currentPath}`; + } + render() { const diffData = objectPath.get(this.config, "addons.filetreediff.diff"); if (!diffData) { return nothing; } - const diffAddedUrls = generateDiffList(diffData.added, "Added"); - const diffDeletedUrls = generateDiffList(diffData.deleted, "Deleted"); - const diffModifiedUrls = generateDiffList(diffData.modified, "Modified"); + + const currentUrl = this.getCurrentPageUrl(); + const renderSection = (files, label) => { + if (!files.length) return nothing; + const emoji = label === "Added" ? "+ " : "± "; + return html` + + ${files.map( + (f) => html` + + `, + )} + + `; + }; + + const hasCurrentFile = [...diffData.added, ...diffData.modified].some( + (f) => f.urls.current === currentUrl, + ); return html` -
- ${iconFile.node[0]} -
- Files changed in this version ${this.renderCloseButton()} -
-
- ${diffAddedUrls} ${diffModifiedUrls} ${diffDeletedUrls} +
+
+ +
`; } - renderCloseButton() { - library.add(faCircleXmark); - const xmark = icon(faCircleXmark, { - title: "Close notification", - }); - return html` - - ${xmark.node[0]} - - `; - } + _handleDocDiffShow = (event) => { + this.docDiffEnabled = true; + }; - closeNotification(e) { - // Avoid going back to the top of the page when closing the notification - e.preventDefault(); - this.dismissed = true; + _handleDocDiffHide = (event) => { + this.docDiffEnabled = false; + }; + + connectedCallback() { + super.connectedCallback(); + document.addEventListener( + EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW, + this._handleDocDiffShow, + ); + document.addEventListener( + EVENT_READTHEDOCS_DOCDIFF_HIDE, + this._handleDocDiffHide, + ); + } - // Avoid event propagation - return false; + disconnectedCallback() { + document.removeEventListener( + EVENT_READTHEDOCS_DOCDIFF_ADDED_REMOVED_SHOW, + this._handleDocDiffShow, + ); + document.removeEventListener( + EVENT_READTHEDOCS_DOCDIFF_HIDE, + this._handleDocDiffHide, + ); + super.disconnectedCallback(); } } @@ -131,11 +166,8 @@ export class FileTreeDiffAddon extends AddonBase { constructor(config) { super(); - this.config = config; - this.showDiff(); - // If there are no elements found, inject one let elems = document.querySelectorAll("readthedocs-filetreediff"); if (!elems.length) { elems = [new FileTreeDiffElement()]; @@ -147,21 +179,6 @@ export class FileTreeDiffAddon extends AddonBase { elem.loadConfig(config); } } - - showDiff() { - // const outdated = objectPath.get(this.config, "addons.filetreediff.oudated", false); - const diffData = objectPath.get(this.config, "addons.filetreediff.diff"); - - for (let f of diffData.added) { - console.debug(`File: ${f.filename}, URL: ${f.urls.current}`); - } - for (let f of diffData.modified) { - console.debug(`File: ${f.filename}, URL: ${f.urls.current}`); - } - for (let f of diffData.deleted) { - console.debug(`File: ${f.filename}, URL: ${f.urls.current}`); - } - } } customElements.define("readthedocs-filetreediff", FileTreeDiffElement); diff --git a/src/index.js b/src/index.js index b6827651..06aac021 100644 --- a/src/index.js +++ b/src/index.js @@ -26,15 +26,16 @@ export function setup() { ethicalads.EthicalAdsAddon, search.SearchAddon, - // HotKeys has to be initialized before DocDiff because when + // HotKeys & FileTreeDiff have to be initialized before DocDiff because when // `?readthedocs-diff=true` DocDiff triggers an event that HotKeys has to // listen to to update its internal state. + // https://github.com/readthedocs/addons/blob/47645b013724cdf244716b549a5baa28409fafcb/src/docdiff.js#L105-L111 hotkeys.HotKeysAddon, - docdiff.DocDiffAddon, + filetreediff.FileTreeDiffAddon, linkpreviews.LinkPreviewsAddon, - filetreediff.FileTreeDiffAddon, customscript.CustomScriptAddon, + docdiff.DocDiffAddon, ]; return new Promise((resolve) => {