Repository: darkreader/darkreader
Pull request #3594 (2020-09-04 19:03:48)

make "today" indicator visible in asana calendar


Patch:
diff --git a/package-lock.json b/package-lock.json
index aa54d4f3..9a2df40c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "darkreader",
-  "version": "4.9.18",
+  "version": "4.9.19",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 63b756a1..0de536a9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "darkreader",
-  "version": "4.9.18",
+  "version": "4.9.19",
   "description": "Dark theme for every website",
   "scripts": {
     "api": "node tasks/build.js --api",
diff --git a/src/background/extension.ts b/src/background/extension.ts
index 239828e7..65190fc3 100644
--- a/src/background/extension.ts
+++ b/src/background/extension.ts
@@ -103,7 +103,6 @@ export class Extension {
 
         this.startAutoTimeCheck();
         this.news.subscribe();
-        this.user.cleanup();
     }
 
     private popupOpeningListener: () => void = null;
diff --git a/src/background/newsmaker.ts b/src/background/newsmaker.ts
index c3c0a74a..a6216b02 100644
--- a/src/background/newsmaker.ts
+++ b/src/background/newsmaker.ts
@@ -1,6 +1,7 @@
 import {getBlogPostURL} from '../utils/links';
 import {getDuration} from '../utils/time';
 import {News} from '../definitions';
+import {readSyncStorage, readLocalStorage, writeSyncStorage, writeLocalStorage} from './utils/extension-api';
 
 export default class Newsmaker {
     static UPDATE_INTERVAL = getDuration({hours: 4});
@@ -26,56 +27,58 @@ export default class Newsmaker {
         }
     }
 
+    private async getReadNews(): Promise<string[]> {
+        let sync = await readSyncStorage({readNews: []});
+        let local = await readLocalStorage({readNews: []});
+        return Array.from(new Set([
+            ...sync ? sync.readNews : [],
+            ...local ? local.readNews : [],
+        ]));
+    }
+
     private async getNews() {
         try {
             const response = await fetch(`https://darkreader.github.io/blog/posts.json?date=${(new Date()).toISOString().substring(0, 10)}`, {cache: 'no-cache'});
             const $news = await response.json();
-            return new Promise<News[]>((resolve, reject) => {
-                chrome.storage.sync.get({readNews: []}, ({readNews}) => {
-                    const news: News[] = $news.map(({id, date, headline, important}) => {
-                        const url = getBlogPostURL(id);
-                        const read = this.isRead(id, readNews);
-                        return {id, date, headline, url, important, read};
-                    });
-                    for (let i = 0; i < news.length; i++) {
-                        const date = new Date(news[i].date);
-                        if (isNaN(date.getTime())) {
-                            reject(new Error(`Unable to parse date ${date}`));
-                            return;
-                        }
-                    }
-                    resolve(news);
-                });
+            const readNews = await this.getReadNews();
+            const news: News[] = $news.map(({id, date, headline, important}) => {
+                const url = getBlogPostURL(id);
+                const read = this.isRead(id, readNews);
+                return {id, date, headline, url, important, read};
             });
+            for (let i = 0; i < news.length; i++) {
+                const date = new Date(news[i].date);
+                if (isNaN(date.getTime())) {
+                    throw new Error(`Unable to parse date ${date}`);
+                }
+            }
+            return news;
         } catch (err) {
             console.error(err);
             return null;
         }
     }
 
-    markAsRead(...ids: string[]) {
-        return new Promise((resolve) => {
-            chrome.storage.sync.get({readNews: []}, ({readNews}) => {
-                const results = readNews.slice();
-                let changed = false;
-                ids.forEach((id) => {
-                    if (readNews.indexOf(id) < 0) {
-                        results.push(id);
-                        changed = true;
-                    }
-                });
-                if (changed) {
-                    this.latest = this.latest.map(({id, date, url, headline, important}) => {
-                        const read = this.isRead(id, results);
-                        return {id, date, url, headline, important, read};
-                    });
-                    this.onUpdate(this.latest);
-                    chrome.storage.sync.set({readNews: results}, () => resolve());
-                } else {
-                    resolve();
-                }
-            });
+    async markAsRead(...ids: string[]) {
+        const readNews = await this.getReadNews();
+        const results = readNews.slice();
+        let changed = false;
+        ids.forEach((id) => {
+            if (readNews.indexOf(id) < 0) {
+                results.push(id);
+                changed = true;
+            }
         });
+        if (changed) {
+            this.latest = this.latest.map(({id, date, url, headline, important}) => {
+                const read = this.isRead(id, results);
+                return {id, date, url, headline, important, read};
+            });
+            this.onUpdate(this.latest);
+            const obj = {readNews: results};
+            await writeLocalStorage(obj);
+            await writeSyncStorage(obj);
+        }
     }
 
     isRead(id: string, readNews: string[]) {
diff --git a/src/background/user-storage.ts b/src/background/user-storage.ts
index a7742041..481e01a4 100644
--- a/src/background/user-storage.ts
+++ b/src/background/user-storage.ts
@@ -1,6 +1,8 @@
 import {DEFAULT_SETTINGS, DEFAULT_THEME} from '../defaults';
+import {debounce} from '../utils/debounce';
 import {isURLMatched} from '../utils/url';
 import {UserSettings} from '../definitions';
+import {readSyncStorage, readLocalStorage, writeSyncStorage, writeLocalStorage} from './utils/extension-api';
 
 const SAVE_TIMEOUT = 1000;
 
@@ -15,91 +17,71 @@ export default class UserStorage {
         this.settings = await this.loadSettingsFromStorage();
     }
 
-    cleanup() {
-        chrome.storage.local.remove(['activationTime', 'deactivationTime']);
-        chrome.storage.sync.remove(['activationTime', 'deactivationTime']);
+    private fillDefaults(settings: UserSettings) {
+        settings.theme = {...DEFAULT_THEME, ...settings.theme};
+        settings.time = {...DEFAULT_SETTINGS.time, ...settings.time};
+        settings.presets.forEach((preset) => {
+            preset.theme = {...DEFAULT_THEME, ...preset.theme};
+        });
+        settings.customThemes.forEach((site) => {
+            site.theme = {...DEFAULT_THEME, ...site.theme};
+        });
     }
 
-    private loadSettingsFromStorage() {
-        return new Promise<UserSettings>((resolve) => {
-            chrome.storage.local.get(DEFAULT_SETTINGS, (local: UserSettings) => {
-                local.syncSettings = local.syncSettings || DEFAULT_SETTINGS.syncSettings;
-                if (!local.syncSettings) {
-                    local.theme = {...DEFAULT_SETTINGS.theme, ...local.theme};
-                    local.time = {...DEFAULT_SETTINGS.time, ...local.time};
-                    local.customThemes.forEach((site) => {
-                        site.theme = {...DEFAULT_SETTINGS.theme, ...site.theme};
-                    });
-                    resolve(local);
-                    return;
-                }
+    private async loadSettingsFromStorage() {
+        const local = await readLocalStorage(DEFAULT_SETTINGS);
+        if (local.syncSettings == null) {
+            local.syncSettings = DEFAULT_SETTINGS.syncSettings;
+        }
+        if (!local.syncSettings) {
+            this.fillDefaults(local);
+            return local;
+        }
 
-                chrome.storage.sync.get({...DEFAULT_SETTINGS, config: 'empty'}, ($sync: UserSettings & {config: any}) => {
-                    let sync: UserSettings;
-                    if (!$sync) {
-                        this.saveSyncSetting(false);
-                        resolve(this.loadSettingsFromStorage());
-                        return;
-                    }
-                    if ($sync.config === 'empty') {
-                        delete $sync.config;
-                        sync = $sync;
-                    } else {
-                        sync = this.migrateSettings_4_6_2($sync) as UserSettings;
-                    }
-                    sync.theme = {...DEFAULT_SETTINGS.theme, ...sync.theme};
-                    sync.time = {...DEFAULT_SETTINGS.time, ...sync.time};
-                    sync.presets.forEach((preset) => {
-                        preset.theme = {...DEFAULT_SETTINGS.theme, ...preset.theme};
-                    });
-                    sync.customThemes.forEach((site) => {
-                        site.theme = {...DEFAULT_SETTINGS.theme, ...site.theme};
-                    });
-                    resolve(sync);
-                });
-            });
-        });
-    }
+        const $sync = await readSyncStorage(DEFAULT_SETTINGS);
+        if (!$sync) {
+            console.warn('Sync settings are missing');
+            local.syncSettings = false;
+            this.set({syncSettings: false});
+            this.saveSyncSetting(false);
+            return local;
+        }
 
-    async saveSettings() {
-        const saved = await this.saveSettingsIntoStorage(this.settings);
-        this.settings = saved;
+        const sync = await readSyncStorage(DEFAULT_SETTINGS);
+        this.fillDefaults(sync);
+        return sync;
     }
 
-    saveSyncSetting(sync: boolean) {
-        chrome.storage.sync.set({syncSettings: sync}, () => {
-            if (chrome.runtime.lastError) {
-                console.warn('Settings synchronization was disabled due to error:', chrome.runtime.lastError);
-            }
-        });
-        chrome.storage.local.set({syncSettings: sync});
+    async saveSettings() {
+        await this.saveSettingsIntoStorage();
     }
 
-    private saveSettingsIntoStorage(settings: UserSettings) {
-        if (this.timeout) {
-            clearInterval(this.timeout);
+    async saveSyncSetting(sync: boolean) {
+        const obj = {syncSettings: sync};
+        await writeLocalStorage(obj);
+        try {
+            await writeSyncStorage(obj);
+        } catch (err) {
+            console.warn('Settings synchronization was disabled due to error:', chrome.runtime.lastError);
+            this.set({syncSettings: false});
         }
-        return new Promise<UserSettings>((resolve) => {
-            this.timeout = setTimeout(() => {
-                this.timeout = null;
-                if (settings.syncSettings) {
-                    chrome.storage.sync.set(settings, () => {
-                        if (chrome.runtime.lastError) {
-                            console.warn('Settings synchronization was disabled due to error:', chrome.runtime.lastError);
-                            const local: UserSettings = {...settings, syncSettings: false};
-                            chrome.storage.local.set(local, () => resolve(local));
-                        } else {
-                            resolve(settings);
-                        }
-                    });
-                } else {
-                    chrome.storage.local.set(settings, () => resolve(settings));
-                }
-            }, SAVE_TIMEOUT);
-        });
     }
 
-    private timeout: number = null;
+    private saveSettingsIntoStorage = debounce(SAVE_TIMEOUT, async () => {
+        const settings = this.settings;
+        if (settings.syncSettings) {
+            try {
+                await writeSyncStorage(settings);
+            } catch (err) {
+                console.warn('Settings synchronization was disabled due to error:', chrome.runtime.lastError);
+                this.set({syncSettings: false});
+                await this.saveSyncSetting(false);
+                await writeLocalStorage(settings);
+            }
+        } else {
+            await writeLocalStorage(settings);
+        }
+    });
 
     set($settings: Partial<UserSettings>) {
         if ($settings.siteList) {
@@ -128,47 +110,4 @@ export default class UserStorage {
         }
         this.settings = {...this.settings, ...$settings};
     }
-
-    private migrateSettings_4_6_2(settings_4_6_2: any) {
-        function migrateTheme(filterConfig_4_6_2: any) {
-            const f = filterConfig_4_6_2;
-            return {
-                ...DEFAULT_THEME,
-                mode: f.mode,
-                brightness: f.brightness,
-                contrast: f.contrast,
-                grayscale: f.grayscale,
-                sepia: f.sepia,
-                useFont: f.useFont,
-                fontFamily: f.fontFamily,
-                textStroke: f.textStroke,
-                engine: f.engine,
-                stylesheet: f.stylesheet,
-            };
-        }
-
-        try {
-            const s = settings_4_6_2;
-            const settings: UserSettings = {
-                ...DEFAULT_SETTINGS,
-                enabled: s.enabled,
-                theme: migrateTheme(s.config),
-                customThemes: s.config.custom ? s.config.custom.map((c) => {
-                    return {
-                        url: c.url,
-                        theme: migrateTheme(c.config),
-                    };
-                }) : [],
-                siteList: s.config.siteList,
-                applyToListedOnly: s.config.invertListed,
-                changeBrowserTheme: s.config.changeBrowserTheme,
-            };
-            chrome.storage.sync.remove('config');
-            chrome.storage.sync.set(settings);
-            return settings;
-        } catch (err) {
-            console.error('Settings migration error:', err, 'Loaded settings:', settings_4_6_2);
-            return DEFAULT_SETTINGS;
-        }
-    }
 }
diff --git a/src/background/utils/extension-api.ts b/src/background/utils/extension-api.ts
index 84e409c9..78744552 100644
--- a/src/background/utils/extension-api.ts
+++ b/src/background/utils/extension-api.ts
@@ -31,6 +31,42 @@ export function canInjectScript(url: string) {
     );
 }
 
+export function readSyncStorage<T extends {[key: string]: any}>(defaults: T): Promise<T> {
+    return new Promise<T>((resolve) => {
+        chrome.storage.sync.get(defaults, (sync: T) => {
+            resolve(sync);
+        });
+    });
+}
+
+export function readLocalStorage<T extends {[key: string]: any}>(defaults: T): Promise<T> {
+    return new Promise<T>((resolve) => {
+        chrome.storage.local.get(defaults, (local: T) => {
+            resolve(local);
+        });
+    });
+}
+
+export function writeSyncStorage<T extends {[key: string]: any}>(values: T): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+        chrome.storage.sync.set(values, () => {
+            if (chrome.runtime.lastError) {
+                reject(chrome.runtime.lastError);
+                return;
+            }
+            resolve();
+        });
+    });
+}
+
+export function writeLocalStorage<T extends {[key: string]: any}>(values: T): Promise<void> {
+    return new Promise<void>((resolve) => {
+        chrome.storage.local.set(values, () => {
+            resolve();
+        });
+    });
+}
+
 export function getFontList() {
     return new Promise<string[]>((resolve) => {
         if (!chrome.fontSettings) {
diff --git a/src/config/dynamic-theme-fixes.config b/src/config/dynamic-theme-fixes.config
index e0aa7329..93008a0a 100644
--- a/src/config/dynamic-theme-fixes.config
+++ b/src/config/dynamic-theme-fixes.config
@@ -475,6 +475,13 @@ CSS
 
 ================================
 
+asana.com
+
+INVERT
+.DatePickerCalendarDate--today .DatePickerCalendarDate-button::after
+
+================================
+
 asus.com
 
 INVERT
@@ -1286,6 +1293,16 @@ video#vjs_video_3_html5_api.vjs-tech {
 
 ================================
 
+daum.net
+
+CSS
+.txt_pctop,
+.bg_login {
+    background-image: none !important;
+}
+
+================================
+
 debian.org
 
 CSS
@@ -3200,6 +3217,14 @@ INVERT
 
 ================================
 
+launchpad.net
+
+INVERT
+.edit-controls
+#launchpad-logo-and-name
+
+================================
+
 leetcode.com
 leetcode-cn.com
 
@@ -3346,6 +3371,14 @@ img {
 
 ================================
 
+m.dianping.com
+
+INVERT
+.flash-screen
+.header-download-img
+
+================================
+
 madshi.net
 
 CSS
@@ -4370,6 +4403,15 @@ body {
 
 ================================
 
+personal.vanguard.com
+
+CSS
+.hidePageIfJSdisabled {
+    display: block !important;
+}
+
+================================
+
 pgatour.com
 
 CSS
@@ -4441,6 +4483,13 @@ INVERT
 
 ================================
 
+portal.qiniu.com
+
+INVERT
+.global-loading-content img
+
+================================
+
 praca.money.pl
 
 CSS
@@ -4572,6 +4621,14 @@ CSS
 
 ================================
 
+qwant.com
+
+INVERT
+.background-home__logo
+.home__logo__container .home__logo
+
+================================
+
 rachel53461.wordpress.com
 
 CSS
@@ -4680,6 +4737,15 @@ CSS
 
 ================================
 
+resmigazete.gov.tr
+
+CSS
+img[src="/assets/img/arma.png"] {
+    filter: hue-rotate(180deg) invert(1) brightness(2);
+}
+
+================================
+
 richie-bendall.ml
 
 CSS
@@ -6097,6 +6163,21 @@ INVERT
 
 ================================
 
+vbulletin.com
+
+CSS
+.std {
+    background-color: var(--darkreader-neutral-background) !important;
+}
+.hero-text,
+#hometext li,
+#hometext h4,
+#hometext_buttons div p {
+    color: var(--darkreader-neutral-background) !important;
+}
+
+================================
+
 vc.ru
 
 INVERT
diff --git a/src/manifest.json b/src/manifest.json
index c45ab2fe..dfd92b42 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -1,7 +1,7 @@
 {
     "manifest_version": 2,
     "name": "Dark Reader",
-    "version": "4.9.18",
+    "version": "4.9.19",
     "author": "Alexander Shutau",
     "description": "__MSG_extension_description__",
     "default_locale": "en",
diff --git a/src/ui/popup/components/site-toggle/checkmark-icon.tsx b/src/ui/popup/components/site-toggle/checkmark-icon.tsx
index 60b9bcc7..a8b20e5b 100644
--- a/src/ui/popup/components/site-toggle/checkmark-icon.tsx
+++ b/src/ui/popup/components/site-toggle/checkmark-icon.tsx
@@ -1,10 +1,10 @@
 import {m} from 'malevic';
 
-export default function CheckmarkIcon({isEnabled}) {
+export default function CheckmarkIcon({isChecked}) {
     return (
         <svg viewBox="0 0 8 8">
             <path
-                d={(isEnabled ?
+                d={(isChecked ?
                     'M1,4 l2,2 l4,-4 v1 l-4,4 l-2,-2 Z' :
                     'M2,2 l4,4 v1 l-4,-4 Z M2,6 l4,-4 v1 l-4,4 Z'
                 )} />
diff --git a/src/ui/popup/components/site-toggle/index.tsx b/src/ui/popup/components/site-toggle/index.tsx
index 3be71794..58720081 100644
--- a/src/ui/popup/components/site-toggle/index.tsx
+++ b/src/ui/popup/components/site-toggle/index.tsx
@@ -32,12 +32,12 @@ export default function SiteToggleButton({data, tab, actions}: ExtWrapper & {tab
         <Button
             class={{
                 'site-toggle': true,
-                'site-toggle--active': pdf ? data.settings.enableForPDF : isSiteEnabled,
+                'site-toggle--active': isSiteEnabled,
                 'site-toggle--disabled': !toggleHasEffect
             }}
             onclick={onSiteToggleClick}
         >
-            <span class="site-toggle__mark"><CheckmarkIcon isEnabled={isSiteEnabled} /></span>
+            <span class="site-toggle__mark"><CheckmarkIcon isChecked={isSiteEnabled} /></span>
             {' '}
             <span class="site-toggle__url" >{pdf ? 'PDF' : urlText}</span>
         </Button>
diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts
new file mode 100644
index 00000000..2fb4464f
--- /dev/null
+++ b/src/utils/debounce.ts
@@ -0,0 +1,12 @@
+export function debounce<F extends (...args: any[]) => any>(delay: number, fn: F): F {
+    let timeoutId: number = null;
+    return ((...args: any[]) => {
+        if (timeoutId) {
+            clearTimeout(timeoutId);
+        }
+        timeoutId = setTimeout(() => {
+            timeoutId = null;
+            fn(...args);
+        }, delay);
+    }) as any;
+}
diff --git a/src/utils/url.ts b/src/utils/url.ts
index 3ffc5fb9..74345859 100644
--- a/src/utils/url.ts
+++ b/src/utils/url.ts
@@ -140,7 +140,7 @@ export function isURLEnabled(url: string, userSettings: UserSettings, {isProtect
     if (isProtected && !userSettings.enableForProtectedPages) {
         return false;
     }
-    if (isPDF(url) && userSettings.enableForPDF) {
+    if (isPDF(url)) {
         return userSettings.enableForPDF;
     }
     const isURLInUserList = isURLInList(url, userSettings.siteList);


Test patch:
diff --git a/tests/utils/url.tests.ts b/tests/utils/url.tests.ts
index 7ab7a334..ab969ca7 100644
--- a/tests/utils/url.tests.ts
+++ b/tests/utils/url.tests.ts
@@ -102,11 +102,21 @@ test('URL is enabled', () => {
         {siteList: ['darkreader.org'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
         {isProtected: false, isInDarkList: true},
     )).toBe(true);
+    expect(isURLEnabled(
+        'https://www.google.com/file.pdf',
+        {enableForPDF: true, siteList: ['darkreader.org'], siteListEnabled: [], applyToListedOnly: false} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toBe(true);
     expect(isURLEnabled(
         'https://www.google.com/file.pdf',
         {enableForPDF: true, siteList: ['darkreader.org'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,
         {isProtected: false, isInDarkList: false},
     )).toBe(true);
+    expect(isURLEnabled(
+        'https://www.google.com/file.pdf',
+        {enableForPDF: false, siteList: ['darkreader.org'], siteListEnabled: [], applyToListedOnly: false} as UserSettings,
+        {isProtected: false, isInDarkList: false},
+    )).toBe(false);
     expect(isURLEnabled(
         'https://www.google.com/file.pdf/resource',
         {enableForPDF: true, siteList: ['darkreader.org'], siteListEnabled: [], applyToListedOnly: true} as UserSettings,