aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview
diff options
context:
space:
mode:
Diffstat (limited to 'src/webview')
-rw-r--r--src/webview/badge.ts2
-rw-r--r--src/webview/contextMenuBuilder.js248
-rw-r--r--src/webview/darkmode.ts6
-rw-r--r--src/webview/lib/RecipeWebview.js25
-rw-r--r--src/webview/lib/Userscript.js10
-rw-r--r--src/webview/recipe.js41
-rw-r--r--src/webview/sessionHandler.ts17
-rw-r--r--src/webview/spellchecker.ts2
8 files changed, 198 insertions, 153 deletions
diff --git a/src/webview/badge.ts b/src/webview/badge.ts
index 024e29b3f..8e8b66c0c 100644
--- a/src/webview/badge.ts
+++ b/src/webview/badge.ts
@@ -21,7 +21,7 @@ export class BadgeHandler {
21 // Parse number to integer 21 // Parse number to integer
22 // This will correct errors that recipes may introduce, e.g. 22 // This will correct errors that recipes may introduce, e.g.
23 // by sending a String instead of an integer 23 // by sending a String instead of an integer
24 const parsedNumber = parseInt(text.toString(), 10); 24 const parsedNumber = Number.parseInt(text.toString(), 10);
25 const adjustedNumber = Number.isNaN(parsedNumber) ? 0 : parsedNumber; 25 const adjustedNumber = Number.isNaN(parsedNumber) ? 0 : parsedNumber;
26 return Math.max(adjustedNumber, 0); 26 return Math.max(adjustedNumber, 0);
27 } 27 }
diff --git a/src/webview/contextMenuBuilder.js b/src/webview/contextMenuBuilder.js
index 8c39e6d04..eb15aa138 100644
--- a/src/webview/contextMenuBuilder.js
+++ b/src/webview/contextMenuBuilder.js
@@ -6,7 +6,8 @@
6 * 6 *
7 * Source: https://github.com/electron-userland/electron-spellchecker/blob/master/src/context-menu-builder.js 7 * Source: https://github.com/electron-userland/electron-spellchecker/blob/master/src/context-menu-builder.js
8 */ 8 */
9import { clipboard, ipcRenderer, nativeImage } from 'electron'; 9// eslint-disable-next-line no-unused-vars
10import { clipboard, ipcRenderer, nativeImage, WebContents } from 'electron';
10import { Menu, MenuItem } from '@electron/remote'; 11import { Menu, MenuItem } from '@electron/remote';
11import { cmdOrCtrlShortcutKey, isMac } from '../environment'; 12import { cmdOrCtrlShortcutKey, isMac } from '../environment';
12 13
@@ -16,7 +17,8 @@ import { openExternalUrl } from '../helpers/url-helpers';
16const { URL } = require('url'); 17const { URL } = require('url');
17 18
18function matchesWord(string) { 19function matchesWord(string) {
19 const regex = /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+/g; 20 const regex =
21 /[A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+/g;
20 22
21 return string.match(regex); 23 return string.match(regex);
22} 24}
@@ -56,12 +58,12 @@ module.exports = class ContextMenuBuilder {
56 /** 58 /**
57 * Creates an instance of ContextMenuBuilder 59 * Creates an instance of ContextMenuBuilder
58 * 60 *
59 * @param {webContents} webContents Current webContents 61 * @param {WebContents} webContents Current webContents
60 * @param {Boolean} debugMode If true, display the "Inspect Element" menu item. 62 * @param {Boolean} debugMode If true, display the "Inspect Element" menu item.
61 * @param {function} processMenu If passed, this method will be passed the menu to change 63 * @param {function} processMenu If passed, this method will be passed the menu to change
62 * it prior to display. Signature: (menu, info) => menu 64 * it prior to display. Signature: (menu, info) => menu
63 */ 65 */
64 constructor(webContents, debugMode = false, processMenu = (m) => m) { 66 constructor(webContents, debugMode = false, processMenu = m => m) {
65 this.debugMode = debugMode; 67 this.debugMode = debugMode;
66 this.processMenu = processMenu; 68 this.processMenu = processMenu;
67 this.menu = null; 69 this.menu = null;
@@ -115,7 +117,10 @@ module.exports = class ContextMenuBuilder {
115 return this.buildMenuForImage(info); 117 return this.buildMenuForImage(info);
116 } 118 }
117 119
118 if (info.isEditable || (info.inputFieldType && info.inputFieldType !== 'none')) { 120 if (
121 info.isEditable ||
122 (info.inputFieldType && info.inputFieldType !== 'none')
123 ) {
119 return this.buildMenuForTextInput(info); 124 return this.buildMenuForTextInput(info);
120 } 125 }
121 126
@@ -157,12 +162,17 @@ module.exports = class ContextMenuBuilder {
157 const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); 162 const isEmailAddress = menuInfo.linkURL.startsWith('mailto:');
158 163
159 const copyLink = new MenuItem({ 164 const copyLink = new MenuItem({
160 label: isEmailAddress ? this.stringTable.copyMail() : this.stringTable.copyLinkUrl(), 165 label: isEmailAddress
166 ? this.stringTable.copyMail()
167 : this.stringTable.copyLinkUrl(),
161 click: () => { 168 click: () => {
162 // Omit the mailto: portion of the link; we just want the address 169 // Omit the mailto: portion of the link; we just want the address
163 const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; 170 const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL;
164 clipboard.writeText(url); 171 clipboard.writeText(url);
165 this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Link URL copied: ${url}`); 172 this._sendNotificationOnClipboardEvent(
173 menuInfo.clipboardNotifications,
174 () => `Link URL copied: ${url}`,
175 );
166 }, 176 },
167 }); 177 });
168 178
@@ -250,11 +260,13 @@ module.exports = class ContextMenuBuilder {
250 const webContents = this.getWebContents(); 260 const webContents = this.getWebContents();
251 // Add each spelling suggestion 261 // Add each spelling suggestion
252 for (const suggestion of menuInfo.dictionarySuggestions) { 262 for (const suggestion of menuInfo.dictionarySuggestions) {
253 menu.append(new MenuItem({ 263 menu.append(
254 label: suggestion, 264 new MenuItem({
255 // eslint-disable-next-line no-loop-func 265 label: suggestion,
256 click: () => webContents.replaceMisspelling(suggestion), 266 // eslint-disable-next-line no-loop-func
257 })); 267 click: () => webContents.replaceMisspelling(suggestion),
268 }),
269 );
258 } 270 }
259 271
260 // Allow users to add the misspelled word to the dictionary 272 // Allow users to add the misspelled word to the dictionary
@@ -262,7 +274,10 @@ module.exports = class ContextMenuBuilder {
262 menu.append( 274 menu.append(
263 new MenuItem({ 275 new MenuItem({
264 label: this.stringTable.addToDictionary(), 276 label: this.stringTable.addToDictionary(),
265 click: () => webContents.session.addWordToSpellCheckerDictionary(menuInfo.misspelledWord), 277 click: () =>
278 webContents.session.addWordToSpellCheckerDictionary(
279 menuInfo.misspelledWord,
280 ),
266 }), 281 }),
267 ); 282 );
268 } 283 }
@@ -274,7 +289,7 @@ module.exports = class ContextMenuBuilder {
274 * Adds search-related menu items. 289 * Adds search-related menu items.
275 */ 290 */
276 addSearchItems(menu, menuInfo) { 291 addSearchItems(menu, menuInfo) {
277 if (!menuInfo.selectionText || menuInfo.selectionText.length < 1) { 292 if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) {
278 return menu; 293 return menu;
279 } 294 }
280 295
@@ -287,7 +302,9 @@ module.exports = class ContextMenuBuilder {
287 const webContents = this.getWebContents(); 302 const webContents = this.getWebContents();
288 303
289 const lookUpDefinition = new MenuItem({ 304 const lookUpDefinition = new MenuItem({
290 label: this.stringTable.lookUpDefinition({ word: menuInfo.selectionText.trim() }), 305 label: this.stringTable.lookUpDefinition({
306 word: menuInfo.selectionText.trim(),
307 }),
291 click: () => webContents.showDefinitionForSelection(), 308 click: () => webContents.showDefinitionForSelection(),
292 }); 309 });
293 310
@@ -295,9 +312,13 @@ module.exports = class ContextMenuBuilder {
295 } 312 }
296 313
297 const search = new MenuItem({ 314 const search = new MenuItem({
298 label: this.stringTable.searchWith({ searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine] }), 315 label: this.stringTable.searchWith({
316 searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine],
317 }),
299 click: () => { 318 click: () => {
300 const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ searchTerm: encodeURIComponent(menuInfo.selectionText) }); 319 const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({
320 searchTerm: encodeURIComponent(menuInfo.selectionText),
321 });
301 openExternalUrl(url, true); 322 openExternalUrl(url, true);
302 }, 323 },
303 }); 324 });
@@ -319,10 +340,14 @@ module.exports = class ContextMenuBuilder {
319 const copyImage = new MenuItem({ 340 const copyImage = new MenuItem({
320 label: this.stringTable.copyImage(), 341 label: this.stringTable.copyImage(),
321 click: () => { 342 click: () => {
322 const result = this.convertImageToBase64(menuInfo.srcURL, 343 const result = this.convertImageToBase64(menuInfo.srcURL, dataURL =>
323 (dataURL) => clipboard.writeImage(nativeImage.createFromDataURL(dataURL))); 344 clipboard.writeImage(nativeImage.createFromDataURL(dataURL)),
324 345 );
325 this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image copied from URL: ${menuInfo.srcURL}`); 346
347 this._sendNotificationOnClipboardEvent(
348 menuInfo.clipboardNotifications,
349 () => `Image copied from URL: ${menuInfo.srcURL}`,
350 );
326 return result; 351 return result;
327 }, 352 },
328 }); 353 });
@@ -333,7 +358,10 @@ module.exports = class ContextMenuBuilder {
333 label: this.stringTable.copyImageUrl(), 358 label: this.stringTable.copyImageUrl(),
334 click: () => { 359 click: () => {
335 const result = clipboard.writeText(menuInfo.srcURL); 360 const result = clipboard.writeText(menuInfo.srcURL);
336 this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image URL copied: ${menuInfo.srcURL}`); 361 this._sendNotificationOnClipboardEvent(
362 menuInfo.clipboardNotifications,
363 () => `Image URL copied: ${menuInfo.srcURL}`,
364 );
337 return result; 365 return result;
338 }, 366 },
339 }); 367 });
@@ -345,20 +373,22 @@ module.exports = class ContextMenuBuilder {
345 const downloadImage = new MenuItem({ 373 const downloadImage = new MenuItem({
346 label: this.stringTable.downloadImage(), 374 label: this.stringTable.downloadImage(),
347 click: () => { 375 click: () => {
348 const urlWithoutBlob = menuInfo.srcURL.substr(5); 376 const urlWithoutBlob = menuInfo.srcURL.slice(5);
349 this.convertImageToBase64(menuInfo.srcURL, 377 this.convertImageToBase64(menuInfo.srcURL, dataURL => {
350 (dataURL) => { 378 const url = new window.URL(urlWithoutBlob);
351 const url = new window.URL(urlWithoutBlob); 379 const fileName = url.pathname.slice(1);
352 const fileName = url.pathname.substr(1); 380 ipcRenderer.send('download-file', {
353 ipcRenderer.send('download-file', { 381 content: dataURL,
354 content: dataURL, 382 fileOptions: {
355 fileOptions: { 383 name: fileName,
356 name: fileName, 384 mime: 'image/png',
357 mime: 'image/png', 385 },
358 },
359 });
360 }); 386 });
361 this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image downloaded: ${urlWithoutBlob}`); 387 });
388 this._sendNotificationOnClipboardEvent(
389 menuInfo.clipboardNotifications,
390 () => `Image downloaded: ${urlWithoutBlob}`,
391 );
362 }, 392 },
363 }); 393 });
364 394
@@ -373,12 +403,14 @@ module.exports = class ContextMenuBuilder {
373 */ 403 */
374 addCut(menu, menuInfo) { 404 addCut(menu, menuInfo) {
375 const webContents = this.getWebContents(); 405 const webContents = this.getWebContents();
376 menu.append(new MenuItem({ 406 menu.append(
377 label: this.stringTable.cut(), 407 new MenuItem({
378 accelerator: `${cmdOrCtrlShortcutKey()}+X`, 408 label: this.stringTable.cut(),
379 enabled: menuInfo.editFlags.canCut, 409 accelerator: `${cmdOrCtrlShortcutKey()}+X`,
380 click: () => webContents.cut(), 410 enabled: menuInfo.editFlags.canCut,
381 })); 411 click: () => webContents.cut(),
412 }),
413 );
382 414
383 return menu; 415 return menu;
384 } 416 }
@@ -388,12 +420,14 @@ module.exports = class ContextMenuBuilder {
388 */ 420 */
389 addCopy(menu, menuInfo) { 421 addCopy(menu, menuInfo) {
390 const webContents = this.getWebContents(); 422 const webContents = this.getWebContents();
391 menu.append(new MenuItem({ 423 menu.append(
392 label: this.stringTable.copy(), 424 new MenuItem({
393 accelerator: `${cmdOrCtrlShortcutKey()}+C`, 425 label: this.stringTable.copy(),
394 enabled: menuInfo.editFlags.canCopy, 426 accelerator: `${cmdOrCtrlShortcutKey()}+C`,
395 click: () => webContents.copy(), 427 enabled: menuInfo.editFlags.canCopy,
396 })); 428 click: () => webContents.copy(),
429 }),
430 );
397 431
398 return menu; 432 return menu;
399 } 433 }
@@ -403,21 +437,23 @@ module.exports = class ContextMenuBuilder {
403 */ 437 */
404 addPaste(menu, menuInfo) { 438 addPaste(menu, menuInfo) {
405 const webContents = this.getWebContents(); 439 const webContents = this.getWebContents();
406 menu.append(new MenuItem({ 440 menu.append(
407 label: this.stringTable.paste(), 441 new MenuItem({
408 accelerator: `${cmdOrCtrlShortcutKey()}+V`, 442 label: this.stringTable.paste(),
409 enabled: menuInfo.editFlags.canPaste, 443 accelerator: `${cmdOrCtrlShortcutKey()}+V`,
410 click: () => webContents.paste(), 444 enabled: menuInfo.editFlags.canPaste,
411 })); 445 click: () => webContents.paste(),
446 }),
447 );
412 448
413 return menu; 449 return menu;
414 } 450 }
415 451
416 addPastePlain(menu, menuInfo) { 452 addPastePlain(menu, menuInfo) {
417 if ( 453 if (
418 menuInfo.editFlags.canPaste 454 menuInfo.editFlags.canPaste &&
419 && !menuInfo.linkText 455 !menuInfo.linkText &&
420 && !menuInfo.hasImageContents 456 !menuInfo.hasImageContents
421 ) { 457 ) {
422 const webContents = this.getWebContents(); 458 const webContents = this.getWebContents();
423 menu.append( 459 menu.append(
@@ -463,21 +499,21 @@ module.exports = class ContextMenuBuilder {
463 * @param {String} outputFormat The image format to use, defaults to 'image/png' 499 * @param {String} outputFormat The image format to use, defaults to 'image/png'
464 */ 500 */
465 convertImageToBase64(url, callback, outputFormat = 'image/png') { 501 convertImageToBase64(url, callback, outputFormat = 'image/png') {
466 let canvas = document.createElement('CANVAS'); 502 let canvas = document.createElement('canvas');
467 const ctx = canvas.getContext('2d'); 503 const ctx = canvas.getContext('2d');
468 // eslint-disable-next-line no-undef 504 // eslint-disable-next-line no-undef
469 const img = new Image(); 505 const img = new Image();
470 img.crossOrigin = 'Anonymous'; 506 img.crossOrigin = 'Anonymous';
471 507
472 img.onload = () => { 508 img.addEventListener('load', () => {
473 canvas.height = img.height; 509 canvas.height = img.height;
474 canvas.width = img.width; 510 canvas.width = img.width;
475 ctx.drawImage(img, 0, 0); 511 ctx?.drawImage(img, 0, 0);
476 512
477 const dataURL = canvas.toDataURL(outputFormat); 513 const dataURL = canvas.toDataURL(outputFormat);
478 canvas = null; 514 canvas = null;
479 callback(dataURL); 515 callback(dataURL);
480 }; 516 });
481 517
482 img.src = url; 518 img.src = url;
483 } 519 }
@@ -487,12 +523,14 @@ module.exports = class ContextMenuBuilder {
487 */ 523 */
488 goBack(menu) { 524 goBack(menu) {
489 const webContents = this.getWebContents(); 525 const webContents = this.getWebContents();
490 menu.append(new MenuItem({ 526 menu.append(
491 label: this.stringTable.goBack(), 527 new MenuItem({
492 accelerator: `${cmdOrCtrlShortcutKey()}+left`, 528 label: this.stringTable.goBack(),
493 enabled: webContents.canGoBack(), 529 accelerator: `${cmdOrCtrlShortcutKey()}+left`,
494 click: () => webContents.goBack(), 530 enabled: webContents.canGoBack(),
495 })); 531 click: () => webContents.goBack(),
532 }),
533 );
496 534
497 return menu; 535 return menu;
498 } 536 }
@@ -502,12 +540,14 @@ module.exports = class ContextMenuBuilder {
502 */ 540 */
503 goForward(menu) { 541 goForward(menu) {
504 const webContents = this.getWebContents(); 542 const webContents = this.getWebContents();
505 menu.append(new MenuItem({ 543 menu.append(
506 label: this.stringTable.goForward(), 544 new MenuItem({
507 accelerator: `${cmdOrCtrlShortcutKey()}+right`, 545 label: this.stringTable.goForward(),
508 enabled: webContents.canGoForward(), 546 accelerator: `${cmdOrCtrlShortcutKey()}+right`,
509 click: () => webContents.goForward(), 547 enabled: webContents.canGoForward(),
510 })); 548 click: () => webContents.goForward(),
549 }),
550 );
511 551
512 return menu; 552 return menu;
513 } 553 }
@@ -516,14 +556,19 @@ module.exports = class ContextMenuBuilder {
516 * Adds the 'copy page url' menu item. 556 * Adds the 'copy page url' menu item.
517 */ 557 */
518 copyPageUrl(menu, menuInfo) { 558 copyPageUrl(menu, menuInfo) {
519 menu.append(new MenuItem({ 559 menu.append(
520 label: this.stringTable.copyPageUrl(), 560 new MenuItem({
521 enabled: true, 561 label: this.stringTable.copyPageUrl(),
522 click: () => { 562 enabled: true,
523 clipboard.writeText(window.location.href); 563 click: () => {
524 this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Page URL copied: ${window.location.href}`); 564 clipboard.writeText(window.location.href);
525 }, 565 this._sendNotificationOnClipboardEvent(
526 })); 566 menuInfo.clipboardNotifications,
567 () => `Page URL copied: ${window.location.href}`,
568 );
569 },
570 }),
571 );
527 572
528 return menu; 573 return menu;
529 } 574 }
@@ -533,15 +578,17 @@ module.exports = class ContextMenuBuilder {
533 */ 578 */
534 goToHomePage(menu, menuInfo) { 579 goToHomePage(menu, menuInfo) {
535 const baseURL = new URL(menuInfo.pageURL); 580 const baseURL = new URL(menuInfo.pageURL);
536 menu.append(new MenuItem({ 581 menu.append(
537 label: this.stringTable.goToHomePage(), 582 new MenuItem({
538 accelerator: `${cmdOrCtrlShortcutKey()}+Home`, 583 label: this.stringTable.goToHomePage(),
539 enabled: true, 584 accelerator: `${cmdOrCtrlShortcutKey()}+Home`,
540 click: () => { 585 enabled: true,
541 // webContents.loadURL(baseURL.origin); 586 click: () => {
542 window.location.href = baseURL.origin; 587 // webContents.loadURL(baseURL.origin);
543 }, 588 window.location.href = baseURL.origin;
544 })); 589 },
590 }),
591 );
545 592
546 return menu; 593 return menu;
547 } 594 }
@@ -550,13 +597,15 @@ module.exports = class ContextMenuBuilder {
550 * Adds the 'open in browser' menu item. 597 * Adds the 'open in browser' menu item.
551 */ 598 */
552 openInBrowser(menu, menuInfo) { 599 openInBrowser(menu, menuInfo) {
553 menu.append(new MenuItem({ 600 menu.append(
554 label: this.stringTable.openInBrowser(), 601 new MenuItem({
555 enabled: true, 602 label: this.stringTable.openInBrowser(),
556 click: () => { 603 enabled: true,
557 openExternalUrl(menuInfo.pageURL, true); 604 click: () => {
558 }, 605 openExternalUrl(menuInfo.pageURL, true);
559 })); 606 },
607 }),
608 );
560 609
561 return menu; 610 return menu;
562 } 611 }
@@ -566,9 +615,8 @@ module.exports = class ContextMenuBuilder {
566 return; 615 return;
567 } 616 }
568 // eslint-disable-next-line no-new 617 // eslint-disable-next-line no-new
569 new window.Notification('Data copied into Clipboard', 618 new window.Notification('Data copied into Clipboard', {
570 { 619 body: notificationText(),
571 body: notificationText(), 620 });
572 });
573 } 621 }
574}; 622};
diff --git a/src/webview/darkmode.ts b/src/webview/darkmode.ts
index e06c22f11..7b9407049 100644
--- a/src/webview/darkmode.ts
+++ b/src/webview/darkmode.ts
@@ -1,5 +1,3 @@
1/* eslint no-bitwise: ["error", { "int32Hint": true }] */
2
3import { join } from 'path'; 1import { join } from 'path';
4import { pathExistsSync, readFileSync } from 'fs-extra'; 2import { pathExistsSync, readFileSync } from 'fs-extra';
5 3
@@ -7,7 +5,9 @@ const debug = require('debug')('Ferdi:DarkMode');
7 5
8const chars = [...'abcdefghijklmnopqrstuvwxyz']; 6const chars = [...'abcdefghijklmnopqrstuvwxyz'];
9 7
10const ID = [...Array(20)].map(() => chars[Math.random() * chars.length | 0]).join(''); 8const ID = [...Array.from({ length: 20 })]
9 .map(() => chars[Math.trunc(Math.random() * chars.length)])
10 .join('');
11 11
12export function injectDarkModeStyle(recipePath: string) { 12export function injectDarkModeStyle(recipePath: string) {
13 const darkModeStyle = join(recipePath, 'darkmode.css'); 13 const darkModeStyle = join(recipePath, 'darkmode.css');
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js
index 157da7693..4085b925b 100644
--- a/src/webview/lib/RecipeWebview.js
+++ b/src/webview/lib/RecipeWebview.js
@@ -1,5 +1,5 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer } from 'electron';
2import { exists, pathExistsSync, readFileSync } from 'fs-extra'; 2import { pathExistsSync, readFileSync, existsSync } from 'fs-extra';
3 3
4const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); 4const debug = require('debug')('Ferdi:Plugin:RecipeWebview');
5 5
@@ -62,12 +62,13 @@ class RecipeWebview {
62 * be an absolute path to the file 62 * be an absolute path to the file
63 */ 63 */
64 injectCSS(...files) { 64 injectCSS(...files) {
65 files.forEach(async (file) => { 65 // eslint-disable-next-line unicorn/no-array-for-each
66 files.forEach(file => {
66 if (pathExistsSync(file)) { 67 if (pathExistsSync(file)) {
67 const styles = document.createElement('style'); 68 const styles = document.createElement('style');
68 styles.innerHTML = readFileSync(file, 'utf8'); 69 styles.innerHTML = readFileSync(file, 'utf8');
69 70
70 document.querySelector('head').appendChild(styles); 71 document.querySelector('head').append(styles);
71 72
72 debug('Append styles', styles); 73 debug('Append styles', styles);
73 } 74 }
@@ -75,14 +76,16 @@ class RecipeWebview {
75 } 76 }
76 77
77 injectJSUnsafe(...files) { 78 injectJSUnsafe(...files) {
78 Promise.all(files.map(async (file) => { 79 Promise.all(
79 if (await exists(file)) { 80 files.map(file => {
80 return readFileSync(file, 'utf8'); 81 if (existsSync(file)) {
81 } 82 return readFileSync(file, 'utf8');
82 debug('Script not found', file); 83 }
83 return null; 84 debug('Script not found', file);
84 })).then(async (scripts) => { 85 return null;
85 const scriptsFound = scripts.filter((script) => script !== null); 86 }),
87 ).then(scripts => {
88 const scriptsFound = scripts.filter(script => script !== null);
86 if (scriptsFound.length > 0) { 89 if (scriptsFound.length > 0) {
87 debug('Inject scripts to main world', scriptsFound); 90 debug('Inject scripts to main world', scriptsFound);
88 ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound); 91 ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound);
diff --git a/src/webview/lib/Userscript.js b/src/webview/lib/Userscript.js
index 2043d9fff..bed2b1ff8 100644
--- a/src/webview/lib/Userscript.js
+++ b/src/webview/lib/Userscript.js
@@ -28,7 +28,7 @@ export default class Userscript {
28 * 28 *
29 * @param {*} settings 29 * @param {*} settings
30 */ 30 */
31 // eslint-disable-next-line 31 // eslint-disable-next-line camelcase
32 internal_setSettings(settings) { 32 internal_setSettings(settings) {
33 // This is needed to get a clean JS object from the settings itself to provide better accessibility 33 // This is needed to get a clean JS object from the settings itself to provide better accessibility
34 // Otherwise this will be a mobX instance 34 // Otherwise this will be a mobX instance
@@ -95,9 +95,7 @@ export default class Userscript {
95 * @param {*} value 95 * @param {*} value
96 */ 96 */
97 set(key, value) { 97 set(key, value) {
98 window.localStorage.setItem( 98 window.localStorage.setItem(`ferdi-user-${key}`, JSON.stringify(value));
99 `ferdi-user-${key}`, JSON.stringify(value),
100 );
101 } 99 }
102 100
103 /** 101 /**
@@ -107,9 +105,7 @@ export default class Userscript {
107 * @return Value of the key 105 * @return Value of the key
108 */ 106 */
109 get(key) { 107 get(key) {
110 return JSON.parse(window.localStorage.getItem( 108 return JSON.parse(window.localStorage.getItem(`ferdi-user-${key}`));
111 `ferdi-user-${key}`,
112 ));
113 } 109 }
114 110
115 /** 111 /**
diff --git a/src/webview/recipe.js b/src/webview/recipe.js
index a3ae4513f..d7032da3f 100644
--- a/src/webview/recipe.js
+++ b/src/webview/recipe.js
@@ -1,3 +1,4 @@
1/* eslint-disable global-require */
1/* eslint-disable import/first */ 2/* eslint-disable import/first */
2import { contextBridge, desktopCapturer, ipcRenderer } from 'electron'; 3import { contextBridge, desktopCapturer, ipcRenderer } from 'electron';
3import { BrowserWindow, getCurrentWebContents } from '@electron/remote'; 4import { BrowserWindow, getCurrentWebContents } from '@electron/remote';
@@ -104,16 +105,13 @@ window.open = (url, frameName, features) => {
104// then overwrite the corresponding field of the window object by injected JS. 105// then overwrite the corresponding field of the window object by injected JS.
105contextBridge.exposeInMainWorld('ferdi', { 106contextBridge.exposeInMainWorld('ferdi', {
106 open: window.open, 107 open: window.open,
107 setBadge: (direct, indirect) => 108 setBadge: (direct, indirect) => badgeHandler.setBadge(direct, indirect),
108 badgeHandler.setBadge(direct, indirect), 109 safeParseInt: text => badgeHandler.safeParseInt(text),
109 safeParseInt: (text) =>
110 badgeHandler.safeParseInt(text),
111 displayNotification: (title, options) => 110 displayNotification: (title, options) =>
112 notificationsHandler.displayNotification(title, options), 111 notificationsHandler.displayNotification(title, options),
113 clearStorageData: (storageLocations) => 112 clearStorageData: storageLocations =>
114 sessionHandler.clearStorageData(storageLocations), 113 sessionHandler.clearStorageData(storageLocations),
115 releaseServiceWorkers: () => 114 releaseServiceWorkers: () => sessionHandler.releaseServiceWorkers(),
116 sessionHandler.releaseServiceWorkers(),
117 getDisplayMediaSelector, 115 getDisplayMediaSelector,
118 getCurrentWebContents, 116 getCurrentWebContents,
119 BrowserWindow, 117 BrowserWindow,
@@ -173,12 +171,12 @@ class RecipeController {
173 findInPage = null; 171 findInPage = null;
174 172
175 async initialize() { 173 async initialize() {
176 Object.keys(this.ipcEvents).forEach(channel => { 174 for (const channel of Object.keys(this.ipcEvents)) {
177 ipcRenderer.on(channel, (...args) => { 175 ipcRenderer.on(channel, (...args) => {
178 debug('Received IPC event for channel', channel, 'with', ...args); 176 debug('Received IPC event for channel', channel, 'with', ...args);
179 this[this.ipcEvents[channel]](...args); 177 this[this.ipcEvents[channel]](...args);
180 }); 178 });
181 }); 179 }
182 180
183 debug('Send "hello" to host'); 181 debug('Send "hello" to host');
184 setTimeout(() => ipcRenderer.sendToHost('hello'), 100); 182 setTimeout(() => ipcRenderer.sendToHost('hello'), 100);
@@ -210,7 +208,7 @@ class RecipeController {
210 delete require.cache[require.resolve(modulePath)]; 208 delete require.cache[require.resolve(modulePath)];
211 try { 209 try {
212 this.recipe = new RecipeWebview(badgeHandler, notificationsHandler); 210 this.recipe = new RecipeWebview(badgeHandler, notificationsHandler);
213 // eslint-disable-next-line 211 // eslint-disable-next-line import/no-dynamic-require
214 require(modulePath)(this.recipe, { ...config, recipe }); 212 require(modulePath)(this.recipe, { ...config, recipe });
215 debug('Initialize Recipe', config, recipe); 213 debug('Initialize Recipe', config, recipe);
216 214
@@ -218,8 +216,8 @@ class RecipeController {
218 216
219 // Make sure to update the WebView, otherwise the custom darkmode handler may not be used 217 // Make sure to update the WebView, otherwise the custom darkmode handler may not be used
220 this.update(); 218 this.update();
221 } catch (err) { 219 } catch (error) {
222 console.error('Recipe initialization failed', err); 220 console.error('Recipe initialization failed', error);
223 } 221 }
224 222
225 this.loadUserFiles(recipe, config); 223 this.loadUserFiles(recipe, config);
@@ -234,12 +232,12 @@ class RecipeController {
234 const data = readFileSync(userCss); 232 const data = readFileSync(userCss);
235 styles.innerHTML += data.toString(); 233 styles.innerHTML += data.toString();
236 } 234 }
237 document.querySelector('head').appendChild(styles); 235 document.querySelector('head').append(styles);
238 236
239 const userJs = join(recipe.path, 'user.js'); 237 const userJs = join(recipe.path, 'user.js');
240 if (pathExistsSync(userJs)) { 238 if (pathExistsSync(userJs)) {
241 const loadUserJs = () => { 239 const loadUserJs = () => {
242 // eslint-disable-next-line 240 // eslint-disable-next-line import/no-dynamic-require
243 const userJsModule = require(userJs); 241 const userJsModule = require(userJs);
244 242
245 if (typeof userJsModule === 'function') { 243 if (typeof userJsModule === 'function') {
@@ -392,15 +390,14 @@ class RecipeController {
392 } 390 }
393 391
394 // Remove dark reader if (universal) dark mode was just disabled 392 // Remove dark reader if (universal) dark mode was just disabled
395 if (this.universalDarkModeInjected) { 393 if (
396 if ( 394 this.universalDarkModeInjected &&
397 !this.settings.app.darkMode || 395 (!this.settings.app.darkMode ||
398 !this.settings.service.isDarkModeEnabled || 396 !this.settings.service.isDarkModeEnabled ||
399 !this.settings.app.universalDarkMode 397 !this.settings.app.universalDarkMode)
400 ) { 398 ) {
401 disableDarkMode(); 399 disableDarkMode();
402 this.universalDarkModeInjected = false; 400 this.universalDarkModeInjected = false;
403 }
404 } 401 }
405 } 402 }
406 403
diff --git a/src/webview/sessionHandler.ts b/src/webview/sessionHandler.ts
index 6a7e62ac5..4961da12b 100644
--- a/src/webview/sessionHandler.ts
+++ b/src/webview/sessionHandler.ts
@@ -9,20 +9,21 @@ export class SessionHandler {
9 const { session } = getCurrentWebContents(); 9 const { session } = getCurrentWebContents();
10 session.flushStorageData(); 10 session.flushStorageData();
11 session.clearStorageData({ storages: storageLocations }); 11 session.clearStorageData({ storages: storageLocations });
12 } catch (err) { 12 } catch (error) {
13 debug(err); 13 debug(error);
14 } 14 }
15 } 15 }
16 16
17 async releaseServiceWorkers() { 17 async releaseServiceWorkers() {
18 try { 18 try {
19 const registrations = await window.navigator.serviceWorker.getRegistrations(); 19 const registrations =
20 registrations.forEach(r => { 20 await window.navigator.serviceWorker.getRegistrations();
21 r.unregister(); 21 for (const registration of registrations) {
22 registration.unregister();
22 debug('ServiceWorker unregistered'); 23 debug('ServiceWorker unregistered');
23 }); 24 }
24 } catch (err) { 25 } catch (error) {
25 debug(err); 26 debug(error);
26 } 27 }
27 } 28 }
28} 29}
diff --git a/src/webview/spellchecker.ts b/src/webview/spellchecker.ts
index 30b4ef075..d0f6663d5 100644
--- a/src/webview/spellchecker.ts
+++ b/src/webview/spellchecker.ts
@@ -11,7 +11,7 @@ debug('Spellchecker default locale is', defaultLocale);
11export function getSpellcheckerLocaleByFuzzyIdentifier(identifier: string) { 11export function getSpellcheckerLocaleByFuzzyIdentifier(identifier: string) {
12 const locales = Object.keys(SPELLCHECKER_LOCALES).filter((key) => key.toLocaleLowerCase() === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); 12 const locales = Object.keys(SPELLCHECKER_LOCALES).filter((key) => key.toLocaleLowerCase() === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase());
13 13
14 return locales.length >= 1 ? locales[0] : null; 14 return locales.length > 0 ? locales[0] : null;
15} 15}
16 16
17export function switchDict(locale: string) { 17export function switchDict(locale: string) {