Skip to content

Conversation

@nin0-dev
Copy link
Contributor

@nin0-dev nin0-dev commented Nov 2, 2025

ishare-1762337280-google chrome ishare-1762337312-google chrome

@nin0-dev nin0-dev changed the title update extension icon add chrome extension popup Nov 2, 2025
@Vendicated
Copy link
Owner

aislop

@nin0-dev nin0-dev force-pushed the ext-popout branch 3 times, most recently from 64f3636 to c4bcd26 Compare November 3, 2025 01:44
@nin0-dev nin0-dev marked this pull request as ready for review November 3, 2025 09:49
@nin0-dev
Copy link
Contributor Author

nin0-dev commented Nov 3, 2025

this should be ready, @paigely i played with the style a bit you might want to take a look

@paigely
Copy link

paigely commented Nov 4, 2025

this should be ready, @paigely i played with the style a bit you might want to take a look

looks great!!

document.querySelector("#clear-css").addEventListener("click", async () => {
if (
confirm(
"This will disable QuickCSS, any added themes and will remove all online themes. Do you want to continue?"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't actually do what it claims to do

if (!rawSettings) return;
const settings = JSON.parse(rawSettings);
for (const name in settings.plugins) {
settings.plugins[name].enabled = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this, bad user experience. We should just add a safe mode

also marking settings as dirty (i.e. syncing to cloud) makes it even worse because this is definitely not what the user wants

});
document
.querySelector("#import-settings")
.addEventListener("click", async () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like all this essentially duplicated from Vencord code, it hurts maintainability drastically. These should be in Vencord's source and exported on Vencord's global object

remote: await (async () => {
let remote = process.env.VENCORD_REMOTE;
if (!remote) {
const res = await promisify(exec)(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be here, it should be in the same place as the git hash

buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png", "icon-popup.webp", ...["html", "css", "js"].map(e => `popup.${e}`)]),
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifest.json", "icon.png", "icon-popup.webp", ...["html", "css", "js"].map(e => `popup.${e}`)]),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't do this cursed map

also since you deleted the firefox specific manifest, the background script is now completely unused and firefox has no csp bypass anymore

}
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very bad layout. You want grid, not flex.

<div class="button-grid">
    <button>One</button>
    <button>Two</button>
    <button>Three</button>
    <button>Four</button>
    <button class="full-width">Five</button>
</div>
.button-grid {
  display: grid;
  gap: 1em;
  grid-template-columns: repeat(2, 1fr);

  .full-width {
      grid-column: 1 / -1;
  }
}
Image

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the class, you could even do this to do it automatically:

.button-grid {
  display: grid;
  gap: 1em;
  grid-template-columns: repeat(2, 1fr);

  > :last-child:nth-child(odd) {
      grid-column: 1 / -1;
  }
}

active: true,
lastFocusedWindow: true
});
if (!tab.url) return undefined; // not a discord tab since it doesn't have the permissions
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!tab.url) return undefined; // not a discord tab since it doesn't have the permissions
if (!tab.url) return null; // not a discord tab since it doesn't have the permissions

const api = globalThis.browser || globalThis.chrome;

async function getDiscordTab() {
let [tab] = await chrome.tabs.query({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this use chrome instead of the api variable?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it's vibecoded

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HORROR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it's vibecoded

true

@@ -0,0 +1,269 @@
const api = globalThis.browser || globalThis.chrome;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use something like this then always refer to browser

Suggested change
const api = globalThis.browser || globalThis.chrome;
if (typeof browser === "undefined") {
var browser = chrome;
}

@Vendicated
Copy link
Owner

Other than that very cute idea and ui!

@Vendicated
Copy link
Owner

abandonware

@nin0-dev
Copy link
Contributor Author

nin0-dev commented Dec 6, 2025

soon

@Vendicated
Copy link
Owner

Vendicated commented Dec 7, 2025

This is not just abandonware — this is straight up abandonedware

@thororen1234
Copy link
Contributor

but soontm vee

@nin0-dev
Copy link
Contributor Author

nin0-dev commented Dec 7, 2025

do copilot review @Vendicated

@Vendicated
Copy link
Owner

@copilot finish this pr

@Vendicated Vendicated requested a review from Copilot December 7, 2025 21:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Chrome extension popup interface for Vencord Web, providing users with quick access to settings management and information about their installation. The popup displays version info, git commit hash, and action buttons for common tasks like importing/exporting settings, disabling plugins, and resetting themes.

Key Changes:

  • Added popup UI with HTML, CSS, and JavaScript for managing Vencord settings
  • Updated build script to generate and include metadata.json with version and repository information
  • Modified manifest.json to include popup configuration and necessary permissions
  • Removed manifestv2.json in favor of unified manifest.json for both Chrome and Firefox

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
browser/popup.html Popup HTML structure with version display and action buttons
browser/popup.css Styling for the popup with gruvbox-inspired color scheme
browser/popup.js Popup functionality including settings import/export, plugin management, and theme reset
browser/icon-popup.webp New popup icon asset
browser/icon.png Updated extension icon
scripts/build/buildWeb.mjs Build script updated to generate metadata.json and include popup files in extension packages
browser/manifest.json Added popup action and scripting permission
browser/manifestv2.json Removed deprecated manifest v2 file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const api = globalThis.browser || globalThis.chrome;

async function getDiscordTab() {
let [tab] = await chrome.tabs.query({
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent API usage: The function uses chrome.tabs.query but the global API reference is stored as api which could be either browser or chrome. Consider using api.tabs.query for consistency with the established pattern.

Suggested change
let [tab] = await chrome.tabs.query({
let [tab] = await api.tabs.query({

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TRUEEE I TOLD YOU

Comment on lines +36 to +268
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
const rawSettings = localStorage.getItem("VencordSettings");
if (!rawSettings) return;
const settings = JSON.parse(rawSettings);
settings.useQuickCss = false;
localStorage.setItem(
"VencordSettings",
JSON.stringify(settings)
);
localStorage.setItem("Vencord_settingsDirty", "true");

window.location.reload();
},
args: []
});
}
});
document
.querySelector("#disable-plugins")
.addEventListener("click", async () => {
if (
confirm(
"This will disable all plugins and reload Discord. Do you want to continue?"
)
) {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
const rawSettings =
localStorage.getItem("VencordSettings");
if (!rawSettings) return;
const settings = JSON.parse(rawSettings);
for (const name in settings.plugins) {
settings.plugins[name].enabled = false;
}
localStorage.setItem(
"VencordSettings",
JSON.stringify(settings)
);
localStorage.setItem("Vencord_settingsDirty", "true");

window.location.reload();
},
args: []
});
}
});
document.querySelector("#reset").addEventListener("click", async () => {
if (
prompt(
'This will clear all your Vencord settings in this browser, including plugins, themes and QuickCSS! Make sure that you have a backup before continuing. To continue, type "YES".'
) === "YES"
) {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
localStorage.removeItem("VencordSettings");
localStorage.removeItem("Vencord_settingsDirty");
indexedDB.deleteDatabase("VencordData");
indexedDB.deleteDatabase("VencordThemes");
window.location.reload();
},
args: []
});
}
});
document
.querySelector("#export-settings")
.addEventListener("click", async () => {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: async () => {
function getVencordQuickCss() {
return new Promise((resolve, reject) => {
const req = indexedDB.open("VencordData");

req.onerror = () => reject(req.error);

req.onsuccess = () => {
const db = req.result;
const tx = db.transaction(
"VencordStore",
"readonly"
);
const store = tx.objectStore("VencordStore");
const getReq = store.get("VencordQuickCss");

getReq.onsuccess = () => resolve(getReq.result);
getReq.onerror = () => reject(getReq.error);
};
});
}

const content = localStorage.getItem("VencordSettings");
let quickCss = "";
try {
quickCss = await getVencordQuickCss();
} catch {}

if (content) {
const filename = `vencord-settings-backup-${
new Date().toISOString().split("T")[0]
}.json`;

const a = document.createElement("a");
a.href = URL.createObjectURL(
new File(
[
new TextEncoder().encode(
JSON.stringify(
{
settings: JSON.parse(content),
quickCss: quickCss || ""
},
null,
4
)
)
],
filename,
{ type: "application/json" }
)
);
a.download = filename;

document.body.appendChild(a);
a.click();
setImmediate(() => {
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
});
}
},
args: []
});
});
document
.querySelector("#import-settings")
.addEventListener("click", async () => {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: async () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async (event) => {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = async (e) => {
const content = e.target.result;
try {
const data = JSON.parse(content);
localStorage.setItem(
"VencordSettings",
JSON.stringify(data.settings)
);
localStorage.setItem(
"Vencord_settingsDirty",
"true"
);

try {
await new Promise((resolve, reject) => {
const req = indexedDB.open(
"VencordData",
1
);
req.onupgradeneeded = () => {
const db = req.result;
if (
!db.objectStoreNames.contains(
"VencordStore"
)
) {
db.createObjectStore(
"VencordStore"
);
}
};
req.onerror = () => reject(req.error);
req.onsuccess = () => {
const db = req.result;
const tx = db.transaction(
"VencordStore",
"readwrite"
);
const store =
tx.objectStore("VencordStore");
const putReq = store.put(
data.quickCss || "",
"VencordQuickCss"
);
putReq.onsuccess = () => resolve();
putReq.onerror = () =>
reject(putReq.error);
tx.oncomplete = () => db.close();
tx.onerror = () => reject(tx.error);
};
});
} catch {}

window.location.reload();
} catch (error) {
alert(
"Failed to import settings. Is this even a Vencord Settings file?"
);
}
};
reader.readAsText(file);
};
input.click();
},
args: []
});
});
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent API usage: All instances of chrome.scripting.executeScript and chrome.tabs.query should use the api constant defined at the top of the file for cross-browser compatibility (supporting both Chrome and Firefox).

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +267
args: []
});
}
});
document
.querySelector("#disable-plugins")
.addEventListener("click", async () => {
if (
confirm(
"This will disable all plugins and reload Discord. Do you want to continue?"
)
) {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
const rawSettings =
localStorage.getItem("VencordSettings");
if (!rawSettings) return;
const settings = JSON.parse(rawSettings);
for (const name in settings.plugins) {
settings.plugins[name].enabled = false;
}
localStorage.setItem(
"VencordSettings",
JSON.stringify(settings)
);
localStorage.setItem("Vencord_settingsDirty", "true");

window.location.reload();
},
args: []
});
}
});
document.querySelector("#reset").addEventListener("click", async () => {
if (
prompt(
'This will clear all your Vencord settings in this browser, including plugins, themes and QuickCSS! Make sure that you have a backup before continuing. To continue, type "YES".'
) === "YES"
) {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
localStorage.removeItem("VencordSettings");
localStorage.removeItem("Vencord_settingsDirty");
indexedDB.deleteDatabase("VencordData");
indexedDB.deleteDatabase("VencordThemes");
window.location.reload();
},
args: []
});
}
});
document
.querySelector("#export-settings")
.addEventListener("click", async () => {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: async () => {
function getVencordQuickCss() {
return new Promise((resolve, reject) => {
const req = indexedDB.open("VencordData");

req.onerror = () => reject(req.error);

req.onsuccess = () => {
const db = req.result;
const tx = db.transaction(
"VencordStore",
"readonly"
);
const store = tx.objectStore("VencordStore");
const getReq = store.get("VencordQuickCss");

getReq.onsuccess = () => resolve(getReq.result);
getReq.onerror = () => reject(getReq.error);
};
});
}

const content = localStorage.getItem("VencordSettings");
let quickCss = "";
try {
quickCss = await getVencordQuickCss();
} catch {}

if (content) {
const filename = `vencord-settings-backup-${
new Date().toISOString().split("T")[0]
}.json`;

const a = document.createElement("a");
a.href = URL.createObjectURL(
new File(
[
new TextEncoder().encode(
JSON.stringify(
{
settings: JSON.parse(content),
quickCss: quickCss || ""
},
null,
4
)
)
],
filename,
{ type: "application/json" }
)
);
a.download = filename;

document.body.appendChild(a);
a.click();
setImmediate(() => {
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
});
}
},
args: []
});
});
document
.querySelector("#import-settings")
.addEventListener("click", async () => {
const tab = await getDiscordTab();
await chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: async () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async (event) => {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = async (e) => {
const content = e.target.result;
try {
const data = JSON.parse(content);
localStorage.setItem(
"VencordSettings",
JSON.stringify(data.settings)
);
localStorage.setItem(
"Vencord_settingsDirty",
"true"
);

try {
await new Promise((resolve, reject) => {
const req = indexedDB.open(
"VencordData",
1
);
req.onupgradeneeded = () => {
const db = req.result;
if (
!db.objectStoreNames.contains(
"VencordStore"
)
) {
db.createObjectStore(
"VencordStore"
);
}
};
req.onerror = () => reject(req.error);
req.onsuccess = () => {
const db = req.result;
const tx = db.transaction(
"VencordStore",
"readwrite"
);
const store =
tx.objectStore("VencordStore");
const putReq = store.put(
data.quickCss || "",
"VencordQuickCss"
);
putReq.onsuccess = () => resolve();
putReq.onerror = () =>
reject(putReq.error);
tx.oncomplete = () => db.close();
tx.onerror = () => reject(tx.error);
};
});
} catch {}

window.location.reload();
} catch (error) {
alert(
"Failed to import settings. Is this even a Vencord Settings file?"
);
}
};
reader.readAsText(file);
};
input.click();
},
args: []
});
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused args parameter: The args: [] parameter is consistently passed but never used in any of the injected functions. Consider removing it for cleaner code.

Copilot uses AI. Check for mistakes.
],

"permissions": ["declarativeNetRequest"],
"permissions": ["scripting", "declarativeNetRequest"],
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "scripting" permission is added to support the popup functionality (executeScript API), but the "activeTab" permission would be more appropriate and privacy-friendly. The "activeTab" permission only grants temporary access to the current tab when the user invokes the extension (e.g., clicking the popup), rather than requiring broad scripting permissions across all tabs.

Suggested change
"permissions": ["scripting", "declarativeNetRequest"],
"permissions": ["activeTab", "declarativeNetRequest"],

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
<p>Vencord</p>
<p id="local-version">v</p>
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantic HTML issue: Using <p> tags within an <h1> for the version and commit hash is semantically incorrect. The <p> element represents a paragraph and shouldn't be nested inside a heading. Consider using <span> elements instead, which are appropriate for inline text within headings.

Suggested change
<p>Vencord</p>
<p id="local-version">v</p>
<span>Vencord</span>
<span id="local-version">v</span>

Copilot uses AI. Check for mistakes.
@paigely
Copy link

paigely commented Dec 8, 2025

thanks for training copilot on some of my work vee

@Vendicated
Copy link
Owner

ywyw

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants