|
@@ -0,0 +1,147 @@
|
|
|
|
|
+import Foundation
|
|
|
|
|
+
|
|
|
|
|
+enum RedditThemeStyle {
|
|
|
|
|
+ static func earlyInjectionScript(isDarkMode: Bool) -> String {
|
|
|
|
|
+ """
|
|
|
|
|
+ (function() {
|
|
|
|
|
+ const wantDark = \(isDarkMode ? "true" : "false");
|
|
|
|
|
+ window.__reddoraTheme = { wantsDark: wantDark };
|
|
|
|
|
+
|
|
|
|
|
+ const installMatchMediaOverride = (wantsDark) => {
|
|
|
|
|
+ if (window.__reddoraTheme.matchMediaInstalled) return;
|
|
|
|
|
+ const original = window.matchMedia.bind(window);
|
|
|
|
|
+ window.matchMedia = function(query) {
|
|
|
|
|
+ const result = original(query);
|
|
|
|
|
+ if (typeof query === 'string' && query.includes('prefers-color-scheme')) {
|
|
|
|
|
+ const matches = query.includes('dark') ? wantsDark : !wantsDark;
|
|
|
|
|
+ return {
|
|
|
|
|
+ matches,
|
|
|
|
|
+ media: query,
|
|
|
|
|
+ onchange: null,
|
|
|
|
|
+ addEventListener: (...args) => result.addEventListener(...args),
|
|
|
|
|
+ removeEventListener: (...args) => result.removeEventListener(...args),
|
|
|
|
|
+ dispatchEvent: (...args) => result.dispatchEvent(...args),
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ };
|
|
|
|
|
+ window.__reddoraTheme.matchMediaInstalled = true;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ installMatchMediaOverride(wantDark);
|
|
|
|
|
+ })();
|
|
|
|
|
+ """
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static func syncScript(isDarkMode: Bool) -> String {
|
|
|
|
|
+ """
|
|
|
|
|
+ (function() {
|
|
|
|
|
+ const wantDark = \(isDarkMode ? "true" : "false");
|
|
|
|
|
+ window.__reddoraTheme = window.__reddoraTheme || {};
|
|
|
|
|
+ window.__reddoraTheme.wantsDark = wantDark;
|
|
|
|
|
+
|
|
|
|
|
+ const installMatchMediaOverride = (wantsDark) => {
|
|
|
|
|
+ const original = window.matchMedia.bind(window);
|
|
|
|
|
+ window.matchMedia = function(query) {
|
|
|
|
|
+ const result = original(query);
|
|
|
|
|
+ if (typeof query === 'string' && query.includes('prefers-color-scheme')) {
|
|
|
|
|
+ const matches = query.includes('dark') ? wantsDark : !wantsDark;
|
|
|
|
|
+ return {
|
|
|
|
|
+ matches,
|
|
|
|
|
+ media: query,
|
|
|
|
|
+ onchange: null,
|
|
|
|
|
+ addEventListener: (...args) => result.addEventListener(...args),
|
|
|
|
|
+ removeEventListener: (...args) => result.removeEventListener(...args),
|
|
|
|
|
+ dispatchEvent: (...args) => result.dispatchEvent(...args),
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ };
|
|
|
|
|
+ window.__reddoraTheme.matchMediaInstalled = true;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ installMatchMediaOverride(wantDark);
|
|
|
|
|
+
|
|
|
|
|
+ const isDrawerHidden = (element) => !element || (element.offsetWidth === 0 && element.offsetHeight === 0);
|
|
|
|
|
+
|
|
|
|
|
+ const isRedditDarkModeActive = () => {
|
|
|
|
|
+ const header = document.querySelector('header');
|
|
|
|
|
+ const themedNode = header?.firstElementChild;
|
|
|
|
|
+ if (themedNode) {
|
|
|
|
|
+ const background = getComputedStyle(themedNode)
|
|
|
|
|
+ .getPropertyValue('--shreddit-content-background')
|
|
|
|
|
+ .trim()
|
|
|
|
|
+ .toLowerCase();
|
|
|
|
|
+ if (background === '#0e1113' || background === '#1a1a1b') return true;
|
|
|
|
|
+ if (background === '#ffffff' || background === '#fff') return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const root = document.documentElement;
|
|
|
|
|
+ if (root.classList.contains('theme-dark')) return true;
|
|
|
|
|
+ if (root.classList.contains('theme-light')) return false;
|
|
|
|
|
+
|
|
|
|
|
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const toggleViaDrawer = () => {
|
|
|
|
|
+ const drawerButton = document.querySelector('#expand-user-drawer-button');
|
|
|
|
|
+ const drawer = document.querySelector('#user-drawer-content');
|
|
|
|
|
+ const switchInput = document.querySelector(
|
|
|
|
|
+ '#darkmode-list-item faceplate-switch-input[name="darkmode-switch-name"]'
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!drawerButton || !drawer || !switchInput) return false;
|
|
|
|
|
+
|
|
|
|
|
+ const drawerWasHidden = isDrawerHidden(drawer);
|
|
|
|
|
+ if (drawerWasHidden) drawerButton.click();
|
|
|
|
|
+
|
|
|
|
|
+ const isChecked = switchInput.checked === true
|
|
|
|
|
+ || switchInput.getAttribute('checked') !== null
|
|
|
|
|
+ || switchInput.getAttribute('aria-checked') === 'true';
|
|
|
|
|
+ if (isChecked !== wantDark) {
|
|
|
|
|
+ switchInput.click();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (drawerWasHidden) {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (!isDrawerHidden(drawer)) drawerButton.click();
|
|
|
|
|
+ }, 250);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const syncViaAPI = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch('/api/v1/me/prefs', {
|
|
|
|
|
+ method: 'PATCH',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ credentials: 'same-origin',
|
|
|
|
|
+ body: JSON.stringify({ nightmode: wantDark }),
|
|
|
|
|
+ });
|
|
|
|
|
+ return response.ok;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const run = async () => {
|
|
|
|
|
+ const current = isRedditDarkModeActive();
|
|
|
|
|
+ if (current === wantDark) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (toggleViaDrawer()) return;
|
|
|
|
|
+
|
|
|
|
|
+ const updated = await syncViaAPI();
|
|
|
|
|
+ if (updated) {
|
|
|
|
|
+ window.location.reload();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (document.readyState === 'loading') {
|
|
|
|
|
+ document.addEventListener('DOMContentLoaded', () => run(), { once: true });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ run();
|
|
|
|
|
+ }
|
|
|
|
|
+ })();
|
|
|
|
|
+ """
|
|
|
|
|
+ }
|
|
|
|
|
+}
|