diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 122702f..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "env": { - "browser": true - }, - - - // see http://eslint.org/docs/rules/ for explanations - "rules": { - "no-extra-parens": 1, - "no-regex-spaces": 1, - "no-else-return": 1, - "no-empty": 1, - "no-empty-function": 1, - "no-implicit-coercion": 1, - "no-magic-numbers": 1, - "no-undef": 1, - "no-unused-vars": 1, - "no-undef-init": 1, - "no-shadow": [1, { "builtinGlobals": true }], - "vars-on-top": 1, - - "no-constant-condition": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-func-assign": 2, - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-sparse-arrays": 2, - "no-unreachable": 2, - "use-isnan": 2, - "valid-typeof": 2, - "consistent-return": 2, - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "dot-notation": 2, - "eqeqeq": 2, - "no-eval": 2, - "no-implied-eval": 2, - "no-loop-func": 2, - "no-multi-spaces": [2, { "exceptions": { "VariableDeclarator": true } }], - "no-new-func": 2, - "no-new-wrappers": 2, - "no-new": 2, - "no-octal-escape": 2, - "no-octal": 2, - "no-param-reassign": 2, - "no-redeclare": [2, { "builtinGlobals": true }], - "no-script-url": 2, - "no-self-assign": 2, - "no-self-compare": 2, - "no-void": 2, - "strict": [2, "global"], - "radix": 2, - "yoda": 2, - "no-delete-var": 2, - "no-use-before-define": 2, - "array-bracket-spacing": [2, "never"], - "block-spacing": [2, "never"], - "brace-style": 2, - "comma-spacing": [2, {"before": false, "after": true}], - "comma-style": 2, - "computed-property-spacing": [2, "never"], - "func-style": [2, "declaration"], - "no-lonely-if": 2, - "no-mixed-spaces-and-tabs": [2, "smart-tabs"], - "no-spaced-func": 2, - "no-trailing-spaces": 2, - "object-curly-spacing": [2, "never"], - "quotes": [2, "single"], - - "semi": [2, "never"], - "no-extra-semi": 2 - } -} diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..17bc8e5 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,88 @@ +extends: eslint:recommended + +env: + es6: true, + browser: true, + webextensions: true + +rules: + # warnings + no-control-regex: warn + no-ternary: warn + prefer-const: warn + prefer-template: warn + no-magic-numbers: + - warn + - ignoreArrayIndexes: true + # errors + yoda: error + radix: error + no-useless-return: error + no-useless-concat: error + no-unused-expressions: error + no-self-compare: error + no-script-url: error + no-return-assign: error + no-octal-escape: error + no-loop-func: error + no-lone-blocks: error + no-invalid-this: error + no-implied-eval: error + no-floating-decimal: error + no-eval: error + no-eq-null: error + no-empty-function: error + no-else-return: error + no-alert: error + eqeqeq: error + dot-notation: error + no-shadow: error + no-undef-init: error + no-use-before-define: error + brace-style: error + comma-dangle: error + comma-spacing: error + eol-last: error + func-call-spacing: error + keyword-spacing: error + linebreak-style: error + no-bitwise: error + no-lonely-if: error + no-multi-assign: error + no-multiple-empty-lines: error + no-nested-ternary: error + no-trailing-spaces: error + semi-spacing: error + space-before-blocks: error + space-in-parens: error + space-infix-ops: error + switch-colon-spacing: error + wrap-regex: error + no-confusing-arrow: error + no-useless-computed-key: error + no-var: error + template-curly-spacing: error + semi: + - error + - never + no-extra-parens: + - error + - all + valid-typeof: + - error + - requireStringLiterals: true + strict: + - error + - global + curly: + - error + - multi-line + indent: + - error + - tab + quotes: + - error + - single + space-before-function-paren: + - error + - never diff --git a/Chrome/_locales/de/messages.json b/Chrome/_locales/de/messages.json index a0ffdc6..6bf6d48 100644 --- a/Chrome/_locales/de/messages.json +++ b/Chrome/_locales/de/messages.json @@ -51,6 +51,14 @@ "message": "Eine URL pro Zeile!", "description": "Zweiter fett formatierter Satz auf der Einstellungsseite" }, + "optionsAudible": { + "message": "Tabs die Audio spielen wach halten", + "description": "Checkbox für die Audio-Einstellung" + }, + "optionsThumbnails": { + "message": "Screenshot von manuell schlafen gelegten Tabs anzeigen (experimentell!)", + "description": "Checkbox für die Thumbnail-Einstellung" + }, "optionsSaveButton": { "message": "Speichern", "description": "Text des Speichern-Buttons" @@ -63,4 +71,4 @@ "message": "Aufwecken", "description": "Text des Aufweck-Buttons einer inaktiven Seite" } -} \ No newline at end of file +} diff --git a/Chrome/_locales/en/messages.json b/Chrome/_locales/en/messages.json index eb2e26b..4f62dff 100644 --- a/Chrome/_locales/en/messages.json +++ b/Chrome/_locales/en/messages.json @@ -51,6 +51,14 @@ "message": "One URL per line!", "description": "options page second bold sentence" }, + "optionsAudible": { + "message": "keep audio-playing tabs awake", + "description": "checkbox for the audio-option" + }, + "optionsThumbnails": { + "message": "show screenshot of manually hibernated tabs (experimental!)", + "description": "checkbox for the thumbnail-option" + }, "optionsSaveButton": { "message": "Save", "description": "save button text" @@ -63,4 +71,4 @@ "message": "Wake up!", "description": "text of the button to wake up a hibernating page" } -} \ No newline at end of file +} diff --git a/Chrome/eventPage.js b/Chrome/eventPage.js index 657c0f5..7270ace 100644 --- a/Chrome/eventPage.js +++ b/Chrome/eventPage.js @@ -1,91 +1,112 @@ 'use strict' -var whitelist -var whitelistArray = new Array() +let whitelistArray = new Array() +let caffeinatedAudio = false +let thumbnails = false chrome.storage.sync.get(function(items) { - if (items.whitelist) { - whitelist = items.whitelist - whitelistArray = whitelist.split('\n') + if (items.whitelist) whitelistArray = items.whitelist.split('\n') + if (items.audible !== undefined) caffeinatedAudio = items.audible + if (items.thumbnails !== undefined) thumbnails = items.thumbnails +}) + +chrome.storage.onChanged.addListener(function(changes, area) { + if (area === 'sync') { + chrome.storage.sync.get(function(items) { + if (items.whitelist) whitelistArray = items.whitelist.split('\n') + if (items.audible !== undefined) caffeinatedAudio = items.audible + if (items.thumbnails !== undefined) thumbnails = items.thumbnails + }) } }) +chrome.runtime.onInstalled.addListener(function() { + chrome.contextMenus.create({ + id : 'SleepTab', + title : chrome.i18n.getMessage('contextMenuTitle'), + contexts : ['page'] + }) +}) + function inWhitelist(url) { - var listed = false whitelistArray.forEach(function(item) { if (url.startsWith(item)) { - listed = true + return true } }) - return listed + return false } -function sleepTab(html, tab) { - var pageInfo = { +function sleepTab(html, tab, img) { + const pageInfo = { url: tab.url, title: tab.title, hibernationInfo: chrome.i18n.getMessage('hibernationPageInfo'), buttonText: chrome.i18n.getMessage('hibernationPageButton'), favIconUrl: tab.favIconUrl } - var pageHtml = html.replace(/\{\/\*pageInfoObject\*\/\}/, JSON.stringify(pageInfo)) - var dataURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(pageHtml) - chrome.tabs.update(tab.id, {url: dataURL}) -} -chrome.browserAction.onClicked.addListener(function(tab) { - var c = 0 + const pageHtml = html.replace(/\{\/\*pageInfoObject\*\/\}/, JSON.stringify(pageInfo)) - var xmlHttp = new XMLHttpRequest() - xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true) - xmlHttp.onload = function () { - var html = xmlHttp.responseText + if (img && thumbnails) { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') - chrome.windows.getAll({populate: true}, function(windows) { - windows.forEach(function(win) { - win.tabs.forEach(function(tab) { - if (tab.active || tab.highlighted || tab.pinned) return - if (tab.status !== 'complete') return - if (!tab.url.match(/^https?:\/\//)) return - if (inWhitelist(tab.url)) return + const i = new Image() + i.src = img + i.onload = function() { + canvas.width = 300 + canvas.height = canvas.width * (i.height / i.width) + ctx.drawImage(i, 0, 0, canvas.width, canvas.height) + img = `` - sleepTab(html, tab) - c++ - }) - }) + const dataURL = `data:text/html;charset=UTF-8,${encodeURIComponent(pageHtml.replace(//, img))}` + chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true}) + } + return + } + const dataURL = `data:text/html;charset=UTF-8,${encodeURIComponent(pageHtml)}` + chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true}) +} + +function sleepSingle(tab) { + const xmlHttp = new XMLHttpRequest() + xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true) + xmlHttp.onload = function() { + chrome.tabs.captureVisibleTab({format: 'png'}, function(img) { + sleepTab(xmlHttp.responseText, tab, img) }) } xmlHttp.send(null) -}) - -const onInstalledDetails = {details: {'OnInstalledReason': 'installed'}} - -chrome.runtime.onInstalled.addListener(function(onInstalledDetails) { - chrome.contextMenus.create({ - id : 'SleepTab', - title : chrome.i18n.getMessage('contextMenuTitle'), - contexts : ['page'] - }) -}) +} -chrome.contextMenus.onClicked.addListener(function(info, tab) { - var xmlHttp = new XMLHttpRequest() +chrome.browserAction.onClicked.addListener(function() { + const xmlHttp = new XMLHttpRequest() xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true) - xmlHttp.onload = function () { - var html = xmlHttp.responseText - sleepTab(html, tab) + xmlHttp.onload = function() { + chrome.tabs.query({ + active: false, + pinned: false, + highlighted: false, + status: 'complete', + url: ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*'] + }, function(tabs) { + tabs.forEach(function(tab) { + if (inWhitelist(tab.url)) return + if (tab.audible && caffeinatedAudio) return + sleepTab(xmlHttp.responseText, tab) + }) + }) } xmlHttp.send(null) }) -chrome.commands.onCommand.addListener(function(command) { - chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tab) { - var xmlHttp = new XMLHttpRequest() - xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true) - xmlHttp.onload = function () { - var html = xmlHttp.responseText - sleepTab(html, tab[0]) - } - xmlHttp.send(null) +chrome.contextMenus.onClicked.addListener(function(info, tab) { + sleepSingle(tab) +}) + +chrome.commands.onCommand.addListener(function() { + chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) { + sleepSingle(tabs[0]) }) }) diff --git a/Chrome/hibernationPage/index.html b/Chrome/hibernationPage/index.html index f502884..ad760a8 100644 --- a/Chrome/hibernationPage/index.html +++ b/Chrome/hibernationPage/index.html @@ -2,60 +2,49 @@ - + ☾ 
-

""

-

-

+

""

+

+ +

diff --git a/Chrome/manifest.json b/Chrome/manifest.json index 1b7e49e..9b02f1f 100644 --- a/Chrome/manifest.json +++ b/Chrome/manifest.json @@ -2,9 +2,11 @@ "manifest_version": 2, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", - "version": "0.2.1", + "homepage_url": "https://github.com/343max/TabHibernation", + "version": "1.0", "default_locale": "en", "permissions": [ + "activeTab", "tabs", "contextMenus", "storage" @@ -38,5 +40,5 @@ "48": "icon48.png", "128": "icon128.png" }, - "minimum_chrome_version": "40.0" + "minimum_chrome_version": "54.0" } diff --git a/Chrome/options.html b/Chrome/options.html index 877a91a..2cbb5b3 100644 --- a/Chrome/options.html +++ b/Chrome/options.html @@ -19,6 +19,17 @@


+ +

+ + +

+ +

+ + +

+
diff --git a/Chrome/options.js b/Chrome/options.js index fa70ba1..c5f0156 100644 --- a/Chrome/options.js +++ b/Chrome/options.js @@ -1,9 +1,12 @@ 'use strict' function save_options() { - var whitelist = document.getElementById('whitelist').value - chrome.storage.sync.set({whitelist: whitelist}, function() { - var status = document.getElementById('status') + const whitelist = document.getElementById('whitelist').value + const audible = document.getElementById('audible').checked + const thumbnails = document.getElementById('thumbnails').checked + + chrome.storage.sync.set({whitelist: whitelist, audible: audible, thumbnails: thumbnails}, function() { + const status = document.getElementById('status') status.textContent = chrome.i18n.getMessage('saveOptions') setTimeout(function() { status.textContent = '' @@ -12,8 +15,10 @@ function save_options() { } function restore_options() { - chrome.storage.sync.get({whitelist: ''}, function(items) { + chrome.storage.sync.get(function(items) { document.getElementById('whitelist').value = items.whitelist + document.getElementById('audible').checked = items.audible + document.getElementById('thumbnails').checked = items.thumbnails }) } @@ -25,5 +30,7 @@ document.getElementById('secondParagraph').innerText = chrome.i18n.getMessage('o document.getElementById('thirdParagraph').innerText = chrome.i18n.getMessage('optionsThirdParagraph') document.getElementById('firstStrong').innerText = chrome.i18n.getMessage('optionsFirstStrong') document.getElementById('secondStrong').innerText = chrome.i18n.getMessage('optionsSecondStrong') +document.getElementById('audibleSpan').innerText = chrome.i18n.getMessage('optionsAudible') +document.getElementById('thumbnailsSpan').innerText = chrome.i18n.getMessage('optionsThumbnails') document.getElementById('save').innerText = chrome.i18n.getMessage('optionsSaveButton') document.getElementById('save').addEventListener('click', save_options)