-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathzenhaven.uc.js
More file actions
373 lines (325 loc) · 14.8 KB
/
zenhaven.uc.js
File metadata and controls
373 lines (325 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// ==UserScript==
// @name Custom Toolbox UI (Safe Mode)
// @description Only injects UI when #navigator-toolbox has [haven]
// @include main
// ==/UserScript==
import * as UC_API from "chrome://userchromejs/content/uc_api.sys.mjs";
import { parseElement } from "./utils/parse.js";
import { downloadsSection } from "./sections/download.js";
import { workspacesSection } from "./sections/workspace.js";
import { historySection } from "./sections/history.js";
import { notesSection } from "./sections/notes.js";
(function () {
const { document } = window;
if (window.haven) {
console.log("[ZenHaven] Already initialized. Aborting.");
return;
}
console.log("[ZenHaven] Script loaded");
class ZenHaven {
constructor() {
this.sections = new Map();
this.activeSectionId = null;
this.uiInitialized = false;
this.elements = {
toolbox: null,
customToolbar: null,
functionsContainer: null,
havenContainer: null,
bottomButtons: null,
mediaToolbar: null,
};
console.log("[ZenHaven] Core object created");
}
addSection(config) {
if (
!config.id ||
!config.label ||
!config.icon ||
typeof config.init !== "function"
) {
console.error(
"[ZenHaven] Invalid section config: id, label, icon, and init function are required.",
config
);
return;
}
if (this.sections.has(config.id)) {
console.warn(
`[ZenHaven] Section with id "${config.id}" already exists. Overwriting.`
);
}
const conditionMet =
!config.condition ||
(typeof config.condition === "function" && config.condition());
if (conditionMet) {
this.sections.set(config.id, config);
console.log(`[ZenHaven] Section "${config.id}" registered.`);
} else {
console.log(
`[ZenHaven] Condition not met for section "${config.id}". Skipping registration.`
);
}
}
initializeUI() {
console.log("[ZenHaven] Setting up UI...");
this.elements.toolbox = document.getElementById("navigator-toolbox");
if (
!this.elements.toolbox ||
!this.elements.toolbox.hasAttribute("haven")
) {
console.log(
"[ZenHaven] Toolbox not found or haven attribute is missing."
);
return;
}
console.log("[ZenHaven] Haven attribute found, proceeding with UI setup");
// Hide all children except the toolbox itself
Array.from(this.elements.toolbox.children).forEach((child) => {
child.style.display = "none";
});
// Create container for new UI elements
const customContainer = parseElement(`<div id="custom-toolbar">
<div id="toolbar-header">
<span class="toolbarbutton-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" fill="currentColor"/>
</svg>
</span>
<span class="header-text">Haven</span>
</div>
<div id="functions-container"></div>
</div>`);
this.elements.customToolbar = customContainer;
this.elements.functionsContainer = customContainer.querySelector(
"#functions-container"
);
this.elements.toolbox.appendChild(customContainer);
// Create buttons from registered sections
this.sections.forEach((section) => this.createNavButton(section));
// Handle bottom buttons
this.elements.bottomButtons = document.getElementById(
"zen-sidebar-bottom-buttons"
);
this.elements.mediaToolbar = document.getElementById(
"zen-media-controls-toolbar"
);
const workspacesButton = this.elements.bottomButtons?.querySelector(
"#zen-workspaces-button"
);
if (this.elements.bottomButtons && workspacesButton) {
customContainer.appendChild(this.elements.bottomButtons);
workspacesButton.style.display = "none";
}
// Create sidebar container
const sidebarSplitter = document.getElementById("zen-sidebar-splitter");
if (sidebarSplitter) {
const sidebarContainer = parseElement(
`<div id="zen-haven-container" style="height: 100%; width: 60vw; position: relative; display: none; flex-direction: column;"></div>`
);
this.elements.havenContainer = sidebarContainer;
const tabbox = document.getElementById("tabbrowser-tabbox");
if (tabbox) {
tabbox.parentNode.insertBefore(sidebarContainer, tabbox);
console.log(
"[ZenHaven] Sidebar container added before tabbrowser-tabbox"
);
} else {
sidebarSplitter.parentNode.insertBefore(
sidebarContainer,
sidebarSplitter.nextSibling
);
console.log(
"[ZenHaven] Tabbox not found, sidebar container added after splitter"
);
}
}
this.uiInitialized = true;
console.log("[ZenHaven] UI setup complete");
}
destroyUI() {
console.log("[ZenHaven] Restoring original UI");
// Restore bottom buttons
if (this.elements.bottomButtons && this.elements.mediaToolbar) {
this.elements.mediaToolbar.parentNode.insertBefore(
this.elements.bottomButtons,
this.elements.mediaToolbar.nextSibling
);
const workspacesButton = this.elements.bottomButtons.querySelector(
"#zen-workspaces-button"
);
if (workspacesButton) {
workspacesButton.style.display = "";
}
console.log("[ZenHaven] Bottom buttons restored after media controls");
}
// Show all original toolbox children
if (this.elements.toolbox) {
Array.from(this.elements.toolbox.children).forEach((child) => {
if (child.id !== "custom-toolbar") {
child.style.display = "";
}
});
}
// Remove our custom elements
this.elements.customToolbar?.remove();
this.elements.havenContainer?.remove();
// Reset state
this.activeSectionId = null;
this.uiInitialized = false;
Object.keys(this.elements).forEach((key) => (this.elements[key] = null));
}
createNavButton(section) {
const customDiv =
parseElement(`<div class="custom-button" id="haven-${section.id}-button">
<span class="icon">${section.icon}</span>
<span class="label">${section.label}</span>
</div>`);
customDiv.addEventListener("click", () =>
this.activateSection(section.id)
);
customDiv.addEventListener("mousedown", () =>
customDiv.classList.add("clicked")
);
customDiv.addEventListener("mouseup", () =>
customDiv.classList.remove("clicked")
);
customDiv.addEventListener("mouseleave", () =>
customDiv.classList.remove("clicked")
);
this.elements.functionsContainer.appendChild(customDiv);
}
activateSection(id) {
if (!this.uiInitialized) return;
// If clicking the same button, toggle off
if (this.activeSectionId === id) {
this.deactivateCurrentSection();
return;
}
this.deactivateCurrentSection();
const section = this.sections.get(id);
if (!section) {
console.error(`[ZenHaven] No section found for id: ${id}`);
return;
}
console.log(`[ZenHaven] Activating section: ${id}`);
const contentElement = section.init();
if (contentElement instanceof HTMLElement) {
section.contentElement = contentElement;
this.elements.havenContainer.setAttribute(`haven-${id}`, "");
this.elements.havenContainer.appendChild(contentElement);
this.elements.havenContainer.style.display = "flex";
document.getElementById(`haven-${id}-button`)?.classList.add("active");
this.activeSectionId = id;
} else {
console.error(
`[ZenHaven] Section "${id}" init() did not return a valid DOM element.`
);
}
}
deactivateCurrentSection() {
if (!this.activeSectionId || !this.uiInitialized) return;
const oldSection = this.sections.get(this.activeSectionId);
if (oldSection && typeof oldSection.destroy === "function") {
oldSection.destroy();
}
if (oldSection && oldSection.contentElement) {
oldSection.contentElement.remove();
delete oldSection.contentElement;
}
Array.from(this.elements.havenContainer.attributes)
.filter((attr) => attr.name.startsWith("haven-"))
.forEach((attr) =>
this.elements.havenContainer.removeAttribute(attr.name)
);
this.elements.havenContainer.style.display = "none";
document
.getElementById(`haven-${this.activeSectionId}-button`)
?.classList.remove("active");
this.activeSectionId = null;
}
}
window.haven = new ZenHaven();
// --- SECTION DEFINITIONS ---
window.haven.addSection(downloadsSection);
window.haven.addSection(workspacesSection);
window.haven.addSection(historySection);
window.haven.addSection(notesSection);
// --- INITIALIZATION & OBSERVER LOGIC ---
function handleHavenAttributeChange() {
const toolbox = document.getElementById("navigator-toolbox");
if (!toolbox) return;
console.log("[ZenHaven] Haven attribute changed");
if (toolbox.hasAttribute("haven")) {
if (!window.haven.uiInitialized) {
window.haven.initializeUI();
}
} else {
if (window.haven.uiInitialized) {
window.haven.destroyUI();
}
}
}
function createToolboxObserver() {
console.log("[ZenHaven] Setting up toolbox observer");
const toolbox = document.getElementById("navigator-toolbox");
if (!toolbox) return;
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (
mutation.type === "attributes" &&
mutation.attributeName === "haven"
) {
handleHavenAttributeChange();
break;
}
}
});
observer.observe(toolbox, { attributes: true });
console.log("[ZenHaven] Toolbox observer active");
// Initial check in case attribute is already present on load
handleHavenAttributeChange();
}
function createHavenToggle() {
const iconSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="3.048 2.032 500 500" fill="black" width="500px" height="500px"><path d="M 117.019 229.189 L 358.157 229.189 C 370.419 229.189 380.359 239.378 380.359 251.948 L 380.359 359.696 C 380.359 361.892 380.056 364.016 379.49 366.025 L 379.49 376.137 C 379.49 422.439 342.874 459.973 297.705 459.973 L 175.73 459.973 C 130.562 459.973 93.946 422.439 93.946 376.137 L 93.946 318.55 C 93.946 314.379 94.243 310.281 94.817 306.275 L 94.817 251.948 C 94.817 239.378 104.756 229.189 117.019 229.189 Z M 163.119 286.634 L 163.119 298.372 C 163.119 308.337 170.999 316.415 180.72 316.415 L 293.541 316.415 C 303.261 316.415 311.142 308.337 311.142 298.372 L 311.142 286.634 C 311.142 276.67 303.261 268.592 293.541 268.592 L 180.72 268.592 C 170.999 268.592 163.119 276.67 163.119 286.634 Z"/><path d="M -269.92 -189.491 Q -252.676 -214.41 -233.996 -189.491 L -204.641 -150.333 Q -185.961 -125.414 -221.885 -125.414 L -278.336 -125.414 Q -314.26 -125.414 -297.017 -150.333 Z" transform="matrix(-1, 0, 0, -1, 0, 0)"/><path d="M -192.578 148.485 Q -154.729 125.322 -114.7 148.485 L -53.312 184.008 Q -13.283 207.171 -91.161 207.171 L -210.592 207.171 Q -288.47 207.171 -250.621 184.008 Z" transform="matrix(-1, 0, 0, 1, 0, -0.000008)"/><path d="M -193.628 147.815 C -168.84 132.499 -142.9 132.494 -116.065 147.806 L -53.102 183.819 C -46.393 187.659 -42.055 191.078 -40.213 194.014 C -39.269 195.518 -38.936 197.044 -39.287 198.349 C -39.636 199.647 -40.695 200.872 -42.299 201.867 C -48.553 205.742 -64.621 207.669 -90.335 207.671 L -211.418 207.671 C -237.131 207.669 -253.446 205.744 -260.209 201.876 C -261.935 200.888 -263.133 199.69 -263.651 198.406 C -264.175 197.107 -264.045 195.573 -263.299 194.058 C -261.844 191.109 -257.946 187.673 -251.748 183.828 Z M -251.222 184.678 C -257.352 188.473 -261.031 191.721 -262.402 194.501 C -263.068 195.851 -263.163 196.943 -262.724 198.032 C -262.279 199.134 -261.325 200.085 -259.712 201.007 C -253.12 204.779 -237.073 206.673 -211.418 206.671 L -90.335 206.671 C -64.681 206.673 -48.901 204.781 -42.826 201.016 C -41.348 200.101 -40.545 199.177 -40.252 198.089 C -39.961 197.006 -40.206 195.906 -41.06 194.545 C -42.813 191.752 -46.951 188.486 -53.598 184.687 L -116.561 148.674 C -143.148 133.428 -168.577 133.424 -193.102 148.665 Z" transform="matrix(-1, 0, 0, 1, 0, -0.000008000000889296643)"/><path d="M 356.927 112.69 C 371.223 91.321 386.116 91.321 401.604 112.69 L 438.111 163.062 C 453.599 184.431 446.45 195.116 416.666 195.116 L 346.46 195.116 C 316.675 195.116 308.931 184.431 323.228 163.062 L 356.927 112.69 Z" style="transform-box: fill-box; transform-origin: 50% 50%;" transform="matrix(0, 1, -1, 0, 0.000004, -0.000051)"/><ellipse cx="278.361" cy="56.089" rx="35.604" ry="32.308"/><path d="M 123.465 146.514 L 140.608 128.052"/><path d="M 148.52 97.722 L 134.014 138.602"/><path d="M 137.763 85.801 Q 140.895 51.242 178.448 85.801 L 237.459 140.109 Q 275.012 174.668 234.327 174.668 L 170.392 174.668 Q 129.707 174.668 132.84 140.109 Z" transform="matrix(0.991726, 0.128375, -0.128375, 0.991726, -3.073037, -6.853564)"/></svg>`;
const image = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
iconSVG
)}`;
const openHaven = () => {
const toolbox = document.getElementById("navigator-toolbox");
if (toolbox) {
toolbox.toggleAttribute("haven");
}
};
console.log("[ZenHaven] Toggle button added to sidebar bottom buttons");
const widget = {
id: "zen-haven",
type: "toolbarbutton",
label: "Zen Haven",
tooltip: "Zen Haven",
class: "toolbarbutton-1",
image,
callback: openHaven,
};
UC_API.Utils.createWidget(widget);
}
function startup() {
console.log("[ZenHaven] Startup sequence initiated.");
createToolboxObserver();
createHavenToggle();
}
if (gBrowserInit.delayedStartupFinished) {
console.log("[ZenHaven] Browser already started");
startup();
} else {
console.log("[ZenHaven] Waiting for browser startup");
let observer = new MutationObserver(() => {
if (gBrowserInit.delayedStartupFinished) {
console.log("[ZenHaven] Browser startup detected");
observer.disconnect();
startup();
}
});
observer.observe(document, { childList: true, subtree: true });
}
})();