├── v3 ├── polyfill │ └── README ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── active │ │ │ ├── 16.png │ │ │ └── 32.png │ │ ├── error │ │ │ ├── 16.png │ │ │ └── 32.png │ │ ├── disabled │ │ │ ├── 16.png │ │ │ └── 32.png │ │ └── skipped │ │ │ ├── 16.png │ │ │ └── 32.png │ ├── sounds │ │ ├── 1.mp3 │ │ ├── 2.mp3 │ │ ├── 3.mp3 │ │ ├── 4.mp3 │ │ ├── 5.mp3 │ │ ├── 6.mp3 │ │ ├── 7.mp3 │ │ ├── 8.mp3 │ │ ├── play.js │ │ └── play.html │ ├── popup │ │ ├── extra │ │ │ └── README │ │ ├── img │ │ │ └── arrow.svg │ │ ├── health.js │ │ ├── keyboard.js │ │ ├── permission.js │ │ ├── rate.js │ │ ├── startup.js │ │ ├── actives.js │ │ ├── index.html │ │ ├── index.js │ │ └── index.css │ ├── scripts │ │ ├── ste.js │ │ ├── sha.js │ │ └── vcd.js │ ├── locale.js │ ├── counter │ │ ├── index.html │ │ ├── index.css │ │ └── index.js │ └── options │ │ ├── index.css │ │ ├── index.html │ │ └── index.js ├── defaults.js ├── manifest.json ├── _locales │ ├── zh_TW │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── uk │ │ └── messages.json │ ├── sv │ │ └── messages.json │ ├── en │ │ └── messages.json │ └── de │ │ └── messages.json ├── context.js └── api.js ├── others └── old.zip ├── v2 ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 36.png │ │ ├── 38.png │ │ ├── 48.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── disabled │ │ │ ├── 16.png │ │ │ ├── 18.png │ │ │ ├── 19.png │ │ │ ├── 32.png │ │ │ ├── 36.png │ │ │ └── 38.png │ │ └── skipped │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ └── 48.png │ ├── popup │ │ ├── fontello.woff │ │ ├── fontello.css │ │ ├── index.html │ │ ├── index.css │ │ └── index.js │ └── options │ │ ├── matched.json │ │ ├── index.css │ │ ├── matched.js │ │ ├── index.html │ │ └── index.js ├── plugins │ └── badge │ │ ├── .eslintrc │ │ └── core.js ├── plugins.js └── manifest.json ├── .gitignore ├── .github └── FUNDING.yml └── README.md /v3/polyfill/README: -------------------------------------------------------------------------------- 1 | https://github.com/kenchris/urlpattern-polyfill 2 | -------------------------------------------------------------------------------- /others/old.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/others/old.zip -------------------------------------------------------------------------------- /v2/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/128.png -------------------------------------------------------------------------------- /v2/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/16.png -------------------------------------------------------------------------------- /v2/data/icons/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/18.png -------------------------------------------------------------------------------- /v2/data/icons/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/19.png -------------------------------------------------------------------------------- /v2/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/256.png -------------------------------------------------------------------------------- /v2/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/32.png -------------------------------------------------------------------------------- /v2/data/icons/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/36.png -------------------------------------------------------------------------------- /v2/data/icons/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/38.png -------------------------------------------------------------------------------- /v2/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/48.png -------------------------------------------------------------------------------- /v2/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/512.png -------------------------------------------------------------------------------- /v2/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/64.png -------------------------------------------------------------------------------- /v3/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/128.png -------------------------------------------------------------------------------- /v3/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/16.png -------------------------------------------------------------------------------- /v3/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/256.png -------------------------------------------------------------------------------- /v3/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/32.png -------------------------------------------------------------------------------- /v3/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/48.png -------------------------------------------------------------------------------- /v3/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/512.png -------------------------------------------------------------------------------- /v3/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/64.png -------------------------------------------------------------------------------- /v3/data/sounds/1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/1.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/2.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/3.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/4.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/5.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/6.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/7.mp3 -------------------------------------------------------------------------------- /v3/data/sounds/8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/sounds/8.mp3 -------------------------------------------------------------------------------- /v2/data/popup/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/popup/fontello.woff -------------------------------------------------------------------------------- /v3/data/icons/active/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/active/16.png -------------------------------------------------------------------------------- /v3/data/icons/active/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/active/32.png -------------------------------------------------------------------------------- /v3/data/icons/error/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/error/16.png -------------------------------------------------------------------------------- /v3/data/icons/error/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/error/32.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/16.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/18.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/19.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/32.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/36.png -------------------------------------------------------------------------------- /v2/data/icons/disabled/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/disabled/38.png -------------------------------------------------------------------------------- /v2/data/icons/skipped/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/skipped/16.png -------------------------------------------------------------------------------- /v2/data/icons/skipped/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/skipped/32.png -------------------------------------------------------------------------------- /v2/data/icons/skipped/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v2/data/icons/skipped/48.png -------------------------------------------------------------------------------- /v3/data/icons/disabled/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/disabled/16.png -------------------------------------------------------------------------------- /v3/data/icons/disabled/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/disabled/32.png -------------------------------------------------------------------------------- /v3/data/icons/skipped/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/skipped/16.png -------------------------------------------------------------------------------- /v3/data/icons/skipped/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james-fray/tab-reloader/HEAD/v3/data/icons/skipped/32.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | addon-sdk* 2 | node_modules/ 3 | server/node_modules/ 4 | builds/unpacked 5 | test/ 6 | .DS_Store 7 | Thumbs.db 8 | -------------------------------------------------------------------------------- /v3/data/popup/extra/README: -------------------------------------------------------------------------------- 1 | https://github.com/jakiestfu/Behave.js/blob/c3c289ed346b1525a08401d588c15627bc020cdf/behave.js 2 | -------------------------------------------------------------------------------- /v2/plugins/badge/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../../.eslintrc.json", 3 | "parserOptions": { 4 | "sourceType": "module" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /v3/data/popup/img/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /v3/data/sounds/play.js: -------------------------------------------------------------------------------- 1 | const args = new URLSearchParams(location.search); 2 | 3 | const audio = new Audio(args.get('src')); 4 | audio.volume = Number(args.get('volume')); 5 | audio.onerror = audio.onended = () => chrome.runtime.sendMessage({ 6 | method: 'close-document' 7 | }); 8 | audio.play(); 9 | -------------------------------------------------------------------------------- /v3/data/sounds/play.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /v3/data/scripts/ste.js: -------------------------------------------------------------------------------- 1 | { 2 | const ste = () => { 3 | window.stop(); 4 | const e = (document.scrollingElement || document.body); 5 | e.scrollTop = e.scrollHeight; 6 | }; 7 | if (document.readyState === 'loading') { 8 | document.addEventListener('DOMContentLoaded', ste); 9 | } 10 | else { 11 | ste(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /v3/data/popup/health.js: -------------------------------------------------------------------------------- 1 | /* global api */ 2 | 3 | const check = () => { 4 | if (confirm('Worker is not responding! Would you like to restart the extension?')) { 5 | chrome.runtime.reload(); 6 | } 7 | }; 8 | check.id = setTimeout(check, 2000); 9 | 10 | api.post.bg({ 11 | method: 'echo' 12 | }, r => { 13 | if (r) { 14 | clearTimeout(check.id); 15 | console.info('health check passed'); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /v3/data/locale.js: -------------------------------------------------------------------------------- 1 | // localization 2 | [...document.querySelectorAll('[data-i18n]')].forEach(e => { 3 | for (const id of e.dataset.i18n.split('|')) { 4 | const a = id.split('@'); 5 | let method = 'textContent'; 6 | if (e.dataset.i18nValue) { 7 | method = e.dataset.i18nValue; 8 | } 9 | else if (a.length === 2) { 10 | method = a[1]; 11 | } 12 | else if (e.tagName === 'INPUT') { 13 | method = 'value'; 14 | } 15 | e[method] = chrome.i18n.getMessage(a[0]); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /v3/data/counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
00:00:00
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /v2/plugins.js: -------------------------------------------------------------------------------- 1 | /* plug-in system */ 2 | const startup = () => chrome.storage.local.get({ 3 | './plugins/badge/core.js': false 4 | }, prefs => { 5 | if (prefs['./plugins/badge/core.js']) { 6 | import('./plugins/badge/core.js').then(o => o.enable()); 7 | } 8 | }); 9 | chrome.runtime.onStartup.addListener(startup); 10 | chrome.runtime.onInstalled.addListener(startup); 11 | 12 | chrome.storage.onChanged.addListener(ps => { 13 | // AMO does not like dynamic imports 14 | if ('./plugins/badge/core.js' in ps) { 15 | import('./plugins/badge/core.js').then(o => o[ps['./plugins/badge/core.js'].newValue ? 'enable' : 'disable']()); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: webextension?product=tab-reloader 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /v3/data/popup/keyboard.js: -------------------------------------------------------------------------------- 1 | /* keyboard support */ 2 | 3 | document.addEventListener('keydown', e => { 4 | if (e.target.type === 'text' || e.target.type === 'number' || e.target.tagName === 'TEXTAREA') { 5 | return; 6 | } 7 | 8 | const enabled = document.body.dataset.enabled === 'true'; 9 | const meta = e.ctrlKey || e.metaKey; 10 | 11 | if (e.code === 'Escape' && e.shiftKey) { 12 | window.close(); 13 | } 14 | else if (e.code === 'Escape' && enabled) { 15 | e.preventDefault(); 16 | document.getElementById('disable').dispatchEvent(new Event('click')); 17 | } 18 | else if (e.code === 'KeyS' && !enabled) { 19 | e.preventDefault(); 20 | e.stopPropagation(); 21 | document.body.dataset.forced = e.shiftKey; 22 | document.getElementById('enable').click(); 23 | } 24 | else if (e.code.startsWith('Digit') && meta && !enabled) { 25 | e.preventDefault(); 26 | document.querySelectorAll('#presets .entry')[Number(e.key) - 1].dispatchEvent(new Event('click', { 27 | bubbles: true 28 | })); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /v3/data/popup/permission.js: -------------------------------------------------------------------------------- 1 | /* global tab, api */ 2 | 3 | // request permission 4 | document.addEventListener('change', async e => { 5 | if (e.target.checked && e.target.dataset.permission === 'true') { 6 | const url = tab.url; 7 | 8 | try { 9 | if (tab.url.startsWith('http')) { 10 | let origin = url.replace(/^https*/, '*'); 11 | try { 12 | origin = '*://' + (new URL(tab.url)).hostname + '/'; 13 | } 14 | catch (e) {} 15 | 16 | const granted = await api.permissions.request({ 17 | origins: [origin] 18 | }); 19 | if (granted !== true) { 20 | throw Error('NOT_GRANTED'); 21 | } 22 | } 23 | else { 24 | const granted = await api.permissions.request({ 25 | origins: [url] 26 | }); 27 | if (granted !== true) { 28 | throw Error('NOT_GRANTED'); 29 | } 30 | } 31 | } 32 | catch (ee) { 33 | console.error(ee); 34 | setTimeout(() => e.target.checked = false, 500); 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /v3/data/popup/rate.js: -------------------------------------------------------------------------------- 1 | chrome.storage.local.get({ 2 | 'rate': true, 3 | 'crate': 0 4 | }, prefs => { 5 | document.getElementById('rate').dataset.hide = prefs['rate'] === false || prefs.crate < 5 || Math.random() < 0.5; 6 | 7 | if (prefs.crate < 5) { 8 | prefs.crate += 1; 9 | chrome.storage.local.set({crate: prefs.crate}); 10 | } 11 | }); 12 | 13 | document.getElementById('rate').onclick = () => { 14 | let url = 'https://chrome.google.com/webstore/detail/tab-reloader/dejobinhdiimklegodgbmbifijpppopn/reviews/'; 15 | if (/Edg/.test(navigator.userAgent)) { 16 | url = 'https://microsoftedge.microsoft.com/addons/detail/amclpbiglkmdhodbgnchnkmfdghnabik'; 17 | } 18 | else if (/Firefox/.test(navigator.userAgent)) { 19 | url = 'https://addons.mozilla.org/firefox/addon/tab-reloader/reviews/'; 20 | } 21 | else if (/OPR/.test(navigator.userAgent)) { 22 | url = 'https://addons.opera.com/extensions/details/tab-reloader/'; 23 | } 24 | 25 | chrome.storage.local.set({ 26 | 'rate': false 27 | }, () => chrome.tabs.create({ 28 | url 29 | })); 30 | }; 31 | -------------------------------------------------------------------------------- /v3/defaults.js: -------------------------------------------------------------------------------- 1 | const defaults = {}; 2 | 3 | defaults.profile = { 4 | 'period': '00:05:00', 5 | 'variation': 0, 6 | 'current': false, 7 | 'nofocus': false, 8 | 'cache': false, 9 | 'form': false, 10 | 'offline': false, 11 | 'discarded': false, 12 | 'nodiscard': false, 13 | 'randomize': false, 14 | 'skip-auto-add': false, 15 | 'scroll-to-end': false, 16 | 'visual-countdown': false, 17 | 'stop-on-address-change': false, 18 | 'switch': false, 19 | 'sound': false, 20 | 'sound-value': 1, 21 | 'blocked-words': '', 22 | 'blocked-period': '00:00:00 - 23:59:59', 23 | 'code': false, 24 | 'code-value': '', 25 | 'pre-code': false, 26 | 'pre-code-value': '' 27 | }; 28 | 29 | defaults.presets = [ 30 | {hh: 0, mm: 0, ss: 30}, 31 | {hh: 0, mm: 5, ss: 0}, 32 | {hh: 0, mm: 15, ss: 0}, 33 | {hh: 0, mm: 30, ss: 0}, 34 | {hh: 1, mm: 0, ss: 0}, 35 | {hh: 5, mm: 0, ss: 0} 36 | ]; 37 | 38 | // how many profiles to keep 39 | defaults['max-number-of-profiles'] = 50; 40 | 41 | defaults['schedule-offset'] = 0; 42 | 43 | defaults['badge-color'] = '#5e5e5e'; 44 | 45 | defaults['removed.jobs'] = 5 * 24 * 60 * 60 * 1000; // ms 46 | -------------------------------------------------------------------------------- /v3/data/counter/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | 4 | --bg: #fff; 5 | --fg: #444; 6 | --border-color: #d9d9d9; 7 | } 8 | 9 | @media (prefers-color-scheme: dark) { 10 | :root { 11 | color-scheme: dark light; 12 | 13 | --bg: #202124; 14 | --fg: #b5bec6; 15 | --border-color: #494c50; 16 | } 17 | } 18 | 19 | body { 20 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; 21 | background-color: var(--bg); 22 | accent-color: var(--bg); 23 | color: var(--fg); 24 | display: grid; 25 | place-items: center; 26 | width: 100vw; 27 | height: 100vh; 28 | box-sizing: border-box; 29 | margin: 0; 30 | border: solid 1px var(--border-color); 31 | } 32 | #cbc { 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | font-size: 20vw; 37 | } 38 | input[type=radio] { 39 | position: absolute; 40 | opacity: 0.2; 41 | cursor: pointer; 42 | margin: 0; 43 | } 44 | input[type=radio]:checked { 45 | opacity: 1; 46 | } 47 | #tl { 48 | top: 5px; 49 | left: 5px; 50 | } 51 | #tr { 52 | top: 5px; 53 | right: 5px; 54 | } 55 | #bl { 56 | bottom: 5px; 57 | left: 5px; 58 | } 59 | #br { 60 | bottom: 5px; 61 | right: 5px; 62 | } 63 | -------------------------------------------------------------------------------- /v2/data/options/matched.json: -------------------------------------------------------------------------------- 1 | { 2 | "country-flags": { 3 | "name": "Country Flags & IP WHOIS" 4 | }, 5 | "work-offline": { 6 | "name": "Work Offline" 7 | }, 8 | "save-images": { 9 | "name": "Download All Images" 10 | }, 11 | "privacy-settings": { 12 | "name": "Privacy Settings" 13 | }, 14 | "tab-discard": { 15 | "name": "Auto Tab Discard" 16 | }, 17 | "send-to": { 18 | "name": "Send to VLC" 19 | }, 20 | "two-factor-authenticator": { 21 | "name": "Open Two-Factor Authenticator" 22 | }, 23 | "useragent-switcher": { 24 | "name": "User-Agent Switcher and Manager" 25 | }, 26 | "block-site": { 27 | "name": "Block Site" 28 | }, 29 | "chrome-reader-view": { 30 | "name": "Reader View" 31 | }, 32 | "dark-theme": { 33 | "name": "Dark Theme" 34 | }, 35 | "mute-tab": { 36 | "name": "Mute Tab" 37 | }, 38 | "proxy-switcher": { 39 | "name": "Proxy Switcher" 40 | }, 41 | "font-finder": { 42 | "name": "Font Finder" 43 | }, 44 | "popup-blocker": { 45 | "name": "Popup Blocker" 46 | }, 47 | "audio-equalizer": { 48 | "name": "Audio Equalizer" 49 | }, 50 | "ecleaner": { 51 | "name": "Forget Button" 52 | }, 53 | "search-all-tabs": { 54 | "name": "Search all Tabs" 55 | }, 56 | "bookmarks-commander": { 57 | "name": "Bookmarks Commander" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /v2/data/popup/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('./fontello.woff') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | [class^="icon-"]:before, [class*=" icon-"]:before { 9 | font-family: "fontello"; 10 | font-style: normal; 11 | font-weight: normal; 12 | speak: none; 13 | 14 | display: inline-block; 15 | text-decoration: inherit; 16 | width: 1em; 17 | margin-right: .2em; 18 | text-align: center; 19 | /* opacity: .8; */ 20 | 21 | /* For safety - reset parent styles, that can break glyph codes*/ 22 | font-variant: normal; 23 | text-transform: none; 24 | 25 | /* fix buttons height, for twitter bootstrap */ 26 | line-height: 1em; 27 | 28 | /* Animation center compensation - margins should be symmetric */ 29 | /* remove if not needed */ 30 | margin-left: .2em; 31 | 32 | /* you can be more comfortable with increased icons size */ 33 | /* font-size: 120%; */ 34 | 35 | /* Font smoothing. That was taken from TWBS */ 36 | -webkit-font-smoothing: antialiased; 37 | -moz-osx-font-smoothing: grayscale; 38 | 39 | /* Uncomment for 3D effect */ 40 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 41 | } 42 | 43 | .icon-stopwatch:before { content: '\e800'; } /* '' */ 44 | .icon-toggle-off:before { content: '\e803'; } /* '' */ 45 | .icon-toggle-on:before { content: '\e805'; } /* '' */ 46 | -------------------------------------------------------------------------------- /v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tab Reloader (page auto refresh)", 3 | "description": "An easy-to-use tab reloader with custom reloading time settings for individual tabs", 4 | "version": "0.3.8", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "tabs", 9 | "alarms", 10 | "webNavigation", 11 | "contextMenus" 12 | ], 13 | "optional_permissions": [ 14 | "" 15 | ], 16 | "browser_action": { 17 | "default_icon": { 18 | "16": "data/icons/disabled/16.png", 19 | "18": "data/icons/disabled/18.png", 20 | "19": "data/icons/disabled/19.png", 21 | "32": "data/icons/disabled/32.png", 22 | "36": "data/icons/disabled/36.png", 23 | "38": "data/icons/disabled/38.png" 24 | }, 25 | "default_popup": "data/popup/index.html" 26 | }, 27 | "background": { 28 | "scripts": [ 29 | "plugins.js", 30 | "common.js" 31 | ] 32 | }, 33 | "homepage_url": "https://add0n.com/tab-reloader.html", 34 | "icons": { 35 | "16": "data/icons/16.png", 36 | "18": "data/icons/18.png", 37 | "19": "data/icons/19.png", 38 | "32": "data/icons/32.png", 39 | "36": "data/icons/36.png", 40 | "38": "data/icons/38.png", 41 | "48": "data/icons/48.png", 42 | "64": "data/icons/64.png", 43 | "128": "data/icons/128.png", 44 | "256": "data/icons/256.png", 45 | "512": "data/icons/512.png" 46 | }, 47 | "options_ui": { 48 | "page": "data/options/index.html", 49 | "open_in_tab": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /v3/data/scripts/sha.js: -------------------------------------------------------------------------------- 1 | { 2 | // session storage 3 | const {href} = location; 4 | 5 | let e = document.body; 6 | try { 7 | e = document.querySelector( 8 | localStorage.getItem('tab-reloader-element-' + href) || 9 | localStorage.getItem('tab-reloader-element') 10 | ) || e; 11 | } 12 | catch (e) {} 13 | 14 | console.info('Tab Reloader', 'Calculating hash of', e); 15 | 16 | // crypto.subtle is not available on http 17 | const content = e.innerText || ''; 18 | 19 | chrome.runtime.sendMessage({ 20 | method: 'sha256', 21 | message: content 22 | }, hash => { 23 | if (hash) { // Firefox sometimes does not return valid hash 24 | const oh = sessionStorage.getItem('tab-reloader-hash-' + href); 25 | 26 | if (hash && oh && oh !== hash) { 27 | if (window.switch) { 28 | chrome.runtime.sendMessage({ 29 | method: 'activate-tab' 30 | }); 31 | } 32 | const src = localStorage.getItem('tab-reloader-sound-' + href) || 33 | localStorage.getItem('tab-reloader-sound') || 34 | window.src; 35 | 36 | if (src) { 37 | const volume = Number( 38 | localStorage.getItem('tab-reloader-volume-' + href) || 39 | localStorage.getItem('tab-reloader-volume') 40 | ) || 1; 41 | 42 | chrome.runtime.sendMessage({ 43 | method: 'play-sound', 44 | src, 45 | volume 46 | }); 47 | } 48 | } 49 | 50 | console.info('Tab Reloader', 'Hash is', hash); 51 | sessionStorage.setItem('tab-reloader-hash-' + href, hash); 52 | } 53 | else { 54 | console.warn('cannot calculate hash', hash); 55 | } 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /v3/data/scripts/vcd.js: -------------------------------------------------------------------------------- 1 | { 2 | const remove = () => { 3 | for (const e of document.querySelectorAll('.sadh6Hjii')) { 4 | e.remove(); 5 | } 6 | }; 7 | remove(); 8 | 9 | const style = (c = () => {}) => chrome.storage.local.get({ 10 | 'counter-position': 'top: 10px; right: 10px;', 11 | 'counter-size': 'width: 200px; height: 100px;' 12 | }, prefs => { 13 | iframe.style = ` 14 | position: fixed; 15 | ${prefs['counter-position']}, 16 | ${prefs['counter-size']}, 17 | border: none; 18 | color-scheme: light; 19 | z-index: calc(Infinity); 20 | border: none; 21 | `; 22 | c(prefs); 23 | }); 24 | 25 | const iframe = document.createElement('iframe'); 26 | iframe.classList.add('sadh6Hjii'); 27 | 28 | style(prefs => { 29 | const v = 30 | (prefs['counter-position'].includes('top') ? 't' : 'b') + 31 | (prefs['counter-position'].includes('left') ? 'l' : 'r'); 32 | 33 | const args = new URLSearchParams(); 34 | args.set('tabId', self.tabId); 35 | args.set('period', self.period); 36 | args.set('position', v); 37 | iframe.src = chrome.runtime.getURL('/data/counter/index.html') + '?' + args.toString(); 38 | 39 | document.documentElement.append(iframe); 40 | }); 41 | 42 | const position = prefs => { 43 | if (prefs['counter-position']) { 44 | style(); 45 | } 46 | }; 47 | const observe = request => { 48 | if (request.method === 'kill-counter') { 49 | remove(); 50 | chrome.runtime.onMessage.removeListener(observe); 51 | chrome.storage.onChanged.removeListener(position); 52 | } 53 | }; 54 | chrome.runtime.onMessage.addListener(observe); 55 | chrome.storage.onChanged.addListener(position); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /v3/data/popup/startup.js: -------------------------------------------------------------------------------- 1 | /* global api, defaults, active, disable */ 2 | 3 | let tab; 4 | const startup = []; 5 | 6 | /* presets */ 7 | api.storage.get({ 8 | 'presets': defaults.presets 9 | }).then(({presets}) => { 10 | const f = document.createDocumentFragment(); 11 | 12 | presets.forEach((preset, n) => { 13 | const span = document.createElement('span'); 14 | span.textContent = api.convert.obj2str(preset); 15 | span.preset = preset; 16 | span.classList.add('entry'); 17 | if (n < 10) { 18 | span.title = 'Ctrl/Command + ' + (n + 1); 19 | } 20 | f.appendChild(span); 21 | }); 22 | 23 | document.getElementById('presets-body').appendChild(f); 24 | }); 25 | document.getElementById('presets').onclick = e => { 26 | if (e.target.preset) { 27 | document.getElementById('period').value = api.convert.obj2str(e.target.preset); 28 | } 29 | }; 30 | 31 | const profile = prefs => { 32 | for (const [id, value] of Object.entries(prefs)) { 33 | const e = document.getElementById(id); 34 | if (e) { 35 | e[e.type === 'checkbox' ? 'checked' : 'value'] = value; 36 | } 37 | } 38 | }; 39 | 40 | startup.push(alarm => { 41 | api.post.bg({ 42 | method: 'search-for-profile-anyway', 43 | alarm, 44 | url: tab.url 45 | }, r => { 46 | if (r.active) { 47 | tab.profile = r.profile; 48 | active(); 49 | } 50 | 51 | profile(r.profile); 52 | }); 53 | }); 54 | startup.push((o, firstRun) => { 55 | if (firstRun === false && !o) { 56 | disable(); 57 | } 58 | }); 59 | 60 | /* init */ 61 | api.tabs.active().then(async tabs => { 62 | tab = tabs.filter(t => t.active).shift(); 63 | const o = await api.alarms.get(tab.id.toString()); 64 | 65 | for (const c of startup) { 66 | c(o, true); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tab Reloader provides a browser action' popup to set up a reloading job for each browser tab with a custom period ranging from 10 seconds to 1 month. The extension also has a badge number which indicates the number of active reloading jobs. You can see the list of all tabs with active reloading jobs in the browser action's popup. 2 | 3 | ### YouTube Preview 4 | [![YouTube Preview](https://img.youtube.com/vi/zAhQlorZZTc/0.jpg)](https://www.youtube.com/watch?v=zAhQlorZZTc) 5 | 6 | ### Features: 7 | 8 | 1. Define variable (random) reloading times with a predefined range 9 | 2. Define whether reloading occurs when the tab is active or not 10 | 3. Restores reloading jobs after a restart (session manager) 11 | 4. Set custom rules to start reloading a tab when URL or hostname matches. 12 | 5. Optionally move to the bottom of a tab after reloading occurs to read the new content 13 | 6. Reload all tabs in the current window or all browser windows 14 | 7. Reload local files (file://) 15 | 8. Ask the extension to bypass form submissions 16 | 9. Define a policy that prevents reloading based on the time (date) and URL. 17 | 10. Run custom JS code on each reload. 18 | 11. Reload only a few times and then stop. 19 | 20 | ### Links: 21 | 22 | * FAQs page: https://webextension.org/listing/tab-reloader.html 23 | * Usage Review: https://webextension.org/blog/2022/04/17/tab-reloader-extension.html 24 | * Chrome Webstore: https://chrome.google.com/webstore/detail/tab-reloader-page-auto-re/dejobinhdiimklegodgbmbifijpppopn 25 | * Firefox add-ons: https://addons.mozilla.org/en-US/firefox/addon/tab-reloader/ 26 | * Opera addons: https://addons.opera.com/en/extensions/details/tab-reloader/ 27 | * Edge addons: https://microsoftedge.microsoft.com/addons/detail/tab-reloader-page-auto-r/amclpbiglkmdhodbgnchnkmfdghnabik 28 | -------------------------------------------------------------------------------- /v3/data/popup/actives.js: -------------------------------------------------------------------------------- 1 | /* global api, remaining */ 2 | 3 | { 4 | const cache = {}; 5 | 6 | const ids = []; 7 | api.alarms.forEach(async o => { 8 | const tabId = Number(o.name); 9 | const tab = await api.tabs.get(tabId); 10 | 11 | if (tab) { 12 | if (tab.active !== true) { 13 | const div = document.createElement('div'); 14 | div.tabId = tabId; 15 | div.classList.add('entry', 'button'); 16 | div.title = tab.title + ' -> ' + tab.url; 17 | 18 | const timer = document.createElement('span'); 19 | timer.textContent = '00:20'; 20 | timer.classList.add('timer'); 21 | div.append(timer); 22 | 23 | const title = document.createElement('span'); 24 | title.textContent = tab.title || tab.url; 25 | title.classList.add('title'); 26 | div.append(title); 27 | 28 | document.getElementById('actives').append(div); 29 | 30 | cache[tabId] = timer; 31 | } 32 | } 33 | else { 34 | ids.push(tabId); 35 | } 36 | }).then(() => { 37 | if (ids.length) { 38 | api.post.bg({ 39 | reason: 'tab-not-found-on-popup', 40 | method: 'remove-jobs', 41 | ids 42 | }); 43 | } 44 | 45 | if (Object.keys(cache)) { 46 | let timer; 47 | const once = () => { 48 | api.alarms.forEach(o => { 49 | const tabId = Number(o.name); 50 | 51 | if (cache[tabId]) { 52 | cache[tabId].textContent = remaining(o); 53 | } 54 | }); 55 | 56 | clearTimeout(timer); 57 | timer = setTimeout(once, 1000); 58 | }; 59 | once(); 60 | } 61 | }); 62 | } 63 | document.getElementById('actives').onclick = e => { 64 | const tabId = e.target.tabId; 65 | 66 | if (tabId) { 67 | api.tabs.activate(tabId).then(() => window.close()); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /v2/data/options/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, sans-serif; 3 | font-size: 13px; 4 | max-width: 80%; 5 | margin: 10px auto; 6 | background-color: #fff; 7 | color: #4d5156; 8 | } 9 | @media screen and (max-width: 600px) { 10 | body { 11 | margin: 10px; 12 | max-width: unset; 13 | } 14 | } 15 | 16 | button { 17 | height: 24px; 18 | color: rgb(68, 68, 68); 19 | background-image: linear-gradient(rgb(237, 237, 237), rgb(237, 237, 237) 38%, rgb(222, 222, 222)); 20 | box-shadow: rgba(0, 0, 0, 0.08) 0 1px 0, rgba(255, 255, 255, 0.75) 0 1px 2px inset; 21 | text-shadow: rgb(240, 240, 240) 0 1px 0; 22 | } 23 | button, 24 | textarea, 25 | input { 26 | border: solid 1px rgba(0, 0, 0, 0.25); 27 | color: #4d5156; 28 | } 29 | 30 | textarea { 31 | width: 100%; 32 | } 33 | input[type=number] { 34 | width: 45px; 35 | padding: 5px; 36 | } 37 | h1 { 38 | font-size: 18px; 39 | font-weight: normal; 40 | display: inline-block; 41 | margin-top: 30px; 42 | } 43 | td { 44 | padding: 2px 0; 45 | } 46 | td:first-child { 47 | vertical-align: top; 48 | padding-right: 5px; 49 | } 50 | a { 51 | text-decoration: none; 52 | user-select: none; 53 | color: #07c; 54 | } 55 | .desc { 56 | background-color: #e8e8e8; 57 | } 58 | div.desc, 59 | .desc td { 60 | padding: 5px; 61 | } 62 | .hide { 63 | display: none; 64 | } 65 | 66 | #example, 67 | #keywords, 68 | #events, 69 | #desc-1, 70 | #desc-2, 71 | #desc-3, 72 | #desc-4 { 73 | cursor: pointer; 74 | user-select: none; 75 | color: #07c; 76 | } 77 | 78 | .grid-1, 79 | .grid-2, 80 | .grid-3, 81 | .grid-4 { 82 | display: grid; 83 | align-items: center; 84 | } 85 | .grid-1 { 86 | grid-template-columns: 32px 1fr; 87 | grid-row-gap: 5px; 88 | } 89 | .grid-2 { 90 | grid-template-columns: 32px 1fr 60px; 91 | } 92 | .grid-3 { 93 | grid-template-columns: 32px 1fr 60px; 94 | } 95 | .grid-4 { 96 | grid-template-columns: 32px 1fr; 97 | grid-row-gap: 5px; 98 | } 99 | #color, 100 | #history_timeout { 101 | justify-self: end; 102 | } 103 | -------------------------------------------------------------------------------- /v3/data/counter/index.js: -------------------------------------------------------------------------------- 1 | /* global api */ 2 | 3 | const args = new URLSearchParams(location.search); 4 | 5 | const tabId = args.get('tabId'); 6 | const period = args.get('period'); 7 | 8 | 9 | let isReloading = false; 10 | addEventListener('beforeunload', () => { 11 | isReloading = true; 12 | }); 13 | 14 | const remaining = (o, p) => { 15 | let remaining = (o.scheduledTime - Date.now()) / 1000; 16 | 17 | // if chrome.alarms misses, force sync 18 | if (remaining < -2 && isReloading === false) { 19 | chrome.runtime.sendMessage({ 20 | method: 'synchronous-timings' 21 | }); 22 | } 23 | 24 | // if (remaining < 0 && p) { 25 | // const period = api.convert.secods( 26 | // api.convert.str2obj(p) 27 | // ); 28 | // while (period > 0 && remaining < 0) { 29 | // remaining += period; 30 | // } 31 | // } 32 | // 33 | if (remaining < 0) { 34 | remaining = 0; 35 | } 36 | 37 | const v = api.convert.sec2obj(remaining); 38 | return api.convert.obj2str(v); 39 | }; 40 | 41 | // display timer 42 | let timer; 43 | const once = () => api.alarms.get(tabId).then(o => { 44 | if (o) { 45 | const v = remaining(o, period); 46 | document.getElementById('cbc').textContent = v; 47 | } 48 | 49 | clearTimeout(timer); 50 | timer = setTimeout(once, 1000); 51 | }); 52 | once(); 53 | 54 | addEventListener('visibilitychange', () => { 55 | if (document.visibilityState === 'hidden') { 56 | clearTimeout(timer); 57 | } 58 | else { 59 | once(); 60 | } 61 | }); 62 | 63 | document.getElementById(args.get('position')).checked = true; 64 | 65 | addEventListener('change', e => { 66 | if (e.target.id === 'tl') { 67 | chrome.storage.local.set({ 68 | 'counter-position': 'top: 10px; left: 10px;' 69 | }); 70 | } 71 | else if (e.target.id === 'tr') { 72 | chrome.storage.local.set({ 73 | 'counter-position': 'top: 10px; right: 10px;' 74 | }); 75 | } 76 | else if (e.target.id === 'bl') { 77 | chrome.storage.local.set({ 78 | 'counter-position': 'bottom: 10px; left: 10px;' 79 | }); 80 | } 81 | else if (e.target.id === 'br') { 82 | chrome.storage.local.set({ 83 | 'counter-position': 'bottom: 10px; right: 10px;' 84 | }); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Tab Reloader (page auto refresh)", 4 | "description": "__MSG_description__", 5 | "default_locale": "en", 6 | "version": "0.6.6", 7 | "permissions": [ 8 | "storage", 9 | "tabs", 10 | "alarms", 11 | "webNavigation", 12 | "contextMenus", 13 | "idle", 14 | "scripting", 15 | "offscreen", 16 | "declarativeNetRequestWithHostAccess" 17 | ], 18 | "optional_host_permissions": [ 19 | "" 20 | ], 21 | "action": { 22 | "default_icon": { 23 | "16": "/data/icons/disabled/16.png", 24 | "32": "/data/icons/disabled/32.png" 25 | }, 26 | "default_popup": "/data/popup/index.html", 27 | "default_title": "Tab Reloader" 28 | }, 29 | "background": { 30 | "service_worker": "worker.js", 31 | "scripts": [ 32 | "api.js", "defaults.js", "reload.js", "context.js", "worker.js" 33 | ] 34 | }, 35 | "homepage_url": "https://webextension.org/listing/tab-reloader.html", 36 | "icons": { 37 | "16": "/data/icons/16.png", 38 | "32": "/data/icons/32.png", 39 | "48": "/data/icons/48.png", 40 | "64": "/data/icons/64.png", 41 | "128": "/data/icons/128.png", 42 | "256": "/data/icons/256.png", 43 | "512": "/data/icons/512.png" 44 | }, 45 | "options_ui": { 46 | "page": "/data/options/index.html", 47 | "open_in_tab": true 48 | }, 49 | "commands": { 50 | "_execute_action": { 51 | "description": "Execute Action" 52 | }, 53 | "reload_all": { 54 | "description": "Reload all tabs" 55 | }, 56 | "reload_all_discarded": { 57 | "description": "Reload all discarded tabs" 58 | }, 59 | "reload_window": { 60 | "description": "Reload all tabs in the current window" 61 | }, 62 | "reload_window_discarded": { 63 | "description": "Reload all discarded tabs in the current window" 64 | }, 65 | "reload_tabs_left": { 66 | "description": "Reload tabs to the left" 67 | }, 68 | "reload_tabs_right": { 69 | "description": "Reload tabs to the right" 70 | }, 71 | "stop_all": { 72 | "description": "Stop all Reloading Jobs" 73 | } 74 | }, 75 | "web_accessible_resources": [{ 76 | "resources": ["data/counter/index.html"], 77 | "matches": ["*://*/*"] 78 | }], 79 | "browser_specific_settings": { 80 | "gecko": { 81 | "id": "jid0-bnmfwWw2w2w4e4edvcdDbnMhdVg@jetpack", 82 | "strict_min_version": "128.0" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /v3/data/options/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: dark light; 3 | 4 | --bg: #fff; 5 | --bg-desc: #e8e8e8; 6 | --fg: #4d5156; 7 | --bt: #4d5156; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | body { 12 | --fg: #e7eaed; 13 | --bg: #202124; 14 | --bg-desc: #2b2a33; 15 | --bt: #202124; 16 | } 17 | } 18 | 19 | body { 20 | font-family: "Helvetica Neue", Helvetica, sans-serif; 21 | font-size: 14px; 22 | background-color: var(--bg); 23 | color: var(--fg); 24 | margin-inline: auto; 25 | width: min(100% - 2rem, 70rem); 26 | } 27 | 28 | button { 29 | height: 24px; 30 | color: var(--bt); 31 | background-image: linear-gradient(rgb(237, 237, 237), rgb(237, 237, 237) 38%, rgb(222, 222, 222)); 32 | box-shadow: rgba(0, 0, 0, 0.08) 0 1px 0, rgba(255, 255, 255, 0.75) 0 1px 2px inset; 33 | text-shadow: rgb(240, 240, 240) 0 1px 0; 34 | border: solid 1px rgba(0, 0, 0, 0.25); 35 | font-size: inherit; 36 | } 37 | textarea, 38 | input[type=text], 39 | input[type=number] { 40 | border: solid 1px rgba(0, 0, 0, 0.25); 41 | color: var(--fg); 42 | } 43 | textarea { 44 | width: 100%; 45 | padding: 10px; 46 | } 47 | input[type=text] { 48 | padding: 5px; 49 | } 50 | input[type=number] { 51 | width: 45px; 52 | padding: 5px; 53 | } 54 | h1 { 55 | font-size: 18px; 56 | font-weight: normal; 57 | display: inline-block; 58 | margin-top: 30px; 59 | } 60 | td { 61 | padding: 2px 0; 62 | } 63 | td:first-child { 64 | vertical-align: top; 65 | padding-right: 5px; 66 | } 67 | a { 68 | text-decoration: none; 69 | user-select: none; 70 | color: #07c; 71 | } 72 | .desc { 73 | background-color: var(--bg-desc); 74 | } 75 | div.desc, 76 | .desc td { 77 | padding: 10px; 78 | } 79 | .hide { 80 | display: none; 81 | } 82 | 83 | #example, 84 | #keywords, 85 | #events, 86 | #desc-1, 87 | #desc-2, 88 | #desc-3, 89 | #desc-4 { 90 | cursor: pointer; 91 | user-select: none; 92 | color: #07c; 93 | } 94 | 95 | .grid-1, 96 | .grid-2, 97 | .grid-3, 98 | .grid-4 { 99 | display: grid; 100 | align-items: center; 101 | grid-gap: 5px; 102 | } 103 | .grid-1 { 104 | grid-template-columns: min-content 1fr; 105 | } 106 | .grid-3 { 107 | grid-template-columns: min-content 1fr 60px; 108 | } 109 | #color, 110 | #history_timeout { 111 | justify-self: end; 112 | } 113 | 114 | .disabled { 115 | opacity: 0.5; 116 | pointer-events: none; 117 | } 118 | .save { 119 | text-align: center; 120 | padding: 10px; 121 | border: solid 1px var(--fg); 122 | } 123 | .top { 124 | margin-top: 10px; 125 | } 126 | 127 | @supports (-webkit-hyphens: none) and (not (-moz-appearance: none)) { 128 | #support { 129 | display: none; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /v2/plugins/badge/core.js: -------------------------------------------------------------------------------- 1 | /* global log, timeout, storage */ 2 | 3 | const _f = timeout.set; 4 | const _s = timeout.stop; 5 | let t; 6 | 7 | const clean = () => { 8 | const count = Object.values(storage).filter(o => o.status).length; 9 | for (const [id, o] of Object.entries(storage)) { 10 | if (o.status !== true) { 11 | const tabId = Number(id); 12 | chrome.browserAction.setBadgeText({ 13 | tabId, 14 | text: count ? String(count) : '' 15 | }); 16 | } 17 | } 18 | }; 19 | 20 | chrome.alarms.onAlarm.addListener(o => { 21 | if (o.name === 'badge-updates') { 22 | log('badge plugin alarm is fired'); 23 | const now = Date.now(); 24 | 25 | clean(); 26 | for (const [id, o] of Object.entries(storage)) { 27 | if (o.status !== true) { 28 | continue; 29 | } 30 | const tabId = Number(id); 31 | // left in minutes 32 | const left = Math.round((o.vperiod - (now - o.time)) / 1000 / 60); 33 | if (left < 60) { 34 | chrome.browserAction.setBadgeText({ 35 | tabId, 36 | text: left + 'm' 37 | }); 38 | } 39 | else if (left < 24 * 60) { 40 | chrome.browserAction.setBadgeText({ 41 | tabId, 42 | text: Math.ceil(left / 60) + 'h' 43 | }); 44 | } 45 | else { 46 | chrome.browserAction.setBadgeText({ 47 | tabId, 48 | text: Math.round(left / 60 / 24) + 'd' 49 | }); 50 | } 51 | } 52 | } 53 | }); 54 | const check = () => { 55 | if (Object.values(storage).some(o => o.status)) { 56 | log('badge plugin alarm is set to one minute'); 57 | chrome.alarms.create('badge-updates', { 58 | when: Date.now(), 59 | periodInMinutes: 1 60 | }); 61 | } 62 | else { 63 | log('badge plugin alarm is removed'); 64 | chrome.alarms.clear('badge-updates'); 65 | clean(); 66 | } 67 | }; 68 | 69 | function enable() { 70 | console.log('badge plugin is enabled'); 71 | 72 | timeout.set = function(...args) { 73 | clearTimeout(t); 74 | t = setTimeout(check, 1000); 75 | return _f.apply(this, args); 76 | }; 77 | timeout.stop = function(...args) { 78 | clearTimeout(t); 79 | t = setTimeout(check, 1000); 80 | return _s.apply(this, args); 81 | }; 82 | check(); 83 | } 84 | function disable() { 85 | log('badge plugin is disabled'); 86 | timeout.set = _f; 87 | timeout.stop = _s; 88 | chrome.alarms.clear('badge-updates'); 89 | const count = Object.values(storage).filter(o => o.status).length; 90 | for (const [id, o] of Object.entries(storage)) { 91 | if (o.status) { 92 | chrome.browserAction.setBadgeText({ 93 | tabId: Number(id), 94 | text: String(count) 95 | }); 96 | } 97 | } 98 | } 99 | 100 | export { 101 | enable, 102 | disable 103 | }; 104 | -------------------------------------------------------------------------------- /v2/data/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 |
Enable Reloader for this tab:Disabled
Do not reload if tab is active:Disabled
Reload if window is not focused:Disabled
Use cache while reloading:Disabled
Bypass form submission:Disabled
Do not reload if offline:Disabled
Scroll to the end after reload:Disabled
Run JS code after each reload:
44 | 45 |
48 |

Adjust Reloading Time

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
DaysHoursMinutesSecondsVariation (%)
65 |

66 | Presets: 67 |

68 |

Status

69 |
70 | [Current Tab]: 71 |
72 | [All Jobs]: Currently, Tab Reloader is active on 0 tabs 73 |
74 |
75 |

Active Jobs

76 |
77 |
    78 |
    79 |
    80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /v2/data/popup/index.css: -------------------------------------------------------------------------------- 1 | @supports (-moz-appearance: none) { 2 | input[type=button] { 3 | height: 24px; 4 | color: rgb(68, 68, 68); 5 | background-image: linear-gradient(rgb(237, 237, 237), rgb(237, 237, 237) 38%, rgb(222, 222, 222)); 6 | box-shadow: rgba(0, 0, 0, 0.08) 0 1px 0, rgba(255, 255, 255, 0.75) 0 1px 2px inset; 7 | text-shadow: rgb(240, 240, 240) 0 1px 0; 8 | border: solid 1px rgba(0, 0, 0, 0.25); 9 | } 10 | } 11 | body { 12 | overflow: hidden; 13 | margin: 0 5px 10px 5px; 14 | width: 500px; 15 | } 16 | body.ffo { 17 | width: unset; 18 | } 19 | 20 | table, 21 | body, 22 | input { 23 | font-family: "Helvetica Neue", "Helvetica CY", Arial, sans-serif; 24 | font-size: 12px; 25 | line-height: 1.42857; 26 | color: #444; 27 | background-color: #fff; 28 | } 29 | 30 | table { 31 | width: calc(100%); 32 | border-collapse: collapse; 33 | } 34 | th, 35 | td { 36 | padding-top: 5px; 37 | } 38 | 39 | h1 { 40 | font-size: 100%; 41 | } 42 | 43 | textarea, 44 | input[type=number] { 45 | padding: 10px; 46 | background-color: #444; 47 | color: #fff; 48 | border: solid 1px #fff; 49 | box-shadow: 0 0 2px #444; 50 | } 51 | input[type=number] { 52 | -moz-appearance: textfield; 53 | width: 50px; 54 | text-align: center; 55 | font-size: 120%; 56 | } 57 | input[type=number]::-webkit-outer-spin-button, 58 | input[type=number]::-webkit-inner-spin-button { 59 | -webkit-appearance: none; 60 | margin: 0; 61 | } 62 | tr[mode=info] { 63 | font-size: 80%; 64 | } 65 | textarea { 66 | width: 100%; 67 | box-sizing: border-box; 68 | resize: vertical; 69 | } 70 | textarea::placeholder { 71 | color: #c1c1c1; 72 | } 73 | 74 | .fancy { 75 | position: relative; 76 | line-height: 0.5; 77 | text-align: center; 78 | width: 100%; 79 | } 80 | .fancy b { 81 | color: #444; 82 | position: relative; 83 | background-color: #fff; 84 | z-index: 2; 85 | padding: 0 5px; 86 | } 87 | .fancy::before { 88 | border-top: dotted 1px #444; 89 | content: ' '; 90 | width: 100%; 91 | display: block; 92 | top: 3px; 93 | position: absolute; 94 | z-index: 1; 95 | } 96 | 97 | td[data-type=current], 98 | td[data-type=nofocus], 99 | td[data-type=offline], 100 | td[data-type=cache], 101 | td[data-type=scoll-to-end], 102 | td[data-type=form], 103 | td[data-type=enable] { 104 | cursor: pointer; 105 | width: 75px; 106 | white-space: nowrap; 107 | user-select: none; 108 | -webkit-user-select: none; 109 | } 110 | td[data-type=current]::before, 111 | td[data-type=nofocus]::before, 112 | td[data-type=offline]::before, 113 | td[data-type=cache]::before, 114 | td[data-type=form]::before, 115 | td[data-type=scoll-to-end]::before, 116 | td[data-type=enable]::before { 117 | padding-right: 5px; 118 | } 119 | 120 | input[disabled], 121 | body[data-enabled=true] textarea, 122 | body[data-enabled=true] [data-type='current'], 123 | body[data-enabled=true] [data-type='nofocus'], 124 | body[data-enabled=true] [data-type='offline'], 125 | body[data-enabled=true] [data-type='cache'], 126 | body[data-enabled=true] [data-type='scoll-to-end'], 127 | body[data-enabled=true] [data-type='form'], 128 | body[data-enabled=true] [data-type='hh'], 129 | body[data-enabled=true] [data-type='mm'], 130 | body[data-enabled=true] [data-type='ss'], 131 | body[data-enabled=true] [data-type='vr'], 132 | body[data-enabled=true] [data-type='dd'], 133 | body[data-enabled=true] .presets span { 134 | opacity: 0.5; 135 | pointer-events: none; 136 | } 137 | textarea[disabled] { 138 | display: none; 139 | } 140 | a, 141 | a:visited { 142 | text-decoration: none; 143 | user-select: none; 144 | color: #07c; 145 | } 146 | 147 | body[data-jobs="0"] #jobs { 148 | display: none; 149 | } 150 | #jobs > div { 151 | max-height: 80px; 152 | overflow: auto; 153 | margin-bottom: 10px; 154 | } 155 | #jobs ol { 156 | padding: 0 5px; 157 | margin: 0; 158 | list-style: none; 159 | } 160 | #jobs li { 161 | cursor: pointer; 162 | overflow: hidden; 163 | white-space: nowrap; 164 | text-overflow: ellipsis; 165 | } 166 | #jobs li:hover { 167 | background-color: rgba(0, 0, 0, 0.1); 168 | } 169 | #permit { 170 | background-color: #e6e6e6; 171 | border: none; 172 | cursor: pointer; 173 | } 174 | #permit:disabled { 175 | visibility: hidden; 176 | } 177 | 178 | .presets { 179 | display: grid; 180 | grid-template-columns: min-content repeat(6, 1fr); 181 | justify-items: center; 182 | align-items: center; 183 | } 184 | .presets span { 185 | background-color: #e6e6e6; 186 | padding: 2px 5px; 187 | cursor: pointer; 188 | } 189 | 190 | body.ffo .presets { 191 | grid-template-columns: min-content repeat(4, 1fr); 192 | } 193 | body.ffo .presets span[data-mm="30"], 194 | body.ffo .presets span[data-hh="5"] { 195 | display: none; 196 | } 197 | 198 | .attn { 199 | font-size: 140%; 200 | line-height: 42px; 201 | } 202 | .attn td { 203 | border-bottom: solid 1px #e6e6e6; 204 | } 205 | -------------------------------------------------------------------------------- /v2/data/options/matched.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | const shuffle = array => { 5 | for (let i = array.length - 1; i > 0; i -= 1) { 6 | const j = Math.floor(Math.random() * (i + 1)); 7 | [array[i], array[j]] = [array[j], array[i]]; 8 | } 9 | 10 | return array; 11 | }; 12 | 13 | const root = document.getElementById('explore'); 14 | 15 | const INC = Number(root.dataset.inc || 100); 16 | const count = Number(localStorage.getItem('explore-count') || INC - 5); 17 | const cols = Number(root.dataset.cols || 3); 18 | 19 | const style = document.createElement('style'); 20 | style.textContent = ` 21 | #explore { 22 | background-color: #fff; 23 | position: relative; 24 | color: #969696; 25 | user-select: none; 26 | } 27 | #explore[data-loaded=true] { 28 | margin: 4px; 29 | padding: 5px; 30 | box-shadow: 0 0 4px #ccc; 31 | border: solid 1px #ccc; 32 | } 33 | #explore .close { 34 | position: absolute; 35 | right: 6px; 36 | top: 4px; 37 | cursor: pointer; 38 | } 39 | #explore>table { 40 | margin-top: 10px; 41 | table-layout: fixed; 42 | width: 100%; 43 | border-collapse: collapse; 44 | } 45 | #explore a { 46 | text-decoration: none; 47 | color: #000; 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | } 52 | #explore td:first-child a { 53 | justify-content: flex-start; 54 | } 55 | #explore td:last-child a { 56 | justify-content: flex-end; 57 | } 58 | #explore .title { 59 | border-left: solid 1px #ccc; 60 | display: inline-block; 61 | align-items: center; 62 | overflow: hidden; 63 | text-overflow: ellipsis; 64 | white-space: nowrap; 65 | padding-left: 5px; 66 | } 67 | #explore .icon { 68 | min-width: 28px; 69 | height: 28px; 70 | display: inline-flex; 71 | align-items: center; 72 | justify-content: center; 73 | border-radius: 50%; 74 | color: #fff; 75 | margin-right: 5px; 76 | font-size: 10px; 77 | font-weight: 100; 78 | } 79 | #explore .explore { 80 | position: absolute; 81 | right: 10px; 82 | z-index: 1000000; 83 | cursor: pointer; 84 | font-size: 15px; 85 | }`; 86 | document.documentElement.appendChild(style); 87 | 88 | const cload = () => fetch('matched.json').then(r => r.json()).then(build); 89 | const explore = () => { 90 | const span = document.createElement('span'); 91 | span.textContent = '↯'; 92 | span.title = 'Explore more'; 93 | span.classList.add('explore'); 94 | root.appendChild(span); 95 | span.onclick = () => { 96 | root.textContent = ''; 97 | localStorage.setItem('explore-count', INC); 98 | cload(); 99 | }; 100 | }; 101 | const build = json => { 102 | if (json.length === 0) { 103 | return; 104 | } 105 | root.dataset.loaded = true; 106 | root.textContent = 'Explore more'; 107 | const table = document.createElement('table'); 108 | const tr = document.createElement('tr'); 109 | const span = document.createElement('span'); 110 | span.classList.add('close'); 111 | span.textContent = '✕'; 112 | span.onclick = () => { 113 | root.textContent = ''; 114 | root.dataset.loaded = false; 115 | localStorage.setItem('explore-count', 0); 116 | explore(); 117 | }; 118 | root.appendChild(span); 119 | 120 | const {homepage_url} = chrome.runtime.getManifest(); 121 | const origin = homepage_url.split('/').slice(0, -1).join('/'); 122 | const colors = shuffle( 123 | ['524c84', '606470', '755da3', 'c06c84', '393e46', '446e5c', '693e52', '1d566e', '693e52', 'd95858', 'f27370'] 124 | ); 125 | shuffle(Object.entries(json)).slice(0, cols).forEach(([id, {name}], i) => { 126 | const td = document.createElement('td'); 127 | const a = Object.assign(document.createElement('a'), { 128 | target: '_blank', 129 | title: 'Click to browse', 130 | href: origin + '/' + id + '.html?context=explore' 131 | }); 132 | 133 | const icon = document.createElement('span'); 134 | icon.textContent = name.split(' ').slice(0, 2).map(s => s[0]).join('').toUpperCase(); 135 | icon.classList.add('icon'); 136 | icon.style['background-color'] = '#' + colors[i]; 137 | a.appendChild(icon); 138 | 139 | const span = document.createElement('span'); 140 | span.classList.add('title'); 141 | span.textContent = name; 142 | a.appendChild(span); 143 | td.appendChild(a); 144 | tr.appendChild(td); 145 | }); 146 | table.appendChild(tr); 147 | root.appendChild(table); 148 | }; 149 | const init = () => { 150 | if (count >= INC) { 151 | if (count < INC + 3) { 152 | cload(); 153 | } 154 | else { 155 | explore(); 156 | } 157 | if (count > INC + 5) { 158 | localStorage.setItem('explore-count', INC - 6); 159 | } 160 | else { 161 | localStorage.setItem('explore-count', count + 1); 162 | } 163 | } 164 | else { 165 | explore(); 166 | localStorage.setItem('explore-count', count + 1); 167 | } 168 | }; 169 | if (/Edg/.test(navigator.userAgent) === false) { 170 | init(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /v2/data/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Options Page :: Tab Reloader 5 | 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 | 16 |
    17 |
    18 | 19 | 20 | 21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 | 28 | 29 | 30 |
    31 |
    32 | 33 | Default reloading time: 34 |
    35 | Days: 36 | Hours: 37 | Minutes: 38 | Seconds: 39 |
    40 |
    41 |
    42 |

    Defaults

    43 |
    44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 |
    59 |

    Presets

    60 | 61 |
    62 |
    63 |

    Custom Jobs

    (Inset a Sample, Supported Keywords, Supported Events, Toggle Description) 64 |
    65 | 66 |
    67 | JSON list of hostnames to set an automatic refresh after each start-up. You can manually ask the extension to set the JSON rules from "Restore old reloading jobs" context menu over the browser action button. If you need URL matching instead of hostname matching, remove the "hostname" key and append "url" key instead. 68 |
    69 |
    70 | 71 | 72 |
    73 |
    If this option is checked and the new page's hostname matches a key in the JSON object, a new reloading job will automatically be set for the current tab.
    74 |

    Reloading Policy

    75 | 76 |

    Plug-ins:

    77 |
    78 | 79 | 80 |
    81 |

    Misc

    82 |
    83 | 84 | 85 | 86 | 87 |
    88 |
    89 |
    90 |

    Tools

    (Toggle Description) 91 |
    92 |
    93 | Clear: Clear session restore object (clear memory of current reloading jobs) 94 |
    95 | Export: Export all settings to a JSON file and download it to the default download directory 96 |
    97 | Import: Import settings from a JSON file (this will overwrite current settings and restarts the extension) 98 |
    99 | Reset: Reset settings to the factory values 100 |
    101 |
    102 | 103 | - 104 | - 105 |
    106 |
    107 | 108 | 109 | - 110 | 111 |
    112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /v3/data/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 | 13 | 14 | 15 | 16 | % 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 |
    24 | 25 | 26 | 27 |
    28 |
    29 | 30 |
    31 | 32 |
    33 | 34 |
    35 | 36 |
    37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 64 | 65 | 66 | 67 | 68 |
    69 | 79 | 80 |
    81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 92 | 93 | 94 | 104 | 105 |
    106 |
    107 |
    108 | 109 |
    110 | 111 | 112 | 113 |
    114 |
    115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /v3/data/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    12 |
    13 | 14 | 15 |
    16 |
    17 | 18 | 19 | 20 |
    21 |
    22 |

    23 |
    24 | 25 |
    26 | 27 | 28 |
    29 | 30 |
    31 | 32 | 33 |
    34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 |
    59 |

    60 | 61 |
    62 |
    63 |

    64 |
    65 | 66 | 69 | 70 | 73 | 74 | 77 | 78 | 81 | 82 | 85 | 86 | 89 | 90 | 93 | 94 | 97 |
    98 |
    99 |
    100 |

    (, , , ) 101 |
    102 | 103 |
    104 | 105 |
    106 |
    107 | 108 | 109 |
    110 |
    111 |

    112 | 113 |

    114 |
    115 | 116 |
    117 | 118 | 119 | () 120 |
    121 | 122 | 123 | 124 | 125 |
    126 |
    127 |

    () 128 |
    129 |
    130 | 131 |
    132 | 133 |
    134 | 135 |
    136 |
    137 | 138 | - - 139 |
    140 |
    141 | 142 | 143 | - 144 | 145 |
    146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /v3/data/popup/index.js: -------------------------------------------------------------------------------- 1 | /* global api, tab, Behave, startup */ 2 | 3 | // start or stop a job 4 | document.addEventListener('mousedown', e => { 5 | document.body.dataset.forced = e.shiftKey; 6 | 7 | // https://bugzilla.mozilla.org/show_bug.cgi?id=812389 8 | if (api.firefox && e.target.getAttribute('for') === 'enable') { 9 | document.getElementById('enable').click(); 10 | } 11 | }); 12 | 13 | const remaining = (o, profile) => { 14 | let remaining = (o.scheduledTime - Date.now()) / 1000; 15 | if (remaining < 0 && profile) { 16 | const period = api.convert.secods( 17 | api.convert.str2obj(profile.period) 18 | ); 19 | while (period > 0 && remaining < 0) { 20 | remaining += period; 21 | } 22 | } 23 | // 24 | if (remaining < 0) { 25 | remaining = 0; 26 | } 27 | 28 | const v = api.convert.sec2obj(remaining); 29 | return api.convert.obj2str(v); 30 | }; 31 | 32 | const generate = (forced = false) => { 33 | const time = api.convert.str2obj(document.getElementById('period').value); 34 | let period = Math.max(1, api.convert.secods(time)); 35 | if (forced === false) { 36 | period = Math.max(10, period); 37 | } 38 | return { 39 | 'period': api.convert.obj2str(api.convert.sec2obj(period)), 40 | 'variation': Math.min(100, Math.max(0, document.getElementById('variation').value)), 41 | 'current': document.getElementById('current').checked, 42 | 'nofocus': document.getElementById('nofocus').checked, 43 | 'cache': document.getElementById('cache').checked, 44 | 'form': document.getElementById('form').checked, 45 | 'offline': document.getElementById('offline').checked, 46 | 'discarded': document.getElementById('discarded').checked, 47 | 'nodiscard': document.getElementById('nodiscard').checked, 48 | 'randomize': document.getElementById('randomize').checked, 49 | 'skip-auto-add': document.getElementById('skip-auto-add').checked, 50 | 'scroll-to-end': document.getElementById('scroll-to-end').checked, 51 | 'visual-countdown': document.getElementById('visual-countdown').checked, 52 | 'stop-on-address-change': document.getElementById('stop-on-address-change').checked, 53 | 'switch': document.getElementById('switch').checked, 54 | 'sound': document.getElementById('sound').checked, 55 | 'sound-value': document.getElementById('sound-value').value, 56 | 'blocked-words': document.getElementById('blocked-words').value, 57 | 'blocked-period': document.getElementById('blocked-period').value, 58 | 'code': document.getElementById('code').checked, 59 | 'code-value': document.getElementById('code-value').value, 60 | 'pre-code': document.getElementById('pre-code').checked, 61 | 'pre-code-value': document.getElementById('pre-code-value').value 62 | }; 63 | }; 64 | 65 | document.getElementById('enable').onchange = async e => { 66 | // register 67 | if (e.target.checked) { 68 | api.post.bg({ 69 | method: 'add-jobs', 70 | profile: generate(document.body.dataset.forced === 'true'), 71 | tabs: await api.tabs.active() 72 | }, active); 73 | } 74 | // unregister 75 | else { 76 | api.post.bg({ 77 | 'reason': 'user-request', 78 | 'method': 'remove-jobs', 79 | 'ids': (await api.tabs.active()).map(t => t.id), 80 | 'skip-echo': true 81 | }); 82 | } 83 | }; 84 | 85 | // display timer 86 | let timer; 87 | const active = () => { 88 | document.getElementById('enable').checked = true; 89 | document.body.dataset.enabled = true; 90 | 91 | const once = () => api.alarms.get(tab.id.toString()).then(o => { 92 | if (o) { 93 | document.querySelector('#timer div').textContent = remaining(o, tab.profile); 94 | } 95 | 96 | clearTimeout(timer); 97 | timer = setTimeout(once, 1000); 98 | }); 99 | once(); 100 | }; 101 | 102 | // disable active timer 103 | const disable = () => { 104 | clearTimeout(timer); 105 | document.body.dataset.enabled = false; 106 | document.getElementById('enable').checked = false; 107 | document.getElementById('enable').dispatchEvent(new Event('change')); 108 | }; 109 | document.getElementById('disable').onclick = disable; 110 | 111 | // code section 112 | new Behave({ 113 | textarea: document.getElementById('code-value'), 114 | replaceTab: true, 115 | softTabs: true, 116 | tabSize: 2 117 | }); 118 | new Behave({ 119 | textarea: document.getElementById('pre-code-value'), 120 | replaceTab: true, 121 | softTabs: true, 122 | tabSize: 2 123 | }); 124 | 125 | // reload 126 | api.post.fired(request => { 127 | if (request.method === 'reload-interface') { 128 | api.alarms.get(tab.id.toString()).then(o => { 129 | // location.reload(); 130 | for (const c of startup) { 131 | c(o, false); 132 | } 133 | }); 134 | } 135 | }); 136 | 137 | // save as policy 138 | document.getElementById('save-as-json').onclick = async e => { 139 | try { 140 | const {hostname, origin, pathname} = new URL(tab.url); 141 | if (hostname) { 142 | const prefs = await api.storage.get({ 143 | 'json': [] 144 | }); 145 | const j = generate(); 146 | Object.assign(j, api.convert.str2obj(j.period)); 147 | delete j.period; 148 | if (j.code && j['code-value']) { 149 | j.code = j['code-value']; 150 | } 151 | else { 152 | j.code = ''; 153 | } 154 | delete j['code-value']; 155 | if (j['pre-code'] && j['pre-code-value']) { 156 | j['pre-code'] = j['pre-code-value']; 157 | } 158 | else { 159 | j['pre-code'] = ''; 160 | } 161 | delete j['pre-code-value']; 162 | if (e.shiftKey || e.ctrlKey || e.metaKey) { 163 | const url = e.shiftKey ? tab.url : (origin + pathname); 164 | 165 | // remove old entries (only match with url) 166 | prefs.json = prefs.json.filter(o => o.url !== url); 167 | prefs.json.push({ 168 | url, 169 | ...j 170 | }); 171 | } 172 | else { 173 | // remove old entries (only match with hostname) 174 | prefs.json = prefs.json.filter(o => o.hostname !== hostname); 175 | prefs.json.push({ 176 | hostname, 177 | ...j 178 | }); 179 | } 180 | api.storage.set(prefs); 181 | e.target.textContent = chrome.i18n.getMessage('popup_saved'); 182 | } 183 | else { 184 | throw Error('Tab does not have a valid address'); 185 | } 186 | } 187 | catch (e) { 188 | console.warn(e); 189 | alert(e.message); 190 | } 191 | }; 192 | 193 | // test sounds 194 | document.getElementById('test-sound').onclick = () => { 195 | const src = document.getElementById('sound-value').value; 196 | 197 | const audio = new Audio(); 198 | audio.src = '/data/sounds/' + src + '.mp3'; 199 | audio.play(); 200 | }; 201 | 202 | // options 203 | document.getElementById('options').addEventListener('toggle', e => api.storage.set({ 204 | 'options': e.target.open 205 | })); 206 | api.storage.get({ 207 | 'options': false 208 | }).then(prefs => document.getElementById('options').open = prefs.options); 209 | 210 | // links 211 | for (const a of [...document.querySelectorAll('[data-href]')]) { 212 | if (a.hasAttribute('href') === false) { 213 | a.href = chrome.runtime.getManifest().homepage_url + '#' + a.dataset.href; 214 | } 215 | } 216 | // pre-code exmaple 217 | document.getElementById('pre-code-inset-example').onchange = e => { 218 | let code = ''; 219 | if (e.target.value === '1') { 220 | code = `document.currentScript.dataset.continue = document.body.innerText.includes("hello");`; 221 | } 222 | else if (e.target.value === '2') { 223 | code = `document.currentScript.dataset.continue = Math.random() < 0.5;`; 224 | } 225 | else if (e.target.value === '3') { 226 | code = `document.currentScript.dataset.continue = document.visibilityState === 'visible';`; 227 | } 228 | if (code) { 229 | document.getElementById('pre-code-value').value = code; 230 | } 231 | e.target.value = 0; 232 | }; 233 | -------------------------------------------------------------------------------- /v3/data/popup/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | 4 | --bg: #fff; 5 | --bg-inactive: #f1f1f1; 6 | --bg-active: #e1f1ff; 7 | --bg-button-1: rgb(237, 237, 237); 8 | --bg-button-2: rgb(222, 222, 222); 9 | --fg: #444; 10 | --fg-link: #1850c1; 11 | --border-width: 5px; 12 | --border-color: #d9d9d9; 13 | --gap: 5px; 14 | } 15 | 16 | @media (prefers-color-scheme: dark) { 17 | :root { 18 | --bg: #202124; 19 | --bg-inactive: #292a2d; 20 | --bg-active: #000; 21 | --bg-button-1: #202124; 22 | --bg-button-2: #292a2d; 23 | --fg: #b5bec6; 24 | --fg-link: #96b2e8; 25 | --border-width: 5px; 26 | --border-color: #494c50; 27 | } 28 | } 29 | 30 | body { 31 | overflow: hidden; 32 | font-size: 13px; 33 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; 34 | color: var(--fg); 35 | background-color: var(--bg); 36 | width: 650px; 37 | user-select: none; 38 | margin: 0; 39 | display: grid; 40 | grid-template-rows: min-content min-content 1fr; 41 | max-height: 600px; 42 | } 43 | body > * { 44 | padding: 10px; 45 | width: calc(100vw - 20px); 46 | } 47 | 48 | header { 49 | display: grid; 50 | grid-template-columns: 1fr min-content; 51 | padding: 0; 52 | width: 100vw; 53 | } 54 | 55 | @media screen and (max-width: 500px) { 56 | header { 57 | grid-template-columns: 1fr; 58 | } 59 | } 60 | 61 | header > div { 62 | padding: 10px; 63 | overflow: hidden; 64 | text-overflow: ellipsis; 65 | } 66 | header > div:last-child { 67 | border-left: solid var(--border-width) var(--bg-inactive); 68 | display: flex; 69 | align-items: center; 70 | justify-content: center; 71 | font-weight: bold; 72 | white-space: nowrap; 73 | padding-left: 20px; 74 | padding-right: 20px; 75 | } 76 | 77 | @media screen and (max-width: 500px) { 78 | header > div:last-child { 79 | border-left: none; 80 | } 81 | } 82 | 83 | footer { 84 | z-index: 1; 85 | } 86 | 87 | select { 88 | padding: 5px; 89 | color: var(--fg); 90 | outline: none; 91 | } 92 | textarea, 93 | input[type=text], 94 | input[type=number] { 95 | padding: 5px; 96 | outline: none; 97 | color: var(--fg); 98 | background-color: var(--bg); 99 | border: solid 1px var(--border-color); 100 | } 101 | textarea { 102 | resize: vertical; 103 | overflow: auto !important; 104 | } 105 | textarea:focus, 106 | input[type=text]:focus, 107 | input[type=number]:focus { 108 | background-color: var(--bg-active) !important; 109 | } 110 | a, 111 | a:visited { 112 | color: var(--fg-link); 113 | text-decoration: none; 114 | } 115 | 116 | .button { 117 | color: var(--fg); 118 | background-image: linear-gradient(var(--bg-button-1), var(--bg-button-1) 38%, var(--bg-button-2)); 119 | border: solid 1px var(--border-color); 120 | cursor: pointer; 121 | transition: 100ms; 122 | display: inline-block; 123 | padding: 0 10px; 124 | box-sizing: border-box; 125 | line-height: 28px; 126 | } 127 | .button:active { 128 | opacity: 0.5; 129 | } 130 | 131 | #period-container { 132 | display: grid; 133 | white-space: nowrap; 134 | grid-template-columns: minmax(20vw, min-content) 5fr min-content 1fr min-content; 135 | align-items: center; 136 | grid-gap: 5px; 137 | } 138 | #period-container * { 139 | overflow: hidden; 140 | text-overflow: ellipsis; 141 | } 142 | #period-container input { 143 | width: 100%; 144 | box-sizing: border-box; 145 | min-width: 80px; 146 | } 147 | 148 | #enable { 149 | display: none; 150 | } 151 | #enable:checked ~ [data-i18n="popup_start"] { 152 | display: none; 153 | } 154 | #enable:not(:checked) ~ [data-i18n="popup_stop"] { 155 | display: none; 156 | } 157 | 158 | @media screen and (max-width: 500px) { 159 | label[for="enable"].button { 160 | padding: 20px; 161 | margin-bottom: 15px; 162 | } 163 | } 164 | 165 | #options { 166 | background-color: var(--bg-inactive); 167 | margin-bottom: 10px; 168 | } 169 | #options summary { 170 | display: flex; 171 | align-items: center; 172 | cursor: pointer; 173 | } 174 | #options summary::marker { 175 | display: none; 176 | content: ""; 177 | } 178 | #options summary::before { 179 | content: ''; 180 | width: 16px; 181 | height: 16px; 182 | background: url('img/arrow.svg') center left no-repeat; 183 | transition: transform 100ms ease-in-out; 184 | } 185 | 186 | @media (prefers-color-scheme: dark) { 187 | #options summary::before { 188 | filter: invert(0.7); 189 | } 190 | } 191 | 192 | #options[open] summary::before { 193 | transform: rotate(90deg); 194 | } 195 | #options summary div { 196 | margin-left: 5px; 197 | flex: 1; 198 | } 199 | #options summary input[data-hide=true] { 200 | display: none; 201 | } 202 | #options[open] summary { 203 | margin-bottom: 10px; 204 | } 205 | 206 | .tree { 207 | white-space: nowrap; 208 | display: grid; 209 | grid-template-columns: min-content minmax(10vw, min-content) minmax(50vw, 1fr); 210 | grid-gap: var(--gap); 211 | align-items: center; 212 | } 213 | .four { 214 | white-space: nowrap; 215 | display: grid; 216 | grid-template-columns: min-content 1fr min-content 1fr; 217 | grid-gap: var(--gap); 218 | align-items: center; 219 | margin-bottom: calc(3 * var(--gap)); 220 | } 221 | .tree *, 222 | .four * { 223 | overflow: hidden; 224 | text-overflow: ellipsis; 225 | } 226 | 227 | #timer { 228 | position: absolute; 229 | top: 0; 230 | left: 0; 231 | right: 0; 232 | bottom: 0; 233 | display: flex; 234 | align-items: start; 235 | justify-content: center; 236 | font-size: 64px; 237 | } 238 | #options[open] ~ #timer { 239 | align-items: center; 240 | } 241 | 242 | @media screen and (max-width: 500px) { 243 | #timer { 244 | align-items: center; 245 | } 246 | } 247 | 248 | body:not([data-enabled=true]) #timer { 249 | display: none; 250 | } 251 | #timer > div { 252 | box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.6); 253 | background-color: var(--bg); 254 | padding: 10px; 255 | margin-top: 5px; 256 | } 257 | #actives:empty ~ #timer > div { 258 | margin-top: 0; 259 | align-self: center; 260 | } 261 | 262 | #timer > svg { 263 | background: var(--bg); 264 | fill: var(--fg); 265 | position: absolute; 266 | top: 10px; 267 | right: 10px; 268 | cursor: pointer; 269 | border-radius: 50%; 270 | padding: 5px; 271 | } 272 | 273 | #presets { 274 | display: flex; 275 | align-items: center; 276 | gap: 5px; 277 | margin-top: 5px; 278 | overflow: hidden; 279 | } 280 | #presets-body { 281 | display: grid; 282 | grid-template-columns: repeat(6, 1fr); 283 | flex: 1; 284 | } 285 | #actives { 286 | overflow: auto; 287 | gap: 5px; 288 | display: flex; 289 | flex-flow: wrap; 290 | } 291 | #actives:empty { 292 | display: none; 293 | } 294 | #actives .entry, 295 | #presets .entry { 296 | height: min-content; 297 | padding: 0 5px; 298 | cursor: pointer; 299 | display: flex; 300 | gap: 1ch; 301 | } 302 | #actives .entry * { 303 | pointer-events: none; 304 | } 305 | #actives .entry .title { 306 | max-width: 13ch; 307 | white-space: nowrap; 308 | text-overflow: ellipsis; 309 | overflow: hidden; 310 | } 311 | #presets .entry { 312 | text-decoration: underline; 313 | } 314 | #presets .entry:active { 315 | background-color: var(--bg-active); 316 | } 317 | 318 | @media screen and (max-width: 450px) { 319 | #presets .entry:nth-child(7) { 320 | display: none; 321 | } 322 | } 323 | 324 | @media screen and (max-width: 400px) { 325 | #presets .entry:nth-child(6) { 326 | display: none; 327 | } 328 | } 329 | 330 | @media screen and (max-width: 350px) { 331 | #presets .entry:nth-child(5) { 332 | display: none; 333 | } 334 | } 335 | 336 | #save-as-json { 337 | justify-self: end; 338 | } 339 | 340 | #sound-container { 341 | display: flex; 342 | justify-self: end; 343 | gap: 5px; 344 | } 345 | 346 | @supports (-moz-appearance:none) { 347 | .chrome { 348 | opacity: 0.5; 349 | pointer-events: none; 350 | text-decoration: line-through; 351 | } 352 | } 353 | 354 | label[for="pre-code"] { 355 | display: flex; 356 | gap: var(--gap); 357 | align-items: center; 358 | } 359 | #pre-code-inset-example { 360 | width: 16px; 361 | height: 10px; 362 | } 363 | 364 | .permission { 365 | cursor: pointer; 366 | } 367 | -------------------------------------------------------------------------------- /v3/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "簡單易用的分頁重整工具,支援自訂重整間隔、單獨分頁設定等進階功能!" 4 | }, 5 | "popup_active": { 6 | "message": "分頁作用中時不重整" 7 | }, 8 | "popup_configs": { 9 | "message": "設定" 10 | }, 11 | "popup_nofocus": { 12 | "message": "視窗未聚焦時重整" 13 | }, 14 | "popup_cache": { 15 | "message": "重整時使用快取(還原狀態)" 16 | }, 17 | "popup_form": { 18 | "message": "略過表單提交提示" 19 | }, 20 | "popup_offline": { 21 | "message": "離線時不重整分頁" 22 | }, 23 | "popup_discarded": { 24 | "message": "分頁已捨棄時不重整" 25 | }, 26 | "popup_randomize": { 27 | "message": "首次重整加入隨機延遲" 28 | }, 29 | "popup_skip_auto_add": { 30 | "message": "未手動結束時停止自動啟動" 31 | }, 32 | "popup_nodiscard": { 33 | "message": "防止分頁被自動捨棄" 34 | }, 35 | "popup_ste": { 36 | "message": "重整後自動捲動至底部" 37 | }, 38 | "popup_switch": { 39 | "message": "內容有變更時自動切換至該分頁" 40 | }, 41 | "popup_sound": { 42 | "message": "內容有變更時播放提示音" 43 | }, 44 | "popup_bw": { 45 | "message": "網址或標題包含下列內容時不重整" 46 | }, 47 | "popup_period": { 48 | "message": "重整區間" 49 | }, 50 | "popup_active_tabs": { 51 | "message": "作用中分頁" 52 | }, 53 | "popup_enable": { 54 | "message": "每隔以下時間重整所選分頁" 55 | }, 56 | "popup_permission": { 57 | "message": "需要分頁網址權限" 58 | }, 59 | "popup_presets": { 60 | "message": "預設方案" 61 | }, 62 | "popup_forced": { 63 | "message": "按住 Shift 強制設置小於10秒的間隔" 64 | }, 65 | "popup_code": { 66 | "message": "執行 JS 程式碼" 67 | }, 68 | "popup_code_desc": { 69 | "message": "每次重整後執行此 JS 程式碼" 70 | }, 71 | "popup_pre_code": { 72 | "message": "執行條件程式碼" 73 | }, 74 | "popup_pre_code_desc": { 75 | "message": "如有定義,必須設定 \"document.currentScript.dataset.continue = true\" 才會重整" 76 | }, 77 | "popup_pre_code_example_1": { 78 | "message": "僅當頁面包含 \"hello\" 字時才重整" 79 | }, 80 | "popup_pre_code_example_2": { 81 | "message": "以50%機率隨機重整頁面" 82 | }, 83 | "popup_pre_code_example_3": { 84 | "message": "僅當頁面可見時才重整" 85 | }, 86 | "popup_variation": { 87 | "message": "變化幅度" 88 | }, 89 | "popup_variation_hint": { 90 | "message": "設定百分比變化,隨機調整重整時間(首重整後生效)" 91 | }, 92 | "popup_enable_keys": { 93 | "message": "'S' 啟用重整\\n'Esc' 停用重整\\nShift+Esc 關閉彈窗\\nCtrl/Command+1-10 選擇預設" 94 | }, 95 | "popup_start": { 96 | "message": "開始重整" 97 | }, 98 | "popup_stop": { 99 | "message": "停止重整" 100 | }, 101 | "popup_save_as_json": { 102 | "message": "儲存為自訂任務" 103 | }, 104 | "popup_save_as_json_desc": { 105 | "message": "Shift+點擊:以完整網址比對\\nCtrl/Command+點擊:以不含參數的網址比對" 106 | }, 107 | "popup_saved": { 108 | "message": "已儲存!" 109 | }, 110 | "popup_sound_1": { 111 | "message": "提示音1" 112 | }, 113 | "popup_sound_2": { 114 | "message": "提示音2" 115 | }, 116 | "popup_sound_3": { 117 | "message": "提示音3" 118 | }, 119 | "popup_sound_4": { 120 | "message": "提示音4" 121 | }, 122 | "popup_sound_5": { 123 | "message": "提示音5" 124 | }, 125 | "popup_sound_6": { 126 | "message": "提示音6" 127 | }, 128 | "popup_sound_7": { 129 | "message": "提示音7" 130 | }, 131 | "popup_sound_8": { 132 | "message": "提示音8" 133 | }, 134 | "popup_test_sound": { 135 | "message": "測試" 136 | }, 137 | "popup_rate": { 138 | "message": "評分" 139 | }, 140 | "popup_vcd": { 141 | "message": "顯示倒數計時" 142 | }, 143 | "options_title": { 144 | "message": "設定頁面 :: 分頁重整器" 145 | }, 146 | "options_badge": { 147 | "message": "在圖示徽章顯示啟用任務數" 148 | }, 149 | "options_color": { 150 | "message": "徽章顏色:" 151 | }, 152 | "options_defaults": { 153 | "message": "預設值" 154 | }, 155 | "options_default_time": { 156 | "message": "預設重整間隔:" 157 | }, 158 | "options_default_variation": { 159 | "message": "預設變化幅度:" 160 | }, 161 | "options_pp_current": { 162 | "message": "「分頁作用中時不重整」預設值" 163 | }, 164 | "options_pp_nofocus": { 165 | "message": "「視窗未聚焦時重整」預設值" 166 | }, 167 | "options_pp_cache": { 168 | "message": "「重整時使用快取」預設值" 169 | }, 170 | "options_pp_form": { 171 | "message": "「略過表單提交提示」預設值" 172 | }, 173 | "options_pp_offline": { 174 | "message": "「離線時不重整」預設值" 175 | }, 176 | "options_pp_discarded": { 177 | "message": "「分頁已捨棄時不重整」預設值" 178 | }, 179 | "options_pp_nodiscard": { 180 | "message": "「防止分頁被自動捨棄」預設值" 181 | }, 182 | "options_pp_randomize": { 183 | "message": "「首次重整加入隨機延遲」預設值" 184 | }, 185 | "options_pp_scroll_to_end": { 186 | "message": "「重整後自動捲動至底部」預設值" 187 | }, 188 | "options_pp_visual_countdown": { 189 | "message": "「顯示倒數計時」預設值" 190 | }, 191 | "options_presets": { 192 | "message": "預設方案" 193 | }, 194 | "options_custom_jobs": { 195 | "message": "自訂任務" 196 | }, 197 | "options_sample": { 198 | "message": "插入範例" 199 | }, 200 | "options_keywords": { 201 | "message": "支援關鍵字" 202 | }, 203 | "options_events": { 204 | "message": "支援事件" 205 | }, 206 | "options_desc": { 207 | "message": "切換說明" 208 | }, 209 | "options_json_desc": { 210 | "message": "用 JSON 格式列出網址主機,可在啟動後自動重整。若需依據完整網址比對,請移除 \"hostname\" 並改用 \"url\"。詳情請參閱常見問題。" 211 | }, 212 | "options_dynamic_json": { 213 | "message": "新分頁載入時自動比對 JSON 並安裝重整任務" 214 | }, 215 | "options_dynamic_json_desc": { 216 | "message": "啟用後,會為比對到的頁面自動安裝自訂重整任務" 217 | }, 218 | "options_policy": { 219 | "message": "重整條件" 220 | }, 221 | "options_misc": { 222 | "message": "其他" 223 | }, 224 | "options_reassign": { 225 | "message": "新分頁如有舊任務則嘗試重新分配" 226 | }, 227 | "options_faqs": { 228 | "message": "更新時開啟常見問題" 229 | }, 230 | "options_tools": { 231 | "message": "工具" 232 | }, 233 | "options_export_desc": { 234 | "message": "匯出:將所有設定存為 JSON 檔並下載" 235 | }, 236 | "options_import_desc": { 237 | "message": "匯入:由 JSON 檔還原設定(將覆蓋當前設定並重啟擴充功能)" 238 | }, 239 | "options_reset_desc": { 240 | "message": "重設:還原所有設定至預設值" 241 | }, 242 | "options_reset": { 243 | "message": "重設預設值" 244 | }, 245 | "options_export": { 246 | "message": "匯出" 247 | }, 248 | "options_import": { 249 | "message": "匯入" 250 | }, 251 | "options_permission": { 252 | "message": "網址權限" 253 | }, 254 | "options_instruction": { 255 | "message": "使用說明" 256 | }, 257 | "options_ofq": { 258 | "message": "開啟常見問題" 259 | }, 260 | "options_support": { 261 | "message": "支持開發" 262 | }, 263 | "options_save": { 264 | "message": "儲存設定" 265 | }, 266 | "options_saved": { 267 | "message": "設定已儲存" 268 | }, 269 | "options_save_reminder": { 270 | "message": "請務必點擊「儲存設定」按鈕!" 271 | }, 272 | "options_disables": { 273 | "message": "停用右鍵選單" 274 | }, 275 | "options_startup_restore_delay": { 276 | "message": "重啟後恢復任務延遲時間(秒):" 277 | }, 278 | "options_help": { 279 | "message": "了解更多" 280 | }, 281 | "bg_reload_tabs": { 282 | "message": "僅重整這些分頁一次" 283 | }, 284 | "bg_all_tabs": { 285 | "message": "全部分頁" 286 | }, 287 | "bg_reload_all_discarded": { 288 | "message": "全部已捨棄分頁" 289 | }, 290 | "bg_reload_window": { 291 | "message": "當前視窗全部分頁" 292 | }, 293 | "bg_reload_window_discarded": { 294 | "message": "當前視窗全部已捨棄分頁" 295 | }, 296 | "bg_reload_tabs_left": { 297 | "message": "左側分頁" 298 | }, 299 | "bg_reload_tabs_right": { 300 | "message": "右側分頁" 301 | }, 302 | "bg_stop": { 303 | "message": "停止重整" 304 | }, 305 | "bg_stop_all": { 306 | "message": "全部分頁" 307 | }, 308 | "bg_csp": { 309 | "message": "內容安全政策" 310 | }, 311 | "bg_csp_remove": { 312 | "message": "移除本分頁的 CSP 防護(允許內嵌指令碼執行)" 313 | }, 314 | "bg_csp_reset": { 315 | "message": "還原預設" 316 | }, 317 | "bg_no_reload": { 318 | "message": "已選分頁" 319 | }, 320 | "bg_reload_every": { 321 | "message": "定時重整所選分頁" 322 | }, 323 | "bg_reload_every_10s": { 324 | "message": "每10秒" 325 | }, 326 | "bg_reload_every_30s": { 327 | "message": "每30秒" 328 | }, 329 | "bg_reload_every_1m": { 330 | "message": "每分鐘" 331 | }, 332 | "bg_reload_every_5m": { 333 | "message": "每5分鐘" 334 | }, 335 | "bg_reload_every_15m": { 336 | "message": "每15分鐘" 337 | }, 338 | "bg_reload_every_1h": { 339 | "message": "每小時" 340 | }, 341 | "bg_restart": { 342 | "message": "重啟擴充功能" 343 | }, 344 | "bg_options": { 345 | "message": "設定" 346 | }, 347 | "bg_msg_1": { 348 | "message": "分頁網址無效" 349 | }, 350 | "bg_msg_2": { 351 | "message": "未取得此網址的存取權" 352 | }, 353 | "bg_msg_3": { 354 | "message": "警告:移除內容安全政策(CSP)可能使網站有安全風險(如跨站指令碼攻擊)。\n\n確定要繼續嗎?" 355 | }, 356 | "commands_reload_all": { 357 | "message": "重整全部分頁" 358 | }, 359 | "commands_reload_all_discarded": { 360 | "message": "重整全部已捨棄分頁" 361 | }, 362 | "commands_reload_window": { 363 | "message": "重整當前視窗全部分頁" 364 | }, 365 | "commands_reload_window_discarded": { 366 | "message": "重整當前視窗全部已捨棄分頁" 367 | }, 368 | "commands_reload_tabs_left": { 369 | "message": "重整左側分頁" 370 | }, 371 | "commands_reload_tabs_right": { 372 | "message": "重整右側分頁" 373 | }, 374 | "commands_stop": { 375 | "message": "停止所有重整任務" 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /v3/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "简单易用的标签页刷新工具,支持自定义刷新间隔、独立标签页设置等高级功能!" 4 | }, 5 | "popup_active": { 6 | "message": "标签页激活时不刷新" 7 | }, 8 | "popup_configs": { 9 | "message": "设置" 10 | }, 11 | "popup_nofocus": { 12 | "message": "窗口未聚焦时刷新" 13 | }, 14 | "popup_cache": { 15 | "message": "刷新时使用缓存(恢复状态)" 16 | }, 17 | "popup_form": { 18 | "message": "绕过表单提交提示" 19 | }, 20 | "popup_offline": { 21 | "message": "离线时不刷新标签页" 22 | }, 23 | "popup_discarded": { 24 | "message": "标签页已卸载时不刷新" 25 | }, 26 | "popup_randomize": { 27 | "message": "首次刷新添加随机延迟" 28 | }, 29 | "popup_skip_auto_add": { 30 | "message": "未手动停止时禁用自动启动" 31 | }, 32 | "popup_nodiscard": { 33 | "message": "防止标签页被卸载" 34 | }, 35 | "popup_ste": { 36 | "message": "刷新后滚动至页面底部" 37 | }, 38 | "popup_switch": { 39 | "message": "内容变化时自动切换至该标签页" 40 | }, 41 | "popup_sound": { 42 | "message": "内容变化时播放提示音" 43 | }, 44 | "popup_bw": { 45 | "message": "地址或标题包含以下内容时不刷新" 46 | }, 47 | "popup_period": { 48 | "message": "刷新时间范围" 49 | }, 50 | "popup_active_tabs": { 51 | "message": "活动标签页" 52 | }, 53 | "popup_enable": { 54 | "message": "每间隔以下时间刷新选定标签页" 55 | }, 56 | "popup_permission": { 57 | "message": "需要标签页的域名权限" 58 | }, 59 | "popup_presets": { 60 | "message": "预设方案" 61 | }, 62 | "popup_forced": { 63 | "message": "使用 Shift 键强制设置小于10秒的间隔" 64 | }, 65 | "popup_code": { 66 | "message": "执行JS代码" 67 | }, 68 | "popup_code_desc": { 69 | "message": "每次刷新后执行此JS代码" 70 | }, 71 | "popup_pre_code": { 72 | "message": "执行策略代码" 73 | }, 74 | "popup_pre_code_desc": { 75 | "message": "若定义此函数,必须设置 \"document.currentScript.dataset.continue = true\" 才会执行刷新" 76 | }, 77 | "popup_pre_code_example_1": { 78 | "message": "仅当页面包含 \"hello\" 时刷新" 79 | }, 80 | "popup_pre_code_example_2": { 81 | "message": "按50%概率随机刷新页面" 82 | }, 83 | "popup_pre_code_example_3": { 84 | "message": "仅当页面可见时刷新" 85 | }, 86 | "popup_variation": { 87 | "message": "浮动范围" 88 | }, 89 | "popup_variation_hint": { 90 | "message": "设置百分比浮动随机刷新时间(首次刷新后生效)" 91 | }, 92 | "popup_enable_keys": { 93 | "message": "'S'键启用刷新\\n'Esc'键禁用刷新\\nShift+Esc关闭弹窗\\nCtrl/Command+1-10选择预设" 94 | }, 95 | "popup_start": { 96 | "message": "开始刷新" 97 | }, 98 | "popup_stop": { 99 | "message": "停止刷新" 100 | }, 101 | "popup_save_as_json": { 102 | "message": "保存为自定义任务" 103 | }, 104 | "popup_save_as_json_desc": { 105 | "message": "Shift+点击:按完整URL匹配(非域名)\\nCtrl/Command+点击:按排除参数的URL匹配" 106 | }, 107 | "popup_saved": { 108 | "message": "已保存!" 109 | }, 110 | "popup_sound_1": { 111 | "message": "提示音1" 112 | }, 113 | "popup_sound_2": { 114 | "message": "提示音2" 115 | }, 116 | "popup_sound_3": { 117 | "message": "提示音3" 118 | }, 119 | "popup_sound_4": { 120 | "message": "提示音4" 121 | }, 122 | "popup_sound_5": { 123 | "message": "提示音5" 124 | }, 125 | "popup_sound_6": { 126 | "message": "提示音6" 127 | }, 128 | "popup_sound_7": { 129 | "message": "提示音7" 130 | }, 131 | "popup_sound_8": { 132 | "message": "提示音8" 133 | }, 134 | "popup_test_sound": { 135 | "message": "测试" 136 | }, 137 | "popup_rate": { 138 | "message": "给我评分" 139 | }, 140 | "popup_vcd": { 141 | "message": "显示视觉倒计时" 142 | }, 143 | "options_title": { 144 | "message": "选项设置 :: 标签页刷新器" 145 | }, 146 | "options_badge": { 147 | "message": "在徽章区域显示活动任务总数" 148 | }, 149 | "options_color": { 150 | "message": "徽章颜色:" 151 | }, 152 | "options_defaults": { 153 | "message": "默认设置" 154 | }, 155 | "options_default_time": { 156 | "message": "默认刷新间隔:" 157 | }, 158 | "options_default_variation": { 159 | "message": "默认浮动范围:" 160 | }, 161 | "options_pp_current": { 162 | "message": "\"标签页激活时不刷新\"默认值" 163 | }, 164 | "options_pp_nofocus": { 165 | "message": "\"窗口未聚焦时刷新\"默认值" 166 | }, 167 | "options_pp_cache": { 168 | "message": "\"刷新时使用缓存\"默认值" 169 | }, 170 | "options_pp_form": { 171 | "message": "\"绕过表单提交提示\"默认值" 172 | }, 173 | "options_pp_offline": { 174 | "message": "\"离线时不刷新\"默认值" 175 | }, 176 | "options_pp_discarded": { 177 | "message": "\"标签页已卸载时不刷新\"默认值" 178 | }, 179 | "options_pp_nodiscard": { 180 | "message": "\"防止标签页被卸载\"默认值" 181 | }, 182 | "options_pp_randomize": { 183 | "message": "\"首次刷新添加随机延迟\"默认值" 184 | }, 185 | "options_pp_scroll_to_end": { 186 | "message": "\"刷新后滚动至页面底部\"默认值" 187 | }, 188 | "options_pp_visual_countdown": { 189 | "message": "\"显示视觉倒计时\"默认值" 190 | }, 191 | "options_presets": { 192 | "message": "预设方案" 193 | }, 194 | "options_custom_jobs": { 195 | "message": "自定义任务" 196 | }, 197 | "options_sample": { 198 | "message": "插入示例" 199 | }, 200 | "options_keywords": { 201 | "message": "支持的关键词" 202 | }, 203 | "options_events": { 204 | "message": "支持的事件" 205 | }, 206 | "options_desc": { 207 | "message": "切换描述" 208 | }, 209 | "options_json_desc": { 210 | "message": "使用JSON格式列出域名以设置启动后自动刷新。若需URL匹配(非域名),请移除\"hostname\"键并添加\"url\"键。详见常见问题解答。" 211 | }, 212 | "options_dynamic_json": { 213 | "message": "新页面加载时始终检查JSON对象以安装刷新任务" 214 | }, 215 | "options_dynamic_json_desc": { 216 | "message": "启用后,将为匹配列表中任意键的新页面安装自定义刷新任务" 217 | }, 218 | "options_policy": { 219 | "message": "刷新策略" 220 | }, 221 | "options_misc": { 222 | "message": "其他设置" 223 | }, 224 | "options_reassign": { 225 | "message": "新标签页曾有过活动任务时尝试重新分配" 226 | }, 227 | "options_faqs": { 228 | "message": "更新时打开常见问题页面" 229 | }, 230 | "options_tools": { 231 | "message": "工具" 232 | }, 233 | "options_export_desc": { 234 | "message": "导出:将设置保存为JSON文件并下载至默认目录" 235 | }, 236 | "options_import_desc": { 237 | "message": "导入:从JSON文件恢复设置(将覆盖当前设置并重启扩展)" 238 | }, 239 | "options_reset_desc": { 240 | "message": "重置:恢复为出厂设置" 241 | }, 242 | "options_reset": { 243 | "message": "重置默认值" 244 | }, 245 | "options_export": { 246 | "message": "导出" 247 | }, 248 | "options_import": { 249 | "message": "导入" 250 | }, 251 | "options_permission": { 252 | "message": "域名权限" 253 | }, 254 | "options_instruction": { 255 | "message": "使用说明" 256 | }, 257 | "options_ofq": { 258 | "message": "打开常见问题页面" 259 | }, 260 | "options_support": { 261 | "message": "支持开发" 262 | }, 263 | "options_save": { 264 | "message": "保存选项" 265 | }, 266 | "options_saved": { 267 | "message": "选项已保存" 268 | }, 269 | "options_save_reminder": { 270 | "message": "请务必点击\"保存选项\"按钮!" 271 | }, 272 | "options_disables": { 273 | "message": "禁用右键菜单项" 274 | }, 275 | "options_startup_restore_delay": { 276 | "message": "重启后恢复任务的延迟时间(秒):" 277 | }, 278 | "options_help": { 279 | "message": "了解更多" 280 | }, 281 | "bg_reload_tabs": { 282 | "message": "一次性刷新标签页" 283 | }, 284 | "bg_all_tabs": { 285 | "message": "所有标签页" 286 | }, 287 | "bg_reload_all_discarded": { 288 | "message": "所有已卸载标签页" 289 | }, 290 | "bg_reload_window": { 291 | "message": "当前窗口所有标签页" 292 | }, 293 | "bg_reload_window_discarded": { 294 | "message": "当前窗口所有已卸载标签页" 295 | }, 296 | "bg_reload_tabs_left": { 297 | "message": "左侧标签页" 298 | }, 299 | "bg_reload_tabs_right": { 300 | "message": "右侧标签页" 301 | }, 302 | "bg_stop": { 303 | "message": "停止刷新" 304 | }, 305 | "bg_stop_all": { 306 | "message": "所有标签页" 307 | }, 308 | "bg_csp": { 309 | "message": "内容安全策略" 310 | }, 311 | "bg_csp_remove": { 312 | "message": "移除此标签页的CSP保护(允许内联脚本执行)" 313 | }, 314 | "bg_csp_reset": { 315 | "message": "重置为默认" 316 | }, 317 | "bg_no_reload": { 318 | "message": "选定标签页" 319 | }, 320 | "bg_reload_every": { 321 | "message": "定时刷新选定标签页" 322 | }, 323 | "bg_reload_every_10s": { 324 | "message": "每10秒" 325 | }, 326 | "bg_reload_every_30s": { 327 | "message": "每30秒" 328 | }, 329 | "bg_reload_every_1m": { 330 | "message": "每分钟" 331 | }, 332 | "bg_reload_every_5m": { 333 | "message": "每5分钟" 334 | }, 335 | "bg_reload_every_15m": { 336 | "message": "每15分钟" 337 | }, 338 | "bg_reload_every_1h": { 339 | "message": "每小时" 340 | }, 341 | "bg_restart": { 342 | "message": "重启扩展" 343 | }, 344 | "bg_options": { 345 | "message": "选项" 346 | }, 347 | "bg_msg_1": { 348 | "message": "标签页地址无效" 349 | }, 350 | "bg_msg_2": { 351 | "message": "未获得此域名的访问权限" 352 | }, 353 | "bg_msg_3": { 354 | "message": "警告:移除内容安全策略(CSP)可能导致网站遭受安全风险(如跨站脚本攻击)。\n\n确定要继续吗?" 355 | }, 356 | "commands_reload_all": { 357 | "message": "刷新所有标签页" 358 | }, 359 | "commands_reload_all_discarded": { 360 | "message": "刷新所有已卸载标签页" 361 | }, 362 | "commands_reload_window": { 363 | "message": "刷新当前窗口所有标签页" 364 | }, 365 | "commands_reload_window_discarded": { 366 | "message": "刷新当前窗口所有已卸载标签页" 367 | }, 368 | "commands_reload_tabs_left": { 369 | "message": "刷新左侧标签页" 370 | }, 371 | "commands_reload_tabs_right": { 372 | "message": "刷新右侧标签页" 373 | }, 374 | "commands_stop": { 375 | "message": "停止所有刷新任务" 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /v3/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Удобная перезагрузка вкладок с настраиваемым временным интервалом для отдельных вкладок и многое другое!" 4 | }, 5 | "popup_active": { 6 | "message": "Не перезагружать, если вкладка активна" 7 | }, 8 | "popup_configs": { 9 | "message": "Настройки" 10 | }, 11 | "popup_nofocus": { 12 | "message": "Перезагружать, если окно не в фокусе" 13 | }, 14 | "popup_cache": { 15 | "message": "Использовать кэш (возвращать состояние)" 16 | }, 17 | "popup_form": { 18 | "message": "Обходить отправку формы" 19 | }, 20 | "popup_offline": { 21 | "message": "Не перезагружать при отсутствии интернета" 22 | }, 23 | "popup_discarded": { 24 | "message": "Не перезагружать при отмене загрузки" 25 | }, 26 | "popup_randomize": { 27 | "message": "Случайная задержка при первой перезагрузке" 28 | }, 29 | "popup_nodiscard": { 30 | "message": "Запретить отмену загрузки на вкладке" 31 | }, 32 | "popup_ste": { 33 | "message": "Прокрутить до конца после перезагрузки" 34 | }, 35 | "popup_switch": { 36 | "message": "Вызвать вкладку при изменении страницы" 37 | }, 38 | "popup_sound": { 39 | "message": "Проиграть звук при изменении страницы" 40 | }, 41 | "popup_bw": { 42 | "message": "Не перезагружать, если в адресе и названии" 43 | }, 44 | "popup_period": { 45 | "message": "Перезагружать между" 46 | }, 47 | "popup_active_tabs": { 48 | "message": "Активные вкладки" 49 | }, 50 | "popup_enable": { 51 | "message": "Перезагружать вкладку каждые" 52 | }, 53 | "popup_permission": { 54 | "message": "Требуется разрешение хоста вкладки" 55 | }, 56 | "popup_presets": { 57 | "message": "Шаблоны" 58 | }, 59 | "popup_forced": { 60 | "message": "Использовать клавишу Shift для принудительного интервала меньше 10 сек" 61 | }, 62 | "popup_code": { 63 | "message": "Запускать код JS" 64 | }, 65 | "popup_variation": { 66 | "message": "с разницей" 67 | }, 68 | "popup_variation_hint": { 69 | "message": "Установить разницу в процентах для случайного времени перезагрузки (применяется после первой перезагрузки)" 70 | }, 71 | "popup_enable_keys": { 72 | "message": "Клавиша 'S' - включить перезагрузку\nКлавиша 'Escape' - отключить перезагрузку\nКлавиши Shift + Escape - закрыть всплывающее окно\nКлавиши Ctrl/Command + 1/10 - выбрать шаблон" 73 | }, 74 | "popup_start": { 75 | "message": "Запустить" 76 | }, 77 | "popup_stop": { 78 | "message": "Остановить" 79 | }, 80 | "popup_save_as_json": { 81 | "message": "Сохранить как своё задание" 82 | }, 83 | "popup_saved": { 84 | "message": "Сохранено!" 85 | }, 86 | "popup_sound_1": { 87 | "message": "Звук 1" 88 | }, 89 | "popup_sound_2": { 90 | "message": "Звук 2" 91 | }, 92 | "popup_sound_3": { 93 | "message": "Звук 3" 94 | }, 95 | "popup_sound_4": { 96 | "message": "Звук 4" 97 | }, 98 | "popup_sound_5": { 99 | "message": "Звук 5" 100 | }, 101 | "popup_sound_6": { 102 | "message": "Звук 6" 103 | }, 104 | "popup_sound_7": { 105 | "message": "Звук 7" 106 | }, 107 | "popup_sound_8": { 108 | "message": "Звук 8" 109 | }, 110 | "popup_test_sound": { 111 | "message": "Тест" 112 | }, 113 | "popup_rate": { 114 | "message": "Оценить" 115 | }, 116 | "options_title": { 117 | "message": "Настройки :: Tab Reloader" 118 | }, 119 | "options_badge": { 120 | "message": "Отображать количество активных заданий на значке" 121 | }, 122 | "options_color": { 123 | "message": "Цвет значка:" 124 | }, 125 | "options_defaults": { 126 | "message": "Значения по умолчанию" 127 | }, 128 | "options_default_time": { 129 | "message": "Время перезагрузки по умолчанию:" 130 | }, 131 | "options_default_variation": { 132 | "message": "Разница перезагрузки по умолчанию:" 133 | }, 134 | "options_pp_current": { 135 | "message": "Значение по умолчанию \"Не перезагружать, если вкладка активна\"" 136 | }, 137 | "options_pp_nofocus": { 138 | "message": "Значение по умолчанию \"Перезагружать, если окно не в фокусе\"" 139 | }, 140 | "options_pp_cache": { 141 | "message": "Значение по умолчанию \"Использовать кэш (возвращать состояние)\"" 142 | }, 143 | "options_pp_form": { 144 | "message": "Значение по умолчанию \"Обходить отправку формы\"" 145 | }, 146 | "options_pp_offline": { 147 | "message": "Значение по умолчанию \"Не перезагружать при отсутствии интернета\"" 148 | }, 149 | "options_pp_discarded": { 150 | "message": "Значение по умолчанию \"Не перезагружать при отмене загрузки\"" 151 | }, 152 | "options_pp_nodiscard": { 153 | "message": "Значение по умолчанию \"Запретить отмену загрузки на вкладке\"" 154 | }, 155 | "options_pp_randomize": { 156 | "message": "Значение по умолчанию \"Случайная задержка при первой перезагрузке\"" 157 | }, 158 | "options_pp_scroll_to_end": { 159 | "message": "Значение по умолчанию \"Прокрутить до конца после перезагрузки\"" 160 | }, 161 | "options_presets": { 162 | "message": "Шаблоны" 163 | }, 164 | "options_custom_jobs": { 165 | "message": "Свои задания" 166 | }, 167 | "options_sample": { 168 | "message": "Вставить образец" 169 | }, 170 | "options_keywords": { 171 | "message": "Поддерживаемые ключевые слова" 172 | }, 173 | "options_events": { 174 | "message": "Поддерживаемые события" 175 | }, 176 | "options_desc": { 177 | "message": "Переключить описание" 178 | }, 179 | "options_json_desc": { 180 | "message": "Вставить список доменов в формате JSON для автоматического обновления после каждого запуска. Если необходимо совпадение URL вместо всего домена, удалите ключ \"hostname\" и добавьте вместо него ключ \"url\". Для получения более подробной информации перейдите на страницу FAQ." 181 | }, 182 | "options_dynamic_json": { 183 | "message": "Всегда проверять установку заданий перезагрузки из объекта JSON при загрузке новой страницы." 184 | }, 185 | "options_dynamic_json_desc": { 186 | "message": "Если данная настройка включена, расширение устанавливает новое задание перезагрузки для новых страниц, совпадающих с одним из ключей в списке." 187 | }, 188 | "options_policy": { 189 | "message": "Политика перезагрузки" 190 | }, 191 | "options_misc": { 192 | "message": "Прочее" 193 | }, 194 | "options_reassign": { 195 | "message": "Пытаться назначить задание перезагрузки, если на новой вкладке уже выполняется активное." 196 | }, 197 | "options_faqs": { 198 | "message": "Открывать страницу FAQ при обновлении" 199 | }, 200 | "options_tools": { 201 | "message": "Инструменты" 202 | }, 203 | "options_export_desc": { 204 | "message": "Экспорт: Экспортировать все настройки в файл JSON и загрузить его в папку загрузки по умолчанию" 205 | }, 206 | "options_import_desc": { 207 | "message": "Импорт: Импортировать настройки из файла JSON (это перезапишет текущие настройки и перезапустит расширение)" 208 | }, 209 | "options_reset_desc": { 210 | "message": "Сброс: Сбросить настройки на значения по умолчанию" 211 | }, 212 | "options_reset": { 213 | "message": "Сброс значений" 214 | }, 215 | "options_export": { 216 | "message": "Экспорт" 217 | }, 218 | "options_import": { 219 | "message": "Импорт" 220 | }, 221 | "options_permission": { 222 | "message": "Разрешение хоста" 223 | }, 224 | "options_instruction": { 225 | "message": "Инструкция по использованию" 226 | }, 227 | "options_ofq": { 228 | "message": "Открыть страницу FAQ" 229 | }, 230 | "options_support": { 231 | "message": "Поддержать разработку" 232 | }, 233 | "options_save": { 234 | "message": "Сохранить настройки" 235 | }, 236 | "options_saved": { 237 | "message": "Настройки сохранены" 238 | }, 239 | "options_save_reminder": { 240 | "message": "Не забудьте нажать кнопку \"Сохранить настройки\"!" 241 | }, 242 | "bg_reload_tabs": { 243 | "message": "Перезагружать вкладки" 244 | }, 245 | "bg_all_tabs": { 246 | "message": "Все вкладки" 247 | }, 248 | "bg_reload_all_discarded": { 249 | "message": "Все вкладки с отменённой загрузкой" 250 | }, 251 | "bg_reload_window": { 252 | "message": "Все вкладки в текущем окне" 253 | }, 254 | "bg_reload_window_discarded": { 255 | "message": "Все вкладки с отменённой загрузкой в текущем окне" 256 | }, 257 | "bg_reload_tabs_left": { 258 | "message": "Вкладки слева" 259 | }, 260 | "bg_reload_tabs_right": { 261 | "message": "Вкладки справа" 262 | }, 263 | "bg_stop": { 264 | "message": "Остановить перезагрузку" 265 | }, 266 | "bg_stop_all": { 267 | "message": "Все вкладки" 268 | }, 269 | "bg_no_reload": { 270 | "message": "Эта вкладка" 271 | }, 272 | "bg_reload_every": { 273 | "message": "Перезагружать вкладку" 274 | }, 275 | "bg_reload_every_10s": { 276 | "message": "Каждые 10 сек" 277 | }, 278 | "bg_reload_every_30s": { 279 | "message": "Каждые 30 сек" 280 | }, 281 | "bg_reload_every_1m": { 282 | "message": "Каждую минуту" 283 | }, 284 | "bg_reload_every_5m": { 285 | "message": "Каждые 5 минут" 286 | }, 287 | "bg_reload_every_15m": { 288 | "message": "Каждые 15 минут" 289 | }, 290 | "bg_reload_every_1h": { 291 | "message": "Каждый час" 292 | }, 293 | "bg_restart": { 294 | "message": "Перезапустить расширение" 295 | }, 296 | "bg_options": { 297 | "message": "Настройки" 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /v3/_locales/uk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Зручний перезавантажувач із налаштовуваним часом перезавантаження окремих вкладок і більше!" 4 | }, 5 | "popup_active": { 6 | "message": "Не перезавантажувати, якщо вкладка активна" 7 | }, 8 | "popup_configs": { 9 | "message": "Параметри" 10 | }, 11 | "popup_nofocus": { 12 | "message": "Перезавантажувати, якщо вікно поза фокусом" 13 | }, 14 | "popup_cache": { 15 | "message": "Використовувати кеш під час перезавантаження (відновлювати стан)" 16 | }, 17 | "popup_form": { 18 | "message": "Обхід форми надсилання" 19 | }, 20 | "popup_offline": { 21 | "message": "Не перезавантажувати вкладку, якщо поза мережею" 22 | }, 23 | "popup_discarded": { 24 | "message": "Не перезавантажувати вкладку, якщо відмовлено" 25 | }, 26 | "popup_randomize": { 27 | "message": "Випадкова затримка для першого перезавантаження" 28 | }, 29 | "popup_nodiscard": { 30 | "message": "Запобігати припиненню завантаження вкладки" 31 | }, 32 | "popup_ste": { 33 | "message": "Прокрутити вниз після перезавантаження" 34 | }, 35 | "popup_switch": { 36 | "message": "Перемкнутися на вкладку, якщо вміст змінено" 37 | }, 38 | "popup_sound": { 39 | "message": "Відтворювати звук, якщо вміст змінено" 40 | }, 41 | "popup_bw": { 42 | "message": "Не перезавантажувати, якщо адреса або заголовок містить" 43 | }, 44 | "popup_period": { 45 | "message": "Перезавантажувати протягом" 46 | }, 47 | "popup_active_tabs": { 48 | "message": "Активні вкладки" 49 | }, 50 | "popup_enable": { 51 | "message": "Перезавантажувати цю вкладку кожні" 52 | }, 53 | "popup_permission": { 54 | "message": "Потрібен дозвіл власника вкладки" 55 | }, 56 | "popup_presets": { 57 | "message": "Шаблони" 58 | }, 59 | "popup_forced": { 60 | "message": "Використовувати клавішу Shift для примусових проміжків менших за 10 секунд" 61 | }, 62 | "popup_code": { 63 | "message": "Запускати код JS" 64 | }, 65 | "popup_variation": { 66 | "message": "з похибкою" 67 | }, 68 | "popup_variation_hint": { 69 | "message": "Вкажіть похибку у відсотках для випадковості часу перезавантаження (застосовується після першого перезавантаження)" 70 | }, 71 | "popup_enable_keys": { 72 | "message": "Клавіша 'S' дозволяє перезавантаження\nКлавіша 'Escape' вимикає перезавантаження\nКлавіші Shift + Escape, щоб закрити випадне вікно\nКлавіші Ctrl/Command + 1/10, щоб вибрати шаблон" 73 | }, 74 | "popup_start": { 75 | "message": "Розпочати перезавантаження" 76 | }, 77 | "popup_stop": { 78 | "message": "Зупинити перезавантаження" 79 | }, 80 | "popup_save_as_json": { 81 | "message": "Зберегти як власне завдання" 82 | }, 83 | "popup_saved": { 84 | "message": "Збережено!" 85 | }, 86 | "popup_sound_1": { 87 | "message": "Звук 1" 88 | }, 89 | "popup_sound_2": { 90 | "message": "Звук 2" 91 | }, 92 | "popup_sound_3": { 93 | "message": "Звук 3" 94 | }, 95 | "popup_sound_4": { 96 | "message": "Звук 4" 97 | }, 98 | "popup_sound_5": { 99 | "message": "Звук 5" 100 | }, 101 | "popup_sound_6": { 102 | "message": "Звук 6" 103 | }, 104 | "popup_sound_7": { 105 | "message": "Звук 7" 106 | }, 107 | "popup_sound_8": { 108 | "message": "Звук 8" 109 | }, 110 | "popup_test_sound": { 111 | "message": "Тест" 112 | }, 113 | "popup_rate": { 114 | "message": "Оціни мене" 115 | }, 116 | "options_title": { 117 | "message": "Сторінка параметрів :: Перезавантажувач вкладок" 118 | }, 119 | "options_badge": { 120 | "message": "Відображати загальну кількість активних завдань в області значка" 121 | }, 122 | "options_color": { 123 | "message": "Колір значка:" 124 | }, 125 | "options_defaults": { 126 | "message": "Типові" 127 | }, 128 | "options_default_time": { 129 | "message": "Типовий час перезавантаження:" 130 | }, 131 | "options_default_variation": { 132 | "message": "Типова похибка перезавантаження:" 133 | }, 134 | "options_pp_current": { 135 | "message": "Типове значення для \"Не перезавантажувати, якщо вкладка активна\"" 136 | }, 137 | "options_pp_nofocus": { 138 | "message": "Типове значення для \"Перезавантажувати якщо вікно поза фокусом\"" 139 | }, 140 | "options_pp_cache": { 141 | "message": "Типове значення для \"Використовувати кеш під час перезавантаження\"" 142 | }, 143 | "options_pp_form": { 144 | "message": "Типове значення для \"Обхід форми надсилання\"" 145 | }, 146 | "options_pp_offline": { 147 | "message": "Типове значення для \"Не перезавантажувати, якщо поза мережею\"" 148 | }, 149 | "options_pp_discarded": { 150 | "message": "Типове значення для \"Не перезавантажувати, якщо відмовлено\"" 151 | }, 152 | "options_pp_nodiscard": { 153 | "message": "Типове значення для \"Запобігати припиненню завантаження вкладки\"" 154 | }, 155 | "options_pp_randomize": { 156 | "message": "Типове значення для \"Випадкова затримка для першого перезавантаження\"" 157 | }, 158 | "options_pp_scroll_to_end": { 159 | "message": "Типове значення для \"Прокрутити вниз після перезавантаження\"" 160 | }, 161 | "options_presets": { 162 | "message": "Заготовки" 163 | }, 164 | "options_custom_jobs": { 165 | "message": "Власні завдання" 166 | }, 167 | "options_sample": { 168 | "message": "Вставити зразок" 169 | }, 170 | "options_keywords": { 171 | "message": "Підтримувані ключові слова" 172 | }, 173 | "options_events": { 174 | "message": "Підтримувані події" 175 | }, 176 | "options_desc": { 177 | "message": "Перемкнути опис" 178 | }, 179 | "options_json_desc": { 180 | "message": "Перерахуйте імена хостів у форматі JSON, щоб задати автоматичне оновлення після кожного запуску. Якщо Вам потрібно зіставляти з URL замість назви хоста, вилучіть ключ \"hostname\" і долучіть натомість ключ \"url\". Читайте сторінку ЧаП для додаткових інструкцій." 181 | }, 182 | "options_dynamic_json": { 183 | "message": "Завжди перевіряти встановлення перезавантаження з об’єкта JSON під час завантаження нової сторінки." 184 | }, 185 | "options_dynamic_json_desc": { 186 | "message": "Якщо цей параметр увімкнено, розширення встановлює нове спеціальне перезавантаження для нових сторінок, які відповідають одному з ключів у списку." 187 | }, 188 | "options_policy": { 189 | "message": "Політика перезавантаження" 190 | }, 191 | "options_misc": { 192 | "message": "Різне" 193 | }, 194 | "options_reassign": { 195 | "message": "Спробувати призначити завдання перезавантаження, коли нова вкладка має активне перезавантаження." 196 | }, 197 | "options_faqs": { 198 | "message": "Відкривати сторінку ЧаП під час оновлень" 199 | }, 200 | "options_tools": { 201 | "message": "Засоби" 202 | }, 203 | "options_export_desc": { 204 | "message": "Експорт: Експортувати всі налаштування у файл JSON і завантажити його у типовий каталог завантаження" 205 | }, 206 | "options_import_desc": { 207 | "message": "Імпорт: Імпортувати налаштування з файлу JSON (це перезапише поточні налаштування і перезапустить розширення)" 208 | }, 209 | "options_reset_desc": { 210 | "message": "Скинути: Скинути налаштування до початкових значень" 211 | }, 212 | "options_reset": { 213 | "message": "Відновити типові" 214 | }, 215 | "options_export": { 216 | "message": "Експортувати" 217 | }, 218 | "options_import": { 219 | "message": "Імпортувати" 220 | }, 221 | "options_permission": { 222 | "message": "Дозвіл власника" 223 | }, 224 | "options_instruction": { 225 | "message": "Інструкція з використання" 226 | }, 227 | "options_ofq": { 228 | "message": "Відкрити сторінку ЧаП" 229 | }, 230 | "options_support": { 231 | "message": "Підтримка розробки" 232 | }, 233 | "options_save": { 234 | "message": "Зберегти параметри" 235 | }, 236 | "options_saved": { 237 | "message": "Параметри збережено" 238 | }, 239 | "options_save_reminder": { 240 | "message": "Переконайтеся, що натиснуто кнопку \"Зберегти параметри\"!" 241 | }, 242 | "bg_reload_tabs": { 243 | "message": "Перезавантажити вкладки" 244 | }, 245 | "bg_all_tabs": { 246 | "message": "Усі вкладки" 247 | }, 248 | "bg_reload_all_discarded": { 249 | "message": "Усі відхилені вкладки" 250 | }, 251 | "bg_reload_window": { 252 | "message": "Усі вкладки в поточному вікні" 253 | }, 254 | "bg_reload_window_discarded": { 255 | "message": "Усі відхилені вкладки в поточному вікні" 256 | }, 257 | "bg_reload_tabs_left": { 258 | "message": "Вкладки ліворуч" 259 | }, 260 | "bg_reload_tabs_right": { 261 | "message": "Вкладки праворуч" 262 | }, 263 | "bg_stop": { 264 | "message": "Зупинити перезавантаження" 265 | }, 266 | "bg_stop_all": { 267 | "message": "Усі вкладки" 268 | }, 269 | "bg_no_reload": { 270 | "message": "Ця вкладка" 271 | }, 272 | "bg_reload_every": { 273 | "message": "Перезавантажити вкладку" 274 | }, 275 | "bg_reload_every_10s": { 276 | "message": "Що 10 секунд" 277 | }, 278 | "bg_reload_every_30s": { 279 | "message": "Що 30 секунд" 280 | }, 281 | "bg_reload_every_1m": { 282 | "message": "Що хвилини" 283 | }, 284 | "bg_reload_every_5m": { 285 | "message": "Що 5 хвилин" 286 | }, 287 | "bg_reload_every_15m": { 288 | "message": "Що 15 хвилин" 289 | }, 290 | "bg_reload_every_1h": { 291 | "message": "Щогодини" 292 | }, 293 | "bg_restart": { 294 | "message": "Перезавантажити розширення" 295 | }, 296 | "bg_options": { 297 | "message": "Параметри" 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /v3/context.js: -------------------------------------------------------------------------------- 1 | /* global api, messaging */ 2 | 3 | api.runtime.started(async () => { 4 | const prefs = await api.storage.get({ 5 | 'disabled-menu-items': [] 6 | }); 7 | 8 | api.context.add({ 9 | title: chrome.i18n.getMessage('bg_reload_every'), 10 | id: 'reload.every', 11 | contexts: ['action', 'tab'] 12 | }); 13 | api.context.add({ 14 | title: chrome.i18n.getMessage('bg_reload_every_10s'), 15 | id: 'reload.every.00:00:10', 16 | contexts: ['action', 'tab'], 17 | parentId: 'reload.every' 18 | }); 19 | api.context.add({ 20 | title: chrome.i18n.getMessage('bg_reload_every_30s'), 21 | id: 'reload.every.00:00:30', 22 | contexts: ['action', 'tab'], 23 | parentId: 'reload.every' 24 | }); 25 | api.context.add({ 26 | title: chrome.i18n.getMessage('bg_reload_every_1m'), 27 | id: 'reload.every.00:01:00', 28 | contexts: ['action', 'tab'], 29 | parentId: 'reload.every' 30 | }); 31 | api.context.add({ 32 | title: chrome.i18n.getMessage('bg_reload_every_5m'), 33 | id: 'reload.every.00:05:00', 34 | contexts: ['action', 'tab'], 35 | parentId: 'reload.every' 36 | }); 37 | api.context.add({ 38 | title: chrome.i18n.getMessage('bg_reload_every_15m'), 39 | id: 'reload.every.00:15:00', 40 | contexts: ['action', 'tab'], 41 | parentId: 'reload.every' 42 | }); 43 | api.context.add({ 44 | title: chrome.i18n.getMessage('bg_reload_every_1h'), 45 | id: 'reload.every.01:00:00', 46 | contexts: ['action', 'tab'], 47 | parentId: 'reload.every' 48 | }); 49 | api.context.add({ 50 | title: chrome.i18n.getMessage('bg_reload_tabs'), 51 | id: 'reload', 52 | contexts: ['action', 'tab'] 53 | }); 54 | api.context.add({ 55 | title: chrome.i18n.getMessage('bg_all_tabs'), 56 | id: 'reload.all', 57 | contexts: ['action', 'tab'], 58 | parentId: 'reload', 59 | enabled: prefs['disabled-menu-items'].includes('reload.all') === false 60 | }); 61 | api.context.add({ 62 | title: chrome.i18n.getMessage('bg_reload_all_discarded'), 63 | id: 'reload.all.discarded', 64 | contexts: ['action', 'tab'], 65 | parentId: 'reload', 66 | enabled: prefs['disabled-menu-items'].includes('reload.all.discarded') === false 67 | }); 68 | api.context.add({ 69 | title: chrome.i18n.getMessage('bg_reload_window'), 70 | id: 'reload.window', 71 | contexts: ['action', 'tab'], 72 | parentId: 'reload', 73 | enabled: prefs['disabled-menu-items'].includes('reload.window') === false 74 | }); 75 | api.context.add({ 76 | title: chrome.i18n.getMessage('bg_reload_window_discarded'), 77 | id: 'reload.window.discarded', 78 | contexts: ['action', 'tab'], 79 | parentId: 'reload', 80 | enabled: prefs['disabled-menu-items'].includes('reload.window.discarded') === false 81 | }); 82 | api.context.add({ 83 | title: chrome.i18n.getMessage('bg_reload_tabs_left'), 84 | id: 'reload.tabs.left', 85 | contexts: ['action', 'tab'], 86 | parentId: 'reload', 87 | enabled: prefs['disabled-menu-items'].includes('reload.tabs.left') === false 88 | }); 89 | api.context.add({ 90 | title: chrome.i18n.getMessage('bg_reload_tabs_right'), 91 | id: 'reload.tabs.right', 92 | contexts: ['action', 'tab'], 93 | parentId: 'reload', 94 | enabled: prefs['disabled-menu-items'].includes('reload.tabs.right') === false 95 | }); 96 | api.context.add({ 97 | title: chrome.i18n.getMessage('bg_stop'), 98 | id: 'stop', 99 | contexts: ['action', 'tab'] 100 | }); 101 | api.context.add({ 102 | title: chrome.i18n.getMessage('bg_no_reload'), 103 | id: 'no.reload', 104 | contexts: ['action', 'tab'], 105 | parentId: 'stop', 106 | enabled: prefs['disabled-menu-items'].includes('no.reload') === false 107 | }); 108 | api.context.add({ 109 | title: chrome.i18n.getMessage('bg_stop_all'), 110 | id: 'stop.all', 111 | contexts: ['action', 'tab'], 112 | parentId: 'stop', 113 | enabled: prefs['disabled-menu-items'].includes('stop.all') === false 114 | }); 115 | api.context.add({ 116 | title: chrome.i18n.getMessage('bg_csp'), 117 | id: 'csp', 118 | contexts: ['action', 'tab'] 119 | }); 120 | api.context.add({ 121 | title: chrome.i18n.getMessage('bg_csp_remove'), 122 | id: 'csp.remove', 123 | contexts: ['action', 'tab'], 124 | parentId: 'csp' 125 | }); 126 | api.context.add({ 127 | title: chrome.i18n.getMessage('bg_csp_reset'), 128 | id: 'csp.reset', 129 | contexts: ['action', 'tab'], 130 | parentId: 'csp' 131 | }); 132 | api.context.add({ 133 | contexts: ['action', 'tab'], 134 | type: 'separator' 135 | }); 136 | api.context.add({ 137 | title: chrome.i18n.getMessage('bg_restart'), 138 | id: 'restart', 139 | contexts: ['action', 'tab'] 140 | }); 141 | if (api.firefox) { 142 | api.context.add({ 143 | title: chrome.i18n.getMessage('bg_options'), 144 | id: 'options', 145 | contexts: ['action'] 146 | }); 147 | } 148 | }); 149 | { 150 | const observe = async (info, tab) => { 151 | if (info.menuItemId === 'options') { 152 | chrome.runtime.openOptionsPage(); 153 | } 154 | else if (info.menuItemId.startsWith('reload.every.')) { 155 | const period = info.menuItemId.replace('reload.every.', ''); 156 | 157 | api.alarms.get(tab.id.toString()).then(alarm => messaging({ 158 | method: 'search-for-profile-anyway', 159 | url: tab.url, 160 | alarm 161 | }, {}, r => { 162 | r.profile.period = period; 163 | 164 | api.tabs.active().then(tabs => { 165 | messaging({ 166 | method: 'add-jobs', 167 | tabs, 168 | profile: r.profile 169 | }); 170 | }); 171 | })); 172 | } 173 | else if (info.menuItemId.startsWith('reload.')) { 174 | let tabs = []; 175 | if (info.menuItemId === 'reload.all' || info.menuItemId === 'reload.all.c') { 176 | tabs = await api.tabs.query({}); 177 | } 178 | else if (info.menuItemId === 'reload.all.discarded') { 179 | tabs = (await api.tabs.query({})).filter(t => t.discarded === true || t.status === 'unloaded'); 180 | } 181 | else if (info.menuItemId === 'reload.window' || info.menuItemId === 'reload.window.c') { 182 | tabs = await api.tabs.query({ 183 | currentWindow: true 184 | }); 185 | } 186 | else if (info.menuItemId === 'reload.window.discarded') { 187 | tabs = (await api.tabs.query({ 188 | currentWindow: true 189 | })).filter(t => t.discarded === true || t.status === 'unloaded'); 190 | } 191 | else if (info.menuItemId === 'reload.tabs.right') { 192 | tabs = (await api.tabs.query({ 193 | currentWindow: true 194 | })).filter(t => t.index > tab.index); 195 | } 196 | else if (info.menuItemId === 'reload.tabs.left') { 197 | tabs = (await api.tabs.query({ 198 | currentWindow: true 199 | })).filter(t => t.index < tab.index); 200 | } 201 | else if (info.menuItemId === 'reload.now') { 202 | tabs = [tab]; 203 | } 204 | tabs.forEach(tab => api.tabs.reload(tab, {bypassCache: true})); 205 | } 206 | else if (info.menuItemId === 'no.reload') { 207 | api.tabs.active().then(tabs => { 208 | messaging({ 209 | 'reason': 'user-request', 210 | 'method': 'remove-jobs', 211 | 'ids': tabs.map(t => t.id), 212 | 'skip-echo': true 213 | }); 214 | }); 215 | } 216 | else if (info.menuItemId === 'stop.all') { 217 | api.alarms.keys().then(names => { 218 | const ids = names.map(name => Number(name.replace('job-', ''))); 219 | messaging({ 220 | reason: 'context-stop-all', 221 | method: 'remove-jobs', 222 | ids 223 | }); 224 | }); 225 | } 226 | else if (info.menuItemId === 'restart') { 227 | chrome.runtime.reload(); 228 | } 229 | else if (info.menuItemId === 'csp.remove') { 230 | if (tab.url.startsWith('http')) { 231 | let origin = tab.url.replace(/^https*/, '*'); 232 | try { 233 | origin = '*://' + (new URL(tab.url)).hostname + '/'; 234 | } 235 | catch (e) {} 236 | 237 | api.permissions.request({ 238 | origins: [origin] 239 | }).then(async granted => { 240 | if (granted) { 241 | try { 242 | const [{result}] = await api.inject(tab.id, { 243 | world: 'MAIN', 244 | func: msg => { 245 | return confirm(msg); 246 | }, 247 | args: [chrome.i18n.getMessage('bg_msg_3')] 248 | }); 249 | if (result === true) { 250 | await api.csp.remove(tab.id); 251 | } 252 | } 253 | catch (e) { 254 | console.error(e); 255 | api.notify(tab.id, e); 256 | } 257 | } 258 | else { 259 | api.notify(tab.id, chrome.i18n.getMessage('bg_msg_2')); 260 | } 261 | }).catch(e => api.notify(tab.id, e)); 262 | } 263 | else { 264 | api.notify(tab.id, chrome.i18n.getMessage('bg_msg_1')); 265 | } 266 | } 267 | else if (info.menuItemId === 'csp.reset') { 268 | api.csp.reset(tab.id); 269 | } 270 | }; 271 | api.context.fired(observe); 272 | 273 | api.commands.fired((menuItemId, tab) => observe({ 274 | menuItemId: menuItemId.replace(/_/g, '.') // Edge does not accept "." for a command 275 | }, tab)); 276 | } 277 | -------------------------------------------------------------------------------- /v2/data/options/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = { 4 | 'badge': true, 5 | 'color': '#5e5e5e', 6 | 'faqs': true, 7 | 'use-native': true, 8 | 'history': true, 9 | 'history.timeout': 5000, 10 | 'json': [], 11 | 'dd': 0, 12 | 'hh': 0, 13 | 'mm': 5, 14 | 'ss': 0, 15 | 'dynamic.json': false, 16 | 'policy': {}, 17 | 'log': false, 18 | 'active': 'single', 19 | 'presets': [{ 20 | hh: 0, 21 | mm: 0, 22 | ss: 30 23 | }, { 24 | hh: 0, 25 | mm: 5, 26 | ss: 0 27 | }, { 28 | hh: 0, 29 | mm: 15, 30 | ss: 0 31 | }, { 32 | hh: 0, 33 | mm: 30, 34 | ss: 0 35 | }, { 36 | hh: 1, 37 | mm: 0, 38 | ss: 0 39 | }, { 40 | hh: 5, 41 | mm: 0, 42 | ss: 0 43 | }], 44 | 'pp-current': false, 45 | 'pp-nofocus': false, 46 | 'pp-cache': false, 47 | 'pp-form': false, 48 | 'pp-offline': false, 49 | 'pp-scroll-to-end': false, 50 | './plugins/badge/core.js': false 51 | }; 52 | 53 | const restore = () => chrome.storage.local.get(config, prefs => { 54 | document.getElementById('badge').checked = prefs.badge; 55 | document.getElementById('log').checked = prefs.log; 56 | document.getElementById('color').value = prefs.color; 57 | document.getElementById('use-native').checked = prefs['use-native']; 58 | document.getElementById('faqs').checked = prefs.faqs; 59 | document.getElementById('history').checked = prefs.history; 60 | document.getElementById('history_timeout').value = parseInt(prefs['history.timeout'] / 1000); 61 | document.getElementById('json').value = JSON.stringify(prefs.json, null, ' '); 62 | document.getElementById('dd').value = prefs.dd; 63 | document.getElementById('hh').value = prefs.hh; 64 | document.getElementById('mm').value = prefs.mm; 65 | document.getElementById('ss').value = prefs.ss; 66 | document.getElementById('dynamic.json').checked = prefs['dynamic.json']; 67 | document.getElementById('policy').value = JSON.stringify(prefs.policy, null, ' '); 68 | document.getElementById('active').checked = prefs.active === 'multiple'; 69 | document.getElementById('presets').value = prefs.presets.slice(0, 6).map(o => o.hh.toString().padStart(2, '0') + ':' + 70 | o.mm.toString().padStart(2, '0') + ':' + o.ss.toString().padStart(2, '0')).join(', '); 71 | 72 | 73 | document.getElementById('pp-current').checked = prefs['pp-current']; 74 | document.getElementById('pp-nofocus').checked = prefs['pp-nofocus']; 75 | document.getElementById('pp-cache').checked = prefs['pp-cache']; 76 | document.getElementById('pp-form').checked = prefs['pp-form']; 77 | document.getElementById('pp-offline').checked = prefs['pp-offline']; 78 | document.getElementById('pp-scroll-to-end').checked = prefs['pp-scroll-to-end']; 79 | 80 | document.getElementById('./plugins/badge/core.js').checked = prefs['./plugins/badge/core.js']; 81 | }); 82 | restore(); 83 | 84 | document.getElementById('save').addEventListener('click', () => { 85 | const info = document.getElementById('info'); 86 | const badge = document.getElementById('badge').checked; 87 | 88 | let presets; 89 | if (document.getElementById('presets').value.trim()) { 90 | presets = document.getElementById('presets').value.split(/\s*,\s*/).map(s => { 91 | const [hh, mm, ss] = s.split(/\s*:\s*/); 92 | 93 | const o = { 94 | hh: 0, 95 | mm: 0, 96 | ss: 0 97 | }; 98 | if (isNaN(hh) === false) { 99 | o.hh = Math.max(0, parseInt(hh || '0')); 100 | } 101 | if (isNaN(mm) === false) { 102 | o.mm = Math.min(59, Math.max(0, parseInt(mm || '0'))); 103 | } 104 | if (isNaN(ss) === false) { 105 | o.ss = Math.min(59, Math.max(0, parseInt(ss || '0'))); 106 | } 107 | if (o.ss === 0 && o.mm === 0 && o.hh === 0) { 108 | o.ss = 10; 109 | } 110 | 111 | return o; 112 | }); 113 | } 114 | else { 115 | presets = [{hh: 0, mm: 0, ss: 30}, {hh: 0, mm: 5, ss: 0}, {hh: 0, mm: 15, ss: 0}, {hh: 0, mm: 30, ss: 0}, 116 | {hh: 1, mm: 0, ss: 0}, {hh: 5, mm: 0, ss: 0}]; 117 | } 118 | 119 | try { 120 | chrome.storage.local.set({ 121 | badge, 122 | presets, 123 | 'color': document.getElementById('color').value, 124 | 'faqs': document.getElementById('faqs').checked, 125 | 'use-native': document.getElementById('use-native').checked, 126 | 'log': document.getElementById('log').checked, 127 | 'history': document.getElementById('history').checked, 128 | 'history.timeout': Math.max(2, Number(document.getElementById('history_timeout').value)) * 1000, 129 | 'json': JSON.parse(document.getElementById('json').value.trim() || '[]'), 130 | 'dd': Math.max(Number(document.getElementById('dd').value), 0), 131 | 'hh': Math.min(Math.max(Number(document.getElementById('hh').value), 0), 23), 132 | 'mm': Math.min(Math.max(Number(document.getElementById('mm').value), 0), 59), 133 | 'ss': Math.min(Math.max(Number(document.getElementById('ss').value), 0), 59), 134 | 'dynamic.json': document.getElementById('dynamic.json').checked, 135 | 'policy': JSON.parse(document.getElementById('policy').value.trim() || '{}'), 136 | 'active': document.getElementById('active').checked ? 'multiple' : 'single', 137 | 'pp-current': document.getElementById('pp-current').checked, 138 | 'pp-nofocus': document.getElementById('pp-nofocus').checked, 139 | 'pp-cache': document.getElementById('pp-cache').checked, 140 | 'pp-form': document.getElementById('pp-form').checked, 141 | 'pp-offline': document.getElementById('pp-offline').checked, 142 | 'pp-scroll-to-end': document.getElementById('pp-scroll-to-end').checked, 143 | 144 | './plugins/badge/core.js': document.getElementById('./plugins/badge/core.js').checked 145 | }, () => { 146 | info.textContent = 'Options saved'; 147 | restore(); 148 | 149 | if (badge) { 150 | chrome.runtime.sendMessage({ 151 | method: 'count' 152 | }); 153 | } 154 | else { 155 | chrome.browserAction.setBadgeText({ 156 | text: '' 157 | }); 158 | } 159 | }); 160 | } 161 | catch (e) { 162 | info.textContent = e.message; 163 | } 164 | window.setTimeout(() => info.textContent = '', 3000); 165 | }); 166 | // reset 167 | document.getElementById('reset').addEventListener('click', () => chrome.storage.local.set(config, restore)); 168 | // support 169 | document.getElementById('support').addEventListener('click', () => chrome.tabs.create({ 170 | url: chrome.runtime.getManifest().homepage_url + '?rd=donate' 171 | })); 172 | // open FAQs page 173 | document.getElementById('ofq').addEventListener('click', () => chrome.tabs.create({ 174 | url: chrome.runtime.getManifest().homepage_url 175 | })); 176 | // open usage instruction 177 | document.getElementById('opv').addEventListener('click', () => chrome.tabs.create({ 178 | url: 'https://www.youtube.com/watch?v=zAhQlorZZTc' 179 | })); 180 | // example 181 | document.getElementById('example').addEventListener('click', () => { 182 | document.getElementById('json').value = JSON.stringify([{ 183 | 'hostname': '*.google.com', 184 | 'dd': 0, 185 | 'hh': 0, 186 | 'mm': 1, 187 | 'ss': 0 188 | }, { 189 | 'hostname': 190 | 'www.bing.com', 191 | 'dd': 0, 192 | 'hh': 0, 193 | 'mm': 1, 194 | 'ss': 30 195 | }], null, 2); 196 | }); 197 | // export 198 | document.getElementById('export').addEventListener('click', () => { 199 | chrome.storage.local.get(null, prefs => { 200 | const text = JSON.stringify(prefs, null, 2); 201 | const blob = new Blob([text], {type: 'application/json'}); 202 | const objectURL = URL.createObjectURL(blob); 203 | Object.assign(document.createElement('a'), { 204 | href: objectURL, 205 | type: 'application/json', 206 | download: 'tab-reloader-preferences.json' 207 | }).dispatchEvent(new MouseEvent('click')); 208 | setTimeout(() => URL.revokeObjectURL(objectURL)); 209 | }); 210 | }); 211 | // import 212 | document.getElementById('import').addEventListener('click', () => { 213 | const fileInput = document.createElement('input'); 214 | fileInput.style.display = 'none'; 215 | fileInput.type = 'file'; 216 | fileInput.accept = '.json'; 217 | fileInput.acceptCharset = 'utf-8'; 218 | 219 | document.body.appendChild(fileInput); 220 | fileInput.initialValue = fileInput.value; 221 | fileInput.onchange = readFile; 222 | fileInput.click(); 223 | 224 | function readFile() { 225 | if (fileInput.value !== fileInput.initialValue) { 226 | const file = fileInput.files[0]; 227 | if (file.size > 100e6) { 228 | console.warn('100MB backup? I don\'t believe you.'); 229 | return; 230 | } 231 | const reader = new FileReader(); 232 | reader.onloadend = event => { 233 | fileInput.remove(); 234 | const json = JSON.parse(event.target.result); 235 | chrome.storage.local.clear(() => { 236 | chrome.storage.local.set(json, () => chrome.runtime.reload()); 237 | }); 238 | }; 239 | reader.readAsText(file, 'utf-8'); 240 | } 241 | } 242 | }); 243 | // toggle 244 | document.getElementById('keywords').addEventListener('click', () => chrome.tabs.create({ 245 | url: chrome.runtime.getManifest().homepage_url + '#faq27' 246 | })); 247 | document.getElementById('events').addEventListener('click', () => chrome.tabs.create({ 248 | url: chrome.runtime.getManifest().homepage_url + '#faq26' 249 | })); 250 | document.getElementById('desc-1').addEventListener('click', () => { 251 | document.querySelector('[for="desc-1"]').classList.toggle('hide'); 252 | }); 253 | document.getElementById('desc-2').addEventListener('click', () => { 254 | document.querySelector('[for="desc-2"]').classList.toggle('hide'); 255 | }); 256 | document.getElementById('desc-4').addEventListener('click', () => { 257 | document.querySelector('[for="desc-4"]').classList.toggle('hide'); 258 | }); 259 | // permission 260 | document.getElementById('permission').addEventListener('click', () => chrome.permissions.request({ 261 | origins: [''] 262 | }, granted => { 263 | const info = document.getElementById('info'); 264 | info.textContent = 'Host permission is ' + (granted ? 'granted' : 'denied'); 265 | window.setTimeout(() => info.textContent = '', 3000); 266 | })); 267 | 268 | document.addEventListener('DOMContentLoaded', () => { 269 | for (const a of [...document.querySelectorAll('[data-href]')]) { 270 | if (a.hasAttribute('href') === false) { 271 | a.href = chrome.runtime.getManifest().homepage_url + '#' + a.dataset.href; 272 | } 273 | } 274 | }); 275 | -------------------------------------------------------------------------------- /v3/data/options/index.js: -------------------------------------------------------------------------------- 1 | /* global defaults, api */ 2 | 3 | 'use strict'; 4 | 5 | const config = { 6 | 'badge': true, 7 | 'color': defaults['badge-color'], 8 | 'faqs': true, 9 | 'removed.jobs.enabled': true, 10 | 'json': [], 11 | 'dynamic.json': false, 12 | 'policy': {}, 13 | 'presets': defaults.presets, 14 | 'default-profile': defaults.profile, 15 | 'disabled-menu-items': [], 16 | 'startup-restore-delay': 5000 17 | }; 18 | 19 | const restore = () => chrome.storage.local.get(config, prefs => { 20 | document.getElementById('badge').checked = prefs.badge; 21 | document.getElementById('color').value = prefs.color; 22 | document.getElementById('faqs').checked = prefs.faqs; 23 | document.getElementById('removed.jobs.enabled').checked = prefs['removed.jobs.enabled']; 24 | document.getElementById('json').value = JSON.stringify(prefs.json, null, ' '); 25 | document.getElementById('dynamic.json').checked = prefs['dynamic.json']; 26 | document.getElementById('policy').value = JSON.stringify(prefs.policy, null, ' '); 27 | document.getElementById('presets').value = prefs.presets.map(o => api.convert.obj2str(o)).join(', '); 28 | document.getElementById('pp-period').value = prefs['default-profile'].period; 29 | document.getElementById('pp-variation').value = prefs['default-profile'].variation; 30 | document.getElementById('pp-current').checked = prefs['default-profile'].current; 31 | document.getElementById('pp-nofocus').checked = prefs['default-profile'].nofocus; 32 | document.getElementById('pp-cache').checked = prefs['default-profile'].cache; 33 | document.getElementById('pp-form').checked = prefs['default-profile'].form; 34 | document.getElementById('pp-offline').checked = prefs['default-profile'].offline; 35 | document.getElementById('pp-discarded').checked = prefs['default-profile'].discarded; 36 | document.getElementById('pp-nodiscard').checked = prefs['default-profile'].nodiscard; 37 | document.getElementById('pp-randomize').checked = prefs['default-profile'].randomize; 38 | document.getElementById('pp-scroll-to-end').checked = prefs['default-profile']['scroll-to-end']; 39 | document.getElementById('pp-visual-countdown').checked = prefs['default-profile']['visual-countdown']; 40 | document.getElementById('pp-stop-on-address-change').checked = prefs['default-profile']['stop-on-address-change']; 41 | document.getElementById('reload.all').checked = prefs['disabled-menu-items'].includes('reload.all'); 42 | document.getElementById('reload.all.discarded').checked = 43 | prefs['disabled-menu-items'].includes('reload.all.discarded'); 44 | document.getElementById('reload.window').checked = prefs['disabled-menu-items'].includes('reload.window'); 45 | document.getElementById('reload.window.discarded').checked = 46 | prefs['disabled-menu-items'].includes('reload.window.discarded'); 47 | document.getElementById('reload.tabs.left').checked = prefs['disabled-menu-items'].includes('reload.tabs.left'); 48 | document.getElementById('reload.tabs.right').checked = prefs['disabled-menu-items'].includes('reload.tabs.right'); 49 | document.getElementById('no.reload').checked = prefs['disabled-menu-items'].includes('no.reload'); 50 | document.getElementById('stop.all').checked = prefs['disabled-menu-items'].includes('stop.all'); 51 | document.getElementById('startup-restore-delay').value = Math.round(prefs['startup-restore-delay'] / 1000); 52 | }); 53 | restore(); 54 | 55 | document.getElementById('save').addEventListener('click', () => { 56 | const info = document.getElementById('info'); 57 | const badge = document.getElementById('badge').checked; 58 | 59 | let presets; 60 | if (document.getElementById('presets').value.trim()) { 61 | presets = document.getElementById('presets').value.split(/\s*,\s*/).map(s => { 62 | const o = api.convert.str2obj(s); 63 | const p = Math.max(10, api.convert.secods(o)); 64 | 65 | return api.convert.sec2obj(p); 66 | }); 67 | } 68 | else { 69 | presets = [{hh: 0, mm: 0, ss: 30}, {hh: 0, mm: 5, ss: 0}, {hh: 0, mm: 15, ss: 0}, {hh: 0, mm: 30, ss: 0}, 70 | {hh: 1, mm: 0, ss: 0}, {hh: 5, mm: 0, ss: 0}]; 71 | } 72 | 73 | const o = api.convert.str2obj(document.getElementById('pp-period').value); 74 | const period = Math.max(10, api.convert.secods(o)); 75 | 76 | const profile = Object.assign({}, defaults.profile, { 77 | 'period': api.convert.obj2str(api.convert.sec2obj(period)), 78 | 'variation': Math.min(100, Math.max(0, document.getElementById('pp-variation').valueAsNumber)), 79 | 'current': document.getElementById('pp-current').checked, 80 | 'nofocus': document.getElementById('pp-nofocus').checked, 81 | 'cache': document.getElementById('pp-cache').checked, 82 | 'form': document.getElementById('pp-form').checked, 83 | 'offline': document.getElementById('pp-offline').checked, 84 | 'discarded': document.getElementById('pp-discarded').checked, 85 | 'nodiscard': document.getElementById('pp-nodiscard').checked, 86 | 'randomize': document.getElementById('pp-randomize').checked, 87 | 'scroll-to-end': document.getElementById('pp-scroll-to-end').checked, 88 | 'visual-countdown': document.getElementById('pp-visual-countdown').checked, 89 | 'stop-on-address-change': document.getElementById('pp-stop-on-address-change').checked 90 | }); 91 | 92 | if (badge === false) { 93 | api.button.badge(''); 94 | } 95 | 96 | // update ui 97 | for (const e of document.querySelectorAll('#disabled_items input')) { 98 | chrome.contextMenus.update(e.id, { 99 | enabled: e.checked === false 100 | }); 101 | } 102 | 103 | let delay = document.getElementById('startup-restore-delay').valueAsNumber; 104 | if (isNaN(delay)) { 105 | delay = 5; 106 | } 107 | 108 | try { 109 | chrome.storage.local.set({ 110 | badge, 111 | presets, 112 | 'default-profile': profile, 113 | 'color': document.getElementById('color').value, 114 | 'faqs': document.getElementById('faqs').checked, 115 | 'removed.jobs.enabled': document.getElementById('removed.jobs.enabled').checked, 116 | 'json': JSON.parse(document.getElementById('json').value.trim() || '[]'), 117 | 'dynamic.json': document.getElementById('dynamic.json').checked, 118 | 'policy': JSON.parse(document.getElementById('policy').value.trim() || '{}'), 119 | 'disabled-menu-items': [...document.querySelectorAll('#disabled_items input:checked')].map(e => e.id), 120 | 'startup-restore-delay': delay * 1000 121 | }, () => { 122 | info.textContent = chrome.i18n.getMessage('options_saved'); 123 | restore(); 124 | 125 | if (badge) { 126 | chrome.runtime.sendMessage({ 127 | method: 'count' 128 | }); 129 | } 130 | else { 131 | chrome.action.setBadgeText({ 132 | text: '' 133 | }); 134 | } 135 | }); 136 | } 137 | catch (e) { 138 | info.textContent = e.message; 139 | } 140 | window.setTimeout(() => info.textContent = '', 3000); 141 | }); 142 | // reset 143 | document.getElementById('reset').addEventListener('click', () => chrome.storage.local.set(config, restore)); 144 | // support 145 | document.getElementById('support').addEventListener('click', () => chrome.tabs.create({ 146 | url: chrome.runtime.getManifest().homepage_url + '?rd=donate' 147 | })); 148 | // open FAQs page 149 | document.getElementById('ofq').addEventListener('click', () => chrome.tabs.create({ 150 | url: chrome.runtime.getManifest().homepage_url 151 | })); 152 | // open usage instruction 153 | document.getElementById('opv').addEventListener('click', () => chrome.tabs.create({ 154 | url: 'https://www.youtube.com/watch?v=zAhQlorZZTc' 155 | })); 156 | // example 157 | document.getElementById('example').addEventListener('click', () => { 158 | const jobs = []; 159 | // extract existing jobs 160 | try { 161 | jobs.push(...JSON.parse(document.getElementById('json').value)); 162 | } 163 | catch (e) {} 164 | jobs.push({ 165 | 'hostname': 'www.google.com', 166 | 'dd': 0, 167 | 'hh': 0, 168 | 'mm': 1, 169 | 'ss': 0 170 | }); 171 | document.getElementById('json').value = JSON.stringify(jobs, null, 2); 172 | document.getElementById('json').scrollTop = document.getElementById('json').scrollHeight; 173 | }); 174 | // export 175 | document.getElementById('export').addEventListener('click', () => { 176 | chrome.storage.local.get(null, prefs => { 177 | const text = JSON.stringify(prefs, null, 2); 178 | const blob = new Blob([text], {type: 'application/json'}); 179 | const objectURL = URL.createObjectURL(blob); 180 | Object.assign(document.createElement('a'), { 181 | href: objectURL, 182 | type: 'application/json', 183 | download: 'tab-reloader-preferences.json' 184 | }).dispatchEvent(new MouseEvent('click')); 185 | setTimeout(() => URL.revokeObjectURL(objectURL)); 186 | }); 187 | }); 188 | // import 189 | document.getElementById('import').addEventListener('click', () => { 190 | const fileInput = document.createElement('input'); 191 | fileInput.style.display = 'none'; 192 | fileInput.type = 'file'; 193 | fileInput.accept = '.json'; 194 | fileInput.acceptCharset = 'utf-8'; 195 | 196 | document.body.appendChild(fileInput); 197 | fileInput.initialValue = fileInput.value; 198 | fileInput.onchange = readFile; 199 | fileInput.click(); 200 | 201 | function readFile() { 202 | if (fileInput.value !== fileInput.initialValue) { 203 | const file = fileInput.files[0]; 204 | if (file.size > 100e6) { 205 | console.warn('100MB backup? I don\'t believe you.'); 206 | return; 207 | } 208 | const reader = new FileReader(); 209 | reader.onloadend = event => { 210 | fileInput.remove(); 211 | const json = JSON.parse(event.target.result); 212 | chrome.storage.local.clear(() => { 213 | chrome.storage.local.set(json, () => chrome.runtime.reload()); 214 | }); 215 | }; 216 | reader.readAsText(file, 'utf-8'); 217 | } 218 | } 219 | }); 220 | // toggle 221 | document.getElementById('keywords').addEventListener('click', () => chrome.tabs.create({ 222 | url: chrome.runtime.getManifest().homepage_url + '#faq27' 223 | })); 224 | document.getElementById('events').addEventListener('click', () => chrome.tabs.create({ 225 | url: chrome.runtime.getManifest().homepage_url + '#faq26' 226 | })); 227 | document.getElementById('desc-1').addEventListener('click', () => { 228 | document.querySelector('[for="desc-1"]').classList.toggle('hide'); 229 | }); 230 | document.getElementById('desc-2').addEventListener('click', () => { 231 | document.querySelector('[for="desc-2"]').classList.toggle('hide'); 232 | }); 233 | document.getElementById('desc-4').addEventListener('click', () => { 234 | document.querySelector('[for="desc-4"]').classList.toggle('hide'); 235 | }); 236 | // permission 237 | document.getElementById('permission').addEventListener('click', () => chrome.permissions.request({ 238 | origins: [''] 239 | }, granted => { 240 | const info = document.getElementById('info'); 241 | info.textContent = 'Host permission is ' + (granted ? 'granted' : 'denied'); 242 | window.setTimeout(() => info.textContent = '', 3000); 243 | })); 244 | 245 | document.addEventListener('DOMContentLoaded', () => { 246 | for (const a of [...document.querySelectorAll('[data-href]')]) { 247 | if (a.hasAttribute('href') === false) { 248 | a.href = chrome.runtime.getManifest().homepage_url + '#' + a.dataset.href; 249 | } 250 | } 251 | }); 252 | -------------------------------------------------------------------------------- /v3/api.js: -------------------------------------------------------------------------------- 1 | /* global URLPattern */ 2 | 3 | const api = { 4 | firefox: /Firefox/.test(navigator.userAgent) 5 | }; 6 | 7 | api.notify = (tabId, e, badge = 'E', color = 'red') => { 8 | chrome.action.setTitle({ 9 | tabId, 10 | title: e.message || e 11 | }); 12 | chrome.action.setBadgeBackgroundColor({ 13 | tabId, 14 | color 15 | }); 16 | chrome.action.setBadgeText({ 17 | tabId, 18 | text: badge 19 | }); 20 | chrome.scripting.executeScript({ 21 | target: { 22 | tabId 23 | }, 24 | func: msg => alert(msg), 25 | args: [e.message || e] 26 | }).catch(() => {}); 27 | }; 28 | 29 | api.storage = { 30 | get(prefs) { 31 | return new Promise(resolve => chrome.storage.local.get(prefs, ps => { 32 | if (typeof prefs === 'string') { 33 | resolve(ps[prefs]); 34 | } 35 | else { 36 | resolve(ps); 37 | } 38 | })); 39 | }, 40 | set(prefs) { 41 | return new Promise(resolve => chrome.storage.local.set(prefs, resolve)); 42 | }, 43 | remove(...keys) { 44 | chrome.storage.local.remove(keys); 45 | }, 46 | changed(c) { 47 | chrome.storage.onChanged.addListener(c); 48 | } 49 | }; 50 | 51 | api.convert = { 52 | obj2str(o, separator = ':') { 53 | return Object.values(o).map(value => value.toString().padStart(2, '0')).join(separator); 54 | }, 55 | str2obj(str = '') { 56 | let [hh, mm, ss] = str.split(':'); 57 | hh = hh || '0'; 58 | mm = mm || '0'; 59 | ss = ss || '0'; 60 | 61 | if (isNaN(hh)) { 62 | hh = 0; 63 | } 64 | if (isNaN(mm)) { 65 | mm = 0; 66 | } 67 | if (isNaN(ss)) { 68 | ss = 0; 69 | } 70 | hh = Math.max(0, parseInt(hh)); 71 | mm = Math.min(59, Math.max(0, parseInt(mm))); 72 | ss = Math.min(59, Math.max(0, parseInt(ss))); 73 | 74 | if (ss === 0 && mm === 0 && hh === 0) { 75 | mm = 5; 76 | } 77 | 78 | return {hh, mm, ss}; 79 | }, 80 | secods(o) { 81 | return o.hh * 60 * 60 + o.mm * 60 + o.ss; 82 | }, 83 | sec2obj(num) { 84 | const hh = Math.floor(num / (60 * 60)); 85 | num -= hh * 60 * 60; 86 | const mm = Math.floor(num / 60); 87 | const ss = Math.floor(num % 60); 88 | 89 | return {hh, mm, ss}; 90 | } 91 | }; 92 | 93 | api.clean = { 94 | href(o) { 95 | return o.split('#')[0]; 96 | } 97 | }; 98 | 99 | api.match = (key = '', str = '', parent = undefined) => { 100 | if (key === '' || str === '') { 101 | return false; 102 | } 103 | // RegExp matching 104 | if (key.startsWith('re:')) { 105 | try { 106 | const r = new RegExp(key.slice(3)); 107 | 108 | return r.test(str); 109 | } 110 | catch (e) { 111 | console.warn('Cannot run RegExp matching', e); 112 | return false; 113 | } 114 | } 115 | // URLPattern matching 116 | else if (key.startsWith('pt:')) { 117 | try { 118 | let v = key.slice(3); 119 | if (v.startsWith('http') === false) { 120 | v = 'http{s}?://' + v; 121 | } 122 | const pattern = new URLPattern(v, parent); 123 | 124 | return pattern.test(str); 125 | } 126 | catch (e) { 127 | console.warn('Cannot run URLPattern matching', e); 128 | return false; 129 | } 130 | } 131 | // host matching 132 | else if (key.startsWith('ht:')) { 133 | try { 134 | // https://*.example.com/test* 135 | const [hostname, ...parts] = key.slice(3).replace(/^https?:\/\//, '').split('/'); 136 | const [pathname, search] = parts.join('/').split('?'); 137 | 138 | const pattern = new URLPattern({ 139 | hostname, 140 | search: search ? '?' + search : '*', 141 | pathname: pathname ? '/' + pathname.split('#')[0] : '*' 142 | }); 143 | return pattern.test(str); 144 | } 145 | catch (e) { 146 | console.warn('Cannot run host matching', e); 147 | return false; 148 | } 149 | } 150 | else { 151 | return str.includes(key); 152 | } 153 | }; 154 | 155 | api.idle = { 156 | fired(c) { 157 | if (chrome.idle) { 158 | chrome.idle.onStateChanged.addListener(c); 159 | } 160 | else { 161 | console.error('"chrome.idle" is not supported'); 162 | } 163 | } 164 | }; 165 | 166 | api.alarms = { 167 | INTERNAL: 'INT', 168 | add(name, o, internal = false) { 169 | if (internal) { 170 | name = api.alarms.INTERNAL + '::' + name; 171 | } 172 | return chrome.alarms.create(name, o); 173 | }, 174 | remove(name) { 175 | return chrome.alarms.clear(name); 176 | }, 177 | get(id) { 178 | return chrome.alarms.get(id); 179 | }, 180 | fired(c, internal = false) { 181 | chrome.alarms.onAlarm.addListener(o => { 182 | if (internal === false) { 183 | if (o.name.startsWith(api.alarms.INTERNAL) === false) { 184 | c(o); 185 | } 186 | } 187 | else { 188 | if (o.name.startsWith(api.alarms.INTERNAL)) { 189 | c({ 190 | ...o, 191 | name: o.name.slice(api.alarms.INTERNAL.length + 2) 192 | }); 193 | } 194 | } 195 | }); 196 | }, 197 | /* methods that exclude internal alarms */ 198 | async count() { 199 | const os = await chrome.alarms.getAll(); 200 | const osf = os.filter(o => o.name.startsWith(api.alarms.INTERNAL) === false); 201 | return osf.length; 202 | }, 203 | async keys() { 204 | const os = await chrome.alarms.getAll(); 205 | const osf = os.filter(o => o.name.startsWith(api.alarms.INTERNAL) === false); 206 | return osf.map(o => o.name); 207 | }, 208 | async forEach(c, threads = true) { 209 | const os = await chrome.alarms.getAll(); 210 | const osf = os.filter(o => o.name.startsWith(api.alarms.INTERNAL) === false); 211 | if (threads) { 212 | return Promise.all(osf.map(o => c(o))); 213 | } 214 | else { 215 | for (const o of osf) { 216 | await c(o); 217 | } 218 | } 219 | } 220 | }; 221 | 222 | api.post = { 223 | bg: (o, c) => { 224 | chrome.runtime.sendMessage(o, c); 225 | }, 226 | fired(...args) { 227 | chrome.runtime.onMessage.addListener(...args); 228 | } 229 | }; 230 | 231 | api.tabs = { 232 | active() { 233 | return chrome.tabs.query({ 234 | highlighted: true, 235 | currentWindow: true 236 | }); 237 | }, 238 | activate(tabId) { 239 | return Promise.all([ 240 | new Promise(resolve => chrome.tabs.update(tabId, { 241 | active: true 242 | }, resolve)), 243 | new Promise(resolve => api.tabs.get(tabId).then(t => chrome.windows.update(t.windowId, { 244 | focused: true 245 | }, resolve))) 246 | ]); 247 | }, 248 | get(id) { 249 | return chrome.tabs.get(id).then(tab => { 250 | chrome.runtime.lastError; 251 | return tab; 252 | }).catch(() => null); 253 | }, 254 | window(id) { 255 | return chrome.windows.get(id); 256 | }, 257 | removed(c, windows = false) { 258 | if (windows) { 259 | chrome.windows.onRemoved.addListener(c); 260 | } 261 | else { 262 | chrome.tabs.onRemoved.addListener(c); 263 | } 264 | }, 265 | query(o) { 266 | return chrome.tabs.query(o); 267 | }, 268 | reload(tab, options, form) { 269 | if (form) { 270 | chrome.tabs.update(tab.id, { 271 | url: tab.url.split('#')[0].split('?')[0] 272 | }); 273 | } 274 | else { 275 | chrome.tabs.reload(tab.id, options); 276 | } 277 | }, 278 | loaded(c) { 279 | chrome.webNavigation.onDOMContentLoaded.addListener(d => { 280 | if (d.frameId === 0) { 281 | if (api.firefox) { 282 | clearTimeout(c.id); 283 | c.id = setTimeout(c, 100, d); 284 | } 285 | else { 286 | c(d); 287 | } 288 | } 289 | }); 290 | }, 291 | update(...args) { 292 | chrome.tabs.update(...args); 293 | } 294 | }; 295 | 296 | api.button = { 297 | icon(type, tabId) { 298 | try { 299 | chrome.action.setIcon({ 300 | tabId, 301 | path: { 302 | '16': '/data/icons/' + type + '/16.png', 303 | '32': '/data/icons/' + type + '/32.png' 304 | } 305 | }, () => chrome.runtime.lastError); 306 | } 307 | catch (e) {} 308 | }, 309 | badge(text, tabId) { 310 | api.storage.get({ 311 | badge: true 312 | }).then(({badge}) => { 313 | if (badge) { 314 | const o = { 315 | text: String(text ? text : '') 316 | }; 317 | if (tabId) { 318 | o.tabId = tabId; 319 | } 320 | chrome.action.setBadgeText(o, () => chrome.runtime.lastError); 321 | } 322 | }); 323 | }, 324 | tooltip(title, tabId) { 325 | const o = { 326 | title 327 | }; 328 | if (tabId) { 329 | o.tabId = tabId; 330 | } 331 | chrome.action.setTitle(o); 332 | }, 333 | color(color) { 334 | chrome.action.setBadgeBackgroundColor({ 335 | color 336 | }); 337 | } 338 | }; 339 | 340 | api.context = { 341 | tab: chrome.contextMenus.ContextType ? 'TAB' in chrome.contextMenus.ContextType : true, 342 | add(options) { 343 | if (chrome.contextMenus.ContextType) { 344 | const valids = Object.values(chrome.contextMenus.ContextType); 345 | options.contexts = options.contexts.filter(s => valids.includes(s)); 346 | } 347 | 348 | chrome.contextMenus.create(options, () => { 349 | chrome.runtime.lastError; 350 | }); 351 | }, 352 | fired(c) { 353 | chrome.contextMenus.onClicked.addListener(c); 354 | } 355 | }; 356 | 357 | api.commands = { 358 | fired(c) { 359 | chrome.commands.onCommand.addListener(c); 360 | } 361 | }; 362 | 363 | { 364 | const requests = new Set(); 365 | api.runtime = { 366 | started(c) { 367 | requests.add(c); 368 | } 369 | }; 370 | const once = (...args) => { 371 | if (once.done) { 372 | return; 373 | } 374 | once.done = true; 375 | 376 | for (const r of requests) { 377 | try { 378 | r(...args); 379 | } 380 | catch (e) { 381 | console.error('Cannot start', r); 382 | } 383 | } 384 | requests.clear(); 385 | }; 386 | 387 | chrome.runtime.onStartup.addListener(once); 388 | chrome.runtime.onInstalled.addListener(once); 389 | } 390 | 391 | api.permissions = { 392 | async request(o) { 393 | try { 394 | // const grantted = await chrome.permissions.contains(o); 395 | // if (grantted) { 396 | // return true; 397 | // } 398 | return await chrome.permissions.request(o); 399 | } 400 | catch (e) { 401 | console.error(e); 402 | return false; 403 | } 404 | } 405 | }; 406 | 407 | api.inject = (tabId, o) => { 408 | return chrome.scripting.executeScript({ 409 | target: { 410 | tabId 411 | }, 412 | ...o 413 | }); 414 | }; 415 | 416 | api.csp = { 417 | remove(tabId) { 418 | return chrome.declarativeNetRequest.updateSessionRules({ 419 | removeRuleIds: [tabId], 420 | addRules: [{ 421 | id: tabId, 422 | action: { 423 | type: 'modifyHeaders', 424 | responseHeaders: [{ 425 | header: 'Content-Security-Policy', 426 | operation: 'remove' 427 | }] 428 | }, 429 | condition: { 430 | tabIds: [tabId], 431 | urlFilter: '*/*/*', 432 | resourceTypes: ['main_frame'] 433 | } 434 | }] 435 | }); 436 | }, 437 | reset(tabId) { 438 | return chrome.declarativeNetRequest.updateSessionRules({ 439 | removeRuleIds: [tabId], 440 | addRules: [] 441 | }); 442 | } 443 | }; 444 | -------------------------------------------------------------------------------- /v3/_locales/sv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "En lättanvänd flikomladdare med anpassad omladdningstid för individuella flikar och mer!" 4 | }, 5 | "popup_active": { 6 | "message": "Ladda inte om när fliken är aktiv" 7 | }, 8 | "popup_configs": { 9 | "message": "Inställningar" 10 | }, 11 | "popup_nofocus": { 12 | "message": "Ladda om när fönstret saknar fokus" 13 | }, 14 | "popup_cache": { 15 | "message": "Anv. cache vid omladdning (återställ tillståndet)" 16 | }, 17 | "popup_form": { 18 | "message": "Kringgå formulärinlämning" 19 | }, 20 | "popup_offline": { 21 | "message": "Ladda inte om fliken när uppkoppling saknas" 22 | }, 23 | "popup_discarded": { 24 | "message": "Ladda inte om fliken när den är slängd" 25 | }, 26 | "popup_randomize": { 27 | "message": "Slumpa fördröjning vid första omladdningen" 28 | }, 29 | "popup_nodiscard": { 30 | "message": "Förhindra fliken från att slängas" 31 | }, 32 | "popup_ste": { 33 | "message": "Skrolla längst ned efter omladdning" 34 | }, 35 | "popup_switch": { 36 | "message": "Byt till fliken när innehållet förändras" 37 | }, 38 | "popup_sound": { 39 | "message": "Spela upp ljud när innehållet förändras" 40 | }, 41 | "popup_bw": { 42 | "message": "Ladda inte om när adress/titel innehåller" 43 | }, 44 | "popup_period": { 45 | "message": "Ladda om mellan" 46 | }, 47 | "popup_active_tabs": { 48 | "message": "Aktiva flikar" 49 | }, 50 | "popup_enable": { 51 | "message": "Ladda om valda flikar efter" 52 | }, 53 | "popup_permission": { 54 | "message": "Värdbehörighet för fliken krävs" 55 | }, 56 | "popup_presets": { 57 | "message": "Förval" 58 | }, 59 | "popup_forced": { 60 | "message": "Använd Shift-tangenten för att tvinga perioder mindre än 10 sekunder" 61 | }, 62 | "popup_code": { 63 | "message": "Kör JS-kod" 64 | }, 65 | "popup_code_desc": { 66 | "message": "Kör denna JS-kod efter varje omladdning" 67 | }, 68 | "popup_pre_code": { 69 | "message": "Kör policykod" 70 | }, 71 | "popup_pre_code_desc": { 72 | "message": "Om detta aktiveras måste funktionen ange \"document.currentScript.dataset.continue = true\" för att kunna ladda om" 73 | }, 74 | "popup_pre_code_example_1": { 75 | "message": "Ladda om när sidan innehåller ordet \"hello\"" 76 | }, 77 | "popup_pre_code_example_2": { 78 | "message": "Ladda om slumpartat med ett intervall på 50 %" 79 | }, 80 | "popup_pre_code_example_3": { 81 | "message": "Ladda om när sidan är synlig" 82 | }, 83 | "popup_variation": { 84 | "message": "med variation på" 85 | }, 86 | "popup_variation_hint": { 87 | "message": "Ange variation i procent för att slumpa omladdningstiden (tillämpas efter första omladdningen)" 88 | }, 89 | "popup_enable_keys": { 90 | "message": "Aktivera omladdning med S-tangenten\nInaktivera omladdning med Escape-tangenten\nStäng popup med Shift + Escape-tangenten\nVälj ett förval med Ctrl/Command + 1/10-tangenterna" 91 | }, 92 | "popup_start": { 93 | "message": "Starta omladdning" 94 | }, 95 | "popup_stop": { 96 | "message": "Stoppa omladdning" 97 | }, 98 | "popup_save_as_json": { 99 | "message": "Spara som ett anpassat jobb" 100 | }, 101 | "popup_save_as_json_desc": { 102 | "message": "Shift + klicka för att matcha fullständiga webbadressen istället för värdnamnet.\nCtrl/Kmd + klicka för att matcha fullständiga webbadressen exklusive parametrar." 103 | }, 104 | "popup_saved": { 105 | "message": "Sparades!" 106 | }, 107 | "popup_sound_1": { 108 | "message": "Ljud 1" 109 | }, 110 | "popup_sound_2": { 111 | "message": "Ljud 2" 112 | }, 113 | "popup_sound_3": { 114 | "message": "Ljud 3" 115 | }, 116 | "popup_sound_4": { 117 | "message": "Ljud 4" 118 | }, 119 | "popup_sound_5": { 120 | "message": "Ljud 5" 121 | }, 122 | "popup_sound_6": { 123 | "message": "Ljud 6" 124 | }, 125 | "popup_sound_7": { 126 | "message": "Ljud 7" 127 | }, 128 | "popup_sound_8": { 129 | "message": "Ljud 8" 130 | }, 131 | "popup_test_sound": { 132 | "message": "Testa" 133 | }, 134 | "popup_rate": { 135 | "message": "Betygsätt" 136 | }, 137 | "popup_vcd": { 138 | "message": "Visa visuell nedräkning" 139 | }, 140 | "options_title": { 141 | "message": "Alternativ :: Tab Reloader" 142 | }, 143 | "options_badge": { 144 | "message": "Visa totala antalet aktiva jobb inuti ikonen" 145 | }, 146 | "options_color": { 147 | "message": "Ikonfärg:" 148 | }, 149 | "options_defaults": { 150 | "message": "Standardvärden" 151 | }, 152 | "options_default_time": { 153 | "message": "Standardvärde för omladdningstid:" 154 | }, 155 | "options_default_variation": { 156 | "message": "Standardvärde för varierad omladdningstid:" 157 | }, 158 | "options_pp_current": { 159 | "message": "Standardvärde för \"Ladda inte om när fliken är aktiv\"" 160 | }, 161 | "options_pp_nofocus": { 162 | "message": "Standardvärde för \"Ladda om när fönstret saknar fokus\"" 163 | }, 164 | "options_pp_cache": { 165 | "message": "Standardvärde för \"Använd cache vid omladdning\"" 166 | }, 167 | "options_pp_form": { 168 | "message": "Standardvärde för \"Kringgå formulärinlämning\"" 169 | }, 170 | "options_pp_offline": { 171 | "message": "Standardvärde för \"Ladda inte om fliken när uppkoppling saknas\"" 172 | }, 173 | "options_pp_discarded": { 174 | "message": "Standardvärde för \"Ladda inte om fliken när den är slängd\"" 175 | }, 176 | "options_pp_nodiscard": { 177 | "message": "Standardvärde för \"Förhindra fliken från att slängas\"" 178 | }, 179 | "options_pp_randomize": { 180 | "message": "Standardvärde för \"Slumpa fördröjning vid första omladdningen\"" 181 | }, 182 | "options_pp_scroll_to_end": { 183 | "message": "Standardvärde för \"Skrolla längst ned efter omladdning\"" 184 | }, 185 | "options_pp_visual_countdown": { 186 | "message": "Standardvärde för \"Visa visuell nedräkning\"" 187 | }, 188 | "options_presets": { 189 | "message": "Förval" 190 | }, 191 | "options_custom_jobs": { 192 | "message": "Anpassade jobb" 193 | }, 194 | "options_sample": { 195 | "message": "infoga ett exempel" 196 | }, 197 | "options_keywords": { 198 | "message": "nyckelord som stöds" 199 | }, 200 | "options_events": { 201 | "message": "händelser som stöds" 202 | }, 203 | "options_desc": { 204 | "message": "visa/dölj beskrivning" 205 | }, 206 | "options_json_desc": { 207 | "message": "Lista med värdnamn i JSON-format för att ange en automatiskt omladdning efter varje uppstart. Om du behöver matcha webbadresser i stället för värdnamn tar du bort nyckeln \"hostname\" och lägger i stället till nyckeln \"url\". Läs sidan med frågor och svar för fler instruktioner." 208 | }, 209 | "options_dynamic_json": { 210 | "message": "Kontrollera alltid för att installera omladdningsjobb från JSON-objektet när en ny sida läses in." 211 | }, 212 | "options_dynamic_json_desc": { 213 | "message": "När detta alternativ aktiveras kommer tillägget installera ett nytt anpassat omladdningsjobb för nya sidor som matchar en av nycklarna i listan." 214 | }, 215 | "options_policy": { 216 | "message": "Omladdningspolicy" 217 | }, 218 | "options_misc": { 219 | "message": "Övrigt" 220 | }, 221 | "options_reassign": { 222 | "message": "Försök tilldela ett omladdningsjobb för en ny flik som tidigare har haft ett aktivt omladdningsjobb." 223 | }, 224 | "options_faqs": { 225 | "message": "Öppna sidan med frågor och svar vid uppdateringar" 226 | }, 227 | "options_tools": { 228 | "message": "Verktyg" 229 | }, 230 | "options_export_desc": { 231 | "message": "Exportera: Exportera alla inställningar till en JSON-fil och ladda ned den till standardmappen för nedladdningar" 232 | }, 233 | "options_import_desc": { 234 | "message": "Importera: Importera inställningar från en JSON-fil (detta överskrider nuvarande inställningar och startar om tillägget)" 235 | }, 236 | "options_reset_desc": { 237 | "message": "Återställ: Återställ inställningar till fabriksinställningarna" 238 | }, 239 | "options_reset": { 240 | "message": "Standardvärden" 241 | }, 242 | "options_export": { 243 | "message": "Exportera" 244 | }, 245 | "options_import": { 246 | "message": "Importera" 247 | }, 248 | "options_permission": { 249 | "message": "Värdbehörighet" 250 | }, 251 | "options_instruction": { 252 | "message": "Användningsinstruktioner" 253 | }, 254 | "options_ofq": { 255 | "message": "Frågor och svar" 256 | }, 257 | "options_support": { 258 | "message": "Stöd utvecklingen" 259 | }, 260 | "options_save": { 261 | "message": "Spara alternativ" 262 | }, 263 | "options_saved": { 264 | "message": "Alternativ sparades" 265 | }, 266 | "options_save_reminder": { 267 | "message": "Se till att klicka på knappen \"Spara alternativ\"!" 268 | }, 269 | "options_disables": { 270 | "message": "Inaktivera alternativ i högerklicksmenyn" 271 | }, 272 | "options_startup_restore_delay": { 273 | "message": "Fördröjning i sekunder innan tidigare jobb startas om efter omstart:" 274 | }, 275 | "options_help": { 276 | "message": "Läs mer" 277 | }, 278 | "bg_reload_tabs": { 279 | "message": "Ladda om flikar en gång" 280 | }, 281 | "bg_all_tabs": { 282 | "message": "Alla flikar" 283 | }, 284 | "bg_reload_all_discarded": { 285 | "message": "Alla slängda flikar" 286 | }, 287 | "bg_reload_window": { 288 | "message": "Alla flikar i aktuellt fönster" 289 | }, 290 | "bg_reload_window_discarded": { 291 | "message": "Alla slängda flikar i aktuellt fönster" 292 | }, 293 | "bg_reload_tabs_left": { 294 | "message": "Flikar åt vänster" 295 | }, 296 | "bg_reload_tabs_right": { 297 | "message": "Flikar åt höger" 298 | }, 299 | "bg_stop": { 300 | "message": "Sluta ladda om" 301 | }, 302 | "bg_stop_all": { 303 | "message": "Alla flikar" 304 | }, 305 | "bg_csp": { 306 | "message": "Innehållssäkerhetsprincip (CSP)" 307 | }, 308 | "bg_csp_remove": { 309 | "message": "Ta bort CSP-skydd för denna flik (tillåt att infogade skript körs)" 310 | }, 311 | "bg_csp_reset": { 312 | "message": "Återställ till standard" 313 | }, 314 | "bg_no_reload": { 315 | "message": "Valda flikar" 316 | }, 317 | "bg_reload_every": { 318 | "message": "Ladda om valda flikar" 319 | }, 320 | "bg_reload_every_10s": { 321 | "message": "Var 10:e sekund" 322 | }, 323 | "bg_reload_every_30s": { 324 | "message": "Var 30:e sekund" 325 | }, 326 | "bg_reload_every_1m": { 327 | "message": "Varje minut" 328 | }, 329 | "bg_reload_every_5m": { 330 | "message": "Var 5:e minut" 331 | }, 332 | "bg_reload_every_15m": { 333 | "message": "Var 15:e minut" 334 | }, 335 | "bg_reload_every_1h": { 336 | "message": "Varje timme" 337 | }, 338 | "bg_restart": { 339 | "message": "Starta om tillägget" 340 | }, 341 | "bg_options": { 342 | "message": "Alternativ" 343 | }, 344 | "bg_msg_1": { 345 | "message": "Fliken har en ogiltig adress" 346 | }, 347 | "bg_msg_2": { 348 | "message": "Behörighet att komma åt detta värdnamn beviljades inte" 349 | }, 350 | "bg_msg_3": { 351 | "message": "Varning: Om innehållssäkerhetsprincipen (CSP) tas bort kan webbplatsen bli sårbar för säkerhetsrisker, t.ex. attacker med skriptkörning över flera webbplatser (XSS).\n\nÄr du säker på att du vill fortsätta?" 352 | }, 353 | "commands_reload_all": { 354 | "message": "Ladda om alla flikar" 355 | }, 356 | "commands_reload_all_discarded": { 357 | "message": "Ladda om alla slängda flikar" 358 | }, 359 | "commands_reload_window": { 360 | "message": "Ladda om alla flikar i aktuellt fönster" 361 | }, 362 | "commands_reload_window_discarded": { 363 | "message": "Ladda om alla slängda flikar i aktuellt fönster" 364 | }, 365 | "commands_reload_tabs_left": { 366 | "message": "Ladda om flikar åt vänster" 367 | }, 368 | "commands_reload_tabs_right": { 369 | "message": "Ladda om flikar åt höger" 370 | }, 371 | "commands_stop": { 372 | "message": "Stoppa alla omladdningsjobb" 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /v3/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "An easy-to-use tab reloader with custom reloading time for individual tabs and more!" 4 | }, 5 | "popup_active": { 6 | "message": "Do not reload if tab is active" 7 | }, 8 | "popup_configs": { 9 | "message": "Settings" 10 | }, 11 | "popup_nofocus": { 12 | "message": "Reload if window is not focused" 13 | }, 14 | "popup_cache": { 15 | "message": "Use cache while reloading (restore state)" 16 | }, 17 | "popup_form": { 18 | "message": "Bypass form submission" 19 | }, 20 | "popup_offline": { 21 | "message": "Do not reload tab if offline" 22 | }, 23 | "popup_discarded": { 24 | "message": "Do not reload tab if discarded" 25 | }, 26 | "popup_randomize": { 27 | "message": "Random delay on the first reload" 28 | }, 29 | "popup_skip_auto_add": { 30 | "message": "Don't auto-restart if closed without ending" 31 | }, 32 | "popup_nodiscard": { 33 | "message": "Prevent the tab from discarding" 34 | }, 35 | "popup_ste": { 36 | "message": "Scroll to the end after reload" 37 | }, 38 | "popup_switch": { 39 | "message": "Switch to the tab if content changed" 40 | }, 41 | "popup_sound": { 42 | "message": "Play sound if content changed" 43 | }, 44 | "popup_bw": { 45 | "message": "Do not reload if the address or title contains" 46 | }, 47 | "popup_period": { 48 | "message": "Reload between" 49 | }, 50 | "popup_active_tabs": { 51 | "message": "Active Tabs" 52 | }, 53 | "popup_enable": { 54 | "message": "Reload selected tabs every" 55 | }, 56 | "popup_permission": { 57 | "message": "Tab's host permission is required" 58 | }, 59 | "popup_presets": { 60 | "message": "Presets" 61 | }, 62 | "popup_forced": { 63 | "message": "Use Shift key to force periods less than 10 seconds" 64 | }, 65 | "popup_code": { 66 | "message": "Run JS Code" 67 | }, 68 | "popup_code_desc": { 69 | "message": "Run this JS code after each reload" 70 | }, 71 | "popup_pre_code": { 72 | "message": "Run Policy Code" 73 | }, 74 | "popup_pre_code_desc": { 75 | "message": "If defined, the function must set \"document.currentScript.dataset.continue = true\" for reloading to happen" 76 | }, 77 | "popup_pre_code_example_1": { 78 | "message": "Reload only if the page contains \"hello\" word" 79 | }, 80 | "popup_pre_code_example_2": { 81 | "message": "Reload the page randomly at 50% rate" 82 | }, 83 | "popup_pre_code_example_3": { 84 | "message": "Reload the page only if it is visible" 85 | }, 86 | "popup_variation": { 87 | "message": "with variation" 88 | }, 89 | "popup_variation_hint": { 90 | "message": "Set variation in percent to randomize reloading time (applies after the first reload)" 91 | }, 92 | "popup_enable_keys": { 93 | "message": "'S' key to enable reloading\n'Escape' key to disable reloading\nShift + Escape key to close the popup\nCtrl/Command + 1/10 keys to select a preset" 94 | }, 95 | "popup_start": { 96 | "message": "Start Reloading" 97 | }, 98 | "popup_stop": { 99 | "message": "Stop Reloading" 100 | }, 101 | "popup_save_as_json": { 102 | "message": "Save as a Custom Job" 103 | }, 104 | "popup_save_as_json_desc": { 105 | "message": "Use Shift + Click to match against the full URL instead of the hostname.\nUse Ctrl + Click or Command + Click to match against the full URL excluding parameters." 106 | }, 107 | "popup_saved": { 108 | "message": "Saved!" 109 | }, 110 | "popup_sound_1": { 111 | "message": "Sound 1" 112 | }, 113 | "popup_sound_2": { 114 | "message": "Sound 2" 115 | }, 116 | "popup_sound_3": { 117 | "message": "Sound 3" 118 | }, 119 | "popup_sound_4": { 120 | "message": "Sound 4" 121 | }, 122 | "popup_sound_5": { 123 | "message": "Sound 5" 124 | }, 125 | "popup_sound_6": { 126 | "message": "Sound 6" 127 | }, 128 | "popup_sound_7": { 129 | "message": "Sound 7" 130 | }, 131 | "popup_sound_8": { 132 | "message": "Sound 8" 133 | }, 134 | "popup_test_sound": { 135 | "message": "Test" 136 | }, 137 | "popup_rate": { 138 | "message": "Rate Me" 139 | }, 140 | "popup_vcd": { 141 | "message": "Display visual countdown" 142 | }, 143 | "popup_stop_on_address_change": { 144 | "message": "Skip reload on address change" 145 | }, 146 | "options_title": { 147 | "message": "Options Page :: Tab Reloader" 148 | }, 149 | "options_badge": { 150 | "message": "Display the total number of active jobs in the badge area" 151 | }, 152 | "options_color": { 153 | "message": "Badge color is:" 154 | }, 155 | "options_defaults": { 156 | "message": "Defaults" 157 | }, 158 | "options_default_time": { 159 | "message": "Default reloading time:" 160 | }, 161 | "options_default_variation": { 162 | "message": "Default reloading variation:" 163 | }, 164 | "options_pp_current": { 165 | "message": "Default value for \"Do no reload if tab is active\"" 166 | }, 167 | "options_pp_nofocus": { 168 | "message": "Default value for \"Reload if window is not focused\"" 169 | }, 170 | "options_pp_cache": { 171 | "message": "Default value for \"Use cache while reloading\"" 172 | }, 173 | "options_pp_form": { 174 | "message": "Default value for \"Bypass form submission\"" 175 | }, 176 | "options_pp_offline": { 177 | "message": "Default value for \"Do not reload if offline\"" 178 | }, 179 | "options_pp_discarded": { 180 | "message": "Default value for \"Do not reload if discarded\"" 181 | }, 182 | "options_pp_nodiscard": { 183 | "message": "Default value for \"Prevent the tab from discarding\"" 184 | }, 185 | "options_pp_randomize": { 186 | "message": "Default value for \"Random delay on the first reload\"" 187 | }, 188 | "options_pp_scroll_to_end": { 189 | "message": "Default value for \"Scroll to the end after reload\"" 190 | }, 191 | "options_pp_visual_countdown": { 192 | "message": "Default value for \"Display visual countdown\"" 193 | }, 194 | "options_pp_stop_on_address_change": { 195 | "message": "Default value for \"Skip reload on address change\"" 196 | }, 197 | "options_presets": { 198 | "message": "Presets" 199 | }, 200 | "options_custom_jobs": { 201 | "message": "Custom Jobs" 202 | }, 203 | "options_sample": { 204 | "message": "Insert a Sample" 205 | }, 206 | "options_keywords": { 207 | "message": "Supported Keywords" 208 | }, 209 | "options_events": { 210 | "message": "Supported Events" 211 | }, 212 | "options_desc": { 213 | "message": "Toggle Description" 214 | }, 215 | "options_json_desc": { 216 | "message": "List hostnames in JSON format to set an automatic refresh after each startup. If you need URL matching instead of hostname matching, remove the \"hostname\" key and append the \"url\" key instead. Read the FAQs page for additional instructions." 217 | }, 218 | "options_dynamic_json": { 219 | "message": "Always check to install reloading jobs from the JSON object when a new page loads." 220 | }, 221 | "options_dynamic_json_desc": { 222 | "message": "When this option is enabled, the extension installs a new custom reloading job for new pages matching one of the keys in the list." 223 | }, 224 | "options_policy": { 225 | "message": "Reloading Policy" 226 | }, 227 | "options_misc": { 228 | "message": "Misc" 229 | }, 230 | "options_reassign": { 231 | "message": "Try to assign a reloading job when a new tab used to have an active reloading job." 232 | }, 233 | "options_faqs": { 234 | "message": "Open FAQs page on updates" 235 | }, 236 | "options_tools": { 237 | "message": "Tools" 238 | }, 239 | "options_export_desc": { 240 | "message": "Export: Export all settings to a JSON file and download it to the default download directory" 241 | }, 242 | "options_import_desc": { 243 | "message": "Import: Import settings from a JSON file (this will overwrite current settings and restarts the extension)" 244 | }, 245 | "options_reset_desc": { 246 | "message": "Reset: Reset settings to the factory values" 247 | }, 248 | "options_reset": { 249 | "message": "Reset Defaults" 250 | }, 251 | "options_export": { 252 | "message": "Export" 253 | }, 254 | "options_import": { 255 | "message": "Import" 256 | }, 257 | "options_permission": { 258 | "message": "Host Permission" 259 | }, 260 | "options_instruction": { 261 | "message": "Usage Instruction" 262 | }, 263 | "options_ofq": { 264 | "message": "Open FAQs page" 265 | }, 266 | "options_support": { 267 | "message": "Support Development" 268 | }, 269 | "options_save": { 270 | "message": "Save Options" 271 | }, 272 | "options_saved": { 273 | "message": "Options saved" 274 | }, 275 | "options_save_reminder": { 276 | "message": "Make sure to press the \"Save Options\" button!" 277 | }, 278 | "options_disables": { 279 | "message": "Disable Context Menu Items" 280 | }, 281 | "options_startup_restore_delay": { 282 | "message": "Delay in seconds to restart previous jobs after restart:" 283 | }, 284 | "options_help": { 285 | "message": "Learn More" 286 | }, 287 | "bg_reload_tabs": { 288 | "message": "Reload tabs once" 289 | }, 290 | "bg_all_tabs": { 291 | "message": "All tabs" 292 | }, 293 | "bg_reload_all_discarded": { 294 | "message": "All discarded tabs" 295 | }, 296 | "bg_reload_window": { 297 | "message": "All tabs in the current window" 298 | }, 299 | "bg_reload_window_discarded": { 300 | "message": "All discarded tabs in the current window" 301 | }, 302 | "bg_reload_tabs_left": { 303 | "message": "Tabs to the left" 304 | }, 305 | "bg_reload_tabs_right": { 306 | "message": "Tabs to the right" 307 | }, 308 | "bg_stop": { 309 | "message": "Stop reloading" 310 | }, 311 | "bg_stop_all": { 312 | "message": "All tabs" 313 | }, 314 | "bg_csp": { 315 | "message": "Content Security Policy" 316 | }, 317 | "bg_csp_remove": { 318 | "message": "Remove CSP Protection for this tab (allow inline script execution)" 319 | }, 320 | "bg_csp_reset": { 321 | "message": "Reset to Default" 322 | }, 323 | "bg_no_reload": { 324 | "message": "Selected tabs" 325 | }, 326 | "bg_reload_every": { 327 | "message": "Reload selected tabs" 328 | }, 329 | "bg_reload_every_10s": { 330 | "message": "Every 10 secs" 331 | }, 332 | "bg_reload_every_30s": { 333 | "message": "Every 30 secs" 334 | }, 335 | "bg_reload_every_1m": { 336 | "message": "Every minute" 337 | }, 338 | "bg_reload_every_5m": { 339 | "message": "Every 5 minutes" 340 | }, 341 | "bg_reload_every_15m": { 342 | "message": "Every 15 minutes" 343 | }, 344 | "bg_reload_every_1h": { 345 | "message": "Every hour" 346 | }, 347 | "bg_restart": { 348 | "message": "Restart the extension" 349 | }, 350 | "bg_options": { 351 | "message": "Options" 352 | }, 353 | "bg_msg_1": { 354 | "message": "Tab does not have a valid address" 355 | }, 356 | "bg_msg_2": { 357 | "message": "Permission to access this hostname is not granted" 358 | }, 359 | "bg_msg_3": { 360 | "message": "Warning: Removing the Content Security Policy (CSP) can make the website vulnerable to security risks, such as cross-site scripting (XSS) attacks.\n\nAre you sure you want to proceed?" 361 | }, 362 | "bg_msg_4": { 363 | "message": "Last reloaded at $date$", 364 | "placeholders": { 365 | "date": { 366 | "content": "$1" 367 | } 368 | } 369 | }, 370 | "commands_reload_all": { 371 | "message": "Reload all tabs" 372 | }, 373 | "commands_reload_all_discarded": { 374 | "message": "Reload all discarded tabs" 375 | }, 376 | "commands_reload_window": { 377 | "message": "Reload all tabs in the current window" 378 | }, 379 | "commands_reload_window_discarded": { 380 | "message": "Reload all discarded tabs in the current window" 381 | }, 382 | "commands_reload_tabs_left": { 383 | "message": "Reload tabs to the left" 384 | }, 385 | "commands_reload_tabs_right": { 386 | "message": "Reload tabs to the right" 387 | }, 388 | "commands_stop": { 389 | "message": "Stop all Reloading Jobs" 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /v2/data/popup/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prefs = { 4 | 'dd': 0, 5 | 'hh': 0, 6 | 'mm': 5, 7 | 'ss': 0, 8 | 'pp-current': false, 9 | 'pp-nofocus': false, 10 | 'pp-cache': false, 11 | 'pp-form': false, 12 | 'pp-offline': false, 13 | 'pp-scroll-to-end': false, 14 | 15 | 'presets': [{ 16 | hh: 0, 17 | mm: 0, 18 | ss: 30 19 | }, { 20 | hh: 0, 21 | mm: 5, 22 | ss: 0 23 | }, { 24 | hh: 0, 25 | mm: 15, 26 | ss: 0 27 | }, { 28 | hh: 0, 29 | mm: 30, 30 | ss: 0 31 | }, { 32 | hh: 1, 33 | mm: 0, 34 | ss: 0 35 | }, { 36 | hh: 5, 37 | mm: 0, 38 | ss: 0 39 | }] 40 | }; 41 | 42 | // detect FF's overflow menu 43 | if (document.documentElement.clientWidth < 500) { 44 | document.body.classList.add('ffo'); 45 | } 46 | 47 | const dom = { 48 | get enable() { 49 | return document.querySelector('[data-type=enable]'); 50 | }, 51 | set enable(val) { 52 | const tmp = document.querySelector('[data-type=enable]'); 53 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 54 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 55 | document.body.dataset.enabled = val; 56 | }, 57 | get current() { 58 | return document.querySelector('[data-type=current]').classList.contains('icon-toggle-on'); 59 | }, 60 | set current(val) { 61 | const tmp = document.querySelector('[data-type=current]'); 62 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 63 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 64 | }, 65 | get nofocus() { 66 | return document.querySelector('[data-type=nofocus]').classList.contains('icon-toggle-on'); 67 | }, 68 | set nofocus(val) { 69 | const tmp = document.querySelector('[data-type=nofocus]'); 70 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 71 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 72 | }, 73 | get offline() { 74 | return document.querySelector('[data-type=offline]').classList.contains('icon-toggle-on'); 75 | }, 76 | set offline(val) { 77 | const tmp = document.querySelector('[data-type=offline]'); 78 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 79 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 80 | }, 81 | get cache() { 82 | return document.querySelector('[data-type=cache]').classList.contains('icon-toggle-on'); 83 | }, 84 | set cache(val) { 85 | const tmp = document.querySelector('[data-type=cache]'); 86 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 87 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 88 | }, 89 | get ste() { // scroll-to-end 90 | return document.querySelector('[data-type=scoll-to-end]').classList.contains('icon-toggle-on'); 91 | }, 92 | set ste(val) { 93 | const tmp = document.querySelector('[data-type=scoll-to-end]'); 94 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 95 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 96 | }, 97 | get code() { 98 | return document.querySelector('textarea').value; 99 | }, 100 | set code(val) { 101 | document.querySelector('textarea').value = val || ''; 102 | }, 103 | get form() { 104 | return document.querySelector('[data-type=form]').classList.contains('icon-toggle-on'); 105 | }, 106 | set form(val) { 107 | const tmp = document.querySelector('[data-type=form]'); 108 | tmp.textContent = val ? 'Enabled' : 'Disabled'; 109 | tmp.setAttribute('class', 'icon-toggle-' + (val ? 'on' : 'off')); 110 | }, 111 | get dd() { 112 | return document.querySelector('[data-type=dd]').value; 113 | }, 114 | set dd(val) { 115 | document.querySelector('[data-type=dd]').value = val; 116 | }, 117 | get hh() { 118 | return document.querySelector('[data-type=hh]').value; 119 | }, 120 | set hh(val) { 121 | document.querySelector('[data-type=hh]').value = val; 122 | }, 123 | get mm() { 124 | return document.querySelector('[data-type=mm]').value; 125 | }, 126 | set mm(val) { 127 | document.querySelector('[data-type=mm]').value = val; 128 | }, 129 | get ss() { 130 | return document.querySelector('[data-type=ss]').value; 131 | }, 132 | set ss(val) { 133 | document.querySelector('[data-type=ss]').value = val; 134 | }, 135 | get vr() { 136 | return document.querySelector('[data-type=vr]').value; 137 | }, 138 | set vr(val) { 139 | document.querySelector('[data-type=vr]').value = val; 140 | }, 141 | set msg(val) { // jshint ignore:line 142 | document.querySelector('[data-type=msg]').textContent = val; 143 | }, 144 | set jobs(jobs) { // jshint ignore:line 145 | document.body.dataset.jobs = document.querySelector('[data-type=jobs]').textContent = 146 | jobs.length; 147 | const ol = document.querySelector('#jobs ol'); 148 | for (const job of jobs) { 149 | const li = document.createElement('li'); 150 | li.textContent = job.title; 151 | li.dataset.id = job.id; 152 | ol.appendChild(li); 153 | } 154 | } 155 | }; 156 | let id; 157 | let tab; 158 | 159 | function check() { 160 | window.clearInterval(id); 161 | id = window.setInterval(() => chrome.runtime.sendMessage({ 162 | method: 'request-update', 163 | id: tab.id 164 | }), 1000); 165 | } 166 | 167 | chrome.runtime.onMessage.addListener(request => { 168 | const twoDigit = num => ('00' + num).slice(-2); 169 | if (request.method === 'updated-info') { 170 | const obj = request.data; 171 | 172 | if ('current' in obj) { 173 | dom.current = obj.current; 174 | } 175 | if ('nofocus' in obj) { 176 | dom.nofocus = obj.nofocus; 177 | } 178 | if ('offline' in obj) { 179 | dom.offline = obj.offline; 180 | } 181 | if ('cache' in obj) { 182 | dom.cache = obj.cache; 183 | } 184 | if ('form' in obj) { 185 | dom.form = obj.form; 186 | } 187 | if ('code' in obj) { 188 | dom.code = obj.code; 189 | } 190 | if ('ste' in obj) { 191 | dom.ste = obj.ste; 192 | } 193 | if ('variation' in obj) { 194 | dom.vr = obj.variation || 0; 195 | } 196 | if ('jobs' in obj) { 197 | dom.jobs = obj.jobs; 198 | } 199 | 200 | dom.enable = obj.status; 201 | if (!obj.status) { 202 | id = window.clearInterval(id); 203 | } 204 | else if (!id) { 205 | check(); 206 | } 207 | 208 | Object.assign(dom, { 209 | dd: isNaN(obj.dd) ? prefs.dd : obj.dd, 210 | hh: isNaN(obj.hh) ? prefs.hh : obj.hh, 211 | mm: isNaN(obj.mm) ? prefs.mm : obj.mm, 212 | ss: isNaN(obj.ss) ? prefs.ss : obj.ss 213 | }); 214 | if (obj.status) { 215 | const {dd = 0, hh = 0, mm = 5, ss = 0} = obj.msg; 216 | dom.msg = `Time left to refresh: ${twoDigit(dd)} : ${twoDigit(hh)} : ${twoDigit(mm)} : ${twoDigit(ss)}`; 217 | } 218 | else { 219 | dom.msg = 'Tab Reloader is disabled on this tab'; 220 | } 221 | } 222 | }); 223 | 224 | document.addEventListener('click', e => { 225 | const target = e.target; 226 | const type = target.dataset.type; 227 | if (type === 'enable') { 228 | chrome.runtime.sendMessage({ 229 | method: 'enable', 230 | tab, 231 | data: { 232 | dd: dom.dd, 233 | hh: dom.hh, 234 | mm: dom.mm, 235 | ss: dom.ss, 236 | variation: Number(dom.vr), 237 | current: dom.current, 238 | nofocus: dom.nofocus, 239 | offline: dom.offline, 240 | forced: e.shiftKey, // forced period 241 | cache: dom.cache, 242 | form: dom.form, 243 | code: dom.code, 244 | ste: dom.ste 245 | } 246 | }); 247 | } 248 | else if (type === 'current') { 249 | dom.current = !dom.current; 250 | } 251 | else if (type === 'nofocus') { 252 | dom.nofocus = !dom.nofocus; 253 | } 254 | else if (type === 'offline') { 255 | dom.offline = !dom.offline; 256 | } 257 | else if (type === 'cache') { 258 | dom.cache = !dom.cache; 259 | } 260 | else if (type === 'form') { 261 | dom.form = !dom.form; 262 | } 263 | else if (type === 'scoll-to-end') { 264 | if (dom.ste === false) { 265 | chrome.permissions.request({ 266 | origins: [tab.url] 267 | }, granted => { 268 | if (granted) { 269 | dom.ste = true; 270 | } 271 | else { 272 | dom.ste = false; 273 | } 274 | }); 275 | } 276 | else { 277 | dom.ste = false; 278 | } 279 | } 280 | }); 281 | 282 | document.addEventListener('change', ({target}) => { 283 | if (target.type === 'number') { 284 | const value = Number(target.value); 285 | const min = Number(target.min); 286 | const max = Number(target.max); 287 | target.value = Math.max(min, Math.min(max, value)); 288 | } 289 | }); 290 | 291 | // permit 292 | { 293 | const input = document.getElementById('permit'); 294 | const textarea = document.querySelector('textarea'); 295 | 296 | const check = () => { 297 | const next = granted => { 298 | input.disabled = granted; 299 | textarea.disabled = granted === false; 300 | }; 301 | try { 302 | chrome.permissions.contains({ 303 | origins: [tab.url] 304 | }, granted => { 305 | next(chrome.runtime.lastError ? false : granted); 306 | }); 307 | } 308 | catch (e) { 309 | next(false); 310 | } 311 | }; 312 | 313 | input.check = check; 314 | input.addEventListener('click', () => { 315 | chrome.permissions.request({ 316 | origins: [tab.url] 317 | }, granted => { 318 | const lastError = chrome.runtime.lastError; 319 | if (lastError) { 320 | alert('Code Execution Denied:\n\n' + lastError.message); 321 | } 322 | if (granted) { 323 | check(); 324 | } 325 | }); 326 | }); 327 | } 328 | 329 | // init 330 | chrome.storage.local.get(prefs, ps => { 331 | Object.assign(prefs, ps); 332 | 333 | dom.current = prefs['pp-current']; 334 | dom.nofocus = prefs['pp-nofocus']; 335 | dom.cache = prefs['pp-cache']; 336 | dom.form = prefs['pp-form']; 337 | dom.offline = prefs['pp-offline']; 338 | dom.ste = prefs['pp-scroll-to-end']; 339 | 340 | chrome.tabs.query({ 341 | active: true, 342 | currentWindow: true 343 | }, tabs => { 344 | if (tabs && tabs.length) { 345 | tab = tabs[0]; 346 | chrome.runtime.sendMessage({ 347 | method: 'request-update', 348 | id: tab.id, 349 | extra: true 350 | }); 351 | check(); 352 | document.getElementById('permit').check(); 353 | } 354 | }); 355 | 356 | for (const preset of prefs.presets.slice(0, 6)) { 357 | const span = document.createElement('span'); 358 | span.dataset.hh = preset.hh; 359 | span.dataset.mm = preset.mm; 360 | span.dataset.ss = preset.ss; 361 | 362 | span.textContent = preset.hh.toString().padStart(2, '0') + ':' + preset.mm.toString().padStart(2, '0') + ':' + 363 | preset.ss.toString().padStart(2, '0'); 364 | 365 | document.querySelector('.presets').appendChild(span); 366 | } 367 | }); 368 | 369 | // jobs 370 | document.querySelector('#jobs ol').addEventListener('click', e => { 371 | const id = e.target.dataset.id; 372 | if (id) { 373 | chrome.tabs.update(Number(id), { 374 | active: true 375 | }); 376 | chrome.tabs.get(Number(id), tab => chrome.windows.update(tab.windowId, { 377 | focused: true 378 | })); 379 | } 380 | }); 381 | 382 | // presets 383 | document.addEventListener('click', e => { 384 | const {hh, mm, ss} = e.target.dataset; 385 | if (ss) { 386 | dom.hh = hh; 387 | dom.mm = mm; 388 | dom.ss = ss; 389 | } 390 | }); 391 | 392 | document.addEventListener('DOMContentLoaded', () => { 393 | for (const a of [...document.querySelectorAll('[data-href]')]) { 394 | if (a.hasAttribute('href') === false) { 395 | a.href = chrome.runtime.getManifest().homepage_url + '#' + a.dataset.href; 396 | } 397 | } 398 | }); 399 | -------------------------------------------------------------------------------- /v3/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "Ein einfach zu bedienender Tab-Reloader mit individuellen Ladezeiten für verschiedene Tabs und vielem mehr!" 4 | }, 5 | "popup_active": { 6 | "message": "Nicht neu laden, wenn der Tab aktiv ist" 7 | }, 8 | "popup_configs": { 9 | "message": "Einstellungen" 10 | }, 11 | "popup_nofocus": { 12 | "message": "Neu laden, wenn das Fenster keinen Fokus hat" 13 | }, 14 | "popup_cache": { 15 | "message": "Cache verwenden (Zustand wiederherstellen)" 16 | }, 17 | "popup_form": { 18 | "message": "Übertragung von Formularen umgehen" 19 | }, 20 | "popup_offline": { 21 | "message": "Tab nicht neu laden, wenn er offline ist" 22 | }, 23 | "popup_discarded": { 24 | "message": "Tab nicht neu laden, wenn er entladen wurde" 25 | }, 26 | "popup_randomize": { 27 | "message": "Zufällige Verzögerung beim ersten Neuladen" 28 | }, 29 | "popup_nodiscard": { 30 | "message": "Verhindern, dass der Tab entladen wird" 31 | }, 32 | "popup_ste": { 33 | "message": "Nach dem Neuladen bis zum Ende blättern" 34 | }, 35 | "popup_switch": { 36 | "message": "Zum Tab wechseln bei Änderungen" 37 | }, 38 | "popup_sound": { 39 | "message": "Ton abspielen, wenn Inhalt sich ändert" 40 | }, 41 | "popup_bw": { 42 | "message": "Nicht neu laden, wenn Adresse/Titel enthält" 43 | }, 44 | "popup_period": { 45 | "message": "Neu laden zwischen" 46 | }, 47 | "popup_active_tabs": { 48 | "message": "Aktive Tabs" 49 | }, 50 | "popup_enable": { 51 | "message": "Tab-Auswahl neu laden, alle " 52 | }, 53 | "popup_permission": { 54 | "message": "Berechtigungen für diesen Tab erforderlich" 55 | }, 56 | "popup_presets": { 57 | "message": "Vorgaben" 58 | }, 59 | "popup_forced": { 60 | "message": "Benutze die Umschalttaste (Shift), um Zeiten von weniger als 10 Sekunden zu erzwingen" 61 | }, 62 | "popup_code": { 63 | "message": "JS Code ausführen" 64 | }, 65 | "popup_code_desc": { 66 | "message": "JS Code nach jedem Neuladen ausführen" 67 | }, 68 | "popup_pre_code": { 69 | "message": "Policy Code ausführen" 70 | }, 71 | "popup_pre_code_desc": { 72 | "message": "Falls definiert, muss Funktion \"document.currentScript.dataset.continue = true\" gesetzt sein, damit Neuladen möglich ist" 73 | }, 74 | "popup_pre_code_example_1": { 75 | "message": "Nur neu laden, wenn die Seite das Wort \"hello\" enthält" 76 | }, 77 | "popup_pre_code_example_2": { 78 | "message": "Seite zufällig neu laden mit einer Rate von 50%" 79 | }, 80 | "popup_pre_code_example_3": { 81 | "message": "Seite nur neu laden, wenn sie sichtbar ist" 82 | }, 83 | "popup_variation": { 84 | "message": "mit Variation" 85 | }, 86 | "popup_variation_hint": { 87 | "message": "Variation in Prozent, um Tabs zufällig neu zu laden (gilt nach dem ersten Neuladen)" 88 | }, 89 | "popup_enable_keys": { 90 | "message": "'S'-Taste zum Aktivieren des Reloads\\n'ESC'-Taste zum Deaktivieren des Reloads\\nUmschalttaste + ESC-Taste zum Schließen des Einstellungs-Fensters\\nStrg + 1 bis 10-Tasten zum Auswählen einer Voreinstellung" 91 | }, 92 | "popup_start": { 93 | "message": "Reload starten" 94 | }, 95 | "popup_stop": { 96 | "message": "Reload stoppen" 97 | }, 98 | "popup_save_as_json": { 99 | "message": "Als benutzerdefinierten Auftrag speichern" 100 | }, 101 | "popup_save_as_json_desc": { 102 | "message": "Verwenden Sie die Shift + Mausklick, um einen Abgleich mit der vollständigen URL anstelle des Hostnamens durchzuführen.\nVerwenden Sie Strg + Mausklick oder Command + Mausklick, um einen Abgleich mit der vollständigen URL ohne Parameter durchzuführen." 103 | }, 104 | "popup_saved": { 105 | "message": "Gespeichert!" 106 | }, 107 | "popup_sound_1": { 108 | "message": "Ton 1" 109 | }, 110 | "popup_sound_2": { 111 | "message": "Ton 2" 112 | }, 113 | "popup_sound_3": { 114 | "message": "Ton 3" 115 | }, 116 | "popup_sound_4": { 117 | "message": "Ton 4" 118 | }, 119 | "popup_sound_5": { 120 | "message": "Ton 5" 121 | }, 122 | "popup_sound_6": { 123 | "message": "Ton 6" 124 | }, 125 | "popup_sound_7": { 126 | "message": "Ton 7" 127 | }, 128 | "popup_sound_8": { 129 | "message": "Ton 8" 130 | }, 131 | "popup_test_sound": { 132 | "message": "Test" 133 | }, 134 | "popup_rate": { 135 | "message": "Bewerte mich" 136 | }, 137 | "popup_vcd": { 138 | "message": "Visuellen Countdown anzeigen" 139 | }, 140 | "options_title": { 141 | "message": "Einstellungen :: Tab Reloader" 142 | }, 143 | "options_badge": { 144 | "message": "Anzahl aktiver Jobs in der Symbolleiste anzeigen" 145 | }, 146 | "options_color": { 147 | "message": "Symbol Farbe:" 148 | }, 149 | "options_defaults": { 150 | "message": "Standardwerte" 151 | }, 152 | "options_default_time": { 153 | "message": "Voreingestellte Zeit für das Neuladen:" 154 | }, 155 | "options_default_variation": { 156 | "message": "Voreingestellte Variation für das Neuladen: " 157 | }, 158 | "options_pp_current": { 159 | "message": "Standardwert für \"Nicht neu laden, wenn der Tab aktiv ist\"" 160 | }, 161 | "options_pp_nofocus": { 162 | "message": "Standardwert für \"Neu laden, wenn das Fenster keinen Fokus hat\"" 163 | }, 164 | "options_pp_cache": { 165 | "message": "Standardwert für \"Cache beim Neuladen\"" 166 | }, 167 | "options_pp_form": { 168 | "message": "Standardwert für \"Übertragung von Formularen umgehen\"" 169 | }, 170 | "options_pp_offline": { 171 | "message": "Standardwert für \"Tab nicht neu laden, wenn er offline ist\"" 172 | }, 173 | "options_pp_discarded": { 174 | "message": "Standardwert für \"Tab nicht neu laden, wenn er entladen wurde\"" 175 | }, 176 | "options_pp_nodiscard": { 177 | "message": "Standardwert für \"Verhindern, dass der Tab entladen wird\"" 178 | }, 179 | "options_pp_randomize": { 180 | "message": "Standardwert für \"Zufällige Verzögerung beim ersten Neuladen\"" 181 | }, 182 | "options_pp_scroll_to_end": { 183 | "message": "Standardwert für \"Nach dem Neuladen bis zum Ende blättern\"" 184 | }, 185 | "options_pp_visual_countdown": { 186 | "message": "Standardwert für \"Visuellen Countdown anzeigen\"" 187 | }, 188 | "options_presets": { 189 | "message": "Vorgaben" 190 | }, 191 | "options_custom_jobs": { 192 | "message": "Benutzerdefinierte Aufträge" 193 | }, 194 | "options_sample": { 195 | "message": "Beispiel einfügen" 196 | }, 197 | "options_keywords": { 198 | "message": "Unterstützte Schlüsselwörter" 199 | }, 200 | "options_events": { 201 | "message": "Unterstützte Events" 202 | }, 203 | "options_desc": { 204 | "message": "Beschreibung einblenden" 205 | }, 206 | "options_json_desc": { 207 | "message": "Führen Sie Hostnamen im JSON-Format auf, um eine automatische Aktualisierung nach jedem Start einzustellen. Wenn Sie eine URL anstelle eines Hostnamens abgleichen wollen, dann entfernen Sie den Schlüssel \"hostname\" und fügen stattdessen den Schlüssel \"url\" ein. Lesen Sie die FAQ-Seite für weitere Hinweise." 208 | }, 209 | "options_dynamic_json": { 210 | "message": "Immer überprüfen, ob Reload-Aufträge aus dem JSON-Objekt aktiviert werden können, wenn eine neue Seite geladen wird." 211 | }, 212 | "options_dynamic_json_desc": { 213 | "message": "Wenn diese Option aktiviert ist, aktiviert die Erweiterung einen neuen benutzerdefinierten Auftrag für neue Seiten, die einem der Schlüssel in der Liste entsprechen." 214 | }, 215 | "options_policy": { 216 | "message": "Reload Regeln" 217 | }, 218 | "options_misc": { 219 | "message": "Verschiedenes" 220 | }, 221 | "options_reassign": { 222 | "message": "Versuche einen Reload-Auftrag wieder zuzuweisen, wenn ein Tab einen aktiven Reload-Auftrag hatte." 223 | }, 224 | "options_faqs": { 225 | "message": "FAQ-Seite nach einem Update öffnen" 226 | }, 227 | "options_tools": { 228 | "message": "Werkzeuge" 229 | }, 230 | "options_export_desc": { 231 | "message": "Export: Exportiert alle Einstellungen in eine JSON Datei und speichert sie im Download-Verzeichnis" 232 | }, 233 | "options_import_desc": { 234 | "message": "Import: Importiert alle Einstellungen aus einer JSON-Datei (dies überschreibt die aktuellen Einstellungen und startet die Erweiterung neu)" 235 | }, 236 | "options_reset_desc": { 237 | "message": "Zurücksetzten: Setzt alle Einstellungen auf Standard zurück" 238 | }, 239 | "options_reset": { 240 | "message": "Zurücksetzten auf Standwerte" 241 | }, 242 | "options_export": { 243 | "message": "Export" 244 | }, 245 | "options_import": { 246 | "message": "Import" 247 | }, 248 | "options_permission": { 249 | "message": "Addon-Berechtigungen" 250 | }, 251 | "options_instruction": { 252 | "message": "Anleitung zur Verwendung (engl.)" 253 | }, 254 | "options_ofq": { 255 | "message": "FAQ-Seite öffnen" 256 | }, 257 | "options_support": { 258 | "message": "Projekt unterstützen" 259 | }, 260 | "options_save": { 261 | "message": "Einstellungen speichern" 262 | }, 263 | "options_saved": { 264 | "message": "Einstellungen gespeichert!" 265 | }, 266 | "options_save_reminder": { 267 | "message": "Bitte nicht vergessen, die Einstellungen unten über den Button \"Einstellungen speichern\" zu sichern!" 268 | }, 269 | "options_disables": { 270 | "message": "Kontextmenüeinträge deaktivieren" 271 | }, 272 | "options_startup_restore_delay": { 273 | "message": "Verzögerung in Sekunden um früherer Aufträge, nach einem Neustart, erneut zu starten:" 274 | }, 275 | "options_help": { 276 | "message": "Erfahren Sie mehr" 277 | }, 278 | "bg_reload_tabs": { 279 | "message": "Tabs einmal neu laden" 280 | }, 281 | "bg_all_tabs": { 282 | "message": "Alle Tabs" 283 | }, 284 | "bg_reload_all_discarded": { 285 | "message": "Alle entladenen Tabs" 286 | }, 287 | "bg_reload_window": { 288 | "message": "Alle Tabs im aktuellen Fenster" 289 | }, 290 | "bg_reload_window_discarded": { 291 | "message": "Alle entladenen Tabs im aktuellen Fenster" 292 | }, 293 | "bg_reload_tabs_left": { 294 | "message": "Tabs auf der linken Seite" 295 | }, 296 | "bg_reload_tabs_right": { 297 | "message": "Tabs auf der rechten Seite" 298 | }, 299 | "bg_stop": { 300 | "message": "Reload stoppen" 301 | }, 302 | "bg_stop_all": { 303 | "message": "Alle Tabs" 304 | }, 305 | "bg_csp": { 306 | "message": "Content Security Policy - Sicherheitskonzept" 307 | }, 308 | "bg_csp_remove": { 309 | "message": "CSP Schutz für diesen Tab entfernen (Inline-Skriptausführung erlauben)" 310 | }, 311 | "bg_csp_reset": { 312 | "message": "Zurück auf Standard setzen" 313 | }, 314 | "bg_no_reload": { 315 | "message": "Gewählte Tabs" 316 | }, 317 | "bg_reload_every": { 318 | "message": "Gewählte Tabs neu laden" 319 | }, 320 | "bg_reload_every_10s": { 321 | "message": "Alle 10s" 322 | }, 323 | "bg_reload_every_30s": { 324 | "message": "Alle 30s" 325 | }, 326 | "bg_reload_every_1m": { 327 | "message": "Jede Minute" 328 | }, 329 | "bg_reload_every_5m": { 330 | "message": "Alle 5 Minuten" 331 | }, 332 | "bg_reload_every_15m": { 333 | "message": "Alle 15 Minuten" 334 | }, 335 | "bg_reload_every_1h": { 336 | "message": "Jede Stunde" 337 | }, 338 | "bg_restart": { 339 | "message": "Erweiterung neu starten" 340 | }, 341 | "bg_options": { 342 | "message": "Einstellungen" 343 | }, 344 | "bg_msg_1": { 345 | "message": "Tab hat keine gültige Adresse" 346 | }, 347 | "bg_msg_2": { 348 | "message": "Keine Zugriffsberechtigung für diesen Hostnamen" 349 | }, 350 | "bg_msg_3": { 351 | "message": "Warnung: Das Entfernen der Content Security Policy (CSP) kann die Website anfällig für Sicherheitsrisiken machen, wie z. B. Cross-Site-Scripting (XSS)-Angriffe.\n\nSind Sie sicher, dass Sie fortfahren möchten?" 352 | }, 353 | "commands_reload_all": { 354 | "message": "Alle Tabs neu laden" 355 | }, 356 | "commands_reload_all_discarded": { 357 | "message": "Alle verworfenen Tabs neu laden" 358 | }, 359 | "commands_reload_window": { 360 | "message": "Alle Tabs im aktuellen Fenster neu laden" 361 | }, 362 | "commands_reload_window_discarded": { 363 | "message": "Alle verworfenen Tabs im aktuellen Fenster neu laden" 364 | }, 365 | "commands_reload_tabs_left": { 366 | "message": "Linke Tabs neu laden" 367 | }, 368 | "commands_reload_tabs_right": { 369 | "message": "Rechte Tabs neu laden" 370 | }, 371 | "commands_stop": { 372 | "message": "Alle Reload Aufräge stoppen" 373 | } 374 | } 375 | --------------------------------------------------------------------------------