diff --git a/.gitignore b/.gitignore index d8326b0..1d63279 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ node_modules tags npm-debug.log tmp +.parcel-cache diff --git a/brotab/extension/.parcelrc b/brotab/extension/.parcelrc new file mode 100644 index 0000000..65a11f3 --- /dev/null +++ b/brotab/extension/.parcelrc @@ -0,0 +1,3 @@ +{ + "extends": "@parcel/config-webextension" +} diff --git a/brotab/extension/background.js b/brotab/extension/background.js deleted file mode 100644 index 4491a80..0000000 --- a/brotab/extension/background.js +++ /dev/null @@ -1,656 +0,0 @@ -/* -On startup, connect to the "brotab_mediator" app. -*/ - -//const GET_WORDS_SCRIPT = '[...new Set(document.body.innerText.match(/\\w+/g))].sort().join("\\n");'; -const GET_WORDS_SCRIPT = '[...new Set(document.documentElement.innerText.match(#match_regex#))].sort().join(#join_with#);'; -//const GET_TEXT_SCRIPT = 'document.body.innerText.replace(/\\n|\\r|\\t/g, " ");'; -const GET_TEXT_SCRIPT = 'document.documentElement.innerText.replace(#delimiter_regex#, #replace_with#);'; -const GET_HTML_SCRIPT = 'document.documentElement.innerHTML.replace(#delimiter_regex#, #replace_with#);'; - - -class BrowserTabs { - constructor(browser) { - this._browser = browser; - } - - runtime() { - return this._browser.runtime; - } - - list(queryInfo, onSuccess) { - throw new Error('list is not implemented'); - } - - query(queryInfo, onSuccess) { - throw new Error('query is not implemented'); - } - - close(tab_ids, onSuccess) { - throw new Error('close is not implemented'); - } - - move(tabId, moveOptions, onSuccess) { - throw new Error('move is not implemented'); - } - - update(tabId, options, onSuccess, onError) { - throw new Error('update is not implemented'); - } - - create(createOptions, onSuccess) { - throw new Error('create is not implemented'); - } - - activate(tab_id) { - throw new Error('activate is not implemented'); - } - - getActive(onSuccess) { - throw new Error('getActive is not implemented'); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - throw new Error('runScript is not implemented'); - } - - getBrowserName() { - throw new Error('getBrowserName is not implemented'); - } -} - -class FirefoxTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error listing tabs: ${error}`) - ); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error executing queryTabs: ${error}`) - ); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids).then( - onSuccess, - (error) => console.log(`Error removing tab: ${error}`) - ); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions).then( - onSuccess, - // (tab) => console.log(`Moved: ${tab}`), - (error) => console.log(`Error moving tab: ${error}`) - ); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options).then( - onSuccess, - (error) => { - console.log(`Error updating tab ${tabId}: ${error}`) - onError(error) - } - ); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript(tab_id, {code: script}).then( - (result) => onSuccess(result, payload), - (error) => onError(error, payload) - ); - } - - getBrowserName() { - return "firefox"; - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - browser.windows.update(tab.windowId, {focused: focused}); - }); - } -} - -class ChromeTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - chrome.windows.update(tab.windowId, {focused: focused}); - }); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids, onSuccess); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions, onSuccess); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options, tab => { - if (this._browser.runtime.lastError) { - let error = this._browser.runtime.lastError.message; - console.error(`Could not update tab: ${error}, tabId=${tabId}, options=${JSON.stringify(options)}`) - onError(error) - } else { - onSuccess(tab) - } - }); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions, onSuccess); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}, onSuccess); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript( - tab_id, {code: script}, - (result) => { - // https://stackoverflow.com/a/45603880/258421 - let lastError = chrome.runtime.lastError; - if (lastError) { - onError(lastError, payload); - } else { - onSuccess(result, payload); - } - } - ); - } - - getBrowserName() { - return "chrome/chromium"; - } -} - - -console.log("Detecting browser"); -var port = undefined; -var tabs = undefined; -var browserTabs = undefined; -const NATIVE_APP_NAME = 'brotab_mediator'; -reconnect(); - -function reconnect() { - console.log("Connecting to native app"); - if (typeof browser !== 'undefined') { - port = browser.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Firefox: " + port); - browserTabs = new FirefoxTabs(browser); - - } else if (typeof chrome !== 'undefined') { - port = chrome.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Chrome/Chromium: " + port); - browserTabs = new ChromeTabs(chrome); - - } else { - console.log("Unknown browser detected"); - } -} - - -// see https://stackoverflow.com/a/15479354/258421 -// function naturalCompare(a, b) { -// var ax = [], bx = []; - -// a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); -// b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) }); - -// while(ax.length && bx.length) { -// var an = ax.shift(); -// var bn = bx.shift(); -// var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]); -// if(nn) return nn; -// } - -// return ax.length - bx.length; -// } - -function compareWindowIdTabId(tabA, tabB) { - if (tabA.windowId != tabB.windowId) { - return tabA.windowId - tabB.windowId; - } - return tabA.index - tabB.index; -} - -function listTabsOnSuccess(tabs) { - var lines = []; - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - for (let tab of tabs) { - var line = + tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url; - console.log(line); - lines.push(line); - } - // lines = lines.sort(naturalCompare); - port.postMessage(lines); -} - -function listTabs() { - browserTabs.list({}, listTabsOnSuccess); -} - -function queryTabsOnSuccess(tabs) { - tabs.sort(compareWindowIdTabId); - let lines = tabs.map(tab => `${tab.windowId}.${tab.id}\t${tab.title}\t${tab.url}`) - console.log(lines); - port.postMessage(lines); -} - -function queryTabsOnFailure(error) { - console.error(error); - port.postMessage([]); -} - -function queryTabs(query_info) { - try { - let query = atob(query_info) - query = JSON.parse(query) - - integerKeys = {'windowId': null, 'index': null}; - booleanKeys = {'active': null, 'pinned': null, 'audible': null, 'muted': null, 'highlighted': null, - 'discarded': null, 'autoDiscardable': null, 'currentWindow': null, 'lastFocusedWindow': null}; - - query = Object.entries(query).reduce((o, [k,v]) => { - if (booleanKeys.hasOwnProperty(k) && typeof v != 'boolean') { - if (v.toLowerCase() == 'true') - o[k] = true; - else if (v.toLowerCase() == 'false') - o[k] = false; - else - o[k] = v; - } - else if (integerKeys.hasOwnProperty(k) && typeof v != 'number') - o[k] = Number(v); - else - o[k] = v; - return o; - }, {}) - - browserTabs.query(query, queryTabsOnSuccess); - } - catch(error) { - queryTabsOnFailure(error); - } -} - -// function moveTabs(move_triplets) { -// for (let triplet of move_triplets) { -// const [tabId, windowId, index] = triplet; -// browserTabs.move(tabId, {index: index, windowId: windowId}); -// } -// } - -function moveTabs(move_triplets) { - // move_triplets is a tuple of (tab_id, window_id, new_index) - if (move_triplets.length == 0) { - // this post is only required to make bt move command synchronous. mediator - // is waiting for any reply - port.postMessage('OK'); - return - } - - // we request a move of a single tab and when it happens, we call ourselves - // again with the remaining tabs (first omitted) - const [tabId, windowId, index] = move_triplets[0]; - browserTabs.move(tabId, {index: index, windowId: windowId}, - (tab) => moveTabs(move_triplets.slice(1)) - ); -} - -function closeTabs(tab_ids) { - browserTabs.close(tab_ids, () => port.postMessage('OK')); -} - -function openUrls(urls, window_id) { - if (urls.length == 0) { - console.log('Opening urls done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let url of urls) { - console.log(`Opening another one url ${url}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.create({'url': url, windowId: window_id}, - (tab) => resolve(`${tab.windowId}.${tab.id}`) - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result) - console.log(`Sending ids back: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function createTab(url) { - browserTabs.create({'url': url}, - (tab) => { - console.log(`Created new tab: ${tab.id}`); - port.postMessage([`${tab.windowId}.${tab.id}`]); - }); -} - -function updateTabs(updates) { - if (updates.length == 0) { - console.log('Updating tabs done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let update of updates) { - console.log(`Updating tab ${JSON.stringify(update)}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.update(update.tab_id, update.properties, - (tab) => { resolve(`${tab.windowId}.${tab.id}`) }, - (error) => { - console.error(`Could not update tab: ${error}, update=${JSON.stringify(update)}`) - resolve() - } - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result).filter(x => !!x) - console.log(`Sending ids back after update: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function activateTab(tab_id, focused) { - browserTabs.activate(tab_id, focused); -} - -function getActiveTabs() { - browserTabs.getActive(tabs => { - var result = tabs.map(tab => tab.windowId + "." + tab.id).toString() - console.log(`Active tabs: ${result}`); - port.postMessage(result); - }); -} - -function getWordsScript(match_regex, join_with) { - return GET_WORDS_SCRIPT - .replace('#match_regex#', match_regex) - .replace('#join_with#', join_with); -} - -function getTextScript(delimiter_regex, replace_with) { - return GET_TEXT_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function getHtmlScript(delimiter_regex, replace_with) { - return GET_HTML_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function listOr(list, default_value) { - if ((list.length == 1) && (list[0] == null)) { - return default_value; - } - return list; -} - -function getWordsFromTabs(tabs, match_regex, join_with) { - var promises = []; - console.log(`Getting words from tabs: ${tabs}`); - const script = getWordsScript(match_regex, join_with); - - for (let tab of tabs) { - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, null, - (words, _payload) => { - words = listOr(words, []); - console.log(`Got ${words.length} words from another tab`); - resolve(words); - }, - (error, _payload) => { - console.log(`Could not get words from tab: ${error}`); - resolve([]); - } - ) - ); - promises.push(promise); - } - Promise.all(promises).then( - (all_words) => { - const result = Array.prototype.concat(...all_words); - console.log(`Total number of words: ${result.length}`); - port.postMessage(result); - } - ) -} - -function getWords(tab_id, match_regex, join_with) { - if (tab_id == null) { - console.log(`Getting words for active tabs`); - browserTabs.getActive( - (tabs) => getWordsFromTabs(tabs, match_regex, join_with), - ); - } else { - const script = getWordsScript(match_regex, join_with); - console.log(`Getting words, running a script: ${script}`); - browserTabs.runScript(tab_id, script, null, - (words, _payload) => port.postMessage(listOr(words, [])), - (error, _payload) => console.log(`getWords: tab_id=${tab_id}, could not run script (${script})`), - ); - } -} - -function getTextOrHtmlFromTabs(tabs, scriptGetter, delimiter_regex, replace_with, onSuccess) { - var promises = []; - const script = scriptGetter(delimiter_regex, replace_with) - console.log(`Getting text from tabs: ${tabs.length}, script (${script})`); - - lines = []; - for (let tab of tabs) { - // console.log(`Processing tab ${tab.id}`); - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, tab, - (text, current_tab) => { - // let as_text = JSON.stringify(text); - // I don't know why, but an array of one item is sent here, so I take - // the first item. - if (text && text[0]) { - console.log(`Got ${text.length} chars of text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: text[0]}); - } else { - console.log(`Got empty text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - }, - (error, current_tab) => { - console.log(`Could not get text from tab: ${error}: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - ) - ); - promises.push(promise); - } - - Promise.all(promises).then(onSuccess); -} - -function getTextOnRunScriptSuccess(all_results) { - console.log(`Ready`); - console.log(`Text promises are ready: ${all_results.length}`); - // console.log(`All results: ${JSON.stringify(all_results)}`); - lines = []; - for (let result of all_results) { - // console.log(`result: ${result}`); - tab = result['tab']; - text = result['text']; - // console.log(`Result: ${tab.id}, ${text.length}`); - let line = tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url + "\t" + text; - lines.push(line); - } - // lines = lines.sort(naturalCompare); - console.log(`Total number of lines of text: ${lines.length}`); - port.postMessage(lines); -} - -function getTextOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getTextScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getText(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getTextOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getHtmlOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getHtmlScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getHtml(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getHtmlOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getBrowserName() { - const name = browserTabs.getBrowserName(); - console.log("Sending browser name: " + name); - port.postMessage(name); -} - -/* -Listen for messages from the app. -*/ -port.onMessage.addListener((command) => { - console.log("Received: " + JSON.stringify(command, null, 4)); - - if (command['name'] == 'list_tabs') { - console.log('Listing tabs...'); - listTabs(); - } - - else if (command['name'] == 'query_tabs') { - console.log('Querying tabs...'); - queryTabs(command['query_info']); - } - - else if (command['name'] == 'close_tabs') { - console.log('Closing tabs:', command['tab_ids']); - closeTabs(command['tab_ids']); - } - - else if (command['name'] == 'move_tabs') { - console.log('Moving tabs:', command['move_triplets']); - moveTabs(command['move_triplets']); - } - - else if (command['name'] == 'open_urls') { - console.log('Opening URLs:', command['urls'], command['window_id']); - openUrls(command['urls'], command['window_id']); - } - - else if (command['name'] == 'new_tab') { - console.log('Creating tab:', command['url']); - createTab(command['url']); - } - - else if (command['name'] == 'update_tabs') { - console.log('Updating tabs:', command['updates']); - updateTabs(command['updates']); - } - - else if (command['name'] == 'activate_tab') { - console.log('Activating tab:', command['tab_id']); - activateTab(command['tab_id'], !!command['focused']); - } - - else if (command['name'] == 'get_active_tabs') { - console.log('Getting active tabs'); - getActiveTabs(); - } - - else if (command['name'] == 'get_words') { - console.log('Getting words from tab:', command['tab_id']); - getWords(command['tab_id'], command['match_regex'], command['join_with']); - } - - else if (command['name'] == 'get_text') { - console.log('Getting texts from all tabs'); - getText(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_html') { - console.log('Getting HTML from all tabs'); - getHtml(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_browser') { - console.log('Getting browser name'); - getBrowserName(); - } -}); - -port.onDisconnect.addListener(function() { - console.log("Disconnected"); - if(chrome.runtime.lastError) { - console.warn("Reason: " + chrome.runtime.lastError.message); - } else { - console.warn("lastError is undefined"); - } - //sleep(5000); - console.log("Trying to reconnect"); - reconnect(); -}); - -console.log("Connected to native app " + NATIVE_APP_NAME); - -/* -On a click on the browser action, send the app a message. -*/ -// browser.browserAction.onClicked.addListener(() => { -// // console.log("Sending: ping"); -// // port.postMessage("ping"); -// -// console.log('Listing tabs'); -// listTabs(); -// }); diff --git a/brotab/extension/background.ts b/brotab/extension/background.ts new file mode 100644 index 0000000..168af2a --- /dev/null +++ b/brotab/extension/background.ts @@ -0,0 +1,574 @@ +import browser from "webextension-polyfill"; +/* +On startup, connect to the "brotab_mediator" app. +*/ + +//const GET_WORDS_SCRIPT = '[...new Set(document.body.innerText.match(/\\w+/g))].sort().join("\\n");'; +const GET_WORDS_SCRIPT = + "[...new Set(document.documentElement.innerText.match(#match_regex#))].sort().join(#join_with#);"; +//const GET_TEXT_SCRIPT = 'document.body.innerText.replace(/\\n|\\r|\\t/g, " ");'; +const GET_TEXT_SCRIPT = + "document.documentElement.innerText.replace(#delimiter_regex#, #replace_with#);"; +const GET_HTML_SCRIPT = + "document.documentElement.innerHTML.replace(#delimiter_regex#, #replace_with#);"; + +class BrowserTabs { + name: string; + + constructor(name: string) { + this.name = name; + } + + list(queryInfo, onSuccess) { + browser.tabs + .query(queryInfo) + .then(onSuccess, (error) => console.log(`Error listing tabs: ${error}`)); + } + + query(queryInfo, onSuccess) { + browser.tabs + .query(queryInfo) + .then(onSuccess, (error) => + console.log(`Error executing queryTabs: ${error}`) + ); + } + + close(tab_ids, onSuccess) { + browser.tabs + .remove(tab_ids) + .then(onSuccess, (error) => console.log(`Error removing tab: ${error}`)); + } + + move(tabId, moveOptions, onSuccess) { + browser.tabs.move(tabId, moveOptions).then( + onSuccess, + // (tab) => console.log(`Moved: ${tab}`), + (error) => console.log(`Error moving tab: ${error}`) + ); + } + + update(tabId, options, onSuccess, onError) { + browser.tabs.update(tabId, options).then(onSuccess, (error) => { + console.log(`Error updating tab ${tabId}: ${error}`); + onError(error); + }); + } + + create(createOptions, onSuccess) { + browser.tabs + .create(createOptions) + .then(onSuccess, (error) => console.log(`Error: ${error}`)); + } + + getActive(onSuccess) { + browser.tabs + .query({ active: true }) + .then(onSuccess, (error) => console.log(`Error: ${error}`)); + } + + runScript(tab_id, script, payload, onSuccess, onError) { + browser.tabs.executeScript(tab_id, { code: script }).then( + (result) => onSuccess(result, payload), + (error) => onError(error, payload) + ); + } + + getBrowserName() { + return this.name; + } + + activate(tab_id, focused) { + browser.tabs.update(tab_id, { active: true }); + browser.tabs.get(tab_id).then((tab) => { + if (focused) { + browser.windows.update(tab.windowId, { focused: true }); + } + }); + } +} + +var browserTabs = undefined; + +console.log("Detecting browser"); +if (typeof chrome !== "undefined") { + console.log("It's Chrome/Chromium"); + browserTabs = new BrowserTabs("chrome/chromium"); +} else { + browserTabs = new BrowserTabs("firefox"); +} + +var port: browser.Runtime.Port = undefined; +const NATIVE_APP_NAME = "brotab_mediator"; +reconnect(); + +function reconnect() { + console.log("Connecting to native app"); + port = browser.runtime.connectNative(NATIVE_APP_NAME); +} + +// see https://stackoverflow.com/a/15479354/258421 +// function naturalCompare(a, b) { +// var ax = [], bx = []; + +// a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); +// b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) }); + +// while(ax.length && bx.length) { +// var an = ax.shift(); +// var bn = bx.shift(); +// var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]); +// if(nn) return nn; +// } + +// return ax.length - bx.length; +// } + +function compareWindowIdTabId(tabA, tabB) { + if (tabA.windowId != tabB.windowId) { + return tabA.windowId - tabB.windowId; + } + return tabA.index - tabB.index; +} + +function listTabsOnSuccess(tabs) { + var lines = []; + // Make sure tabs are sorted by their index within a window + tabs.sort(compareWindowIdTabId); + for (let tab of tabs) { + var line = +tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url; + console.log(line); + lines.push(line); + } + // lines = lines.sort(naturalCompare); + port.postMessage(lines); +} + +function listTabs() { + browserTabs.list({}, listTabsOnSuccess); +} + +function queryTabsOnSuccess(tabs) { + tabs.sort(compareWindowIdTabId); + let lines = tabs.map( + (tab) => `${tab.windowId}.${tab.id}\t${tab.title}\t${tab.url}` + ); + console.log(lines); + port.postMessage(lines); +} + +function queryTabsOnFailure(error) { + console.error(error); + port.postMessage([]); +} + +function queryTabs(query_info) { + try { + let query = atob(query_info); + query = JSON.parse(query); + + integerKeys = { windowId: null, index: null }; + booleanKeys = { + active: null, + pinned: null, + audible: null, + muted: null, + highlighted: null, + discarded: null, + autoDiscardable: null, + currentWindow: null, + lastFocusedWindow: null, + }; + + query = Object.entries(query).reduce((o, [k, v]) => { + if (booleanKeys.hasOwnProperty(k) && typeof v != "boolean") { + if (v.toLowerCase() == "true") o[k] = true; + else if (v.toLowerCase() == "false") o[k] = false; + else o[k] = v; + } else if (integerKeys.hasOwnProperty(k) && typeof v != "number") + o[k] = Number(v); + else o[k] = v; + return o; + }, {}); + + browserTabs.query(query, queryTabsOnSuccess); + } catch (error) { + queryTabsOnFailure(error); + } +} + +// function moveTabs(move_triplets) { +// for (let triplet of move_triplets) { +// const [tabId, windowId, index] = triplet; +// browserTabs.move(tabId, {index: index, windowId: windowId}); +// } +// } + +function moveTabs(move_triplets) { + // move_triplets is a tuple of (tab_id, window_id, new_index) + if (move_triplets.length == 0) { + // this post is only required to make bt move command synchronous. mediator + // is waiting for any reply + port.postMessage("OK"); + return; + } + + // we request a move of a single tab and when it happens, we call ourselves + // again with the remaining tabs (first omitted) + const [tabId, windowId, index] = move_triplets[0]; + browserTabs.move(tabId, { index: index, windowId: windowId }, (tab) => + moveTabs(move_triplets.slice(1)) + ); +} + +function closeTabs(tab_ids) { + browserTabs.close(tab_ids, () => port.postMessage("OK")); +} + +function openUrls(urls, window_id) { + if (urls.length == 0) { + console.log("Opening urls done"); + port.postMessage([]); + return; + } + + var promises = []; + for (let url of urls) { + console.log(`Opening another one url ${url}`); + promises.push( + new Promise((resolve, reject) => { + browserTabs.create({ url: url, windowId: window_id }, (tab) => + resolve(`${tab.windowId}.${tab.id}`) + ); + }) + ); + } + Promise.all(promises).then((result) => { + const data = Array.prototype.concat(...result); + console.log(`Sending ids back: ${JSON.stringify(data)}`); + port.postMessage(data); + }); +} + +function createTab(url) { + browserTabs.create({ url: url }, (tab) => { + console.log(`Created new tab: ${tab.id}`); + port.postMessage([`${tab.windowId}.${tab.id}`]); + }); +} + +function updateTabs(updates) { + if (updates.length == 0) { + console.log("Updating tabs done"); + port.postMessage([]); + return; + } + + var promises = []; + for (let update of updates) { + console.log(`Updating tab ${JSON.stringify(update)}`); + promises.push( + new Promise((resolve, reject) => { + browserTabs.update( + update.tab_id, + update.properties, + (tab) => { + resolve(`${tab.windowId}.${tab.id}`); + }, + (error) => { + console.error( + `Could not update tab: ${error}, update=${JSON.stringify(update)}` + ); + resolve(); + } + ); + }) + ); + } + Promise.all(promises).then((result) => { + const data = Array.prototype.concat(...result).filter((x) => !!x); + console.log(`Sending ids back after update: ${JSON.stringify(data)}`); + port.postMessage(data); + }); +} + +function activateTab(tab_id, focused) { + browserTabs.activate(tab_id, focused); +} + +function getActiveTabs() { + browserTabs.getActive((tabs) => { + var result = tabs.map((tab) => tab.windowId + "." + tab.id).toString(); + console.log(`Active tabs: ${result}`); + port.postMessage(result); + }); +} + +function getWordsScript(match_regex, join_with) { + return GET_WORDS_SCRIPT.replace("#match_regex#", match_regex).replace( + "#join_with#", + join_with + ); +} + +function getTextScript(delimiter_regex, replace_with) { + return GET_TEXT_SCRIPT.replace("#delimiter_regex#", delimiter_regex).replace( + "#replace_with#", + replace_with + ); +} + +function getHtmlScript(delimiter_regex, replace_with) { + return GET_HTML_SCRIPT.replace("#delimiter_regex#", delimiter_regex).replace( + "#replace_with#", + replace_with + ); +} + +function listOr(list, default_value) { + if (list.length == 1 && list[0] == null) { + return default_value; + } + return list; +} + +function getWordsFromTabs(tabs, match_regex, join_with) { + var promises = []; + console.log(`Getting words from tabs: ${tabs}`); + const script = getWordsScript(match_regex, join_with); + + for (let tab of tabs) { + var promise = new Promise((resolve, reject) => + browserTabs.runScript( + tab.id, + script, + null, + (words, _payload) => { + words = listOr(words, []); + console.log(`Got ${words.length} words from another tab`); + resolve(words); + }, + (error, _payload) => { + console.log(`Could not get words from tab: ${error}`); + resolve([]); + } + ) + ); + promises.push(promise); + } + Promise.all(promises).then((all_words) => { + const result = Array.prototype.concat(...all_words); + console.log(`Total number of words: ${result.length}`); + port.postMessage(result); + }); +} + +function getWords(tab_id, match_regex, join_with) { + if (tab_id == null) { + console.log(`Getting words for active tabs`); + browserTabs.getActive((tabs) => + getWordsFromTabs(tabs, match_regex, join_with) + ); + } else { + const script = getWordsScript(match_regex, join_with); + console.log(`Getting words, running a script: ${script}`); + browserTabs.runScript( + tab_id, + script, + null, + (words, _payload) => port.postMessage(listOr(words, [])), + (error, _payload) => + console.log( + `getWords: tab_id=${tab_id}, could not run script (${script})` + ) + ); + } +} + +function getTextOrHtmlFromTabs( + tabs, + scriptGetter, + delimiter_regex, + replace_with, + onSuccess +) { + var promises = []; + const script = scriptGetter(delimiter_regex, replace_with); + console.log(`Getting text from tabs: ${tabs.length}, script (${script})`); + + lines = []; + for (let tab of tabs) { + // console.log(`Processing tab ${tab.id}`); + var promise = new Promise((resolve, reject) => + browserTabs.runScript( + tab.id, + script, + tab, + (text, current_tab) => { + // let as_text = JSON.stringify(text); + // I don't know why, but an array of one item is sent here, so I take + // the first item. + if (text && text[0]) { + console.log( + `Got ${text.length} chars of text from another tab: ${current_tab.id}` + ); + resolve({ tab: current_tab, text: text[0] }); + } else { + console.log(`Got empty text from another tab: ${current_tab.id}`); + resolve({ tab: current_tab, text: "" }); + } + }, + (error, current_tab) => { + console.log( + `Could not get text from tab: ${error}: ${current_tab.id}` + ); + resolve({ tab: current_tab, text: "" }); + } + ) + ); + promises.push(promise); + } + + Promise.all(promises).then(onSuccess); +} + +function getTextOnRunScriptSuccess(all_results) { + console.log(`Ready`); + console.log(`Text promises are ready: ${all_results.length}`); + // console.log(`All results: ${JSON.stringify(all_results)}`); + lines = []; + for (let result of all_results) { + // console.log(`result: ${result}`); + tab = result["tab"]; + text = result["text"]; + // console.log(`Result: ${tab.id}, ${text.length}`); + let line = + tab.windowId + + "." + + tab.id + + "\t" + + tab.title + + "\t" + + tab.url + + "\t" + + text; + lines.push(line); + } + // lines = lines.sort(naturalCompare); + console.log(`Total number of lines of text: ${lines.length}`); + port.postMessage(lines); +} + +function getTextOnListSuccess(tabs, delimiter_regex, replace_with) { + // Make sure tabs are sorted by their index within a window + tabs.sort(compareWindowIdTabId); + getTextOrHtmlFromTabs( + tabs, + getTextScript, + delimiter_regex, + replace_with, + getTextOnRunScriptSuccess + ); +} + +function getText(delimiter_regex, replace_with) { + browserTabs.list({ discarded: false }, (tabs) => + getTextOnListSuccess(tabs, delimiter_regex, replace_with) + ); +} + +function getHtmlOnListSuccess(tabs, delimiter_regex, replace_with) { + // Make sure tabs are sorted by their index within a window + tabs.sort(compareWindowIdTabId); + getTextOrHtmlFromTabs( + tabs, + getHtmlScript, + delimiter_regex, + replace_with, + getTextOnRunScriptSuccess + ); +} + +function getHtml(delimiter_regex, replace_with) { + browserTabs.list({ discarded: false }, (tabs) => + getHtmlOnListSuccess(tabs, delimiter_regex, replace_with) + ); +} + +function getBrowserName() { + const name = browserTabs.getBrowserName(); + console.log("Sending browser name: " + name); + port.postMessage(name); +} + +/* +Listen for messages from the app. +*/ +port.onMessage.addListener((command) => { + console.log("Received: " + JSON.stringify(command, null, 4)); + + if (command["name"] == "list_tabs") { + console.log("Listing tabs..."); + listTabs(); + } else if (command["name"] == "query_tabs") { + console.log("Querying tabs..."); + queryTabs(command["query_info"]); + } else if (command["name"] == "close_tabs") { + console.log("Closing tabs:", command["tab_ids"]); + closeTabs(command["tab_ids"]); + } else if (command["name"] == "move_tabs") { + console.log("Moving tabs:", command["move_triplets"]); + moveTabs(command["move_triplets"]); + } else if (command["name"] == "open_urls") { + console.log("Opening URLs:", command["urls"], command["window_id"]); + openUrls(command["urls"], command["window_id"]); + } else if (command["name"] == "new_tab") { + console.log("Creating tab:", command["url"]); + createTab(command["url"]); + } else if (command["name"] == "update_tabs") { + console.log("Updating tabs:", command["updates"]); + updateTabs(command["updates"]); + } else if (command["name"] == "activate_tab") { + console.log("Activating tab:", command["tab_id"]); + activateTab(command["tab_id"], !!command["focused"]); + } else if (command["name"] == "get_active_tabs") { + console.log("Getting active tabs"); + getActiveTabs(); + } else if (command["name"] == "get_words") { + console.log("Getting words from tab:", command["tab_id"]); + getWords(command["tab_id"], command["match_regex"], command["join_with"]); + } else if (command["name"] == "get_text") { + console.log("Getting texts from all tabs"); + getText(command["delimiter_regex"], command["replace_with"]); + } else if (command["name"] == "get_html") { + console.log("Getting HTML from all tabs"); + getHtml(command["delimiter_regex"], command["replace_with"]); + } else if (command["name"] == "get_browser") { + console.log("Getting browser name"); + getBrowserName(); + } +}); + +port.onDisconnect.addListener(function () { + console.log("Disconnected"); + if (browser.runtime.lastError) { + console.warn("Reason: " + browser.runtime.lastError.message); + } else { + console.warn("lastError is undefined"); + } + //sleep(5000); + console.log("Trying to reconnect"); + reconnect(); +}); + +console.log("Connected to native app " + NATIVE_APP_NAME); + +/* +On a click on the browser action, send the app a message. +*/ +// browser.browserAction.onClicked.addListener(() => { +// // console.log("Sending: ping"); +// // port.postMessage("ping"); +// +// console.log('Listing tabs'); +// listTabs(); +// }); diff --git a/brotab/extension/chrome/brotab-icon-128x128.png b/brotab/extension/brotab-icon-128x128.png similarity index 100% rename from brotab/extension/chrome/brotab-icon-128x128.png rename to brotab/extension/brotab-icon-128x128.png diff --git a/brotab/extension/chrome/background.js b/brotab/extension/chrome/background.js deleted file mode 100644 index 4491a80..0000000 --- a/brotab/extension/chrome/background.js +++ /dev/null @@ -1,656 +0,0 @@ -/* -On startup, connect to the "brotab_mediator" app. -*/ - -//const GET_WORDS_SCRIPT = '[...new Set(document.body.innerText.match(/\\w+/g))].sort().join("\\n");'; -const GET_WORDS_SCRIPT = '[...new Set(document.documentElement.innerText.match(#match_regex#))].sort().join(#join_with#);'; -//const GET_TEXT_SCRIPT = 'document.body.innerText.replace(/\\n|\\r|\\t/g, " ");'; -const GET_TEXT_SCRIPT = 'document.documentElement.innerText.replace(#delimiter_regex#, #replace_with#);'; -const GET_HTML_SCRIPT = 'document.documentElement.innerHTML.replace(#delimiter_regex#, #replace_with#);'; - - -class BrowserTabs { - constructor(browser) { - this._browser = browser; - } - - runtime() { - return this._browser.runtime; - } - - list(queryInfo, onSuccess) { - throw new Error('list is not implemented'); - } - - query(queryInfo, onSuccess) { - throw new Error('query is not implemented'); - } - - close(tab_ids, onSuccess) { - throw new Error('close is not implemented'); - } - - move(tabId, moveOptions, onSuccess) { - throw new Error('move is not implemented'); - } - - update(tabId, options, onSuccess, onError) { - throw new Error('update is not implemented'); - } - - create(createOptions, onSuccess) { - throw new Error('create is not implemented'); - } - - activate(tab_id) { - throw new Error('activate is not implemented'); - } - - getActive(onSuccess) { - throw new Error('getActive is not implemented'); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - throw new Error('runScript is not implemented'); - } - - getBrowserName() { - throw new Error('getBrowserName is not implemented'); - } -} - -class FirefoxTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error listing tabs: ${error}`) - ); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error executing queryTabs: ${error}`) - ); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids).then( - onSuccess, - (error) => console.log(`Error removing tab: ${error}`) - ); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions).then( - onSuccess, - // (tab) => console.log(`Moved: ${tab}`), - (error) => console.log(`Error moving tab: ${error}`) - ); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options).then( - onSuccess, - (error) => { - console.log(`Error updating tab ${tabId}: ${error}`) - onError(error) - } - ); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript(tab_id, {code: script}).then( - (result) => onSuccess(result, payload), - (error) => onError(error, payload) - ); - } - - getBrowserName() { - return "firefox"; - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - browser.windows.update(tab.windowId, {focused: focused}); - }); - } -} - -class ChromeTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - chrome.windows.update(tab.windowId, {focused: focused}); - }); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids, onSuccess); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions, onSuccess); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options, tab => { - if (this._browser.runtime.lastError) { - let error = this._browser.runtime.lastError.message; - console.error(`Could not update tab: ${error}, tabId=${tabId}, options=${JSON.stringify(options)}`) - onError(error) - } else { - onSuccess(tab) - } - }); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions, onSuccess); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}, onSuccess); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript( - tab_id, {code: script}, - (result) => { - // https://stackoverflow.com/a/45603880/258421 - let lastError = chrome.runtime.lastError; - if (lastError) { - onError(lastError, payload); - } else { - onSuccess(result, payload); - } - } - ); - } - - getBrowserName() { - return "chrome/chromium"; - } -} - - -console.log("Detecting browser"); -var port = undefined; -var tabs = undefined; -var browserTabs = undefined; -const NATIVE_APP_NAME = 'brotab_mediator'; -reconnect(); - -function reconnect() { - console.log("Connecting to native app"); - if (typeof browser !== 'undefined') { - port = browser.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Firefox: " + port); - browserTabs = new FirefoxTabs(browser); - - } else if (typeof chrome !== 'undefined') { - port = chrome.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Chrome/Chromium: " + port); - browserTabs = new ChromeTabs(chrome); - - } else { - console.log("Unknown browser detected"); - } -} - - -// see https://stackoverflow.com/a/15479354/258421 -// function naturalCompare(a, b) { -// var ax = [], bx = []; - -// a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); -// b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) }); - -// while(ax.length && bx.length) { -// var an = ax.shift(); -// var bn = bx.shift(); -// var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]); -// if(nn) return nn; -// } - -// return ax.length - bx.length; -// } - -function compareWindowIdTabId(tabA, tabB) { - if (tabA.windowId != tabB.windowId) { - return tabA.windowId - tabB.windowId; - } - return tabA.index - tabB.index; -} - -function listTabsOnSuccess(tabs) { - var lines = []; - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - for (let tab of tabs) { - var line = + tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url; - console.log(line); - lines.push(line); - } - // lines = lines.sort(naturalCompare); - port.postMessage(lines); -} - -function listTabs() { - browserTabs.list({}, listTabsOnSuccess); -} - -function queryTabsOnSuccess(tabs) { - tabs.sort(compareWindowIdTabId); - let lines = tabs.map(tab => `${tab.windowId}.${tab.id}\t${tab.title}\t${tab.url}`) - console.log(lines); - port.postMessage(lines); -} - -function queryTabsOnFailure(error) { - console.error(error); - port.postMessage([]); -} - -function queryTabs(query_info) { - try { - let query = atob(query_info) - query = JSON.parse(query) - - integerKeys = {'windowId': null, 'index': null}; - booleanKeys = {'active': null, 'pinned': null, 'audible': null, 'muted': null, 'highlighted': null, - 'discarded': null, 'autoDiscardable': null, 'currentWindow': null, 'lastFocusedWindow': null}; - - query = Object.entries(query).reduce((o, [k,v]) => { - if (booleanKeys.hasOwnProperty(k) && typeof v != 'boolean') { - if (v.toLowerCase() == 'true') - o[k] = true; - else if (v.toLowerCase() == 'false') - o[k] = false; - else - o[k] = v; - } - else if (integerKeys.hasOwnProperty(k) && typeof v != 'number') - o[k] = Number(v); - else - o[k] = v; - return o; - }, {}) - - browserTabs.query(query, queryTabsOnSuccess); - } - catch(error) { - queryTabsOnFailure(error); - } -} - -// function moveTabs(move_triplets) { -// for (let triplet of move_triplets) { -// const [tabId, windowId, index] = triplet; -// browserTabs.move(tabId, {index: index, windowId: windowId}); -// } -// } - -function moveTabs(move_triplets) { - // move_triplets is a tuple of (tab_id, window_id, new_index) - if (move_triplets.length == 0) { - // this post is only required to make bt move command synchronous. mediator - // is waiting for any reply - port.postMessage('OK'); - return - } - - // we request a move of a single tab and when it happens, we call ourselves - // again with the remaining tabs (first omitted) - const [tabId, windowId, index] = move_triplets[0]; - browserTabs.move(tabId, {index: index, windowId: windowId}, - (tab) => moveTabs(move_triplets.slice(1)) - ); -} - -function closeTabs(tab_ids) { - browserTabs.close(tab_ids, () => port.postMessage('OK')); -} - -function openUrls(urls, window_id) { - if (urls.length == 0) { - console.log('Opening urls done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let url of urls) { - console.log(`Opening another one url ${url}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.create({'url': url, windowId: window_id}, - (tab) => resolve(`${tab.windowId}.${tab.id}`) - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result) - console.log(`Sending ids back: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function createTab(url) { - browserTabs.create({'url': url}, - (tab) => { - console.log(`Created new tab: ${tab.id}`); - port.postMessage([`${tab.windowId}.${tab.id}`]); - }); -} - -function updateTabs(updates) { - if (updates.length == 0) { - console.log('Updating tabs done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let update of updates) { - console.log(`Updating tab ${JSON.stringify(update)}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.update(update.tab_id, update.properties, - (tab) => { resolve(`${tab.windowId}.${tab.id}`) }, - (error) => { - console.error(`Could not update tab: ${error}, update=${JSON.stringify(update)}`) - resolve() - } - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result).filter(x => !!x) - console.log(`Sending ids back after update: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function activateTab(tab_id, focused) { - browserTabs.activate(tab_id, focused); -} - -function getActiveTabs() { - browserTabs.getActive(tabs => { - var result = tabs.map(tab => tab.windowId + "." + tab.id).toString() - console.log(`Active tabs: ${result}`); - port.postMessage(result); - }); -} - -function getWordsScript(match_regex, join_with) { - return GET_WORDS_SCRIPT - .replace('#match_regex#', match_regex) - .replace('#join_with#', join_with); -} - -function getTextScript(delimiter_regex, replace_with) { - return GET_TEXT_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function getHtmlScript(delimiter_regex, replace_with) { - return GET_HTML_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function listOr(list, default_value) { - if ((list.length == 1) && (list[0] == null)) { - return default_value; - } - return list; -} - -function getWordsFromTabs(tabs, match_regex, join_with) { - var promises = []; - console.log(`Getting words from tabs: ${tabs}`); - const script = getWordsScript(match_regex, join_with); - - for (let tab of tabs) { - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, null, - (words, _payload) => { - words = listOr(words, []); - console.log(`Got ${words.length} words from another tab`); - resolve(words); - }, - (error, _payload) => { - console.log(`Could not get words from tab: ${error}`); - resolve([]); - } - ) - ); - promises.push(promise); - } - Promise.all(promises).then( - (all_words) => { - const result = Array.prototype.concat(...all_words); - console.log(`Total number of words: ${result.length}`); - port.postMessage(result); - } - ) -} - -function getWords(tab_id, match_regex, join_with) { - if (tab_id == null) { - console.log(`Getting words for active tabs`); - browserTabs.getActive( - (tabs) => getWordsFromTabs(tabs, match_regex, join_with), - ); - } else { - const script = getWordsScript(match_regex, join_with); - console.log(`Getting words, running a script: ${script}`); - browserTabs.runScript(tab_id, script, null, - (words, _payload) => port.postMessage(listOr(words, [])), - (error, _payload) => console.log(`getWords: tab_id=${tab_id}, could not run script (${script})`), - ); - } -} - -function getTextOrHtmlFromTabs(tabs, scriptGetter, delimiter_regex, replace_with, onSuccess) { - var promises = []; - const script = scriptGetter(delimiter_regex, replace_with) - console.log(`Getting text from tabs: ${tabs.length}, script (${script})`); - - lines = []; - for (let tab of tabs) { - // console.log(`Processing tab ${tab.id}`); - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, tab, - (text, current_tab) => { - // let as_text = JSON.stringify(text); - // I don't know why, but an array of one item is sent here, so I take - // the first item. - if (text && text[0]) { - console.log(`Got ${text.length} chars of text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: text[0]}); - } else { - console.log(`Got empty text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - }, - (error, current_tab) => { - console.log(`Could not get text from tab: ${error}: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - ) - ); - promises.push(promise); - } - - Promise.all(promises).then(onSuccess); -} - -function getTextOnRunScriptSuccess(all_results) { - console.log(`Ready`); - console.log(`Text promises are ready: ${all_results.length}`); - // console.log(`All results: ${JSON.stringify(all_results)}`); - lines = []; - for (let result of all_results) { - // console.log(`result: ${result}`); - tab = result['tab']; - text = result['text']; - // console.log(`Result: ${tab.id}, ${text.length}`); - let line = tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url + "\t" + text; - lines.push(line); - } - // lines = lines.sort(naturalCompare); - console.log(`Total number of lines of text: ${lines.length}`); - port.postMessage(lines); -} - -function getTextOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getTextScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getText(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getTextOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getHtmlOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getHtmlScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getHtml(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getHtmlOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getBrowserName() { - const name = browserTabs.getBrowserName(); - console.log("Sending browser name: " + name); - port.postMessage(name); -} - -/* -Listen for messages from the app. -*/ -port.onMessage.addListener((command) => { - console.log("Received: " + JSON.stringify(command, null, 4)); - - if (command['name'] == 'list_tabs') { - console.log('Listing tabs...'); - listTabs(); - } - - else if (command['name'] == 'query_tabs') { - console.log('Querying tabs...'); - queryTabs(command['query_info']); - } - - else if (command['name'] == 'close_tabs') { - console.log('Closing tabs:', command['tab_ids']); - closeTabs(command['tab_ids']); - } - - else if (command['name'] == 'move_tabs') { - console.log('Moving tabs:', command['move_triplets']); - moveTabs(command['move_triplets']); - } - - else if (command['name'] == 'open_urls') { - console.log('Opening URLs:', command['urls'], command['window_id']); - openUrls(command['urls'], command['window_id']); - } - - else if (command['name'] == 'new_tab') { - console.log('Creating tab:', command['url']); - createTab(command['url']); - } - - else if (command['name'] == 'update_tabs') { - console.log('Updating tabs:', command['updates']); - updateTabs(command['updates']); - } - - else if (command['name'] == 'activate_tab') { - console.log('Activating tab:', command['tab_id']); - activateTab(command['tab_id'], !!command['focused']); - } - - else if (command['name'] == 'get_active_tabs') { - console.log('Getting active tabs'); - getActiveTabs(); - } - - else if (command['name'] == 'get_words') { - console.log('Getting words from tab:', command['tab_id']); - getWords(command['tab_id'], command['match_regex'], command['join_with']); - } - - else if (command['name'] == 'get_text') { - console.log('Getting texts from all tabs'); - getText(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_html') { - console.log('Getting HTML from all tabs'); - getHtml(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_browser') { - console.log('Getting browser name'); - getBrowserName(); - } -}); - -port.onDisconnect.addListener(function() { - console.log("Disconnected"); - if(chrome.runtime.lastError) { - console.warn("Reason: " + chrome.runtime.lastError.message); - } else { - console.warn("lastError is undefined"); - } - //sleep(5000); - console.log("Trying to reconnect"); - reconnect(); -}); - -console.log("Connected to native app " + NATIVE_APP_NAME); - -/* -On a click on the browser action, send the app a message. -*/ -// browser.browserAction.onClicked.addListener(() => { -// // console.log("Sending: ping"); -// // port.postMessage("ping"); -// -// console.log('Listing tabs'); -// listTabs(); -// }); diff --git a/brotab/extension/chrome/manifest.json b/brotab/extension/chrome/manifest.json index 84c2cde..0acece2 100644 --- a/brotab/extension/chrome/manifest.json +++ b/brotab/extension/chrome/manifest.json @@ -1,15 +1,14 @@ { - // Extension ID: knldjmfmopnpolahpmmgbagdohdnhkik "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB", "description": "Control your browser's tabs from command line", "manifest_version": 2, "name": "BroTab", "version": "1.4.0", "background": { - "scripts": ["background.js"] + "scripts": ["../background.ts"] }, "icons": { - "128": "brotab-icon-128x128.png" + "128": "../brotab-icon-128x128.png" }, "permissions": ["nativeMessaging", "tabs", "activeTab", ""], "short_name": "BroTab" diff --git a/brotab/extension/firefox/background.js b/brotab/extension/firefox/background.js deleted file mode 100644 index 4491a80..0000000 --- a/brotab/extension/firefox/background.js +++ /dev/null @@ -1,656 +0,0 @@ -/* -On startup, connect to the "brotab_mediator" app. -*/ - -//const GET_WORDS_SCRIPT = '[...new Set(document.body.innerText.match(/\\w+/g))].sort().join("\\n");'; -const GET_WORDS_SCRIPT = '[...new Set(document.documentElement.innerText.match(#match_regex#))].sort().join(#join_with#);'; -//const GET_TEXT_SCRIPT = 'document.body.innerText.replace(/\\n|\\r|\\t/g, " ");'; -const GET_TEXT_SCRIPT = 'document.documentElement.innerText.replace(#delimiter_regex#, #replace_with#);'; -const GET_HTML_SCRIPT = 'document.documentElement.innerHTML.replace(#delimiter_regex#, #replace_with#);'; - - -class BrowserTabs { - constructor(browser) { - this._browser = browser; - } - - runtime() { - return this._browser.runtime; - } - - list(queryInfo, onSuccess) { - throw new Error('list is not implemented'); - } - - query(queryInfo, onSuccess) { - throw new Error('query is not implemented'); - } - - close(tab_ids, onSuccess) { - throw new Error('close is not implemented'); - } - - move(tabId, moveOptions, onSuccess) { - throw new Error('move is not implemented'); - } - - update(tabId, options, onSuccess, onError) { - throw new Error('update is not implemented'); - } - - create(createOptions, onSuccess) { - throw new Error('create is not implemented'); - } - - activate(tab_id) { - throw new Error('activate is not implemented'); - } - - getActive(onSuccess) { - throw new Error('getActive is not implemented'); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - throw new Error('runScript is not implemented'); - } - - getBrowserName() { - throw new Error('getBrowserName is not implemented'); - } -} - -class FirefoxTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error listing tabs: ${error}`) - ); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo).then( - onSuccess, - (error) => console.log(`Error executing queryTabs: ${error}`) - ); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids).then( - onSuccess, - (error) => console.log(`Error removing tab: ${error}`) - ); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions).then( - onSuccess, - // (tab) => console.log(`Moved: ${tab}`), - (error) => console.log(`Error moving tab: ${error}`) - ); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options).then( - onSuccess, - (error) => { - console.log(`Error updating tab ${tabId}: ${error}`) - onError(error) - } - ); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}).then( - onSuccess, - (error) => console.log(`Error: ${error}`) - ); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript(tab_id, {code: script}).then( - (result) => onSuccess(result, payload), - (error) => onError(error, payload) - ); - } - - getBrowserName() { - return "firefox"; - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - browser.windows.update(tab.windowId, {focused: focused}); - }); - } -} - -class ChromeTabs extends BrowserTabs { - list(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - activate(tab_id, focused) { - this._browser.tabs.update(tab_id, {'active': true}); - this._browser.tabs.get(tab_id, function(tab) { - chrome.windows.update(tab.windowId, {focused: focused}); - }); - } - - query(queryInfo, onSuccess) { - this._browser.tabs.query(queryInfo, onSuccess); - } - - close(tab_ids, onSuccess) { - this._browser.tabs.remove(tab_ids, onSuccess); - } - - move(tabId, moveOptions, onSuccess) { - this._browser.tabs.move(tabId, moveOptions, onSuccess); - } - - update(tabId, options, onSuccess, onError) { - this._browser.tabs.update(tabId, options, tab => { - if (this._browser.runtime.lastError) { - let error = this._browser.runtime.lastError.message; - console.error(`Could not update tab: ${error}, tabId=${tabId}, options=${JSON.stringify(options)}`) - onError(error) - } else { - onSuccess(tab) - } - }); - } - - create(createOptions, onSuccess) { - this._browser.tabs.create(createOptions, onSuccess); - } - - getActive(onSuccess) { - this._browser.tabs.query({active: true}, onSuccess); - } - - runScript(tab_id, script, payload, onSuccess, onError) { - this._browser.tabs.executeScript( - tab_id, {code: script}, - (result) => { - // https://stackoverflow.com/a/45603880/258421 - let lastError = chrome.runtime.lastError; - if (lastError) { - onError(lastError, payload); - } else { - onSuccess(result, payload); - } - } - ); - } - - getBrowserName() { - return "chrome/chromium"; - } -} - - -console.log("Detecting browser"); -var port = undefined; -var tabs = undefined; -var browserTabs = undefined; -const NATIVE_APP_NAME = 'brotab_mediator'; -reconnect(); - -function reconnect() { - console.log("Connecting to native app"); - if (typeof browser !== 'undefined') { - port = browser.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Firefox: " + port); - browserTabs = new FirefoxTabs(browser); - - } else if (typeof chrome !== 'undefined') { - port = chrome.runtime.connectNative(NATIVE_APP_NAME); - console.log("It's Chrome/Chromium: " + port); - browserTabs = new ChromeTabs(chrome); - - } else { - console.log("Unknown browser detected"); - } -} - - -// see https://stackoverflow.com/a/15479354/258421 -// function naturalCompare(a, b) { -// var ax = [], bx = []; - -// a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) }); -// b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) }); - -// while(ax.length && bx.length) { -// var an = ax.shift(); -// var bn = bx.shift(); -// var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]); -// if(nn) return nn; -// } - -// return ax.length - bx.length; -// } - -function compareWindowIdTabId(tabA, tabB) { - if (tabA.windowId != tabB.windowId) { - return tabA.windowId - tabB.windowId; - } - return tabA.index - tabB.index; -} - -function listTabsOnSuccess(tabs) { - var lines = []; - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - for (let tab of tabs) { - var line = + tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url; - console.log(line); - lines.push(line); - } - // lines = lines.sort(naturalCompare); - port.postMessage(lines); -} - -function listTabs() { - browserTabs.list({}, listTabsOnSuccess); -} - -function queryTabsOnSuccess(tabs) { - tabs.sort(compareWindowIdTabId); - let lines = tabs.map(tab => `${tab.windowId}.${tab.id}\t${tab.title}\t${tab.url}`) - console.log(lines); - port.postMessage(lines); -} - -function queryTabsOnFailure(error) { - console.error(error); - port.postMessage([]); -} - -function queryTabs(query_info) { - try { - let query = atob(query_info) - query = JSON.parse(query) - - integerKeys = {'windowId': null, 'index': null}; - booleanKeys = {'active': null, 'pinned': null, 'audible': null, 'muted': null, 'highlighted': null, - 'discarded': null, 'autoDiscardable': null, 'currentWindow': null, 'lastFocusedWindow': null}; - - query = Object.entries(query).reduce((o, [k,v]) => { - if (booleanKeys.hasOwnProperty(k) && typeof v != 'boolean') { - if (v.toLowerCase() == 'true') - o[k] = true; - else if (v.toLowerCase() == 'false') - o[k] = false; - else - o[k] = v; - } - else if (integerKeys.hasOwnProperty(k) && typeof v != 'number') - o[k] = Number(v); - else - o[k] = v; - return o; - }, {}) - - browserTabs.query(query, queryTabsOnSuccess); - } - catch(error) { - queryTabsOnFailure(error); - } -} - -// function moveTabs(move_triplets) { -// for (let triplet of move_triplets) { -// const [tabId, windowId, index] = triplet; -// browserTabs.move(tabId, {index: index, windowId: windowId}); -// } -// } - -function moveTabs(move_triplets) { - // move_triplets is a tuple of (tab_id, window_id, new_index) - if (move_triplets.length == 0) { - // this post is only required to make bt move command synchronous. mediator - // is waiting for any reply - port.postMessage('OK'); - return - } - - // we request a move of a single tab and when it happens, we call ourselves - // again with the remaining tabs (first omitted) - const [tabId, windowId, index] = move_triplets[0]; - browserTabs.move(tabId, {index: index, windowId: windowId}, - (tab) => moveTabs(move_triplets.slice(1)) - ); -} - -function closeTabs(tab_ids) { - browserTabs.close(tab_ids, () => port.postMessage('OK')); -} - -function openUrls(urls, window_id) { - if (urls.length == 0) { - console.log('Opening urls done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let url of urls) { - console.log(`Opening another one url ${url}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.create({'url': url, windowId: window_id}, - (tab) => resolve(`${tab.windowId}.${tab.id}`) - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result) - console.log(`Sending ids back: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function createTab(url) { - browserTabs.create({'url': url}, - (tab) => { - console.log(`Created new tab: ${tab.id}`); - port.postMessage([`${tab.windowId}.${tab.id}`]); - }); -} - -function updateTabs(updates) { - if (updates.length == 0) { - console.log('Updating tabs done'); - port.postMessage([]); - return; - } - - var promises = []; - for (let update of updates) { - console.log(`Updating tab ${JSON.stringify(update)}`); - promises.push(new Promise((resolve, reject) => { - browserTabs.update(update.tab_id, update.properties, - (tab) => { resolve(`${tab.windowId}.${tab.id}`) }, - (error) => { - console.error(`Could not update tab: ${error}, update=${JSON.stringify(update)}`) - resolve() - } - ); - })) - }; - Promise.all(promises).then(result => { - const data = Array.prototype.concat(...result).filter(x => !!x) - console.log(`Sending ids back after update: ${JSON.stringify(data)}`); - port.postMessage(data) - }); -} - -function activateTab(tab_id, focused) { - browserTabs.activate(tab_id, focused); -} - -function getActiveTabs() { - browserTabs.getActive(tabs => { - var result = tabs.map(tab => tab.windowId + "." + tab.id).toString() - console.log(`Active tabs: ${result}`); - port.postMessage(result); - }); -} - -function getWordsScript(match_regex, join_with) { - return GET_WORDS_SCRIPT - .replace('#match_regex#', match_regex) - .replace('#join_with#', join_with); -} - -function getTextScript(delimiter_regex, replace_with) { - return GET_TEXT_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function getHtmlScript(delimiter_regex, replace_with) { - return GET_HTML_SCRIPT - .replace('#delimiter_regex#', delimiter_regex) - .replace('#replace_with#', replace_with); -} - -function listOr(list, default_value) { - if ((list.length == 1) && (list[0] == null)) { - return default_value; - } - return list; -} - -function getWordsFromTabs(tabs, match_regex, join_with) { - var promises = []; - console.log(`Getting words from tabs: ${tabs}`); - const script = getWordsScript(match_regex, join_with); - - for (let tab of tabs) { - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, null, - (words, _payload) => { - words = listOr(words, []); - console.log(`Got ${words.length} words from another tab`); - resolve(words); - }, - (error, _payload) => { - console.log(`Could not get words from tab: ${error}`); - resolve([]); - } - ) - ); - promises.push(promise); - } - Promise.all(promises).then( - (all_words) => { - const result = Array.prototype.concat(...all_words); - console.log(`Total number of words: ${result.length}`); - port.postMessage(result); - } - ) -} - -function getWords(tab_id, match_regex, join_with) { - if (tab_id == null) { - console.log(`Getting words for active tabs`); - browserTabs.getActive( - (tabs) => getWordsFromTabs(tabs, match_regex, join_with), - ); - } else { - const script = getWordsScript(match_regex, join_with); - console.log(`Getting words, running a script: ${script}`); - browserTabs.runScript(tab_id, script, null, - (words, _payload) => port.postMessage(listOr(words, [])), - (error, _payload) => console.log(`getWords: tab_id=${tab_id}, could not run script (${script})`), - ); - } -} - -function getTextOrHtmlFromTabs(tabs, scriptGetter, delimiter_regex, replace_with, onSuccess) { - var promises = []; - const script = scriptGetter(delimiter_regex, replace_with) - console.log(`Getting text from tabs: ${tabs.length}, script (${script})`); - - lines = []; - for (let tab of tabs) { - // console.log(`Processing tab ${tab.id}`); - var promise = new Promise( - (resolve, reject) => browserTabs.runScript(tab.id, script, tab, - (text, current_tab) => { - // let as_text = JSON.stringify(text); - // I don't know why, but an array of one item is sent here, so I take - // the first item. - if (text && text[0]) { - console.log(`Got ${text.length} chars of text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: text[0]}); - } else { - console.log(`Got empty text from another tab: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - }, - (error, current_tab) => { - console.log(`Could not get text from tab: ${error}: ${current_tab.id}`); - resolve({tab: current_tab, text: ''}); - } - ) - ); - promises.push(promise); - } - - Promise.all(promises).then(onSuccess); -} - -function getTextOnRunScriptSuccess(all_results) { - console.log(`Ready`); - console.log(`Text promises are ready: ${all_results.length}`); - // console.log(`All results: ${JSON.stringify(all_results)}`); - lines = []; - for (let result of all_results) { - // console.log(`result: ${result}`); - tab = result['tab']; - text = result['text']; - // console.log(`Result: ${tab.id}, ${text.length}`); - let line = tab.windowId + "." + tab.id + "\t" + tab.title + "\t" + tab.url + "\t" + text; - lines.push(line); - } - // lines = lines.sort(naturalCompare); - console.log(`Total number of lines of text: ${lines.length}`); - port.postMessage(lines); -} - -function getTextOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getTextScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getText(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getTextOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getHtmlOnListSuccess(tabs, delimiter_regex, replace_with) { - // Make sure tabs are sorted by their index within a window - tabs.sort(compareWindowIdTabId); - getTextOrHtmlFromTabs(tabs, getHtmlScript, delimiter_regex, replace_with, getTextOnRunScriptSuccess); -} - -function getHtml(delimiter_regex, replace_with) { - browserTabs.list({'discarded': false}, - (tabs) => getHtmlOnListSuccess(tabs, delimiter_regex, replace_with), - ); -} - -function getBrowserName() { - const name = browserTabs.getBrowserName(); - console.log("Sending browser name: " + name); - port.postMessage(name); -} - -/* -Listen for messages from the app. -*/ -port.onMessage.addListener((command) => { - console.log("Received: " + JSON.stringify(command, null, 4)); - - if (command['name'] == 'list_tabs') { - console.log('Listing tabs...'); - listTabs(); - } - - else if (command['name'] == 'query_tabs') { - console.log('Querying tabs...'); - queryTabs(command['query_info']); - } - - else if (command['name'] == 'close_tabs') { - console.log('Closing tabs:', command['tab_ids']); - closeTabs(command['tab_ids']); - } - - else if (command['name'] == 'move_tabs') { - console.log('Moving tabs:', command['move_triplets']); - moveTabs(command['move_triplets']); - } - - else if (command['name'] == 'open_urls') { - console.log('Opening URLs:', command['urls'], command['window_id']); - openUrls(command['urls'], command['window_id']); - } - - else if (command['name'] == 'new_tab') { - console.log('Creating tab:', command['url']); - createTab(command['url']); - } - - else if (command['name'] == 'update_tabs') { - console.log('Updating tabs:', command['updates']); - updateTabs(command['updates']); - } - - else if (command['name'] == 'activate_tab') { - console.log('Activating tab:', command['tab_id']); - activateTab(command['tab_id'], !!command['focused']); - } - - else if (command['name'] == 'get_active_tabs') { - console.log('Getting active tabs'); - getActiveTabs(); - } - - else if (command['name'] == 'get_words') { - console.log('Getting words from tab:', command['tab_id']); - getWords(command['tab_id'], command['match_regex'], command['join_with']); - } - - else if (command['name'] == 'get_text') { - console.log('Getting texts from all tabs'); - getText(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_html') { - console.log('Getting HTML from all tabs'); - getHtml(command['delimiter_regex'], command['replace_with']); - } - - else if (command['name'] == 'get_browser') { - console.log('Getting browser name'); - getBrowserName(); - } -}); - -port.onDisconnect.addListener(function() { - console.log("Disconnected"); - if(chrome.runtime.lastError) { - console.warn("Reason: " + chrome.runtime.lastError.message); - } else { - console.warn("lastError is undefined"); - } - //sleep(5000); - console.log("Trying to reconnect"); - reconnect(); -}); - -console.log("Connected to native app " + NATIVE_APP_NAME); - -/* -On a click on the browser action, send the app a message. -*/ -// browser.browserAction.onClicked.addListener(() => { -// // console.log("Sending: ping"); -// // port.postMessage("ping"); -// -// console.log('Listing tabs'); -// listTabs(); -// }); diff --git a/brotab/extension/firefox/manifest.json b/brotab/extension/firefox/manifest.json index 0d19ad9..60009d2 100644 --- a/brotab/extension/firefox/manifest.json +++ b/brotab/extension/firefox/manifest.json @@ -4,10 +4,10 @@ "name": "BroTab", "version": "1.4.0", "background": { - "scripts": ["background.js"] + "scripts": ["../background.ts"] }, "icons": { - "128": "brotab-icon-128x128.png" + "128": "../brotab-icon-128x128.png" }, "permissions": ["nativeMessaging", "tabs", "activeTab", ""], "applications": { diff --git a/brotab/extension/package.json b/brotab/extension/package.json new file mode 100644 index 0000000..819dea5 --- /dev/null +++ b/brotab/extension/package.json @@ -0,0 +1,16 @@ +{ + "devDependencies": { + "@parcel/config-webextension": "^2.6.2", + "@types/webextension-polyfill": "^0.9.0", + "parcel": "^2.6.2" + }, + "scripts": { + "build:firefox": "parcel build firefox/manifest.json --dist-dir ./dist/firefox", + "watch:firefox": "parcel watch firefox/manifest.json --dist-dir ./dist/firefox", + "build:chrome": "parcel build chrome/manifest.json --dist-dir ./dist/chrome", + "watch:chrome": "parcel watch chrome/manifest.json --dist-dir ./dist/chrome" + }, + "dependencies": { + "webextension-polyfill": "^0.9.0" + } +} diff --git a/brotab/extension/yarn.lock b/brotab/extension/yarn.lock new file mode 100644 index 0000000..e704c68 --- /dev/null +++ b/brotab/extension/yarn.lock @@ -0,0 +1,1461 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@lezer/common@^0.15.0", "@lezer/common@^0.15.7": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.12.tgz#2f21aec551dd5fd7d24eb069f90f54d5bc6ee5e9" + integrity sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig== + +"@lezer/lr@^0.15.4": + version "0.15.8" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.8.tgz#1564a911e62b0a0f75ca63794a6aa8c5dc63db21" + integrity sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg== + dependencies: + "@lezer/common" "^0.15.0" + +"@lmdb/lmdb-darwin-arm64@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz#bc66fa43286b5c082e8fee0eacc17995806b6fbe" + integrity sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A== + +"@lmdb/lmdb-darwin-x64@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz#89d8390041bce6bab24a82a20392be22faf54ffc" + integrity sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA== + +"@lmdb/lmdb-linux-arm64@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz#14fe4c96c2bb1285f93797f45915fa35ee047268" + integrity sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ== + +"@lmdb/lmdb-linux-arm@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz#05bde4573ab10cf21827339fe687148f2590cfa1" + integrity sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw== + +"@lmdb/lmdb-linux-x64@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz#d2f85afd857d2c33d2caa5b057944574edafcfee" + integrity sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q== + +"@lmdb/lmdb-win32-x64@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5" + integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA== + +"@mischnic/json-sourcemap@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz#38af657be4108140a548638267d02a2ea3336507" + integrity sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA== + dependencies: + "@lezer/common" "^0.15.7" + "@lezer/lr" "^0.15.4" + json5 "^2.2.1" + +"@msgpackr-extract/msgpackr-extract-darwin-arm64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.0.2.tgz#01e3669b8b2dc01f6353f2c87e1ec94faf52c587" + integrity sha512-FMX5i7a+ojIguHpWbzh5MCsCouJkwf4z4ejdUY/fsgB9Vkdak4ZnoIEskOyOUMMB4lctiZFGszFQJXUeFL8tRg== + +"@msgpackr-extract/msgpackr-extract-darwin-x64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.0.2.tgz#5ca32f16e6f1b7854001a1a2345b61d4e26a0931" + integrity sha512-DznYtF3lHuZDSRaIOYeif4JgO0NtO2Xf8DsngAugMx/bUdTFbg86jDTmkVJBNmV+cxszz6OjGvinnS8AbJ342g== + +"@msgpackr-extract/msgpackr-extract-linux-arm64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.0.2.tgz#ff629f94379981bf476dffb1439a7c1d3dba2d72" + integrity sha512-b0jMEo566YdM2K+BurSed7bswjo3a6bcdw5ETqoIfSuxKuRLPfAiOjVbZyZBgx3J/TAM/QrvEQ/VN89A0ZAxSg== + +"@msgpackr-extract/msgpackr-extract-linux-arm@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.0.2.tgz#5f6fd30d266c4a90cf989049c7f2e50e5d4fcd4c" + integrity sha512-Gy9+c3Wj+rUlD3YvCZTi92gs+cRX7ZQogtwq0IhRenloTTlsbpezNgk6OCkt59V4ATEWSic9rbU92H/l7XsRvA== + +"@msgpackr-extract/msgpackr-extract-linux-x64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.0.2.tgz#167faa553b9dbffac8b03bf27de9b6f846f0e1bc" + integrity sha512-zrBHaePwcv4cQXxzYgNj0+A8I1uVN97E7/3LmkRocYZ+rMwUsnPpp4RuTAHSRoKlTQV3nSdCQW4Qdt4MXw/iHw== + +"@msgpackr-extract/msgpackr-extract-win32-x64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.0.2.tgz#baea7764b1adf201ce4a792fe971fd7211dad2e4" + integrity sha512-fpnI00dt+yO1cKx9qBXelKhPBdEgvc8ZPav1+0r09j0woYQU2N79w/jcGawSY5UGlgQ3vjaJsFHnGbGvvqdLzg== + +"@parcel/bundler-default@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.6.2.tgz#bfa1be22af985ba2d6dbf1890a36ad4553f819d4" + integrity sha512-XIa3had/MIaTGgRFkHApXwytYs77k4geaNcmlb6nzmAABcYjW1CLYh83Zt0AbzLFsDT9ZcRY3u2UjhNf6efSaw== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/cache@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.6.2.tgz#66163c8f8ac4aac865c4b9eb2197b0d9e6f91a74" + integrity sha512-hhJ6AsEGybeQZd9c/GYqfcKTgZKQXu3Xih6TlnP3gdR3KZoJOnb40ovHD1yYg4COvfcXThKP1cVJ18J6rcv3IA== + dependencies: + "@parcel/fs" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/utils" "2.6.2" + lmdb "2.5.2" + +"@parcel/codeframe@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.6.2.tgz#01a7ae97fdb66457e6704c87cc6031085e539e6e" + integrity sha512-oFlHr6HCaYYsB4SHkU+gn9DKtbzvv3/4NdwMX0/6NAKyYVI7inEsXyPGw2Bbd2ZCFatW9QJZUETF0etvh5AEfQ== + dependencies: + chalk "^4.1.0" + +"@parcel/compressor-raw@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.6.2.tgz#6fec2654c7767a2fef042a37246549d41ee8a586" + integrity sha512-P3c8jjV5HVs+fNDjhvq7PtHXNm687nit1iwTS5VAt+ScXKhKBhoIJ56q+9opcw0jnXVjAAgZqcRZ50oAJBGdKw== + dependencies: + "@parcel/plugin" "2.6.2" + +"@parcel/config-default@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.6.2.tgz#0a1af0ce9a431771851c05fc821da13c8d878b7e" + integrity sha512-kuZFY0rhaioCRX2LqxaMM2ylui6ms/nmdVxuceP4/SAWi/9duc+y1lG2a1zGNShbc6OEgpdQr/W/jxdYM7NJDw== + dependencies: + "@parcel/bundler-default" "2.6.2" + "@parcel/compressor-raw" "2.6.2" + "@parcel/namer-default" "2.6.2" + "@parcel/optimizer-css" "2.6.2" + "@parcel/optimizer-htmlnano" "2.6.2" + "@parcel/optimizer-image" "2.6.2" + "@parcel/optimizer-svgo" "2.6.2" + "@parcel/optimizer-terser" "2.6.2" + "@parcel/packager-css" "2.6.2" + "@parcel/packager-html" "2.6.2" + "@parcel/packager-js" "2.6.2" + "@parcel/packager-raw" "2.6.2" + "@parcel/packager-svg" "2.6.2" + "@parcel/reporter-dev-server" "2.6.2" + "@parcel/resolver-default" "2.6.2" + "@parcel/runtime-browser-hmr" "2.6.2" + "@parcel/runtime-js" "2.6.2" + "@parcel/runtime-react-refresh" "2.6.2" + "@parcel/runtime-service-worker" "2.6.2" + "@parcel/transformer-babel" "2.6.2" + "@parcel/transformer-css" "2.6.2" + "@parcel/transformer-html" "2.6.2" + "@parcel/transformer-image" "2.6.2" + "@parcel/transformer-js" "2.6.2" + "@parcel/transformer-json" "2.6.2" + "@parcel/transformer-postcss" "2.6.2" + "@parcel/transformer-posthtml" "2.6.2" + "@parcel/transformer-raw" "2.6.2" + "@parcel/transformer-react-refresh-wrap" "2.6.2" + "@parcel/transformer-svg" "2.6.2" + +"@parcel/config-webextension@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/config-webextension/-/config-webextension-2.6.2.tgz#8f6edbc5e8d486f33b21edf90e6ac08c6ff68600" + integrity sha512-/cxWJDK2R7gpTiobqXJzQwhnlJuLr7pobK3fwMQSyEXg1ULILiXL4HblOvlOs/kDcVZev50TkqdAGOnoXjmBvQ== + dependencies: + "@parcel/config-default" "2.6.2" + "@parcel/packager-webextension" "2.6.2" + "@parcel/runtime-webextension" "2.6.2" + "@parcel/transformer-raw" "2.6.2" + "@parcel/transformer-webextension" "2.6.2" + +"@parcel/core@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.6.2.tgz#c46d26e2f47967d80f08484f20d31fee7b90e888" + integrity sha512-JlKS3Ux0ngmdooSBbzQLShHJdsapF9E7TGMo1hFaHRquZip/DaqzvysYrgMJlDuCoLArciq5ei7ZKzGeK9zexA== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/cache" "2.6.2" + "@parcel/diagnostic" "2.6.2" + "@parcel/events" "2.6.2" + "@parcel/fs" "2.6.2" + "@parcel/graph" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/package-manager" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + "@parcel/workers" "2.6.2" + abortcontroller-polyfill "^1.1.9" + base-x "^3.0.8" + browserslist "^4.6.6" + clone "^2.1.1" + dotenv "^7.0.0" + dotenv-expand "^5.1.0" + json5 "^2.2.0" + msgpackr "^1.5.4" + nullthrows "^1.1.1" + semver "^5.7.1" + +"@parcel/css-darwin-arm64@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-darwin-arm64/-/css-darwin-arm64-1.11.0.tgz#d52f1cab63a3ae2a45b5fe47197d0d159c25a42c" + integrity sha512-J/zTiY/MTJ5alkN2/A88mccy60k8bfdsP/LCLADoomOnXzRkyQLIznWnxwg7uIhmcjquENcKZRxkQEqrfxj0SQ== + +"@parcel/css-darwin-x64@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-darwin-x64/-/css-darwin-x64-1.11.0.tgz#b9be55f4220630edcdcd29b8942c9bdc809e81c7" + integrity sha512-rG5LwQyFoOZOOTtn04S8qk1+W+mzcmiPXuzdstpD3DSMqpxIhSrogLu9AMt4fIAtHs4Mu/aueq91wazJydF1LA== + +"@parcel/css-linux-arm-gnueabihf@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm-gnueabihf/-/css-linux-arm-gnueabihf-1.11.0.tgz#d01b1e734540f54cf5a8d3735bd2deb8a63d5418" + integrity sha512-H/+ef/lBSVFycUczutzkgtZG2ZeQJTD81LbXa06COa7b7dxSwcjDKFmy07vKvsy0AtYWiNQ44HfIb45HpKd81Q== + +"@parcel/css-linux-arm64-gnu@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-gnu/-/css-linux-arm64-gnu-1.11.0.tgz#391663f0c6b67e9a34cccd4516d4776ee3140ce2" + integrity sha512-O17naID8IiVksSoHENzn8l+FXozv8rD+j0dP/AQIdfAoZQj52IE2Lq174KlxP+dFn15H4t24ji1vVq5xiyFJFw== + +"@parcel/css-linux-arm64-musl@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-musl/-/css-linux-arm64-musl-1.11.0.tgz#707708d6cd8533fb54682dc4b57816416c10b66f" + integrity sha512-hZI24ME1z4f1o5S67UvEkfjtApAIPyNgFyutpg+m85gp5bKxsBgfNcFPFH38xibCx/d/nWKHr5uWarlEBmRIVQ== + +"@parcel/css-linux-x64-gnu@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-gnu/-/css-linux-x64-gnu-1.11.0.tgz#47761079f64ab1c5888c0a3cbbcc3ef4aea869ea" + integrity sha512-aOtlHvkcSNYeBHM3rh8FZqGsOW0WSH42WpSZjkhk69Ckx1G0igmxn59xNbW49S4TdePHND7Qw3/2YKIrxts0Qw== + +"@parcel/css-linux-x64-musl@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-musl/-/css-linux-x64-musl-1.11.0.tgz#e6845cac3d49f547703cfbfb55b0d99d76fadd8b" + integrity sha512-4rdWtui06wnNCkmzyD8t9K+auCgakbHXLOY4IOnH3+ziC1XHBLq0cyhvebu5GhGR2AJeR+k44mvy7DUvKZ6Nqw== + +"@parcel/css-win32-x64-msvc@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css-win32-x64-msvc/-/css-win32-x64-msvc-1.11.0.tgz#7e373f3ed2aeb4dbcdae40e0022f7d47ed747463" + integrity sha512-N76wa3cjQ8zTej+jnuC+3V0WVLdEvf/iB4wmkourg5mfZ4SBP56TBSoyVrtXOpD4tMuULE0RDkKc5cI2WwYyCA== + +"@parcel/css@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/css/-/css-1.11.0.tgz#451645e19779f24fb019a1255548eac96c6385d9" + integrity sha512-B86A34bSk2HzuKYECI0yf5YzaAQYWedq/sJHwpASxyfvyrFb3lEvsV1yQ1PMd2AfZHeYdTv3jWaE1pYXqm0SkQ== + dependencies: + detect-libc "^1.0.3" + optionalDependencies: + "@parcel/css-darwin-arm64" "1.11.0" + "@parcel/css-darwin-x64" "1.11.0" + "@parcel/css-linux-arm-gnueabihf" "1.11.0" + "@parcel/css-linux-arm64-gnu" "1.11.0" + "@parcel/css-linux-arm64-musl" "1.11.0" + "@parcel/css-linux-x64-gnu" "1.11.0" + "@parcel/css-linux-x64-musl" "1.11.0" + "@parcel/css-win32-x64-msvc" "1.11.0" + +"@parcel/diagnostic@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.6.2.tgz#da3fca0d82bc012f49288c963024edd089ca9f41" + integrity sha512-3ODSBkKVihENU763z1/1DhGAWFhYWRxOCOShC72KXp+GFnSgGiBsxclu8NBa/N948Rzp8lqQI8U1nLcKkh0O/w== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + nullthrows "^1.1.1" + +"@parcel/events@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.6.2.tgz#97a1059d1eb93df8d3d426b6b150f829f70f543b" + integrity sha512-IaCjOeA5ercdFVi1EZOmUHhGfIysmCUgc2Th9hMugSFO0I3GzRsBcAdP6XPfWm+TV6sQ/qZRfdk/drUxoAupnw== + +"@parcel/fs-search@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.6.2.tgz#6343a5da4f0753c96c004d6951897f83160c4d45" + integrity sha512-4STid1zqtGnmGjHD/2TG2g/zPDiCTtE3IAS24QYH3eiUAz2uoKGgEqd2tZbZ2yI96jtCuIhC1bzVu8Hbykls7w== + dependencies: + detect-libc "^1.0.3" + +"@parcel/fs@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.6.2.tgz#c3f4ab9f88df6c1416af7c2a7a31b68ced862a16" + integrity sha512-mIhqdF3tjgeoIGqW7Nc/xfM2ClID7o8livwUe5lpQEP+ZaIBiMigXs6ckv3WToCACK+3uylrSD2A/HmlhrxMqQ== + dependencies: + "@parcel/fs-search" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + "@parcel/watcher" "^2.0.0" + "@parcel/workers" "2.6.2" + +"@parcel/graph@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.6.2.tgz#fe777666c6fa09cb89b1570932459a4b5e90b6aa" + integrity sha512-DPH4G/RBFJWayIN2fnhDXqhUw75n7k15YsGzdDKiXuwwz4wMOjoL4cyrI6zOf1SIyh3guRmeTYJ4jjPzwrLYww== + dependencies: + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/hash@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.6.2.tgz#485e31323036abdf3648ba7f8816985296f358ba" + integrity sha512-tFB+cJU1Wqag6WyJgsmx3nx+xhmjcNZqtWh/MtK1lHNnZdDRk6bjr7SapnygBwruz+SmSt5bbdVThcpk2dRCcA== + dependencies: + detect-libc "^1.0.3" + xxhash-wasm "^0.4.2" + +"@parcel/logger@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.6.2.tgz#c99eed0e1ed13ac0c25f5e57355ab1bf5b3eda21" + integrity sha512-Sz5YGCj1DbEiX0/G8Uw97LLZ0uEK+qtWcRAkHNpJpeMiSqDiRNevxXltz42EcLo+oCh4d4wyiVzwi9mNwzhS/Q== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/events" "2.6.2" + +"@parcel/markdown-ansi@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.6.2.tgz#7511f6d32688f8d150828cdd1162774c102070e3" + integrity sha512-N/h9J4eibhc+B+krzvPMzFUWL37GudBIZBa7XSLkcuH6MnYYfh6rrMvhIyyESwk6VkcZNVzAeZrGQqxEs0dHDQ== + dependencies: + chalk "^4.1.0" + +"@parcel/namer-default@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.6.2.tgz#8034fb23d2013ae00e5b73e9f887553bef498075" + integrity sha512-mp7bx/BQaIuohmZP0uE+gAmDBzzH0Yu8F4yCtE611lc6i0mou+nWRhzyKLNC/ieuI8DB3BFh2QQKeTxJn4W0qg== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/node-resolver-core@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.6.2.tgz#46381572e2829cd6b9424ea1cfd8c1330ab9ff4f" + integrity sha512-4b2L5QRYlTybvv3+TIRtwg4PPJXy+cRShCBa8eu1K0Fj297Afe8MOZrcVV+RIr2KPMIRXcIJoqDmOhyci/DynA== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + semver "^5.7.1" + +"@parcel/optimizer-css@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-css/-/optimizer-css-2.6.2.tgz#ae6be6c889ccd19de4868f2e4813e1c9356e4061" + integrity sha512-rjTQ9bOokUzzKDYpwMQxDtPqRcMljcTVvod5GT5azGnw1EbwNv30vqnTu81+sEMyttHydzYrKAM15UGV/JYu1Q== + dependencies: + "@parcel/css" "^1.10.1" + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + browserslist "^4.6.6" + nullthrows "^1.1.1" + +"@parcel/optimizer-htmlnano@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.6.2.tgz#4ca52869708cd154f6eae09012748e769d51f214" + integrity sha512-Doi2hDmsQHLwuBo6w5gvw5u6GBDz8FhkzAlitfG3C96lZxEw2eu0vquY4Li8lbZT9MBNs8zuYiD1QW8sdlv9hA== + dependencies: + "@parcel/plugin" "2.6.2" + htmlnano "^2.0.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + svgo "^2.4.0" + +"@parcel/optimizer-image@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.6.2.tgz#2917f493014ae1b32580c0b8cf5c77bfe17b6949" + integrity sha512-XwFk43s8Dar4N+wXOkpKkeXf1vtu3PSu4ic+M9J0EwNKElrktQ0+paLYmwwp7Xv0tZbRedLAROomUxdXqEMupg== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + "@parcel/workers" "2.6.2" + detect-libc "^1.0.3" + +"@parcel/optimizer-svgo@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.6.2.tgz#e68cbc3d99694adb671244b6df435e1d311ca033" + integrity sha512-X2wPy1VeT2d9oUCue/vAXX907kmLf0o+w0LHghhbApuXjkvJNS2Vz182HIo1rtcS0RH5k3lXxUV0OPQjOC7BOw== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + svgo "^2.4.0" + +"@parcel/optimizer-terser@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.6.2.tgz#3361e2fd51bfdf6736f1e85afb9d6bed207cdb60" + integrity sha512-ZSEVQ3G3zOiVPeHvH+BrHegZybrQj9kWQAaAA92leSqbvf6UaX4xqXbGRg2OttNFtbGYBzIl28Zm4t2SLeUIuA== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + terser "^5.2.0" + +"@parcel/package-manager@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.6.2.tgz#003e8326adf95f85b2a40bb5e5f24a735d58f114" + integrity sha512-xGMqTgnwTE3rgzYwUZMKxR8fzmP5iSYz/gj2H8FR3pEmwh/8xCMtNjTSth+hPVGuqgRZ6JxwpfdY/fXdZ61ViQ== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/fs" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + "@parcel/workers" "2.6.2" + semver "^5.7.1" + +"@parcel/packager-css@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.6.2.tgz#1b8119888f7278612b18b6d08f623e3309b45190" + integrity sha512-zifJqgNUtLZoJ2oeFeLz6OFOBy8FNlVGtGtOqTJZN1SeYd94xNYyeUTwnSsOh2OEDs6HJhggL3o4uEmpM1s9GA== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/packager-html@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.6.2.tgz#ed3e6862bfe419472c68ec81f46cf6f825694051" + integrity sha512-NTJoKcqApMgFOpulok4Ru9QW3BD7d5931ymoow9/bmgDwvJNh2SOMHVx6lqzKRU5x+wlShpYfDur4zOipRev8g== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + +"@parcel/packager-js@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.6.2.tgz#16257b343480490adea619671b56d9cd02c8302a" + integrity sha512-fm5rKWtaExR0W+UEKWivXNPysRFxuBCdskdxDByb1J1JeGMvp7dJElbi8oXDAQM4MnM5EyG7cg47SlMZNTLm4A== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + globals "^13.2.0" + nullthrows "^1.1.1" + +"@parcel/packager-raw@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.6.2.tgz#67f136cc8b404edeb4092ea5f56d277e0e60d0c6" + integrity sha512-Rl3ZkMtMjb+LEvRowijDD8fibUAS6rWK0/vZQMk9cDNYCP2gCpZayLk0HZIGxneeTbosf/0sbngHq4VeRQOnQA== + dependencies: + "@parcel/plugin" "2.6.2" + +"@parcel/packager-svg@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.6.2.tgz#fa21e605640f71a59cc3f5095531d7bed368df77" + integrity sha512-FrGlwtiMs7YBWoVA3vCNHlBcghVYueKzimvufl4r287g1iEmq59pchCqpi6rW83O/mnpUQg9mpP+BmXxuvjLNg== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + posthtml "^0.16.4" + +"@parcel/packager-webextension@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/packager-webextension/-/packager-webextension-2.6.2.tgz#4a87b231f02fcec2e3e315206e8240eae047649d" + integrity sha512-caidDylxzIJL20XvLOuKONjAZ2qAkjR0WEN90MqWUym16yyqkS8hPnEcADWhmkVPHB6aIXrsVQE1aimsDBPYiQ== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/plugin@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.6.2.tgz#d4c8cc558e962e4dfb7154a7f0a023f6abad07ac" + integrity sha512-wbbWsM23Pr+8xtLSvf+UopXdVYlpKCCx6PuuZaZcKo+9IcDCWoGXD4M8Kkz14qBmkFn5uM00mULUqmVdSibB2w== + dependencies: + "@parcel/types" "2.6.2" + +"@parcel/reporter-cli@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.6.2.tgz#df971ce40164f2d6bd77dd6342203aa0052d9753" + integrity sha512-5BWMtQRSXVXMlB/BOkCf8NVLh3qcQVMrj6owuekmqLi/GGC+kGZovzA6YrofVIdNHcoxOZwTIYwjoU3ibJ6yAA== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + chalk "^4.1.0" + term-size "^2.2.1" + +"@parcel/reporter-dev-server@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.6.2.tgz#73e82c7bd6bbe47de61b2170ac9b7799c4e850fd" + integrity sha512-5QtL3ETMFL161jehlIK6rjBM+Pqk5cMhr60s9yLYqE1GY4M4gMj+Act+FXViyM6gmMA38cPxDvUsxTKBYXpFCw== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + +"@parcel/resolver-default@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.6.2.tgz#b417fb4f9713f5bdeceab737ae1dacb8322f2778" + integrity sha512-Lo5sWb5QkjWvdBr+TdmAF6Mszb/sMldBBatc1osQTkHXCy679VMH+lfyiWxHbwK+F1pmdMeBJpYcMxvrgT8EsA== + dependencies: + "@parcel/node-resolver-core" "2.6.2" + "@parcel/plugin" "2.6.2" + +"@parcel/runtime-browser-hmr@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.6.2.tgz#121fe22b5df6b7a8591a23632146c008448240a5" + integrity sha512-M4X0+7dyfdI6smwGUGjGXb8Ns3HX7ZrTemyq4Gc7zp7P/5gWjR8i9eISz46sXmF9bf01a/4dKZpoCC9un1pH1g== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + +"@parcel/runtime-js@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.6.2.tgz#cc46ec03d4fe2a4832cd7709431afba857bd37e0" + integrity sha512-0S3JFwgvs6FmEx2dHta9R0Sfu8vCnFAm4i7Y4efGHtAcTrF2CHjyiz4/hG+RQGJ70eoWW463Q+8qt6EKbkaOBQ== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/runtime-react-refresh@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.6.2.tgz#4504cea4468fbeabf4a94c99a991251c7cd04c59" + integrity sha512-DJTm5D/tUAGZm0o3ndDOPbKwdYrobuvm4jvkPq31LdEUqVvyuzBAMlqQFHc1yJEJDRRWOIQwQP9Y0NQbJmXFfg== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + react-error-overlay "6.0.9" + react-refresh "^0.9.0" + +"@parcel/runtime-service-worker@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.6.2.tgz#f1ea3e768f8ae9d2f5ec119db020595933185393" + integrity sha512-9jV+RwVEeDUI5+eLy8j1tapTNoHHGOY2+JUprcObQkQ8fux7KltQBJWFhpkUdGtz5LTCNXtj9tdycFtS5lmSzg== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/runtime-webextension@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/runtime-webextension/-/runtime-webextension-2.6.2.tgz#de9f1f135be9e9de72c689f5ae5bf4f863d8f665" + integrity sha512-YU81kZsDQzVYJ9hifkAWql/VAK9xfPa5xbobttf93Tp2c6mB86aBTcdwlTQ3Ls609BZtA5bLVC3ITxm90MuWMw== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/source-map@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.1.0.tgz#bd47aa0a93ae261436f7c5da40b09856e1a331d5" + integrity sha512-E7UOEIof2o89LrKk1agSLmwakjigmEdDp1ZaEdsLVEvq63R/bul4Ij5CT+0ZDcijGpl5tnTbQADY9EyYGtjYgQ== + dependencies: + detect-libc "^1.0.3" + +"@parcel/transformer-babel@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.6.2.tgz#0ad994cb4ec4127e544b10e70c884409270f26a6" + integrity sha512-R3qdfhnZhVhsDB8+0wC3CU86dmqx5DwxcTo10Wd1VbA6fiLRSGd4+ZrxJRg491mFTedgtTrUeO6LNYAmMFpCbQ== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + browserslist "^4.6.6" + json5 "^2.2.0" + nullthrows "^1.1.1" + semver "^5.7.0" + +"@parcel/transformer-css@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.6.2.tgz#57470cd55ba2693e1e949c4872d59404745b9f52" + integrity sha512-6lsMdwBUgAyTcd7OIz2lG56jobptGkaRogDmbGFDhmuq/tQ/ZrNElUFmDVeh5cELQlByvj/Qh32cUMnsiMsk3g== + dependencies: + "@parcel/css" "^1.10.1" + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + browserslist "^4.6.6" + nullthrows "^1.1.1" + +"@parcel/transformer-html@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.6.2.tgz#015037625b2036951d6182e7d1b3fb2dec930049" + integrity sha512-DEGv0Gd8BVAO/QZuXRg+A6YieVpIub7YT8xTNA/6vCIAl++y2hYyo9NF2j2xnooYbzW7zd7uDEFawOSd40lxig== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/plugin" "2.6.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/transformer-image@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.6.2.tgz#aed1d3ac50f80441fbf6b08e2c1e3c92e58851e4" + integrity sha512-i2Ug6exFaX64M10Qsq4vza5NP0iRW+aIcao4uGvPHP6d36a0oUfT6tJsOLHh3sDj2ihT8RVJL2TRavSX17TjUA== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/workers" "2.6.2" + nullthrows "^1.1.1" + +"@parcel/transformer-js@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.6.2.tgz#905285b5d6d8047d0420641dee257ee93bac69d8" + integrity sha512-uhXAMTjE/Q61amflV8qVpb73mj+mIdXIMH0cSks1/gDIAxcgIvWvrE14P4TvY6zJ1q1iRJRIRUN6cFSXqjjLSA== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.2" + "@parcel/workers" "2.6.2" + "@swc/helpers" "^0.4.2" + browserslist "^4.6.6" + detect-libc "^1.0.3" + nullthrows "^1.1.1" + regenerator-runtime "^0.13.7" + semver "^5.7.1" + +"@parcel/transformer-json@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.6.2.tgz#37a5c3f4571c81e1a5f2d0c77f266b56e3866ad5" + integrity sha512-QGcIIvbPF/u10ihYvQhxXqb2QMXWSzcBxJrOSIXIl74TUGrWX05D5LmjDA/rzm/n/kvRnBkFNP60R/smYb8x+Q== + dependencies: + "@parcel/plugin" "2.6.2" + json5 "^2.2.0" + +"@parcel/transformer-postcss@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.6.2.tgz#6d613889b73b70ccd912a411c9b272bcf984b4dd" + integrity sha512-yauLUofKnb09tzgg8FE33aDrbqgOgQtGyWfyiKWnoV1j8XTRu/t6R7e2qRysgNsm9Ghzxe1G83iJSli1MGTErA== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + clone "^2.1.1" + nullthrows "^1.1.1" + postcss-value-parser "^4.2.0" + semver "^5.7.1" + +"@parcel/transformer-posthtml@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.6.2.tgz#751ac1d520d42df40ab3d21f2bfe078545295cfb" + integrity sha512-Ly9znYdBnGLDmlyhKQJOekrs35w7fKTSxZ60B3nTtpwSFC/AMr3nv9kPTVi8KDRp2Kh1ahxQlfBIYHCa0RfkXA== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/transformer-raw@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.6.2.tgz#a77ffaa26d59fcf79b5094c1319b6f0922fffe7e" + integrity sha512-CsofYq5g9Zj/FNmhya2R7Xp3WHlzz34mEdN69bds3azRYHCrl/TS33xXcp/9J+74SEIY1Ufh552o1cM3fnSrDQ== + dependencies: + "@parcel/plugin" "2.6.2" + +"@parcel/transformer-react-refresh-wrap@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.6.2.tgz#680a3c724d8ada39a637a10f8cb7fdc33aa138eb" + integrity sha512-7EE68ebISz+oAHm64ZJbz6uJQT4aOoB8QiK3PvuY6+RsP7aK4/FEHGM1afW49KrZbP4lWjloEkcJm/88DfBiGw== + dependencies: + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + react-refresh "^0.9.0" + +"@parcel/transformer-svg@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.6.2.tgz#62795cfbc5ea083d0bd825d77ff0df3b717f05f9" + integrity sha512-s7e/DVte2OT+jUL10+g2+l/y/MqxAb8Avw1asRH0683iEVj6GGS/K4KnHN8WagLwnS6Fb3/InVrzxtb0YKUt2w== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/plugin" "2.6.2" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^5.7.1" + +"@parcel/transformer-webextension@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/transformer-webextension/-/transformer-webextension-2.6.2.tgz#dd860754c682c0db5b7515cbdbe5f7c693053d8c" + integrity sha512-wiNUBl3FKXsN1piClNs2T8ehoozgri3ULWmJLCoQCHjDb3aSuKOE2j91wAkLuOkqPbXH6wJDwmnYvwXaIAKtww== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/diagnostic" "2.6.2" + "@parcel/plugin" "2.6.2" + "@parcel/utils" "2.6.2" + content-security-policy-parser "^0.3.0" + +"@parcel/types@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.6.2.tgz#216313bcaf625e59a2bd525a00c3b1f6701b0d92" + integrity sha512-MV8BFpCIs2jMUvK2RHqzkoiuOQ//JIbrD1zocA2YRW3zuPL/iABvbAABJoXpoPCKikVWOoCWASgBfWQo26VvJQ== + dependencies: + "@parcel/cache" "2.6.2" + "@parcel/diagnostic" "2.6.2" + "@parcel/fs" "2.6.2" + "@parcel/package-manager" "2.6.2" + "@parcel/source-map" "^2.0.0" + "@parcel/workers" "2.6.2" + utility-types "^3.10.0" + +"@parcel/utils@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.6.2.tgz#18d68a56330be8db59c269163b77617043ba8e3a" + integrity sha512-Ug7hpRxjgbY5AopW55nY7MmGMVmwmN+ihfCmxJkBUoESTG/3iq8uME7GjyOgW5DkQc2K7q62i8y8N0wCJT1u4Q== + dependencies: + "@parcel/codeframe" "2.6.2" + "@parcel/diagnostic" "2.6.2" + "@parcel/hash" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/markdown-ansi" "2.6.2" + "@parcel/source-map" "^2.0.0" + chalk "^4.1.0" + +"@parcel/watcher@^2.0.0": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" + integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@parcel/workers@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.6.2.tgz#2cae07db7a752295f11c2952b5026e426e38b19b" + integrity sha512-wBgUjJQm+lDd12fPRUmk09+ujTA9DgwPdqylSFK0OtI/yT6A+2kArUqjp8IwWo2tCJXoMzXBne2XQIWKqMiN4Q== + dependencies: + "@parcel/diagnostic" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/types" "2.6.2" + "@parcel/utils" "2.6.2" + chrome-trace-event "^1.0.2" + nullthrows "^1.1.1" + +"@swc/helpers@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.3.tgz#16593dfc248c53b699d4b5026040f88ddb497012" + integrity sha512-6JrF+fdUK2zbGpJIlN7G3v966PQjyx/dPt1T9km2wj+EUBqgrxCk3uX4Kct16MIm9gGxfKRcfax2hVf5jvlTzA== + dependencies: + tslib "^2.4.0" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/webextension-polyfill@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz#a50b5f03161ef83e46721719b49668cfcaac804a" + integrity sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ== + +abortcontroller-polyfill@^1.1.9: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== + +acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +base-x@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +browserslist@^4.6.6: + version "4.21.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" + integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== + dependencies: + caniuse-lite "^1.0.30001359" + electron-to-chromium "^1.4.172" + node-releases "^2.0.5" + update-browserslist-db "^1.0.4" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001359: + version "1.0.30001363" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz#26bec2d606924ba318235944e1193304ea7c4f15" + integrity sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +content-security-policy-parser@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/content-security-policy-parser/-/content-security-policy-parser-0.3.0.tgz#a14d4ed54c8d098621ba46cabcd20a78be0c691e" + integrity sha512-ub90B4t9EfDPv3DCH7vEwGe4tVMkSm4Ow1HsmvmEQwinDfpTEDmkuJVa5WpzHDTt2bUirNRZuzL6S0msASlJhg== + +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" + integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== + +electron-to-chromium@^1.4.172: + version "1.4.184" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.184.tgz#381d4d111fc82d3376ed690dfb621e675f9078a9" + integrity sha512-IADi390FRdvxWfVX3hjzfTDNVHiTqVo9ar53/7em/SfhUG9YcjVhyQecY/XwmBHRKden/wFud7RWOUH7+7LFng== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +get-port@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" + integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== + +globals@^13.2.0: + version "13.16.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.16.0.tgz#9be4aca28f311aaeb974ea54978ebbb5e35ce46a" + integrity sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q== + dependencies: + type-fest "^0.20.2" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +htmlnano@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-2.0.2.tgz#3e3170941e2446a86211196d740272ebca78f878" + integrity sha512-+ZrQFS4Ub+zd+/fWwfvoYCEGNEa0/zrpys6CyXxvZDwtL7Pl+pOtRkiujyvBQ7Lmfp7/iEPxtOFgxWA16Gkj3w== + dependencies: + cosmiconfig "^7.0.1" + posthtml "^0.16.5" + timsort "^0.3.0" + +htmlparser2@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-json@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-json/-/is-json-2.0.1.tgz#6be166d144828a131d686891b983df62c39491ff" + integrity sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.0, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lmdb@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.5.2.tgz#37e28a9fb43405f4dc48c44cec0e13a14c4a6ff1" + integrity sha512-V5V5Xa2Hp9i2XsbDALkBTeHXnBXh/lEmk9p22zdr7jtuOIY9TGhjK6vAvTpOOx9IKU4hJkRWZxn/HsvR1ELLtA== + dependencies: + msgpackr "^1.5.4" + node-addon-api "^4.3.0" + node-gyp-build-optional-packages "5.0.3" + ordered-binary "^1.2.4" + weak-lru-cache "^1.2.2" + optionalDependencies: + "@lmdb/lmdb-darwin-arm64" "2.5.2" + "@lmdb/lmdb-darwin-x64" "2.5.2" + "@lmdb/lmdb-linux-arm" "2.5.2" + "@lmdb/lmdb-linux-arm64" "2.5.2" + "@lmdb/lmdb-linux-x64" "2.5.2" + "@lmdb/lmdb-win32-x64" "2.5.2" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +msgpackr-extract@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.0.2.tgz#201a8d7ade47e99b3ba277c45736b00e195d4670" + integrity sha512-coskCeJG2KDny23zWeu+6tNy7BLnAiOGgiwzlgdm4oeSsTpqEJJPguHIuKZcCdB7tzhZbXNYSg6jZAXkZErkJA== + dependencies: + node-gyp-build-optional-packages "5.0.2" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "2.0.2" + +msgpackr@^1.5.4: + version "1.6.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.6.1.tgz#4f3c94d6a5b819b838ffc736eddaf60eba436d20" + integrity sha512-Je+xBEfdjtvA4bKaOv8iRhjC8qX2oJwpYH4f7JrG4uMVJVmnmkAT4pjKdbztKprGj3iwjcxPzb5umVZ02Qq3tA== + optionalDependencies: + msgpackr-extract "^2.0.2" + +node-addon-api@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-addon-api@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + +node-gyp-build-optional-packages@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz#3de7d30bd1f9057b5dfbaeab4a4442b7fe9c5901" + integrity sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g== + +node-gyp-build-optional-packages@5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" + integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== + +node-gyp-build@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +ordered-binary@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.3.0.tgz#a116d64c923278216e335602d279750b2ebd746e" + integrity sha512-knIeYepTI6BDAzGxqFEDGtI/iGqs57H32CInAIxEvAHG46vk1Di0CEpyc1A7iY39B1mfik3g3KLYwOTNnnMHLA== + +parcel@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.6.2.tgz#4585208880f42b24935c7d3ad167dcd42f8174eb" + integrity sha512-q6hrD3rm9M4S/VBVTcOs3pl55cnRwWfco7n8hZoAqnInWjWB+Khu92LRBMerMBTdE15Y+lJhWrXNdimDYstfhQ== + dependencies: + "@parcel/config-default" "2.6.2" + "@parcel/core" "2.6.2" + "@parcel/diagnostic" "2.6.2" + "@parcel/events" "2.6.2" + "@parcel/fs" "2.6.2" + "@parcel/logger" "2.6.2" + "@parcel/package-manager" "2.6.2" + "@parcel/reporter-cli" "2.6.2" + "@parcel/reporter-dev-server" "2.6.2" + "@parcel/utils" "2.6.2" + chalk "^4.1.0" + commander "^7.0.0" + get-port "^4.2.0" + v8-compile-cache "^2.0.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +posthtml-parser@^0.10.1: + version "0.10.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" + integrity sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg== + dependencies: + htmlparser2 "^7.1.1" + +posthtml-parser@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.11.0.tgz#25d1c7bf811ea83559bc4c21c189a29747a24b7a" + integrity sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw== + dependencies: + htmlparser2 "^7.1.1" + +posthtml-render@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-3.0.0.tgz#97be44931496f495b4f07b99e903cc70ad6a3205" + integrity sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA== + dependencies: + is-json "^2.0.1" + +posthtml@^0.16.4, posthtml@^0.16.5: + version "0.16.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.16.6.tgz#e2fc407f67a64d2fa3567afe770409ffdadafe59" + integrity sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ== + dependencies: + posthtml-parser "^0.11.0" + posthtml-render "^3.0.0" + +react-error-overlay@6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" + integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== + +react-refresh@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" + integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== + +regenerator-runtime@^0.13.7: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +semver@^5.7.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +svgo@^2.4.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +term-size@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== + +terser@^5.2.0: + version "5.14.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.1.tgz#7c95eec36436cb11cf1902cc79ac564741d19eca" + integrity sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== + +tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +update-browserslist-db@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" + integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +v8-compile-cache@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +weak-lru-cache@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" + integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== + +webextension-polyfill@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz#de6c1941d0ef1b0858b20e9c7b46bbc042c5a960" + integrity sha512-LTtHb0yR49xa9irkstDxba4GATDAcDw3ncnFH9RImoFwDlW47U95ME5sn5IiQX2ghfaECaf6xyXM8yvClIBkkw== + +xxhash-wasm@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" + integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==