diff --git a/background/background.ts b/background/background.ts index 23c580e..534c97e 100644 --- a/background/background.ts +++ b/background/background.ts @@ -1,113 +1,106 @@ -import { getTab, removeTab } from './tab' -import { getPaperDetails, loadPaperDetails } from './papers' -import { createPopup, updatePopupPapers } from './popup' -import { submitError } from '../common/error' -import { LOGGER } from '../common/logger' +import {getTab, removeTab} from './tab' +import {getPaperDetails, loadPaperDetails} from './papers' +import {createPopup, updatePopupPapers} from './popup' +import {submitError} from '../common/error' +import {LOGGER} from '../common/logger' +import {isString} from '../common/utils' chrome.tabs.onRemoved.addListener(async (tabId, removeInfo) => { try { await removeTab(tabId) } catch (err) { - submitError( - { - data: err.message, - tab: tabId, - pageDetails: removeInfo, - }, - err, - err.stack - ).then() + submitError({ + data: err.message, + tab: tabId, + pageDetails: removeInfo, + }, err, err.stack).then() } }) -async function processPage( - details: chrome.webNavigation.WebNavigationFramedCallbackDetails -) { - try { - let tab = await getTab(details.tabId, details.url) - if (tab.processed) { - LOGGER.log( - 'Ignoring page processing due to processed page', - details - ) - return - } +const IGNORE_URL_REGEX = /https:\/\/((papers\.labml\.ai)|((.*\.)?papers\.bar))|chrome:\/\/|about:blank/ - LOGGER.log('Processing page', tab, details) - await removeTab(details.tabId) - await getTab(details.tabId) - chrome.action.setBadgeText({ text: '' }).catch((err) => { - submitError( - { - data: err.message, - tab: details.tabId, - pageDetails: details, - }, - err, - err.stack - ).then() - }) - LOGGER.log('Processing Page', details) - if ( - details.url.match( - /https:\/\/((papers\.labml\.ai)|((.*\.)?papers\.bar))|chrome:\/\/|about:blank/g - ) == null - ) { - chrome.scripting - .insertCSS({ - target: { - tabId: details.tabId, - frameIds: [details.frameId], - }, - files: [ - 'assets/font-awesome/fontawesome.min.css', - 'assets/font-awesome/regular.min.css', - 'assets/font-awesome/brands.min.css', - 'assets/font-awesome/solid.min.css', - 'css/content_script.css', - ], - }) - .catch((err) => { - submitError( - { - data: err.message, - tab: details.tabId, - pageDetails: details, - }, - err, - err.stack - ).then() - }) - chrome.scripting - .executeScript({ - target: { - tabId: details.tabId, - frameIds: [details.frameId], - }, - files: ['js/content_script.js'], - }) - .catch((err) => { - submitError( - { - data: err.message, - tab: details.tabId, - pageDetails: details, - }, - err, - err.stack - ).then() - }) +function isIgnoreUrl(url: string) { + let m = url.match(IGNORE_URL_REGEX) + + return m != null +} + +class PageProcessor { + private tabId: number + private url: string + private frameIds: number[] + + constructor(tabId: number, url: string, frameIds: number[]) { + this.tabId = tabId + this.url = url + this.frameIds = frameIds + } + + async process() { + try { + let tab = await getTab(this.tabId, this.url) + if (tab.processed) { + LOGGER.log('Ignoring page processing due to processed page', this.tabId, this.url) + return + } + + LOGGER.log('Processing page', tab, this.url) + await removeTab(this.tabId) + await getTab(this.tabId) + chrome.action.setBadgeText({text: ''}).catch(this.handleError) + LOGGER.log('Processing Page', this.url) + if (this.url == '') { + this.addContentScripts() + } else if (!isIgnoreUrl(this.url)) { + // TODO: Test this + chrome.permissions.contains({origins: [this.url], permissions: ['scripting']}, + (res) => { + console.log('Permissions', this.url, res) + if (!res) { + return + } + this.addContentScripts() + }) + // chrome.permissions.getAll((p) => { + // console.log('All Perms', p) + // }) + } + } catch (err) { + this.handleError(err) } - } catch (err) { - submitError( - { - data: err.message, - tab: details.tabId, - pageDetails: details, - }, - err, - err.stack - ).then() + } + + addContentScripts() { + chrome.scripting + .insertCSS({ + target: { + tabId: this.tabId, + frameIds: this.frameIds, + }, + files: [ + 'assets/font-awesome/fontawesome.min.css', + 'assets/font-awesome/regular.min.css', + 'assets/font-awesome/brands.min.css', + 'assets/font-awesome/solid.min.css', + 'css/content_script.css', + ], + }).catch(this.handleError) + chrome.scripting + .executeScript({ + target: { + tabId: this.tabId, + frameIds: this.frameIds, + }, + files: ['js/content_script.js'], + }).catch(this.handleError) + } + + handleError = (err) => { + submitError({ + data: err.message, + tab: this.tabId, + url: this.url, + }, err, err.stack).then() } } @@ -117,61 +110,51 @@ chrome.webNavigation.onCompleted.addListener(async (details) => { LOGGER.log('Ignoring iframes') return } - await processPage(details) + let p = new PageProcessor(details.tabId, details.url, [details.frameId]) + await p.process() }) + chrome.webNavigation.onHistoryStateUpdated.addListener(async (details) => { LOGGER.log('Processing History Page', details) if (details.frameId > 0) { LOGGER.log('Ignoring iframes') return } - await processPage(details) + let p = new PageProcessor(details.tabId, details.url, [details.frameId]) + await p.process() }) -async function loadPaperAndUpdateData( - tabId: number, - link, - referer, - sender: chrome.runtime.MessageSender -) { +async function loadPaperAndUpdateData(tabId: number, links: string[], referer, sender: chrome.runtime.MessageSender) { try { let tab = await getTab(tabId) - await tab.add(link) - await loadPaperDetails(link, referer) + await tab.add(links) + await loadPaperDetails(links, referer) updateTab(tabId).then() } catch (e) { - submitError( - { error: e.message, tab: tabId, sender: sender }, - e, - e.stack - ).then() + submitError({ + error: e.message, + tab: tabId, + sender: sender, + }, e, e.stack).then() } } -function handleTabMessage( - message: any, - sender: chrome.runtime.MessageSender, - sendResponse: (response?: any) => void -) { +function handleTabMessage(message: any, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) { LOGGER.assert(message['method'] == 'paper' || message['method'] == 'error') if (message['method'] == 'error') { - submitError( - { - data: message['data'], - tabId: sender.tab.id, - sender: sender, - }, - message['event'], - message['stackTrace'] - ).then() + submitError({ + data: message['data'], + tabId: sender.tab.id, + sender: sender, + }, message['event'], message['stackTrace']).then() sendResponse(`done ${message['paperId']}`) return } // LOGGER.log(paperIds, message) - let link = message['link'] + let links = message['links'] let referer = message['referer'] let tabId = sender.tab.id - loadPaperAndUpdateData(tabId, link, referer, sender).then((value) => { + loadPaperAndUpdateData(tabId, links, referer, sender).then((value) => { sendResponse(`done ${message['paperId']}`) }) } @@ -179,7 +162,12 @@ function handleTabMessage( async function updateTab(tabId: number) { try { let tab = await getTab(tabId) - let papers = await getPaperDetails(tab.papers) + let papers + if (!tab.processed) { + papers = null + } else { + papers = await getPaperDetails(tab.papers) + } updatePopupPapers(tabId, papers) await tab.updatePapers(papers) await chrome.action.setBadgeText({ @@ -187,7 +175,10 @@ async function updateTab(tabId: number) { tabId: tabId, }) } catch (e) { - submitError({ error: e.message, tabId: tabId }, e, e.stack).then() + if (e != null && isString(e.message) && e.message.includes('No tab with id')) { + return // Ignore tab closing while it's being processed + } + submitError({error: e.message, tabId: tabId}, e, e.stack).then() } } @@ -201,38 +192,35 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { handleTabMessage(message, sender, sendResponse) } } catch (err) { - submitError( - { - data: err.message, - tab: sender.tab.id, - sender: sender, - }, - err, - err.stack - ).then() + submitError({ + data: err.message, + tab: sender.tab.id, + sender: sender, + }, err, err.stack).then() sendResponse(`failed ${message['paperId']}`) } return true }) -chrome.runtime.onConnect.addListener(function (port) { +chrome.runtime.onConnect.addListener(function(port) { LOGGER.assert(port.name === 'popup') createPopup(port) - .then((tabId) => { - updateTab(tabId).then() - }) - .catch((err) => { - submitError( - { - error: err.message, - }, - err, - err.stack - ).then() - }) + .then(async (tabId) => { + let tab = await getTab(tabId) + if (!tab.processed) { + let p = new PageProcessor(tabId, '', [0]) + await p.process() + } + updateTab(tabId).then() + }) + .catch((err) => { + submitError({ + error: err.message, + }, err, err.stack).then() + }) }) -self.addEventListener('error', function (ev) { +self.addEventListener('error', function(ev) { // Triggered when it's incapable of doing all the callbacks in a single render frame. Can be safely ignored // Ref: https://stackoverflow.com/a/50387233 if (ev.message.toLowerCase().includes('resizeobserver')) { @@ -243,39 +231,27 @@ self.addEventListener('error', function (ev) { if (ev.message.toLowerCase().search('script error') != -1) { return } - submitError( - { - error: ev.message, - }, - ev, - ev.error.stack - ).then() + submitError({ + error: ev.message, + }, ev, ev.error.stack).then() }) self.addEventListener('rejectionhandled', (ev) => { - submitError( - { - error: { - type: ev.reason.constructor.name, - name: ev.reason.name, - message: ev.reason.message, - }, + submitError({ + error: { + type: ev.reason.constructor.name, + name: ev.reason.name, + message: ev.reason.message, }, - ev, - ev.reason.stack - ).then() + }, ev, ev.reason.stack).then() }) self.addEventListener('unhandledrejection', (ev) => { - submitError( - { - error: { - type: ev.reason.constructor.name, - name: ev.reason.name, - message: ev.reason.message, - }, + submitError({ + error: { + type: ev.reason.constructor.name, + name: ev.reason.name, + message: ev.reason.message, }, - ev, - ev.reason.stack - ).then() + }, ev, ev.reason.stack).then() }) diff --git a/background/db.ts b/background/db.ts index 97ca23a..a5b79e0 100644 --- a/background/db.ts +++ b/background/db.ts @@ -1,5 +1,5 @@ -import { submitError } from '../common/error' -import { LOGGER } from '../common/logger' +import {submitError} from '../common/error' +import {LOGGER} from '../common/logger' class Db { private database: IDBDatabase @@ -11,13 +11,10 @@ class Db { let tabsRequest = indexedDB.open('Data', 2) tabsRequest.onerror = (ev) => { LOGGER.log('Problem opening DB') - submitError( - { - type: 'Problem opening DB', - name: ev.type, - }, - ev - ) + submitError({ + type: 'Problem opening DB', + name: ev.type, + }, ev) reject(ev) } @@ -54,13 +51,10 @@ class Db { LOGGER.log('Opened DB connection') this.database.onerror = (ev) => { LOGGER.log('Error on DB', ev) - submitError( - { - type: 'Error on DB', - name: ev.type, - }, - ev - ) + submitError({ + type: 'Error on DB', + name: ev.type, + }, ev) } resolve() } @@ -139,7 +133,7 @@ class Db { export let DB = new Db() DB.initDatabases() - .then((value) => { - LOGGER.log('DB initializing succeeded') - }) - .catch((reason) => LOGGER.error('DB initializing failed', reason)) + .then((value) => { + LOGGER.log('DB initializing succeeded') + }) + .catch((reason) => LOGGER.error('DB initializing failed', reason)) diff --git a/background/papers.ts b/background/papers.ts index 124f11f..6051d1c 100644 --- a/background/papers.ts +++ b/background/papers.ts @@ -1,6 +1,6 @@ -import { Paper, PaperModel } from '../common/models' -import { DB } from './db' -import { loadPaper } from '../common/utils' +import {Paper, PaperModel} from '../common/models' +import {DB} from './db' +import {loadPaper} from '../common/utils' let paperDetails: { [paperId: string]: Paper } = {} let paperLinks: { [link: string]: string } = {} @@ -9,8 +9,8 @@ export async function getPaperDetails(links: { [link: string]: string }) { let details = {} for (let link in links) { if ( - paperLinks[link] == null || - paperDetails[paperLinks[link]] == null + paperLinks[link] == null || + paperDetails[paperLinks[link]] == null ) { let paper = new Paper() let data @@ -31,22 +31,37 @@ export async function getPaperDetails(links: { [link: string]: string }) { return details } -export async function loadPaperDetails(link: string, referer: string) { - if ( - paperLinks[link] == null || - paperDetails[paperLinks[link]] == null || - paperDetails[paperLinks[link]].data == null - ) { - let data = await loadPaper(link, referer) - let paper = new Paper() - if (data != null) { - paper.loadFrom(data) +function shouldProcessLink(link: string) { + if (paperLinks[link] == null) { + return true + } + if (paperDetails[paperLinks[link]] == null) { + return true + } + return paperDetails[paperLinks[link]].data == null + +} + +export async function loadPaperDetails(links: string[], referer: string) { + let linksToProcess = [] + links.forEach(link => { + if (shouldProcessLink(link)) { + linksToProcess.push(link) } - let id = paper.paperId == null ? link : paper.paperId - paperDetails[id] = paper - paperLinks[link] = id - if (paper.paperId != null) { - await DB.upsertRecord('papers', paper.toJSON()) + }) + if (linksToProcess.length > 0) { + let data = await loadPaper(links, referer) + for (let link of linksToProcess) { + let paper = new Paper() + if (data[link] != null) { + paper.loadFrom(data[link]) + } + let id = paper.paperId == null ? link : paper.paperId + paperDetails[id] = paper + paperLinks[link] = id + if (paper.paperId != null) { + await DB.upsertRecord('papers', paper.toJSON()) + } } } } diff --git a/background/popup.ts b/background/popup.ts index b21c4c1..945d496 100644 --- a/background/popup.ts +++ b/background/popup.ts @@ -1,6 +1,6 @@ -import { Paper } from '../common/models' -import { submitError } from '../common/error' -import { LOGGER } from '../common/logger' +import {Paper} from '../common/models' +import {submitError} from '../common/error' +import {LOGGER} from '../common/logger' let popupTabs: { [tabId: number]: BgPopup } = {} @@ -11,10 +11,7 @@ export async function createPopup(port: chrome.runtime.Port): Promise { return p.tabId } -export function updatePopupPapers( - tabId: number, - papers: { [link: string]: Paper } -) { +export function updatePopupPapers(tabId: number, papers?: { [link: string]: Paper }) { if (popupTabs[tabId] == null) { return } @@ -39,35 +36,31 @@ class BgPopup { this.tabId = (await this.getCurrentTab()).id } } catch (e) { - submitError( - { - error: e.message, - tabId: this.tabId, - }, - e, - e.stack - ).then() + submitError({ + error: e.message, + tabId: this.tabId, + }, e, e.stack).then() } popupTabs[this.tabId] = this } - updatePapers(papers: { [link: string]: Paper }) { + updatePapers(papers?: { [link: string]: Paper }) { + if (papers == null) { + this.port.postMessage({}) + return + } let res = {} for (let link in papers) { - if ( - papers[link] != null && - papers[link].paperId != null && - res[papers[link].paperId] == null - ) { + if (papers[link] != null && papers[link].paperId != null && res[papers[link].paperId] == null) { res[papers[link].paperId] = papers[link].toJSON() } } - this.port.postMessage(res) + this.port.postMessage({papers: res}) } private async getCurrentTab(): Promise { return new Promise((resolve, reject) => { - chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { try { if (tabs.length !== 1) { reject(tabs) @@ -75,14 +68,10 @@ class BgPopup { resolve(tabs[0]) } } catch (err) { - submitError( - { - data: err.message, - tabs: tabs, - }, - err, - err.stack - ).then() + submitError({ + data: err.message, + tabs: tabs, + }, err, err.stack).then() } }) }) diff --git a/background/tab.ts b/background/tab.ts index d1597f7..9720c6d 100644 --- a/background/tab.ts +++ b/background/tab.ts @@ -1,19 +1,40 @@ -import { Paper } from '../common/models' -import { submitError } from '../common/error' -import { DB } from './db' -import { LOGGER } from '../common/logger' +import {Paper} from '../common/models' +import {submitError} from '../common/error' +import {DB} from './db' +import {LOGGER} from '../common/logger' let bgTabs: { [tabId: number]: BgTab } = {} +let lockedTabs: Map void)[]> = new Map void)[]>() -export async function getTab( - tabId: number, - tabUrl: string = null -): Promise { +async function obtainLock(tabId: number) { + if (lockedTabs.has(tabId)) { + return new Promise((resolve) => { + let callbacks = lockedTabs.get(tabId) + callbacks.push(() => { + obtainLock(tabId).then(() => { + resolve() + }) + }) + lockedTabs.set(tabId, callbacks) + }) + } + + lockedTabs.set(tabId, []) + return +} + +function releaseLock(tabId: number) { + let callbacks = lockedTabs.get(tabId) + lockedTabs.delete(tabId) + if (callbacks != null) { + callbacks.forEach((value) => value()) + } +} + +export async function getTab(tabId: number, tabUrl: string = null): Promise { LOGGER.log('Requested tab', tabId, bgTabs, JSON.stringify(bgTabs)) - if ( - bgTabs[tabId] == null || - (tabUrl != null && bgTabs[tabId].tabUrl != tabUrl) - ) { + await obtainLock(tabId) + if (bgTabs[tabId] == null || (tabUrl != null && bgTabs[tabId].tabUrl != tabUrl)) { bgTabs[tabId] = new BgTab(tabId, tabUrl) let data = await DB.getRecord('tabs', tabId) if (data != null && (tabUrl == null || data.tabUrl == tabUrl)) { @@ -22,16 +43,19 @@ export async function getTab( LOGGER.log('Created new tab', tabId, bgTabs, JSON.stringify(bgTabs)) } + releaseLock(tabId) return bgTabs[tabId] } export async function removeTab(tabId: number) { LOGGER.log('Tab remove requested', tabId, bgTabs, JSON.stringify(bgTabs)) + await obtainLock(tabId) if (bgTabs[tabId] != null) { delete bgTabs[tabId] await DB.deleteRecord('tabs', tabId) LOGGER.log('Tab removed', tabId, bgTabs, JSON.stringify(bgTabs)) } + releaseLock(tabId) } interface BgTabModel { @@ -41,6 +65,17 @@ interface BgTabModel { processed: boolean } +function errorIncludesString(err: any, str: string) { + if (err == null) { + return false + } + if (typeof err.message !== 'string' && !(err.message instanceof String)) { + return false + } + + return err.message.includes(str) +} + class BgTab { tabId: number tabUrl?: string @@ -74,23 +109,27 @@ class BgTab { } } - async add(link: string) { - this.processed = true - if (this.papers[link] != null) { + async add(links: string | string[]) { + if (links == null) { return } - this.papers[link] = '' + if (typeof links === 'string') { + links = [links] + } + this.processed = true + links.forEach(link => { + if (this.papers[link] != null) { + return + } + this.papers[link] = '' + }) await DB.upsertRecord('tabs', this.toJSON()) } async updatePapers(papersDetails: { [link: string]: Paper }) { let res = {} for (let link in this.papers) { - if ( - (this.papers[link] == null || this.papers[link] == '') && - papersDetails[link] != null && - papersDetails[link].paperId != null - ) { + if (!this.isPaperFetched(link) && this.hasPaperDetails(papersDetails, link)) { this.papers[link] = papersDetails[link].paperId this.uniquePapers.add(papersDetails[link].paperId) } @@ -104,28 +143,28 @@ class BgTab { try { chrome.tabs.sendMessage(this.tabId, res, (response) => { if (chrome.runtime.lastError != null) { - submitError( - { + // Ignore tab closing while it's being processed?? + if (!errorIncludesString(chrome.runtime.lastError, 'Receiving end does not exist')) { + submitError({ error: chrome.runtime.lastError, tabId: this.tabId, - }, - {} - ) + }, {}) + } resolve({}) return } resolve(response) }) } catch (err) { - submitError( - { + // Ignore tab closing while it's being processed?? + if (!errorIncludesString(err, 'Receiving end does not exist')) { + submitError({ data: err.message, tab: this.tabId, payload: res, - }, - err, - err.stack - ).then() + }, err, err.stack).then() + } + resolve({}) } }) } @@ -138,4 +177,19 @@ class BgTab { processed: this.processed, } } + + private isPaperFetched(link: string) { + if (this.papers[link] == null) { + return false + } + return this.papers[link] != '' + } + + private hasPaperDetails(papersDetails: { [link: string]: Paper }, link: string) { + if (papersDetails[link] == null) { + return false + } + return papersDetails[link].paperId != null + + } } diff --git a/common/error.ts b/common/error.ts index 16effa1..d08776d 100644 --- a/common/error.ts +++ b/common/error.ts @@ -1,10 +1,12 @@ -import { ajaxCall } from './utils' +import {ajaxCall} from './utils' +import {LOGGER} from "./logger"; export async function submitError( data: Object, event: any, stackTrace?: any ): Promise { + LOGGER.error(data, event, stackTrace) let e = event.hasOwnProperty('reason') ? event.reason : event if (stackTrace == null) { let e = new Error() @@ -26,7 +28,8 @@ export async function submitError( } try { msg['rawData'] = JSON.stringify(e) - } catch (e) {} + } catch (e) { + } let jsonData = JSON.stringify(msg, null, '\t') if ( jsonData.includes( @@ -36,10 +39,14 @@ export async function submitError( ) { return {} } - let res: any = await ajaxCall( - 'POST', - `https://papers.labml.ai/api/v1/error`, - { error: jsonData } - ) - return res + try { + let res: any = await ajaxCall( + 'POST', + `https://papers.labml.ai/api/v1/error`, + {error: jsonData} + ) + return res + } catch (e) { + return null + } } diff --git a/common/paper_item.ts b/common/paper_item.ts index 7a36100..8736d27 100644 --- a/common/paper_item.ts +++ b/common/paper_item.ts @@ -84,7 +84,7 @@ export class PapersListItemView { ) if (this.onClose != null) { - let asd = $('span', '.close-btn.fas.fa-times', { + $('span', '.close-btn.fas.fa-times', { on: { click: this.onClose, }, diff --git a/common/settings.ts b/common/settings.ts index e1b236f..a4c13a9 100644 --- a/common/settings.ts +++ b/common/settings.ts @@ -11,6 +11,6 @@ export enum LinkType { } export let DEFAULT_SETTINGS: Settings = { - linkType: LinkType.emoji, + linkType: LinkType.none, emoji: '📎', } diff --git a/common/utils.ts b/common/utils.ts index 2222c31..47d51ec 100644 --- a/common/utils.ts +++ b/common/utils.ts @@ -1,12 +1,7 @@ -import { submitError } from './error' -import { LOGGER } from './logger' +import {submitError} from './error' +import {LOGGER} from './logger' -export async function ajaxCall( - method: string, - url: string, - data: object = null, - headers: { [key: string]: string } = null -) { +export async function ajaxCall(method: string, url: string, data: object = null, headers: { [key: string]: string } = null) { return new Promise((resolve, reject) => { let body if (data != null) { @@ -22,14 +17,18 @@ export async function ajaxCall( accept: 'application/json', }, body: body, - }).then((response) => { - // LOGGER.log('response', response) - if (response.status >= 200 && response.status < 300) { - resolve(response.json()) - } else { - reject(`${response.status} ${url} ${response.text()}`) - } }) + .then((response) => { + // LOGGER.log('response', response) + if (response.status >= 200 && response.status < 300) { + resolve(response.json()) + } else { + reject(`${response.status} ${url} ${response.text()}`) + } + }) + .catch((reason) => { + reject(reason) + }) }) } @@ -45,8 +44,8 @@ export function numberWithLetter(x: number): string { } export function valueOrDefault( - x: string | null | undefined, - defaultValue: string + x: string | null | undefined, + defaultValue: string, ) { if (x == null) { return defaultValue @@ -57,11 +56,7 @@ export function valueOrDefault( return x } -export function numberOrDefault( - x: number | null | undefined, - defaultValue: string, - minVal: number = 0 -) { +export function numberOrDefault(x: number | null | undefined, defaultValue: string, minVal: number = 0) { if (x == null) { return defaultValue } @@ -79,34 +74,43 @@ export function formatDate(dateTime: number) { return date.toDateString() } -export async function loadPaper(link: string, referer: string) { +export async function loadPaper(links: string[], referer: string) { try { let res: any = await ajaxCall( - 'POST', - `https://papers.labml.ai/extension/v1/papers`, - { - urls: [link], - }, - { - 'source-page': referer, - } + 'POST', + `https://papers.labml.ai/extension/v1/papers`, + { + urls: links, + }, + { + 'source-page': referer, + }, ) LOGGER.log('res', res) - if (res[link] != null && res[link]['paper_key'] != null) { - return { - data: res[link], - paperId: res[link]['paper_key'], + let returnVal = {} + links.forEach((link) => { + if (res[link] != null && res[link]['paper_key'] != null) { + returnVal[link] = { + data: res[link], + paperId: res[link]['paper_key'], + } + } else { + returnVal[link] = null } - } - return null + }) + + return returnVal } catch (e) { - submitError( - { - error: e.message, - }, - e, - e.stack - ).then() + submitError({ + error: e.message, + }, e, e.stack).then() } return null } + +export function isString(s) { + if (typeof s === 'string') { + return true + } + return s instanceof String +} diff --git a/content_scripts/content.ts b/content_scripts/content.ts index abf291c..cf3a9a7 100644 --- a/content_scripts/content.ts +++ b/content_scripts/content.ts @@ -1,6 +1,6 @@ -import { addIndicator, requestPaper, updateIndicator } from './papers' -import { DEFAULT_SETTINGS, Settings } from '../common/settings' -import { LOGGER } from '../common/logger' +import {addIndicator, requestPaper, updateIndicator} from './papers' +import {DEFAULT_SETTINGS, Settings} from '../common/settings' +import {LOGGER} from '../common/logger' const SUPPORTED_DOMAINS = [ 'arxiv.org', @@ -42,12 +42,17 @@ function validateLink(link: string) { let settings: Settings function markAllLinks(anchors: NodeListOf) { + let arr: { a: HTMLAnchorElement, link: string }[] = [] for (let a of anchors) { let link = extractPaperLink(a) if (link != null) { - addIndicator(a, link, settings) + arr.push({a: a, link: link}) } } + requestPaper(arr.map(value => value.link)) + arr.forEach(value => { + addIndicator(value.a, value.link, settings) + }) } function setup() { @@ -55,9 +60,7 @@ function setup() { LOGGER.log('ignoring link parsing') if (window['labml_links'] != null) { LOGGER.log('using the old links', window['labml_links']) - for (let link in window['labml_links']) { - requestPaper(link) - } + requestPaper(window['labml_links']) } return } @@ -70,59 +73,59 @@ function setup() { a.classList.remove('paper-link') } } - chrome.storage.sync.get({ settings: DEFAULT_SETTINGS }, (items) => { + chrome.storage.sync.get({settings: DEFAULT_SETTINGS}, (items) => { LOGGER.log('Settings', items) settings = items.settings if (validateLink(document.location.href) != null) { - requestPaper(document.location.href) + requestPaper([document.location.href]) } markAllLinks(document.querySelectorAll('a')) document.addEventListener( - 'DOMNodeInserted', - function (e) { - let node = e.target - // LOGGER.log(node) - if (typeof node.querySelectorAll === 'function') { - setTimeout(() => { - try { - markAllLinks(node.querySelectorAll('a')) - } catch (err) { - LOGGER.error('Send Error 1', err) - chrome.runtime.sendMessage({ - method: 'error', - data: { - error: err.message, - }, - event: err, - stackTrace: err.stack, - }) - } - }, 300) - } - }, - false + 'DOMNodeInserted', + function(e) { + let node = e.target + // LOGGER.log(node) + if (typeof node.querySelectorAll === 'function') { + setTimeout(() => { + try { + markAllLinks(node.querySelectorAll('a')) + } catch (err) { + LOGGER.error('Send Error 1', err) + chrome.runtime.sendMessage({ + method: 'error', + data: { + error: err.message, + }, + event: err, + stackTrace: err.stack, + }) + } + }, 300) + } + }, + false, ) chrome.runtime.onMessage.addListener( - (message, sender, sendResponse) => { - try { - window['labml_links'] = message - updateIndicator(message) - LOGGER.log('tab message', message) - sendResponse('got it') - } catch (err) { - LOGGER.error('Send Error 2', err) - chrome.runtime.sendMessage({ - method: 'error', - data: { - error: err.message, - }, - event: err, - stackTrace: err.stack, - }) - } - } + (message, sender, sendResponse) => { + try { + window['labml_links'] = message + updateIndicator(message) + LOGGER.log('tab message', message) + sendResponse('got it') + } catch (err) { + LOGGER.error('Send Error 2', err) + chrome.runtime.sendMessage({ + method: 'error', + data: { + error: err.message, + }, + event: err, + stackTrace: err.stack, + }) + } + }, ) }) } diff --git a/content_scripts/papers.ts b/content_scripts/papers.ts index eb71a4c..7a3302d 100644 --- a/content_scripts/papers.ts +++ b/content_scripts/papers.ts @@ -1,7 +1,7 @@ -import { Paper, PaperModel } from '../common/models' -import { DetailView } from './details_view' -import { LinkType, Settings } from '../common/settings' -import { LOGGER } from '../common/logger' +import {Paper, PaperModel} from '../common/models' +import {DetailView} from './details_view' +import {LinkType, Settings} from '../common/settings' +import {LOGGER} from '../common/logger' let links: Btn[] = [] @@ -15,11 +15,11 @@ class Btn { private addedIndicator: boolean constructor( - paper: Paper, - link: string, - container: HTMLElement, - linkType: LinkType, - emoji: string + paper: Paper, + link: string, + container: HTMLElement, + linkType: LinkType, + emoji: string, ) { this.paper = paper this.link = link @@ -67,12 +67,12 @@ class Btn { let btn = document.createElement('div') btn.classList.add('paper-link-btn') LOGGER.log( - getComputedStyle(this.container).getPropertyValue('line-height') + getComputedStyle(this.container).getPropertyValue('line-height'), ) let height = - parseFloat( - getComputedStyle(this.container).getPropertyValue('font-size') - ) - 2 + parseFloat( + getComputedStyle(this.container).getPropertyValue('font-size'), + ) - 2 btn.style.height = `${height}px` btn.style.width = `${height}px` btn.style.borderRadius = `${height / 2}px` @@ -99,8 +99,8 @@ class Btn { private addIndicator(parent: Node) { if (parent.firstChild.nodeName === 'BR') { if ( - parent.firstChild.nextSibling != null && - parent.firstChild.nextSibling.nodeName.match(/H[1-6]/) != null + parent.firstChild.nextSibling != null && + parent.firstChild.nextSibling.nodeName.match(/H[1-6]/) != null ) { this.addIndicator(parent.firstChild.nextSibling) return @@ -112,31 +112,31 @@ class Btn { } } -export function requestPaper(link: string) { +export function requestPaper(links: string[]) { chrome.runtime.sendMessage( - { method: 'paper', link: link, referer: document.location.href }, - (res) => { - let err = chrome.runtime.lastError - if (err != null) { - LOGGER.error('Send Error 3', link, err) - chrome.runtime.sendMessage({ - method: 'error', - data: { - error: err.message, - }, - event: err, - stackTrace: 'err.stack', - }) - } - LOGGER.log(res) - } + {method: 'paper', links: links, referer: document.location.href}, + (res) => { + let err = chrome.runtime.lastError + if (err != null) { + LOGGER.error('Send Error 3', links, err) + chrome.runtime.sendMessage({ + method: 'error', + data: { + error: err.message, + }, + event: err, + stackTrace: 'err.stack', + }) + } + LOGGER.log(res) + }, ) } export function addIndicator( - a: HTMLAnchorElement, - link: string, - settings: Settings + a: HTMLAnchorElement, + link: string, + settings: Settings, ) { try { if (a.classList.contains('paper-link')) { @@ -146,14 +146,13 @@ export function addIndicator( if (getComputedStyle(a).getPropertyValue('display') === 'inline') { a.style.display = 'inline-block' } - requestPaper(link) let btn = new Btn( - new Paper(), - link, - a, - settings.linkType, - settings.emoji + new Paper(), + link, + a, + settings.linkType, + settings.emoji, ) links.push(btn) diff --git a/css/popup.scss b/css/popup.scss index 87d5603..c37a6e6 100644 --- a/css/popup.scss +++ b/css/popup.scss @@ -3,8 +3,8 @@ html, body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', - 'Segoe UI Symbol'; + Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol'; } body { @@ -14,6 +14,7 @@ body { background: #ecf0f3; margin: 0; } + #papers-bar-container { .list-group-item { margin-bottom: 10px; @@ -34,4 +35,51 @@ body { .list-container { padding: 8px 8px 50px; } + + /* Loader */ + .loader-container { + width: 100%; + height: 500px; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + + .center-container { + text-align: center; + + .loader { + border-radius: 50%; + width: 40px; + height: 40px; + -webkit-animation: spin 0.5s linear infinite; /* Safari */ + animation: spin 0.5s linear infinite; + display: inline-block; + } + } + } + + /* Safari */ + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + .spin { + -webkit-animation: spin 2s linear infinite; /* Safari */ + animation: spin 2s linear infinite; + } } diff --git a/css/theme.scss b/css/theme.scss index e9b1c27..6af391e 100644 --- a/css/theme.scss +++ b/css/theme.scss @@ -119,4 +119,9 @@ .footer { box-shadow: 3px 3px 6px #d1d9e6, -3px -3px 6px #fff; } + + .loader { + border: 3px solid #f3f3f3; + border-top: 3px solid #eb6134; + } } diff --git a/manifest.json b/manifest.json index e3b8f8b..98af252 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,7 @@ "48": "assets/icon48.png", "128": "assets/icon128.png" }, - "version": "1.0.2", + "version": "1.1.0", "description": "\uD83D\uDD0E View information about research papers linked from websites you visit.", "background": { "service_worker": "js/background.js" @@ -16,13 +16,20 @@ "browser_style": false, "open_in_tab": true }, - "permissions": ["activeTab", "scripting", "webNavigation", "storage"], + "permissions": [ + "activeTab", + "scripting", + "webNavigation", + "storage" + ], "action": { "default_title": "Papers", "default_icon": "assets/icon48.png", "default_popup": "html/popup.html" }, - "host_permissions": [""], + "host_permissions": [ + "" + ], "web_accessible_resources": [ { "resources": ["assets/font-awesome/*"], diff --git a/package-lock.json b/package-lock.json index 65a8afb..9113569 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "papers_extension", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "papers_extension", - "version": "1.0.0", + "version": "1.1.0", "devDependencies": { "@types/chrome": "latest", "copy-webpack-plugin": "^10.0.0", "css-loader": "^6.5.1", "husky": "^7.0.4", "lint-staged": "^12.1.2", + "merge-jsons-webpack-plugin": "^2.0.1", "mini-css-extract-plugin": "^2.4.5", "prettier": "2.5.1", "sass": "^1.44.0", @@ -514,6 +515,12 @@ "node": ">=8" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -523,6 +530,16 @@ "node": ">=8" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -713,6 +730,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, "node_modules/copy-webpack-plugin": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.0.0.tgz", @@ -1090,6 +1113,12 @@ "node": ">=8" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1122,6 +1151,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1263,6 +1309,22 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -1721,6 +1783,15 @@ "node": ">=10" } }, + "node_modules/merge-jsons-webpack-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/merge-jsons-webpack-plugin/-/merge-jsons-webpack-plugin-2.0.1.tgz", + "integrity": "sha512-8GP8rpOX3HSFsm7Gx+b3OAQR7yhgeAQvMqcZOJ+/cQIrqdak1c42a2T2vyeee8pzGPBf7pMLumthPh4CHgv2BA==", + "dev": true, + "dependencies": { + "glob": "7.1.1" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1851,6 +1922,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1911,6 +1994,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -1986,6 +2078,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2506,12 +2607,12 @@ } }, "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/source-map-js": { @@ -2533,6 +2634,15 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -2666,13 +2776,13 @@ } } }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "node_modules/terser-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/through": { @@ -2980,6 +3090,12 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -3402,12 +3518,28 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -3548,6 +3680,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, "copy-webpack-plugin": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.0.0.tgz", @@ -3827,6 +3965,12 @@ "path-exists": "^4.0.0" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -3846,6 +3990,20 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -3943,6 +4101,22 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -4273,6 +4447,15 @@ "yallist": "^4.0.0" } }, + "merge-jsons-webpack-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/merge-jsons-webpack-plugin/-/merge-jsons-webpack-plugin-2.0.1.tgz", + "integrity": "sha512-8GP8rpOX3HSFsm7Gx+b3OAQR7yhgeAQvMqcZOJ+/cQIrqdak1c42a2T2vyeee8pzGPBf7pMLumthPh4CHgv2BA==", + "dev": true, + "requires": { + "glob": "7.1.1" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4366,6 +4549,15 @@ } } }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4411,6 +4603,15 @@ "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", "dev": true }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -4459,6 +4660,12 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4787,9 +4994,9 @@ } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true }, "source-map-js": { @@ -4806,6 +5013,14 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "string-argv": { @@ -4864,14 +5079,6 @@ "commander": "^2.20.0", "source-map": "~0.7.2", "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } } }, "terser-webpack-plugin": { @@ -4885,6 +5092,14 @@ "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", "terser": "^5.7.2" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "through": { @@ -5099,6 +5314,12 @@ } } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 9686c56..dece027 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "author": "Varuna Jayasiri", - "dependencies": {}, "description": "papers.labml.ai chrome extension", "devDependencies": { "@types/chrome": "latest", @@ -8,6 +7,7 @@ "css-loader": "^6.5.1", "husky": "^7.0.4", "lint-staged": "^12.1.2", + "merge-jsons-webpack-plugin": "^2.0.1", "mini-css-extract-plugin": "^2.4.5", "prettier": "2.5.1", "sass": "^1.44.0", @@ -24,8 +24,8 @@ "build": "webpack --config webpack/webpack.prod.js", "prepare": "husky install" }, - "version": "1.0.2", + "version": "1.1.0", "lint-staged": { - "**/*": "prettier --check" + "**/*": "" } } diff --git a/popup/popup.ts b/popup/popup.ts index 8005004..21903ee 100644 --- a/popup/popup.ts +++ b/popup/popup.ts @@ -1,8 +1,9 @@ -import { Weya as $ } from '../lib/weya/weya' -import { clearChildElements } from '../common/dom_utils' -import { PapersListItemView } from '../common/paper_item' -import { submitError } from '../common/error' -import { CustomIconButton } from '../common/ui/button' +import {Weya as $} from '../lib/weya/weya' +import {clearChildElements} from '../common/dom_utils' +import {PapersListItemView} from '../common/paper_item' +import {submitError} from '../common/error' +import {CustomIconButton} from '../common/ui/button' +import {PaperModel} from '../common/models' function log(message: string, data: Object) { if (process.env.NODE_ENV === 'development') { @@ -10,53 +11,85 @@ function log(message: string, data: Object) { } } -document.addEventListener('DOMContentLoaded', function () { - try { - log('popup loaded', new Date()) - let port = chrome.runtime.connect({ name: 'popup' }) - let containerElement = document.getElementById('papers-bar-container') - port.onMessage.addListener(function (msg) { - try { - log('port', msg) - clearChildElements(containerElement) - $(containerElement, ($) => { - $('div', '.list-container', ($) => { - for (let paperId in msg) { - if ( - msg[paperId] != null && - msg[paperId].data != null - ) { - new PapersListItemView({ - item: msg[paperId].data, - onClick: (elem) => { - window.open( - `https://papers.labml.ai/paper/${elem.paper_key}` - ) - }, - }).render($) - } - } - }) - $('div', '.footer', ($) => { - new CustomIconButton({ - icon: '.fas.fa-sliders-h', - text: 'Settings', - onButtonClick: () => { - chrome.runtime.openOptionsPage() - }, - }).render($) +interface ResponseMsg { + papers?: { [link: string]: PaperModel } +} + +class Popup { + private port: chrome.runtime.Port + private readonly containerElement: HTMLElement + private listContainer: HTMLDivElement + + constructor() { + this.port = chrome.runtime.connect({name: 'popup'}) + this.containerElement = document.getElementById('papers-bar-container') + this.port.onMessage.addListener(this.onMessage) + $(this.containerElement, ($) => { + this.listContainer = $('div', '.list-container', $ => { + $('div', '.loader-container', $ => { + $('div', '.center-container', $ => { + $('div.loader', '') }) }) - } catch (err) { - submitError({ error: err.message }, err, err.stack).then() + }) + $('div', '.footer', ($) => { + new CustomIconButton({ + icon: '.fas.fa-sliders-h', + text: 'Settings', + onButtonClick: () => { + chrome.runtime.openOptionsPage() + }, + }).render($) + }) + }) + } + + renderPapers(papers: { [link: string]: PaperModel }) { + clearChildElements(this.listContainer) + $(this.listContainer, ($) => { + for (let paperId in papers) { + if (papers[paperId] != null && papers[paperId].data != null) { + new PapersListItemView({ + item: papers[paperId].data, + onClick: (elem) => { + window.open(`https://papers.labml.ai/paper/${elem.paper_key}`) + }, + }).render($) + } } }) + } + + onMessage = (msg) => { + try { + this.handleMessage(msg) + } catch (err) { + submitError({error: err.message}, err, err.stack).then() + } + + } + + private handleMessage(msg: ResponseMsg) { + log('port', msg) + if (msg.papers == null) { + log('Tab not processed', null) + } else { + this.renderPapers(msg.papers) + } + } +} + +function load() { + try { + new Popup() } catch (err) { - submitError({ error: err.message }, err, err.stack).then() + submitError({error: err.message}, err, err.stack).then() } -}) +} + +document.addEventListener('DOMContentLoaded', load) -window.addEventListener('error', function (ev) { +window.addEventListener('error', function(ev) { // Triggered when it's incapable of doing all the callbacks in a single render frame. Can be safely ignored // Ref: https://stackoverflow.com/a/50387233 if (ev.message.toLowerCase().includes('resizeobserver')) { @@ -69,40 +102,31 @@ window.addEventListener('error', function (ev) { } log('Error', ev) submitError( - { - error: ev.message, - }, - ev, - ev.error.stack - ).then() + { + error: ev.message, + }, ev, ev.error.stack).then() }) window.addEventListener('rejectionhandled', (ev) => { log('HandledRejection', ev) submitError( - { - error: { - type: ev.reason.constructor.name, - name: ev.reason.name, - message: ev.reason.message, - }, - }, - ev, - ev.reason.stack - ).then() + { + error: { + type: ev.reason.constructor.name, + name: ev.reason.name, + message: ev.reason.message, + }, + }, ev, ev.reason.stack).then() }) window.addEventListener('unhandledrejection', (ev) => { log('UnhandledRejection', ev) submitError( - { - error: { - type: ev.reason.constructor.name, - name: ev.reason.name, - message: ev.reason.message, - }, - }, - ev, - ev.reason.stack - ).then() + { + error: { + type: ev.reason.constructor.name, + name: ev.reason.name, + message: ev.reason.message, + }, + }, ev, ev.reason.stack).then() }) diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index e62bdaf..1471238 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -55,7 +55,6 @@ module.exports = { patterns: [ { from: 'assets', to: '../assets' }, { from: 'html', to: '../html' }, - { from: 'manifest.json', to: '../' }, ], options: {}, }), diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js index 1b7c855..13b012b 100644 --- a/webpack/webpack.dev.js +++ b/webpack/webpack.dev.js @@ -1,7 +1,16 @@ const { merge } = require('webpack-merge') const common = require('./webpack.common.js') +var MergeJsonWebpackPlugin = require("merge-jsons-webpack-plugin") module.exports = merge(common, { devtool: 'inline-source-map', mode: 'development', + plugins: [ + new MergeJsonWebpackPlugin({ + files: ['./manifest.json', './manifest.dev.json'], + output: { + fileName: "../manifest.json", + }, + }) + ], }) diff --git a/webpack/webpack.prod.js b/webpack/webpack.prod.js index 41c9791..2f66553 100644 --- a/webpack/webpack.prod.js +++ b/webpack/webpack.prod.js @@ -1,7 +1,16 @@ const { merge } = require('webpack-merge') const common = require('./webpack.common.js') +var MergeJsonWebpackPlugin = require("merge-jsons-webpack-plugin") module.exports = merge(common, { devtool: 'inline-source-map', mode: 'production', + plugins: [ + new MergeJsonWebpackPlugin({ + files: ['./manifest.json', './manifest.prod.json'], + output: { + fileName: "../manifest.json", + }, + }) + ], })