264 lines
10 KiB
JavaScript
264 lines
10 KiB
JavaScript
(function () {
|
|
const { CSS_CLASSES, GLOBAL_ENV, IS_MOBILE } = window;
|
|
|
|
const BUTTON_ID = "rscord-settings-btn";
|
|
const CONTENT_CONTAINER_ID = "custom-content";
|
|
const SWITCH_CONTAINER_ID = "switch-container";
|
|
|
|
const CUSTOM_CSS_ID = "custom-css";
|
|
const CUSTOM_CSS_INPUT_ID = "custom-css-input";
|
|
|
|
const STATUS_COLOR_MAP = {
|
|
"hsl(38, calc(var(--saturation-factor, 1) * 95.7%), 54.1%)": "#FAA61A", // Idle (yellow)
|
|
"hsl(0, calc(var(--saturation-factor, 1) * 100%), 71.4%)": "#F04747", // DND (red)
|
|
"hsl(235, calc(var(--saturation-factor, 1) * 85.6%), 64.7%)": "#7289DA", // Away (blue)
|
|
"hsl(0, calc(var(--saturation-factor, 1) * 0%), 47.5%)": "#747F8D", // Offline (gray)
|
|
"hsl(139, calc(var(--saturation-factor, 1) * 47.3%), 43.9%)": "#43B581", // Online (green)
|
|
};
|
|
|
|
const TAB_CONTENT = `
|
|
<h2 class="${CSS_CLASSES.title}">RSCord Settings</h2>
|
|
<h5 class="${CSS_CLASSES.miniTitle}" style="margin-bottom: 10px">Options</h5>
|
|
<div id="${SWITCH_CONTAINER_ID}"></div>
|
|
<h5 class="${CSS_CLASSES.miniTitle}" style="margin-bottom: 10px">Custom CSS</h5>
|
|
<textarea id="${CUSTOM_CSS_INPUT_ID}" class="${CSS_CLASSES.textInput}" placeholder="CSS goes here" type="text" name="" style="resize: vertical;height: 400px;"></textarea>
|
|
`;
|
|
|
|
function addJoinDates() {
|
|
if (document.querySelector("[data-join-date]")) return;
|
|
|
|
const nitroBadge = document.querySelector(CSS_CLASSES.nitroBadge);
|
|
if (!nitroBadge) return;
|
|
|
|
const date = nitroBadge.getAttribute("aria-label");
|
|
if (date) {
|
|
const formattedDate = date.replace("Subscriber since ", "");
|
|
if (!formattedDate) return;
|
|
}
|
|
|
|
const isCompact = !!document.querySelector("h3.bodyTitle-2Az3VQ");
|
|
const h3Elements = document.querySelectorAll("h3");
|
|
|
|
let anchor = null;
|
|
for (let i = 0; i < h3Elements.length; i++) {
|
|
const h = h3Elements[i];
|
|
if ((h.className.indexOf("bodyTitle") > -1 && h.textContent.indexOf("Role") > -1) ||
|
|
h.textContent.trim() === "Note") {
|
|
anchor = h;
|
|
break;
|
|
}
|
|
}
|
|
if (!anchor) return;
|
|
|
|
const infoText = document.createElement("div");
|
|
infoText.className = isCompact ? CSS_CLASSES.userInfoTitle : CSS_CLASSES.userInfoSectionHeader;
|
|
infoText.textContent = "Member since";
|
|
infoText.style.color = "var(--header-secondary)";
|
|
infoText.dataset.joinDate = "title";
|
|
|
|
const dateText = document.createElement("div");
|
|
dateText.className = isCompact ? CSS_CLASSES.userInfoBody : CSS_CLASSES.userInfoText;
|
|
dateText.textContent = formattedDate;
|
|
dateText.dataset.joinDate = "value";
|
|
|
|
if (isCompact) {
|
|
let infoSection = anchor.previousElementSibling;
|
|
const isNewSection = !infoSection;
|
|
|
|
if (isNewSection) {
|
|
infoSection = document.createElement("div");
|
|
infoSection.className = CSS_CLASSES.userInfoSection;
|
|
anchor.parentNode.insertBefore(infoSection, anchor);
|
|
}
|
|
|
|
infoText.style.marginTop = isNewSection ? "0px" : "10px";
|
|
infoSection.appendChild(infoText);
|
|
infoSection.appendChild(dateText);
|
|
} else {
|
|
anchor.parentNode.insertBefore(infoText, anchor);
|
|
anchor.parentNode.insertBefore(dateText, anchor);
|
|
}
|
|
}
|
|
|
|
async function addSwitches(container) {
|
|
const config = loadConfig();
|
|
const pushRegistered = await isPushRegistered();
|
|
|
|
container.appendChild(createSwitch(
|
|
"Push Notifications",
|
|
"Sends you notifications even when you close the app",
|
|
"push-notifications",
|
|
pushRegistered,
|
|
async (checked) => {
|
|
const publicKey = GLOBAL_ENV.VAPID_KEY;
|
|
if (!publicKey) return false;
|
|
try {
|
|
await (checked ? registerPush(publicKey) : unregisterPush());
|
|
return true;
|
|
} catch(error) {
|
|
alert("Failed to register notifications: ", error.message)
|
|
return false;
|
|
}
|
|
}
|
|
));
|
|
|
|
container.appendChild(createSwitch(
|
|
"Revert Status Icons",
|
|
"Revert the colorblind status icons added in 2019",
|
|
"revert-status",
|
|
config.revertStatusIcons,
|
|
(checked) => saveConfig({ revertStatusIcons: checked })
|
|
));
|
|
}
|
|
|
|
function revertStatusIcons() {
|
|
const statusRects = document.querySelectorAll("rect[fill^='hsl']");
|
|
for (const rect of statusRects) {
|
|
const fill = rect.getAttribute("fill");
|
|
if (STATUS_COLOR_MAP[fill]) {
|
|
rect.setAttribute("fill", STATUS_COLOR_MAP[fill]);
|
|
}
|
|
|
|
const maskRef = rect.getAttribute("mask");
|
|
if (!maskRef) continue;
|
|
|
|
const maskMatch = maskRef.match(/#([^)]+)/);
|
|
const maskId = maskMatch ? maskMatch[1] : null;
|
|
if (!maskId) continue;
|
|
|
|
const mask = document.getElementById(maskId);
|
|
if (!mask) continue;
|
|
|
|
mask.querySelectorAll("[fill='black']").forEach(el => el.remove());
|
|
}
|
|
}
|
|
|
|
function applyCustomCss() {
|
|
const { customCss } = loadConfig();
|
|
|
|
let style = document.getElementById(CUSTOM_CSS_ID);
|
|
if (!style) {
|
|
style = document.createElement("style");
|
|
style.id = CUSTOM_CSS_ID;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
style.textContent = customCss;
|
|
}
|
|
|
|
function setupCssInput(textarea) {
|
|
if (!textarea) return;
|
|
|
|
const config = loadConfig();
|
|
textarea.value = config.customCss;
|
|
|
|
const style = document.getElementById(CUSTOM_CSS_ID);
|
|
if (!style) return;
|
|
|
|
textarea.addEventListener("input", () => {
|
|
style.textContent = textarea.value;
|
|
saveConfig({ customCss: textarea.value });
|
|
});
|
|
}
|
|
|
|
function addSettingsTab() {
|
|
const advancedTab = document.querySelector(`.${CSS_CLASSES.tabItem}[aria-controls="advanced-tab"]`);
|
|
if (!advancedTab) return;
|
|
if (document.getElementById(BUTTON_ID)) return;
|
|
|
|
const settingsBtn = document.createElement("div");
|
|
settingsBtn.id = BUTTON_ID;
|
|
settingsBtn.className = advancedTab.className;
|
|
settingsBtn.role = "tab";
|
|
settingsBtn.tabIndex = -1;
|
|
settingsBtn.ariaSelected = "false";
|
|
settingsBtn.textContent = "RSCord Settings";
|
|
|
|
settingsBtn.addEventListener("click", () => {
|
|
const tabs = settingsBtn.parentElement.querySelectorAll("[role='tab']");
|
|
tabs.forEach(tab => tab.classList.remove(CSS_CLASSES.selected));
|
|
tabs.forEach(tab => tab.setAttribute("aria-selected", "false"));
|
|
settingsBtn.classList.add(CSS_CLASSES.selected);
|
|
settingsBtn.setAttribute("aria-selected", "true");
|
|
|
|
let customContent = document.getElementById(CONTENT_CONTAINER_ID);
|
|
if (!customContent) {
|
|
customContent = document.createElement("div");
|
|
customContent.id = CONTENT_CONTAINER_ID;
|
|
customContent.innerHTML = TAB_CONTENT;
|
|
|
|
const switchContainer = customContent.querySelector(`#${SWITCH_CONTAINER_ID}`);
|
|
addSwitches(switchContainer);
|
|
|
|
document.querySelector(`.${CSS_CLASSES.contentColumn}`).appendChild(customContent);
|
|
|
|
const cssInput = document.getElementById(CUSTOM_CSS_INPUT_ID);
|
|
setupCssInput(cssInput);
|
|
}
|
|
|
|
const contentColumn = document.querySelector(`.${CSS_CLASSES.contentColumn}`);
|
|
Array.from(contentColumn.children).forEach(child => {
|
|
if (child.id !== CONTENT_CONTAINER_ID) child.style.display = "none";
|
|
});
|
|
customContent.style.display = "block";
|
|
});
|
|
|
|
advancedTab.insertAdjacentElement("afterend", settingsBtn);
|
|
return true;
|
|
}
|
|
|
|
const observer = new MutationObserver(() => {
|
|
if (document.querySelector(`.${CSS_CLASSES.settingsSidebar}`)) addSettingsTab();
|
|
if (loadConfig().revertStatusIcons) revertStatusIcons();
|
|
addJoinDates();
|
|
});
|
|
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
applyCustomCss();
|
|
|
|
document.body.addEventListener("click", (e) => {
|
|
const clickedHamburger = e.target.closest(`.${CSS_CLASSES.hamburger}`)
|
|
if (clickedHamburger && IS_MOBILE) {
|
|
const settingsRegion = document.querySelector(`.${CSS_CLASSES.settingsRegion}`)
|
|
settingsRegion.classList.remove(CSS_CLASSES.hidden);
|
|
|
|
const contentRegion = document.querySelector(`.${CSS_CLASSES.contentRegion}`)
|
|
contentRegion.classList.add(CSS_CLASSES.hidden);
|
|
}
|
|
|
|
const clickedTab = e.target.closest("[role='tab']");
|
|
if (!clickedTab) return;
|
|
|
|
const allTabs = document.querySelectorAll("[role='tab']");
|
|
allTabs.forEach(tab => tab.classList.remove(CSS_CLASSES.selected));
|
|
allTabs.forEach(tab => tab.setAttribute("aria-selected", "false"));
|
|
clickedTab.classList.add(CSS_CLASSES.selected);
|
|
clickedTab.setAttribute("aria-selected", "true");
|
|
|
|
const contentColumn = document.querySelector(`.${CSS_CLASSES.contentColumn}`);
|
|
if (!contentColumn) return;
|
|
|
|
if (clickedTab.id === BUTTON_ID) {
|
|
Array.from(contentColumn.children).forEach(child => {
|
|
if (child.id === CONTENT_CONTAINER_ID) {
|
|
child.style.display = "block";
|
|
} else {
|
|
child.style.display = "none";
|
|
}
|
|
|
|
if (IS_MOBILE) {
|
|
const settingsRegion = document.querySelector(`.${CSS_CLASSES.settingsRegion}`)
|
|
settingsRegion.classList.add(CSS_CLASSES.hidden);
|
|
|
|
const contentRegion = document.querySelector(`.${CSS_CLASSES.contentRegion}`)
|
|
contentRegion.classList.remove(CSS_CLASSES.hidden);
|
|
}
|
|
});
|
|
} else {
|
|
Array.from(contentColumn.children).forEach(child => {
|
|
if (child.id !== CONTENT_CONTAINER_ID) child.style.display = "";
|
|
else child.style.display = "none";
|
|
});
|
|
}
|
|
});
|
|
})(); |