Skip to content

feat: Nora settings injection #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
202199d
enhance[bm]: add portable settings tab
typeling1578 Jan 25, 2025
8ef7a8d
fix[bm]: prevent default action
typeling1578 Jan 25, 2025
153c281
fix[bm]: formatting css selectors
typeling1578 Jan 25, 2025
dc59cee
Merge branch 'develop' into nora-settings-injection
typeling1578 Jan 26, 2025
945064b
enhance[bm]: use iframe for portable settings
typeling1578 Jan 26, 2025
5327708
feat[bm]: add actors
typeling1578 Jan 26, 2025
fe140cb
fix[bm]: set interval to 0
typeling1578 Jan 26, 2025
76385fb
fix[bm]: auto resize iframe
typeling1578 Jan 27, 2025
82c845c
feat[bm]: add portable settings contents (part 1)
typeling1578 Jan 27, 2025
d12ae2d
refactor[bm]: Refactor portable settings
typeling1578 Jan 27, 2025
2edc6b1
enhance[bm]: add localizer actor
typeling1578 Jan 27, 2025
59dd335
fix[bm]: if localize id doesn't exist, return null
typeling1578 Jan 27, 2025
8eab945
feat[bm]: add portable-settings contents (part 2)
typeling1578 Jan 27, 2025
17834fc
feat[bm]: add toggle switch
typeling1578 Jan 29, 2025
d182e95
fix[bm]: link label to input
typeling1578 Jan 29, 2025
a71a88f
fix[bm]: add tab focus style
typeling1578 Jan 29, 2025
eee8070
chore[build]: remove 7-zip and jq binaries, use msys2 packages
typeling1578 Jan 30, 2025
cd94e36
Revert "chore[build]: remove 7-zip and jq binaries, use msys2 packages"
typeling1578 Jan 30, 2025
a934824
Merge branch 'develop' into nora-settings-injection
typeling1578 Jan 30, 2025
5d3f145
Merge branch 'develop' into nora-settings-injection
typeling1578 Jan 30, 2025
90fdfa3
fix[build]: remove maintenance service
typeling1578 Jan 31, 2025
6cd4f9b
Revert "fix[build]: remove maintenance service"
typeling1578 Jan 31, 2025
82bafba
Merge branch 'develop' into nora-settings-injection
typeling1578 Jan 31, 2025
f045c7c
Merge branch 'develop' into nora-settings-injection
typeling1578 Feb 2, 2025
27bf2ef
Merge branch 'develop' into nora-settings-injection
typeling1578 Feb 5, 2025
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
29 changes: 29 additions & 0 deletions src/browser-modules/PortableActors.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ActorManagerParent } from "resource://gre/modules/ActorManagerParent.sys.mjs";

const JSWINDOWACTORS = {
NoraPortableSettings: {
parent: {
esModuleURI: "resource:///modules/portable/actors/NoraPortableSettingsParent.sys.mjs",
},
child: {
esModuleURI: "resource:///modules/portable/actors/NoraPortableSettingsChild.sys.mjs",
events: {
DOMDocElementInserted: {},
DOMContentLoaded: { capture: true },
load: { capture: true },
unload: { capture: true },
pageshow: {},
visibilitychange: {},
},
},
matches: ["resource:///modules/portable/portable-settings/*"],
allFrames: true,
},
}

ActorManagerParent.addJSWindowActors(JSWINDOWACTORS);
49 changes: 47 additions & 2 deletions src/browser-modules/PortableInjections.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,28 @@ const documentObserver = {
uriWithoutQueryRef.startsWith("chrome://noraneko-settings/")
) {
const interval = window_.setInterval(async () => {
if (!document_.querySelector("#portable-tab-link")) {
const aboutTabLink = document_.querySelector('a[href="/about"]');

const cloned = aboutTabLink.cloneNode(true);
cloned.id = "portable-tab-link";
cloned.querySelector("div > p").innerText = "Portable Settings";
cloned.addEventListener("click", (e) => {
e.preventDefault();

if (window_.location.pathname != "/portable") {
window_.history.pushState({}, "", "/portable");
// invoke React Router handler
window_.dispatchEvent(new PopStateEvent("popstate"));
}
});

aboutTabLink.insertAdjacentElement("beforebegin", cloned);
}

if (window_.location.pathname == "/about") {
const aboutSectionTitle = document_.querySelector('img[alt="logo"][src="chrome://branding/content/[email protected]"] + p');
const aboutSectionVersionInfo = document_.querySelector('div:has(>img[alt="logo"][src="chrome://branding/content/[email protected]"]) + p');
const aboutSectionVersionInfo = document_.querySelector('div:has(> img[alt="logo"][src="chrome://branding/content/[email protected]"]) + p');
if (!aboutSectionTitle || !aboutSectionVersionInfo) {
return;
}
Expand All @@ -141,7 +160,33 @@ const documentObserver = {
aboutSectionVersionInfo.insertAdjacentElement("beforebegin", portableVersionInfo);
}
}
}, 1);

if (window_.location.pathname == "/portable") {
if (!document_.querySelector("#portable-content")) {
const contentParent = document_.querySelector('div:has(> div > a[href="/about"]) + div > div');

const portableContent = document_.createElement("div");
portableContent.id = "portable-content";

const iframe = document_.createElement("iframe");
iframe.src = "resource:///modules/portable/portable-settings/index.html";
iframe.style.width = "100%";
// iframe.addEventListener("load", () => {
// iframe.style.height = `${iframe.contentWindow.document.body.scrollHeight}px`;
// });
Services.obs.addObserver((subj) => {
const data = subj?.wrappedJSObject;
iframe.style.height = `${data.height}px`;
}, "portable-settings-on-document-size-changed");

portableContent.appendChild(iframe);

contentParent.appendChild(portableContent);
}
} else {
document_.querySelector("#portable-content")?.remove();
}
}, 0);

window_.addEventListener("unload", () => {
window_.clearInterval(interval);
Expand Down
6 changes: 6 additions & 0 deletions src/browser-modules/PortableStartup.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ if (PortableEnvironment.isMainBrowser) {
console.error(e);
}

try {
ChromeUtils.importESModule("resource:///modules/portable/PortableActors.sys.mjs");
} catch (e) {
console.error(e);
}

if (Services.prefs.getBoolPref("portable.update.enabled", false)) {
try {
ChromeUtils.importESModule("resource:///modules/portable/PortableUpdate.sys.mjs");
Expand Down
98 changes: 98 additions & 0 deletions src/browser-modules/actors/NoraPortableSettingsChild.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";

export class NoraPortableSettingsChild extends RemotePageChild {
actorCreated() {
super.actorCreated();
const window = this.contentWindow;
if (!window) {
return;
}

Cu.exportFunction(this.getBrowserPref.bind(this), window, {
defineAs: "getBrowserPref",
});

Cu.exportFunction(this.setBrowserPref.bind(this), window, {
defineAs: "setBrowserPref",
});

Cu.exportFunction(this.portableLocalize.bind(this), window, {
defineAs: "portableLocalize",
});

Cu.exportFunction(this.notifyBrowserObservers.bind(this), window, {
defineAs: "notifyBrowserObservers",
});
}

getBrowserPref({ prefName, prefType, prefDefaultValue }) {
const args = [];
args.push(prefName);
if (prefDefaultValue != undefined) {
args.push(prefDefaultValue);
}

switch (prefType) {
case "string":
return Services.prefs.getStringPref(...args);
case "boolean":
return Services.prefs.getBoolPref(...args);
case "integer":
return Services.prefs.getIntPref(...args);
default:
throw new Error("Invalid pref type");
}
}

#setBrowserPrefResolvers = [];

setBrowserPref(options) {
return this.wrapPromise(new Promise((resolve) => {
this.#setBrowserPrefResolvers.push(resolve);
this.sendAsyncMessage("SetPref", options);
}));
}

#portableLocalizeResolvers = [];

portableLocalize(options) {
return this.wrapPromise(new Promise((resolve) => {
this.#portableLocalizeResolvers.push(resolve);
this.sendAsyncMessage("PortableLocalize", options);
}));
}

#notifyBrowserObserversResolvers = [];

notifyBrowserObservers(options) {
return this.wrapPromise(new Promise((resolve) => {
this.#notifyBrowserObserversResolvers.push(resolve);
this.sendAsyncMessage("NotifyObservers", options);
}));
}

async receiveMessage(message) {
switch (message.name) {
case "SetPref": {
const resolver = this.#setBrowserPrefResolvers.shift();
resolver?.();
break;
}
case "PortableLocalize": {
const resolver = this.#portableLocalizeResolvers.shift();
resolver?.(message.data);
break;
}
case "NotifyObservers": {
const resolver = this.#notifyBrowserObserversResolvers.shift();
resolver?.();
break;
}
}
}
}
49 changes: 49 additions & 0 deletions src/browser-modules/actors/NoraPortableSettingsParent.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PortableI18nL10nLoader, PortableI18nLocalizer } from "resource:///modules/portable/PortableI18nUtils.sys.mjs";

export class NoraPortableSettingsParent extends JSWindowActorParent {
#localizer = null;

async receiveMessage(message) {
const data = message.data;

switch (message.name) {
case "SetPref":
switch (data.prefType) {
case "string":
Services.prefs.setStringPref(data.prefName, data.prefValue);
break;
case "boolean":
Services.prefs.setBoolPref(data.prefName, data.prefValue);
break;
case "integer":
Services.prefs.setIntPref(data.prefName, data.prefValue);
break;
}
this.sendAsyncMessage("SetPref");
break;
case "PortableLocalize":
if (!this.#localizer) {
const locales = await PortableI18nL10nLoader.load();
const availableLocales = Object.keys(locales);
this.#localizer = new PortableI18nLocalizer(
undefined,
availableLocales,
undefined,
locales,
);
}
const localized = this.#localizer.localize(data.id, data.args);
this.sendAsyncMessage("PortableLocalize", localized);
break;
case "NotifyObservers":
Services.obs.notifyObservers(data.data, data.topic);
this.sendAsyncMessage("NotifyObservers");
break;
}
}
}
58 changes: 58 additions & 0 deletions src/browser-modules/portable-settings/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
line-height: 1.5;
}

.toggle {
width: 34px;
height: 20px;
position: relative;
}

.toggle > input {
opacity: 0;
width: 100%;
height: 100%;
z-index: 9999;
position: absolute;
}

.toggle > .toggle-inner-bg {
background-color: rgba(128, 128, 128, 0.2);
width: 34px;
height: 20px;
position: absolute;
top: 0;
left: 0;
border-radius: 99px;
transition: background-color 0.15s ease;
}

.toggle > .toggle-inner-bg > .toggle-inner-potch {
background-color: white;
width: 16px;
height: 16px;
margin: 2px;
border-radius: 99px;
position: absolute;
top: 0;
left: 0;
transition: left 0.15s ease;
}

.toggle:has(> input:checked) > .toggle-inner-bg > .toggle-inner-potch {
left: 14px;
}

.toggle:has(> input:checked) > .toggle-inner-bg {
background-color: #3182f6;
}

.toggle:has(> input:focus-visible) > .toggle-inner-bg {
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
}
71 changes: 71 additions & 0 deletions src/browser-modules/portable-settings/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!DOCTYPE html>

<html>

<head>
<link rel="stylesheet" href="resource:///modules/portable/portable-settings/index.css">
<script src="resource:///modules/portable/portable-settings/index.js"></script>
<script src="resource:///modules/portable/tailwindcss/index.mjs"></script>
<style type="text/tailwindcss">
@layer components {
.card {
@apply rounded-md border px-[20px] py-[15px] flex flex-col border-[#e2e8f0] dark:border-[#ffffff]/16;
}
.card-header {
@apply flex items-center gap-2 mb-2;
}
.card-header-title {
@apply leading-[1.75em] text-[1.375rem];
}
.card-body {
@apply flex flex-col items-stretch gap-4 pl-[10px];
}
.card-body-subtitle {
@apply text-lg;
}
.card-body-item {
@apply w-full;
}
.card-body-item-main {
@apply flex justify-between;
}
.card-body-item-main-title {
@apply mr-3 mb-2 block;
}
.card-body-item-description {
@apply text-sm leading-normal text-[#4a5568] dark:text-[#ffffff]/48;
}
}
</style>
</head>

<body>
<div class="w-full max-w-[700px] mx-auto flex flex-col items-center py-8 box-border">
<p class="text-3xl mb-10 leading-normal">Portable Settings</p>
<p class="mb-8">description</p>
<div class="flex flex-col gap-6 w-full">
<div class="card">
<div class="card-header">
<p class="card-header-title">Basic Settings</p>
</div>
<div class="card-body">
<p class="card-body-subtitle">Updates</p>
<div class="card-body-item">
<div class="card-body-item-main">
<label for="pref-portable-update-auto" class="card-body-item-main-title" data-l10n-id="bm-pref-portable-update-auto-enabled"></label>
<div class="toggle">
<input id="pref-portable-update-auto" type="checkbox" data-preference="portable.update.auto">
<div class="toggle-inner-bg">
<div class="toggle-inner-potch"></div>
</div>
</div>
</div>
<div class="card-body-item-description">description</div>
</div>
</div>
</div>
</div>
</div>
</body>

</html>
Loading