Skip to content

Commit da730b1

Browse files
authored
File Tree Diff: initial implementation (#424)
Added a new addon to show all the filenames and URLs that were added, deleted or modified in the current PR compared to the LATEST version. ![Screenshot_2024-11-07_12-44-02](https://github.com/user-attachments/assets/dfdba906-1eb1-4755-a1e6-eaddbfa51144) I'm rendering this data in a new notification decoupled from the existing one on purpose. I don't want to entangle the code of these two different addons for now, in particular while we are under development/testing. I also see that in the future they may have different UI elements, or the data will be integrated in a more bigger UI element (eg. toolbar) or similar. We can discuss more how this addon will grow and where is a good place to add this data and make that work in a following PR. For now, I want something that we can start testing internally in our own projects. ### Examples ![Screenshot_2024-11-07_14-15-37](https://github.com/user-attachments/assets/d0265b34-8ade-4348-bc8e-e6a3ff8ae0d1) ![Screenshot_2024-11-07_14-35-41](https://github.com/user-attachments/assets/403b6860-60c1-468e-ae5d-2491f6f47b38) ![Screenshot_2024-11-07_14-51-06](https://github.com/user-attachments/assets/0e24664d-672f-40c0-9be4-bd76b2cf4f04) Closes #409 Requires readthedocs/readthedocs.org#11749
1 parent bff2f37 commit da730b1

7 files changed

+367
-10
lines changed

dist/readthedocs-addons.js

Lines changed: 15 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/readthedocs-addons.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/data-validation.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,36 @@ const addons_flyout = {
239239
},
240240
};
241241

242+
// Validator for File Tree Diff Addon
243+
const addons_filetreediff = {
244+
$id: "http://v1.schemas.readthedocs.org/addons.filetreediff.json",
245+
type: "object",
246+
required: ["addons"],
247+
properties: {
248+
addons: {
249+
type: "object",
250+
required: ["filetreediff"],
251+
properties: {
252+
filetreediff: {
253+
type: "object",
254+
required: ["enabled", "diff"],
255+
properties: {
256+
enabled: { type: "boolean" },
257+
diff: {
258+
type: "object",
259+
properties: {
260+
added: { type: "array" },
261+
deleted: { type: "array" },
262+
modified: { type: "array" },
263+
},
264+
},
265+
},
266+
},
267+
},
268+
},
269+
},
270+
};
271+
242272
// Validator for Hotkeys Addon
243273
const addons_hotkeys = {
244274
$id: "http://v1.schemas.readthedocs.org/addons.hotkeys.json",
@@ -443,6 +473,7 @@ export const ajv = new Ajv({
443473
addons_hotkeys,
444474
addons_notifications,
445475
addons_search,
476+
addons_filetreediff,
446477
],
447478
});
448479

src/filetreediff.css

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
:host > div {
2+
margin: 1rem 0rem;
3+
padding-top: 1rem;
4+
padding-bottom: 1rem;
5+
overflow: auto;
6+
border-radius: 0.5rem;
7+
font-family: "Lato", "proxima-nova", "Helvetica Neue", "Arial", "sans-serif";
8+
font-size: 1rem;
9+
color: rgb(64, 64, 64);
10+
background-color: rgb(234, 234, 234);
11+
}
12+
13+
:host > div > div.content > ul {
14+
margin: 0;
15+
padding-left: 20px;
16+
}
17+
18+
:host(.toast) > div {
19+
position: fixed;
20+
padding-top: calc(1rem * 0.75);
21+
padding-bottom: calc(1rem * 0.75);
22+
margin: 0.75rem 0rem;
23+
top: 7rem;
24+
right: 2rem;
25+
z-index: 1750;
26+
font-size: calc(1rem * 0.85);
27+
width: 35rem;
28+
max-width: calc(100vw - 4rem);
29+
}
30+
31+
@media (max-width: 640px) {
32+
:host(.toast) > div {
33+
right: 0.5rem;
34+
}
35+
}
36+
37+
:host > div > svg.header.icon {
38+
height: calc(1rem * 2);
39+
padding: 0.5rem 1.5rem;
40+
float: left;
41+
}
42+
43+
:host(.toast) > div > svg.header.icon {
44+
height: calc(1rem * 1.5);
45+
}
46+
47+
:host > div a {
48+
color: rgb(8, 140, 219);
49+
text-decoration: none;
50+
}
51+
52+
:host > div > .title {
53+
padding: 0.25rem 1rem;
54+
margin-bottom: 0.25rem;
55+
line-height: 1rem;
56+
font-weight: bold;
57+
font-size: 0.85rem;
58+
}
59+
:host > div > div.content {
60+
line-height: 1rem;
61+
font-size: calc(1rem * 0.85);
62+
padding-left: 66px;
63+
}
64+
65+
:host(.toast) > div > .title {
66+
padding: 0rem 1rem;
67+
}
68+
69+
:host > div > .title > .right {
70+
float: right;
71+
}
72+
73+
:host > div > .title > .right > svg {
74+
display: inline-block;
75+
height: 1rem;
76+
vertical-align: middle;
77+
cursor: pointer;
78+
color: rgba(96, 96, 96);
79+
font-weight: normal;
80+
}
81+
82+
:host(.raised) > div {
83+
box-shadow:
84+
0 2px 4px 0 rgba(34, 36, 38, 0.12),
85+
0 2px 10px 0 rgba(34, 36, 38, 0.15);
86+
}

src/filetreediff.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { library, icon } from "@fortawesome/fontawesome-svg-core";
2+
import { faCircleXmark, faFile } from "@fortawesome/free-solid-svg-icons";
3+
import { html, nothing, render, LitElement } from "lit";
4+
import { repeat } from "lit/directives/repeat.js";
5+
import { default as objectPath } from "object-path";
6+
import styleSheet from "./filetreediff.css";
7+
8+
import { AddonBase } from "./utils";
9+
10+
export class FileTreeDiffElement extends LitElement {
11+
static elementName = "readthedocs-filetreediff";
12+
13+
static properties = {
14+
config: { state: true },
15+
dismissed: { state: true },
16+
};
17+
18+
static styles = styleSheet;
19+
20+
constructor() {
21+
super();
22+
this.config = null;
23+
this.dismissed = false;
24+
}
25+
26+
firstUpdated() {
27+
// Add CSS classes to the element on ``firstUpdated`` because we need the
28+
// HTML element to exist in the DOM before being able to add tag attributes.
29+
this.className = this.className || "raised toast";
30+
}
31+
32+
loadConfig(config) {
33+
if (!FileTreeDiffAddon.isEnabled(config)) {
34+
return;
35+
}
36+
37+
this.config = config;
38+
}
39+
40+
render() {
41+
if (this.dismissed) {
42+
return nothing;
43+
}
44+
45+
library.add(faFile);
46+
const iconFile = icon(faFile, {
47+
title: "This version is a pull request version",
48+
classes: ["header", "icon"],
49+
});
50+
51+
const diffdata = objectPath.get(this.config, "addons.filetreediff.diff");
52+
53+
let diffAddedUrls = diffdata.added.length
54+
? html`
55+
<span>Added</span>
56+
<ul>
57+
${repeat(
58+
diffdata.added,
59+
(f) => f.filename,
60+
(f, index) =>
61+
html`<li><a href=${f.urls.version_a}>${f.filename}</a></li>`,
62+
)}
63+
</ul>
64+
`
65+
: nothing;
66+
67+
let diffDeletedUrls = diffdata.deleted.length
68+
? html`
69+
<span>Deleted</span>
70+
<ul>
71+
${repeat(
72+
diffdata.deleted,
73+
(f) => f.filename,
74+
(f, index) =>
75+
html`<li><a href=${f.urls.version_a}>${f.filename}</a></li>`,
76+
)}
77+
</ul>
78+
`
79+
: nothing;
80+
81+
let diffModifiedUrls = diffdata.modified.length
82+
? html`
83+
<span>Modified</span>
84+
<ul>
85+
${repeat(
86+
diffdata.modified,
87+
(f) => f.filename,
88+
(f, index) =>
89+
html`<li><a href=${f.urls.version_a}>${f.filename}</a></li>`,
90+
)}
91+
</ul>
92+
`
93+
: nothing;
94+
95+
return html`
96+
<div>
97+
${iconFile.node[0]}
98+
<div class="title">
99+
List of files changed in this pull request ${this.renderCloseButton()}
100+
</div>
101+
<div class="content">
102+
${diffAddedUrls} ${diffModifiedUrls} ${diffDeletedUrls}
103+
</div>
104+
</div>
105+
`;
106+
}
107+
108+
renderCloseButton() {
109+
library.add(faCircleXmark);
110+
const xmark = icon(faCircleXmark, {
111+
title: "Close notification",
112+
});
113+
return html`
114+
<a href="#" class="right" @click=${this.closeNotification}>
115+
${xmark.node[0]}
116+
</a>
117+
`;
118+
}
119+
120+
closeNotification(e) {
121+
// Avoid going back to the top of the page when closing the notification
122+
e.preventDefault();
123+
this.dismissed = true;
124+
125+
// Avoid event propagation
126+
return false;
127+
}
128+
}
129+
130+
/**
131+
* File Tree Diff addon
132+
*
133+
* UNDER DEVELOPMENT.
134+
*
135+
* Currently, this addon shows in the console all the file changed compared to
136+
* the LATEST version of the project.
137+
*
138+
* @param {Object} config - Addon configuration object
139+
*/
140+
export class FileTreeDiffAddon extends AddonBase {
141+
static jsonValidationURI =
142+
"http://v1.schemas.readthedocs.org/addons.filetreediff.json";
143+
static addonEnabledPath = "addons.filetreediff.enabled";
144+
static addonName = "File Tree Diff";
145+
146+
constructor(config) {
147+
super();
148+
149+
this.config = config;
150+
this.showDiff();
151+
152+
// If there are no elements found, inject one
153+
let elems = document.querySelectorAll("readthedocs-filetreediff");
154+
if (!elems.length) {
155+
elems = [new FileTreeDiffElement()];
156+
document.body.append(elems[0]);
157+
elems[0].requestUpdate();
158+
}
159+
160+
for (const elem of elems) {
161+
elem.loadConfig(config);
162+
}
163+
}
164+
165+
showDiff() {
166+
// const outdated = objectPath.get(this.config, "addons.filetreediff.oudated", false);
167+
const diffData = objectPath.get(this.config, "addons.filetreediff.diff");
168+
169+
for (let f of diffData.added) {
170+
console.debug(`File: ${f.filename}, URL: ${f.urls.version_a}`);
171+
}
172+
for (let f of diffData.modified) {
173+
console.debug(`File: ${f.filename}, URL: ${f.urls.version_a}`);
174+
}
175+
for (let f of diffData.deleted) {
176+
console.debug(`File: ${f.filename}, URL: ${f.urls.version_a}`);
177+
}
178+
}
179+
}
180+
181+
customElements.define("readthedocs-filetreediff", FileTreeDiffElement);

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as docdiff from "./docdiff";
66
import * as flyout from "./flyout";
77
import * as ethicalads from "./ethicalads";
88
import * as hotkeys from "./hotkeys";
9+
import * as filetreediff from "./filetreediff";
910
import {
1011
domReady,
1112
isReadTheDocsEmbedPresent,
@@ -29,6 +30,7 @@ export function setup() {
2930
search.SearchAddon,
3031
docdiff.DocDiffAddon,
3132
hotkeys.HotKeysAddon,
33+
filetreediff.FileTreeDiffAddon,
3234
];
3335

3436
return new Promise((resolve) => {

tests/filetreediff.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { expect, assert, fixture, html } from "@open-wc/testing";
2+
import { FileTreeDiffAddon } from "../src/filetreediff";
3+
4+
describe("FileTreeDiff addon", () => {
5+
it("invalid configuration disables the addon", () => {
6+
expect(
7+
FileTreeDiffAddon.isEnabled({
8+
addons: {
9+
filetreediff: {
10+
enabled: true,
11+
diff: 52, // invalid value
12+
},
13+
},
14+
}),
15+
).to.be.false;
16+
});
17+
18+
it("is disabled", () => {
19+
expect(
20+
FileTreeDiffAddon.isEnabled({
21+
addons: {
22+
filetreediff: {
23+
enabled: false,
24+
diff: {
25+
added: [],
26+
modified: [],
27+
deleted: [],
28+
},
29+
},
30+
},
31+
}),
32+
).to.be.false;
33+
});
34+
35+
it("valid data and enabled", () => {
36+
expect(
37+
FileTreeDiffAddon.isEnabled({
38+
addons: {
39+
filetreediff: {
40+
enabled: true,
41+
diff: {
42+
added: [],
43+
modified: [],
44+
deleted: [],
45+
},
46+
},
47+
},
48+
}),
49+
).to.be.true;
50+
});
51+
});

0 commit comments

Comments
 (0)