diff --git a/pkg/extension/src/manifest.json b/pkg/extension/src/manifest.json
index 25b2d5a32..7bad58854 100644
--- a/pkg/extension/src/manifest.json
+++ b/pkg/extension/src/manifest.json
@@ -2,8 +2,8 @@
"manifest_version": 2,
"name": "process.env.EXTENSION_NAME",
"short_name": "process.env.EXTENSION_NAME",
- "version": "0.1.26",
- "description": "Save articles to your Omnivore library",
+ "version": "2.0.2",
+ "description": "Save PDFs and Articles to your Omnivore library",
"author": "Omnivore Media, Inc",
"default_locale": "en",
"developer": {
@@ -21,14 +21,17 @@
"128": "/images/extension/icon-128.png",
"256": "/images/extension/icon-256.png"
},
-
- "permissions": ["activeTab", "storage", "contextMenus", "https://*/**", "http://*/**"],
-
+ "permissions": [
+ "activeTab",
+ "storage",
+ "contextMenus",
+ "https://*/**",
+ "http://*/**"
+ ],
"background": {
"page": "/views/background.html",
"persistent": true
},
-
"minimum_chrome_version": "21",
"minimum_opera_version": "15",
"applications": {
@@ -41,25 +44,31 @@
"id": "save-extension@omnivore.app"
}
},
-
"content_scripts": [
{
- "matches": ["https://*/**", "http://*/**"],
+ "matches": [
+ "https://*/**",
+ "http://*/**"
+ ],
"js": [
- "/scripts/constants.js",
+ "/scripts/common.js",
+ "/scripts/content/toast.js",
"/scripts/content/page-info.js",
"/scripts/content/prepare-content.js",
- "/scripts/content/toast.js",
"/scripts/content/content-listener-script.js"
]
},
{
- "matches": ["https://*/**", "http://*/**"],
- "js": ["/scripts/content/grab-iframe-content.js"],
+ "matches": [
+ "https://*/**",
+ "http://*/**"
+ ],
+ "js": [
+ "/scripts/content/grab-iframe-content.js"
+ ],
"all_frames": true
}
],
-
"browser_action": {
"default_icon": {
"16": "/images/toolbar/icon-16.png",
@@ -71,15 +80,15 @@
},
"default_title": "Omnivore Save Article"
},
-
"commands": {
"_execute_browser_action": {
- "suggested_key": {
- "default": "Alt + O"
- },
- "description": "Save the current tab to Omnivore"
+ "suggested_key": {
+ "default": "Alt + O"
+ },
+ "description": "Save the current tab to Omnivore"
}
},
-
- "web_accessible_resources": ["views/cta-popup.html"]
-}
+ "web_accessible_resources": [
+ "views/toast.html"
+ ]
+}
\ No newline at end of file
diff --git a/pkg/extension/src/scripts/api.js b/pkg/extension/src/scripts/api.js
new file mode 100644
index 000000000..c4a35a90a
--- /dev/null
+++ b/pkg/extension/src/scripts/api.js
@@ -0,0 +1,128 @@
+function gqlRequest(apiUrl, query) {
+ return fetch(apiUrl, {
+ method: 'POST',
+ redirect: 'follow',
+ credentials: 'include',
+ mode: 'cors',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: query,
+ })
+ .then((response) => response.json())
+ .then((json) => {
+ if (!json['data']) {
+ throw new Error('No response data')
+ }
+ return json['data']
+ })
+}
+
+async function updateLabelsCache(apiUrl, tab) {
+ const query = JSON.stringify({
+ query: `query GetLabels {
+ labels {
+ ... on LabelsSuccess {
+ labels {
+ ...LabelFields
+ }
+ }
+ ... on LabelsError {
+ errorCodes
+ }
+ }
+ }
+ fragment LabelFields on Label {
+ id
+ name
+ color
+ description
+ createdAt
+ }
+ `,
+ })
+
+ const data = await gqlRequest(apiUrl, query)
+ if (!data.labels || data.labels['errorCodes'] || !data.labels['labels']) {
+ console.log('GQL Error updating label cache response:', data, data)
+ console.log(!data.labels, data.labels['errorCodes'], !data.labels['labels'])
+ return []
+ }
+ await setStorage({
+ labels: data.labels.labels,
+ labelsLastUpdated: new Date().toISOString(),
+ })
+
+ return data.labels.labels
+}
+
+async function updatePageTitle(apiUrl, pageId, title) {
+ const mutation = JSON.stringify({
+ query: `mutation UpdatePage($input: UpdatePageInput!) {
+ updatePage(input: $input) {
+ ... on UpdatePageSuccess {
+ updatedPage {
+ id
+ }
+ }
+ ... on UpdatePageError {
+ errorCodes
+ }
+ }
+ }
+ `,
+ variables: {
+ input: {
+ pageId,
+ title,
+ },
+ },
+ })
+
+ const data = await gqlRequest(apiUrl, mutation)
+ if (
+ !data.updatePage ||
+ data.updatePage['errorCodes'] ||
+ !data.updatePage['updatedPage']
+ ) {
+ console.log('GQL Error updating page:', data)
+ throw new Error('Error updating title.')
+ }
+ return data.updatePage.updatePage
+}
+
+async function setLabels(apiUrl, pageId, labelIds) {
+ const mutation = JSON.stringify({
+ query: `mutation SetLabels($input: SetLabelsInput!) {
+ setLabels(input: $input) {
+ ... on SetLabelsSuccess {
+ labels {
+ id
+ }
+ }
+ ... on SetLabelsError {
+ errorCodes
+ }
+ }
+ }
+ `,
+ variables: {
+ input: {
+ pageId,
+ labelIds,
+ },
+ },
+ })
+
+ const data = await gqlRequest(apiUrl, mutation)
+ if (
+ !data.setLabels ||
+ data.setLabels['errorCodes'] ||
+ !data.setLabels['labels']
+ ) {
+ console.log('GQL Error setting labels:', data)
+ throw new Error('Error setting labels.')
+ }
+ return data.setLabels.labels
+}
diff --git a/pkg/extension/src/scripts/background.js b/pkg/extension/src/scripts/background.js
index 914399bdc..efa8ecb2f 100644
--- a/pkg/extension/src/scripts/background.js
+++ b/pkg/extension/src/scripts/background.js
@@ -1,7 +1,5 @@
/* global
ACTIONS
- CREATE_ARTICLE_QUERY
- CREATE_ARTICLE_SAVING_REQUEST_QUERY
ENV_IS_FIREFOX
ENV_IS_EDGE
browserApi
@@ -11,242 +9,164 @@
XMLHttpRequest
*/
-'use strict';
+'use strict'
-import { v4 as uuidv4 } from 'uuid';
+import { v4 as uuidv4 } from 'uuid'
-let authToken = undefined;
-const omnivoreURL = process.env.OMNIVORE_URL;
-const omnivoreGraphqlURL = process.env.OMNIVORE_GRAPHQL_URL;
+let authToken = undefined
+const omnivoreURL = process.env.OMNIVORE_URL
+const omnivoreGraphqlURL = process.env.OMNIVORE_GRAPHQL_URL
-/* storage helper functions */
-function getStorage (keyOrKeys) {
+let pendingRequests = []
+let completedRequests = {}
+
+function getCurrentTab() {
return new Promise((resolve) => {
- browserApi.storage.local.get(keyOrKeys || null, (result) => {
- resolve(result || {});
- });
- });
+ browserApi.tabs.query(
+ {
+ active: true,
+ currentWindow: true,
+ },
+ function (tabs) {
+ resolve(tabs[0] || null)
+ }
+ )
+ })
}
-function getStorageItem (singleKey) {
- return new Promise((resolve) => {
- browserApi.storage.local.get(singleKey, (result) => {
- const finalResult = (result && result[singleKey]) || null;
- resolve(finalResult);
- });
- });
-}
-
-function setStorage (itemsToSet) {
- return new Promise((resolve) => {
- browserApi.storage.local.set(itemsToSet, resolve);
- });
-}
-
-function removeStorage (itemsToRemove) {
- return new Promise((resolve) => {
- browserApi.storage.local.remove(itemsToRemove, resolve);
- });
-}
-
-function getCurrentTab () {
- return new Promise((resolve) => {
- browserApi.tabs.query({
- active: true,
- currentWindow: true
- }, function (tabs) {
- resolve(tabs[0] || null);
- });
- });
-}
-
-function setupConnection(xhr) {
- xhr.setRequestHeader('Content-Type', 'application/json');
- if (authToken) {
- xhr.setRequestHeader('Authorization', authToken);
- }
-}
-
-/* other code */
-function uploadFile ({ id, uploadSignedUrl }, contentType, contentObjUrl) {
+function uploadFile({ id, uploadSignedUrl }, contentType, contentObjUrl) {
return fetch(contentObjUrl)
.then((r) => r.blob())
.then((blob) => {
- return new Promise((resolve) => {
- const xhr = new XMLHttpRequest();
- xhr.open('PUT', uploadSignedUrl, true);
- xhr.setRequestHeader('Content-Type', contentType);
-
- xhr.onerror = () => {
- resolve(undefined);
- };
- xhr.onload = () => {
- // Uploaded.
- resolve({ id });
- };
- xhr.send(blob);
- });
+ return fetch(uploadSignedUrl, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': contentType,
+ },
+ body: blob,
+ })
})
.catch((err) => {
console.error('error uploading file', err)
return undefined
- });
+ })
}
-function savePdfFile(tab, url, contentType, contentObjUrl) {
- return new Promise(resolve => {
- const xhr = new XMLHttpRequest();
- xhr.onreadystatechange = async function () {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- const { data } = JSON.parse(xhr.response);
- if ('errorCodes' in data.uploadFileRequest) {
- if (data.uploadFileRequest.errorCodes[0] === 'UNAUTHORIZED') {
- clearClickCompleteState();
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: 'Unable to save page',
- type: 'error',
- errorCode: 401,
- url: omnivoreURL
- }
- });
- }
- }
-
- if (!data.uploadFileRequest || !data.uploadFileRequest.id || !data.uploadFileRequest.createdPageId || 'errorCodes' in data.uploadFileRequest) {
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: 'Unable to save page',
- type: 'error'
- }
- });
- } else {
- const result = await uploadFile(data.uploadFileRequest, contentType, contentObjUrl);
- URL.revokeObjectURL(contentObjUrl);
-
- if (!result) {
- return undefined
- }
-
- const createdPageId = data.uploadFileRequest.createdPageId
- const url = omnivoreURL + '/article/sr/' + createdPageId
-
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: 'Saved to Omnivore',
- link: url,
- linkText: 'Read Now',
- type: 'success'
- }
- })
- return resolve(data.uploadFileRequest);
- }
- } else if (xhr.status === 400) {
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: 'Unable to save page',
- type: 'error'
- }
- });
+async function uploadFileRequest(url, contentType) {
+ const data = JSON.stringify({
+ query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
+ uploadFileRequest(input:$input) {
+ ... on UploadFileRequestError {
+ errorCodes
}
- resolve(false);
- }
- };
-
- const data = JSON.stringify({
- query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
- uploadFileRequest(input:$input) {
- ... on UploadFileRequestError {
- errorCodes
- }
- ... on UploadFileRequestSuccess {
- id
- createdPageId
- uploadSignedUrl
- }
- }
- }`,
- variables: {
- input: {
- url,
- contentType,
- createPageEntry: true,
+ ... on UploadFileRequestSuccess {
+ id
+ createdPageId
+ uploadSignedUrl
}
}
- });
+ }`,
+ variables: {
+ input: {
+ url,
+ contentType,
+ createPageEntry: true,
+ },
+ },
+ })
- xhr.open('POST', omnivoreGraphqlURL + 'graphql', true);
- setupConnection(xhr);
+ const field = 'uploadFileRequest'
+ const result = await gqlRequest(omnivoreGraphqlURL + 'graphql', data)
- xhr.send(data);
- });
-}
-
-function clearClickCompleteState () {
- getStorageItem('postInstallClickComplete').then((postInstallClickComplete) => {
- if (postInstallClickComplete) {
- removeStorage('postInstallClickComplete');
- }
- });
-}
-
-function handleSaveResponse(tab, field, xhr) {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- const { data } = JSON.parse(xhr.response);
- const item = data[field]
- if (!item) {
- return undefined
- }
-
- if ('errorCodes' in item) {
- const messagePayload = {
- text: descriptions[data.createArticle.errorCodes[0]] || 'Unable to save page',
- type: 'error'
- }
-
- if (item.errorCodes[0] === 'UNAUTHORIZED') {
- messagePayload.errorCode = 401
- messagePayload.url = omnivoreURL
- clearClickCompleteState()
- }
-
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
- payload: messagePayload
- })
-
- return undefined
- }
-
- const url = item['url']
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
+ if (result[field]['errorCodes']) {
+ if (result[field]['errorCodes'][0] === 'UNAUTHORIZED') {
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
payload: {
- text: 'Saved to Omnivore',
- link: url ?? omnivoreURL + '/home',
- linkText: 'Read Now',
- type: 'success'
- }
+ target: 'logged_out',
+ status: 'logged_out',
+ message: 'You are not logged in.',
+ ctx: toolbarCtx,
+ },
})
-
- return item
- } else if (xhr.status === 400) {
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
+ clearClickCompleteState()
+ } else {
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
payload: {
- text: 'Unable to save page',
- type: 'error'
- }
+ status: 'failure',
+ message: 'Unable to save page.',
+ ctx: toolbarCtx,
+ },
})
- return undefined
}
+ return undefined
}
+
+ return result.uploadFileRequest
+}
+
+async function savePdfFile(
+ currentTab,
+ url,
+ requestId,
+ contentType,
+ contentObjUrl
+) {
+ const toolbarCtx = {
+ omnivoreURL,
+ originalURL: url,
+ requestId: requestId,
+ }
+ completedRequests[toolbarCtx.requestId] = undefined
+
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.ShowToolbar,
+ payload: {
+ type: 'loading',
+ ctx: toolbarCtx,
+ },
+ })
+ const uploadRequestResult = await uploadFileRequest(url, contentType)
+ console.log('done uploading pdf', uploadRequestResult)
+ const uploadFileResult = await uploadFile(
+ uploadRequestResult,
+ contentType,
+ contentObjUrl
+ )
+ console.log(' uploadFileResult: ', uploadFileResult)
+ URL.revokeObjectURL(contentObjUrl)
+
+ if (uploadFileResult && uploadRequestResult.createdPageId) {
+ completedRequests[toolbarCtx.requestId] = {
+ requestId: toolbarCtx.requestId,
+ responseId: uploadRequestResult.createdPageId,
+ }
+
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
+ payload: {
+ status: 'success',
+ target: 'page',
+ ctx: {
+ requestId: toolbarCtx.requestId,
+ responseId: uploadRequestResult.createdPageId,
+ },
+ },
+ })
+ }
+
+ return uploadFileResult
+}
+
+function clearClickCompleteState() {
+ getStorageItem('postInstallClickComplete').then(
+ (postInstallClickComplete) => {
+ if (postInstallClickComplete) {
+ removeStorage('postInstallClickComplete')
+ }
+ }
+ )
}
async function saveUrl(currentTab, url) {
@@ -259,176 +179,295 @@ async function saveUrl(currentTab, url) {
}
async function saveApiRequest(currentTab, query, field, input) {
+ const toolbarCtx = {
+ omnivoreURL,
+ originalURL: input.url,
+ requestId: input.clientRequestId,
+ }
+ completedRequests[toolbarCtx.requestId] = undefined
+
+ const requestBody = JSON.stringify({
+ query,
+ variables: {
+ input,
+ },
+ })
+
browserApi.tabs.sendMessage(currentTab.id, {
- action: ACTIONS.ShowMessage,
+ action: ACTIONS.ShowToolbar,
payload: {
type: 'loading',
- text: 'Saving...'
- }
- });
+ ctx: toolbarCtx,
+ },
+ })
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest()
- xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
- setupConnection(xhr)
-
- xhr.onerror = (err) => { reject(err) }
-
- xhr.onload = () => {
- try {
- const res = handleSaveResponse(currentTab, field, xhr)
- if (!res) {
- return reject()
- }
- resolve(res);
- } catch (err) {
- reject(err)
+ try {
+ const result = await gqlRequest(omnivoreGraphqlURL + 'graphql', requestBody)
+ if (result[field]['errorCodes']) {
+ if (result[field]['errorCodes'][0] === 'UNAUTHORIZED') {
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
+ payload: {
+ target: 'logged_out',
+ status: 'logged_out',
+ message: 'You are not logged in.',
+ ctx: toolbarCtx,
+ },
+ })
+ clearClickCompleteState()
+ } else {
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
+ payload: {
+ status: 'failure',
+ message: 'Unable to save page.',
+ ctx: toolbarCtx,
+ },
+ })
}
+ return
}
- const data = JSON.stringify({
- query,
- variables: {
- input
- }
+ const url = result[field] ? result[field]['url'] : undefined
+ const requestId = result[field]
+ ? result[field]['clientRequestId']
+ : undefined
+ browserApi.tabs.sendMessage(currentTab.id, {
+ action: ACTIONS.UpdateStatus,
+ payload: {
+ status: 'success',
+ target: 'page',
+ ctx: {
+ readerURL: url,
+ responseId: requestId,
+ requestId: toolbarCtx.requestId,
+ },
+ },
})
- xhr.send(data);
- })
- .catch((err) => {
- console.log('error saving page', err)
- browserApi.tabs.sendMessage(currentTab.id, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: 'Unable to save page',
- type: 'error',
- }
- });
- return undefined
- });
+ completedRequests[toolbarCtx.requestId] = {
+ readerURL: url,
+ responseId: requestId,
+ requestId: toolbarCtx.requestId,
+ }
+ } catch (err) {
+ console.log('error saving: ', err)
+ }
+
+ processPendingRequests(currentTab.id)
}
-async function saveArticle (tab) {
- browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.GetContent
- }, async (response) => {
- if (!response || typeof response !== 'object') {
- // In the case of an invalid response, we attempt
- // to just save the URL. This can happen in Firefox
- // with PDF URLs
- await saveUrl(tab, tab.url)
- return;
+function updateClientStatus(tabId, target, status, message) {
+ browserApi.tabs.sendMessage(tabId, {
+ action: ACTIONS.UpdateStatus,
+ payload: {
+ target,
+ status,
+ message,
+ },
+ })
+}
+
+async function editTitleRequest(tabId, request, completedResponse) {
+ return updatePageTitle(
+ omnivoreGraphqlURL + 'graphql',
+ completedResponse.responseId,
+ request.title
+ )
+ .then(() => {
+ updateClientStatus(tabId, 'title', 'success', 'Title updated.')
+ return true
+ })
+ .catch((err) => {
+ console.log('caught error updating title: ', err)
+ updateClientStatus(tabId, 'title', 'failure', 'Error updating title.')
+ return true
+ })
+}
+
+async function setLabelsRequest(tabId, request, completedResponse) {
+ return setLabels(
+ omnivoreGraphqlURL + 'graphql',
+ completedResponse.responseId,
+ request.labelIds
+ )
+ .then(() => {
+ updateClientStatus(tabId, 'labels', 'success', 'Labels updated.')
+ return true
+ })
+ .catch(() => {
+ updateClientStatus(tabId, 'labels', 'failure', 'Error updating labels.')
+ return true
+ })
+}
+
+async function processPendingRequests(tabId) {
+ const tabRequests = pendingRequests.filter((pr) => pr.tabId === tabId)
+
+ tabRequests.forEach(async (pr) => {
+ let handled = false
+ const completed = completedRequests[pr.clientRequestId]
+ if (completed) {
+ switch (pr.type) {
+ case 'EDIT_TITLE':
+ handled = await editTitleRequest(tabId, pr, completed)
+ break
+ case 'SET_LABELS':
+ handled = await setLabelsRequest(tabId, pr, completed)
+ break
+ }
}
- const requestId = uuidv4()
- var { type } = response;
- const { pageInfo, doc, uploadContentObjUrl } = response;
-
- if (type == 'html' && handleBackendUrl(tab.url)) {
- type = 'url'
+ if (handled) {
+ const idx = pendingRequests.findIndex((opr) => pr.id === opr.id)
+ if (idx > -1) {
+ pendingRequests.splice(idx, 1)
+ }
}
+ })
- switch(type) {
- case 'html': {
- await saveApiRequest(tab, SAVE_PAGE_QUERY, 'savePage', {
- source: 'extension',
- clientRequestId: requestId,
- originalContent: doc,
- title: pageInfo.title,
- url: encodeURI(tab.url),
- })
- break
+ // TODO: need to handle clearing completedRequests also
+}
+
+async function saveArticle(tab) {
+ browserApi.tabs.sendMessage(
+ tab.id,
+ {
+ action: ACTIONS.GetContent,
+ },
+ async (response) => {
+ if (!response || typeof response !== 'object') {
+ // In the case of an invalid response, we attempt
+ // to just save the URL. This can happen in Firefox
+ // with PDF URLs
+ await saveUrl(tab, tab.url)
+ return
}
- case 'url': {
- await saveApiRequest(tab, SAVE_URL_QUERY, 'saveUrl', {
- source: 'extension',
- clientRequestId: requestId,
- url: encodeURI(tab.url),
- })
- break
+
+ const requestId = uuidv4()
+ var { type } = response
+ const { pageInfo, doc, uploadContentObjUrl } = response
+
+ if (type == 'html' && handleBackendUrl(tab.url)) {
+ type = 'url'
}
- case 'pdf': {
- const uploadResult = await savePdfFile(tab, encodeURI(tab.url), pageInfo.contentType, uploadContentObjUrl);
- if (!uploadResult || !uploadResult.id) {
- // If the upload failed for any reason, try to save the PDF URL instead
+
+ switch (type) {
+ case 'html': {
+ await saveApiRequest(tab, SAVE_PAGE_QUERY, 'savePage', {
+ source: 'extension',
+ clientRequestId: requestId,
+ originalContent: doc,
+ title: pageInfo.title,
+ url: encodeURI(tab.url),
+ })
+ break
+ }
+ case 'url': {
await saveApiRequest(tab, SAVE_URL_QUERY, 'saveUrl', {
source: 'extension',
clientRequestId: requestId,
url: encodeURI(tab.url),
})
- return;
+ break
+ }
+ case 'pdf': {
+ const uploadResult = await savePdfFile(
+ tab,
+ encodeURI(tab.url),
+ requestId,
+ pageInfo.contentType,
+ uploadContentObjUrl
+ )
+ if (!uploadResult || !uploadResult.id) {
+ // If the upload failed for any reason, try to save the PDF URL instead
+ await saveApiRequest(tab, SAVE_URL_QUERY, 'saveUrl', {
+ source: 'extension',
+ clientRequestId: requestId,
+ url: encodeURI(tab.url),
+ })
+ return
+ }
+ break
}
- break
}
}
- });
+ )
}
// credit: https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
-function executeScripts (tabId, scriptsArray, onCompleteCallback) {
- function createCallback (tabId, injectDetails, innerCallback) {
+function executeScripts(tabId, scriptsArray, onCompleteCallback) {
+ function createCallback(tabId, injectDetails, innerCallback) {
return function () {
- browserScriptingApi.executeScript(tabId, injectDetails, innerCallback);
- };
+ browserScriptingApi.executeScript(tabId, injectDetails, innerCallback)
+ }
}
// any callback passed by caller is called upon all scripts execute completion
- let callback = onCompleteCallback;
+ let callback = onCompleteCallback
for (let i = scriptsArray.length - 1; i >= 0; --i) {
- callback = createCallback(tabId, { file: scriptsArray[i] }, callback);
+ callback = createCallback(tabId, { file: scriptsArray[i] }, callback)
}
if (callback !== null) {
- callback(); // execute outermost function
+ callback() // execute outermost function
}
}
-function cleanupTabState (tabId) {
+function cleanupTabState(tabId) {
getStorage().then(function (result) {
- const itemsToRemove = [];
- const keys = Object.keys(result);
- const keyPrefix = tabId + '_saveInProgress';
+ const itemsToRemove = []
+ const keys = Object.keys(result)
+ const keyPrefix = tabId + '_saveInProgress'
for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
+ const key = keys[i]
if (key.startsWith(keyPrefix)) {
- itemsToRemove.push(key);
+ itemsToRemove.push(key)
}
}
- if (itemsToRemove.length === 0) return;
- removeStorage(itemsToRemove);
- });
+ if (itemsToRemove.length === 0) return
+ removeStorage(itemsToRemove)
+ })
}
/* setup an interval timer and a timeout timer (failsave) to clear interval timer after a timeout */
-function setupTimedInterval (timerCallback, timeoutCallback, callback, delay = 1000, timeout = 10500) {
- const intervalId = setInterval(timerCallback, delay);
+function setupTimedInterval(
+ timerCallback,
+ timeoutCallback,
+ callback,
+ delay = 1000,
+ timeout = 10500
+) {
+ const intervalId = setInterval(timerCallback, delay)
const timeoutId = setTimeout(() => {
- clearInterval(intervalId);
- timeoutCallback();
- }, timeout);
+ clearInterval(intervalId)
+ timeoutCallback()
+ }, timeout)
if (callback && typeof callback === 'function') {
- callback(intervalId, timeoutId);
+ callback(intervalId, timeoutId)
}
}
-async function clearPreviousIntervalTimer (tabId) {
- const prevIntervalId = await getStorageItem(tabId + '_saveInProgress');
- if (!prevIntervalId) return;
+async function clearPreviousIntervalTimer(tabId) {
+ const prevIntervalId = await getStorageItem(tabId + '_saveInProgress')
+ if (!prevIntervalId) return
- clearInterval(prevIntervalId);
+ clearInterval(prevIntervalId)
- const intervalTimeoutId = await getStorageItem(tabId + '_saveInProgressTimeoutId_' + prevIntervalId);
- if (!intervalTimeoutId) return;
+ const intervalTimeoutId = await getStorageItem(
+ tabId + '_saveInProgressTimeoutId_' + prevIntervalId
+ )
+ if (!intervalTimeoutId) return
- clearTimeout(intervalTimeoutId);
+ clearTimeout(intervalTimeoutId)
}
-function onExtensionClick (tabId) {
+function onExtensionClick(tabId) {
/* clear any previous timers on each click */
- clearPreviousIntervalTimer(tabId);
+ clearPreviousIntervalTimer(tabId)
/* Method to check tab loading state prior to save */
- function checkTabLoadingState (onSuccess, onPending) {
+ function checkTabLoadingState(onSuccess, onPending) {
browserApi.tabs.get(tabId, async (tab) => {
if (tab.status !== 'complete') {
// show message to user on page yet to complete load
@@ -436,271 +475,323 @@ function onExtensionClick (tabId) {
action: ACTIONS.ShowMessage,
payload: {
type: 'loading',
- text: 'Page loading...'
- }
- });
+ text: 'Page loading...',
+ },
+ })
if (onPending && typeof onPending === 'function') {
- onPending();
+ onPending()
}
} else {
if (onSuccess && typeof onSuccess === 'function') {
- onSuccess();
+ onSuccess()
+ }
+ await saveArticle(tab)
+ try {
+ await updateLabelsCache(omnivoreGraphqlURL + 'graphql', tab)
+ browserApi.tabs.sendMessage(tab.id, {
+ action: ACTIONS.LabelCacheUpdated,
+ payload: {},
+ })
+ } catch (err) {
+ console.error('error fetching labels', err, omnivoreGraphqlURL)
+ return undefined
}
- await saveArticle(tab);
}
- });
+ })
}
/* call above method immediately, and if tab in loading state then setup timer */
checkTabLoadingState(null, () => {
- setupTimedInterval(() => {
- /* interval timer to check tab/page loading state and save */
- checkTabLoadingState(() => {
- clearPreviousIntervalTimer(tabId);
- });
- }, () => {
- /* timeout handling, clear timer and show timeout msg */
- clearPreviousIntervalTimer(tabId);
+ setupTimedInterval(
+ () => {
+ /* interval timer to check tab/page loading state and save */
+ checkTabLoadingState(() => {
+ clearPreviousIntervalTimer(tabId)
+ })
+ },
+ () => {
+ /* timeout handling, clear timer and show timeout msg */
+ clearPreviousIntervalTimer(tabId)
- browserApi.tabs.get(tabId, async (tab) => {
- /*
- * post timeout, we proceed to save as some sites (people.com) take a
- * long time to reach complete state and remain in interactive state.
- */
- await saveArticle(tab);
- });
- }, (intervalId, timeoutId) => {
- /* Track interval timer and timeout timer in browser storage keyed by tabId */
- const itemsToSet = {};
- itemsToSet[tabId + '_saveInProgress'] = intervalId;
- itemsToSet[tabId + '_saveInProgressTimeoutId_' + intervalId] = timeoutId;
- setStorage(itemsToSet);
- });
- });
+ browserApi.tabs.get(tabId, async (tab) => {
+ /*
+ * post timeout, we proceed to save as some sites (people.com) take a
+ * long time to reach complete state and remain in interactive state.
+ */
+ await saveArticle(tab)
+ })
+ },
+ (intervalId, timeoutId) => {
+ /* Track interval timer and timeout timer in browser storage keyed by tabId */
+ const itemsToSet = {}
+ itemsToSet[tabId + '_saveInProgress'] = intervalId
+ itemsToSet[tabId + '_saveInProgressTimeoutId_' + intervalId] = timeoutId
+ setStorage(itemsToSet)
+ }
+ )
+ })
}
/* After installing extension, if user hasn’t logged into Omnivore, then we show the splash popup */
-function checkAuthOnFirstClickPostInstall (tabId) {
- return getStorageItem('postInstallClickComplete').then(async (postInstallClickComplete) => {
- if (postInstallClickComplete) return true;
+function checkAuthOnFirstClickPostInstall(tabId) {
+ return getStorageItem('postInstallClickComplete').then(
+ async (postInstallClickComplete) => {
+ return true
+ if (postInstallClickComplete) return true
- if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendNativeMessage) {
- const response = await browser.runtime.sendNativeMessage("omnivore", {message: ACTIONS.GetAuthToken})
- if (response.authToken) {
- authToken = response.authToken;
+ if (
+ typeof browser !== 'undefined' &&
+ browser.runtime &&
+ browser.runtime.sendNativeMessage
+ ) {
+ const response = await browser.runtime.sendNativeMessage('omnivore', {
+ message: ACTIONS.GetAuthToken,
+ })
+ if (response.authToken) {
+ authToken = response.authToken
+ }
}
- }
- return new Promise((resolve) => {
- const xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4 && xhr.status === 200) {
- const { data } = JSON.parse(xhr.response);
- if (!data.me) {
- browserApi.tabs.sendMessage(tabId, {
- action: ACTIONS.ShowMessage,
- payload: {
- type: 'loading',
- text: 'Loading...'
- }
- });
- browserApi.tabs.sendMessage(tabId, {
- action: ACTIONS.ShowMessage,
- payload: {
- text: '',
- type: 'error',
- errorCode: 401,
- url: omnivoreURL
- }
- });
- resolve(null);
- } else {
- setStorage({
- postInstallClickComplete: true
- });
- resolve(true);
+ return new Promise((resolve) => {
+ const xhr = new XMLHttpRequest()
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ const { data } = JSON.parse(xhr.response)
+ if (!data.me) {
+ browserApi.tabs.sendMessage(tabId, {
+ action: ACTIONS.ShowMessage,
+ payload: {
+ type: 'loading',
+ text: 'Loading...',
+ },
+ })
+ browserApi.tabs.sendMessage(tabId, {
+ action: ACTIONS.ShowMessage,
+ payload: {
+ text: '',
+ type: 'error',
+ errorCode: 401,
+ url: omnivoreURL,
+ },
+ })
+ resolve(null)
+ } else {
+ setStorage({
+ postInstallClickComplete: true,
+ })
+ resolve(true)
+ }
}
}
- };
- const query = '{me{id}}';
- const data = JSON.stringify({
- query
- });
- xhr.open('POST', omnivoreGraphqlURL + 'graphql', true);
- setupConnection(xhr);
+ const query = '{me{id}}'
+ const data = JSON.stringify({
+ query,
+ })
+ xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
+ setupConnection(xhr)
- xhr.send(data);
- });
- });
+ xhr.send(data)
+ })
+ }
+ )
}
-function handleActionClick () {
+function handleActionClick() {
executeAction(function (currentTab) {
- onExtensionClick(currentTab.id);
+ onExtensionClick(currentTab.id)
})
}
function executeAction(action) {
getCurrentTab().then((currentTab) => {
- browserApi.tabs.sendMessage(currentTab.id, {
- action: ACTIONS.Ping
- }, async function (response) {
- if (response && response.pong) {
- // Content script ready
- const isSignedUp = await checkAuthOnFirstClickPostInstall(currentTab.id);
- if (isSignedUp) {
- action(currentTab);
- }
- } else {
- const extensionManifest = browserApi.runtime.getManifest();
- const contentScripts = extensionManifest.content_scripts;
-
- // No listener on the other end, inject content scripts
- const scriptFiles = [
- ...contentScripts[0].js,
- ...contentScripts[1].js
- ];
- executeScripts(currentTab.id, scriptFiles, async function () {
- const isSignedUp = await checkAuthOnFirstClickPostInstall(currentTab.id);
+ browserApi.tabs.sendMessage(
+ currentTab.id,
+ {
+ action: ACTIONS.Ping,
+ },
+ async function (response) {
+ if (response && response.pong) {
+ // Content script ready
+ const isSignedUp = await checkAuthOnFirstClickPostInstall(
+ currentTab.id
+ )
if (isSignedUp) {
- action(currentTab);
+ action(currentTab)
}
- });
+ } else {
+ const extensionManifest = browserApi.runtime.getManifest()
+ const contentScripts = extensionManifest.content_scripts
+
+ // No listener on the other end, inject content scripts
+ const scriptFiles = [...contentScripts[0].js, ...contentScripts[1].js]
+ executeScripts(currentTab.id, scriptFiles, async function () {
+ const isSignedUp = await checkAuthOnFirstClickPostInstall(
+ currentTab.id
+ )
+ if (isSignedUp) {
+ action(currentTab)
+ }
+ })
+ }
}
- });
- });
+ )
+ })
}
-function getIconPath (active, dark) {
- let iconPath = '/images/toolbar/icon';
+function getIconPath(active, dark) {
+ let iconPath = '/images/toolbar/icon'
if (ENV_IS_FIREFOX) {
- iconPath += '_firefox';
+ iconPath += '_firefox'
} else if (ENV_IS_EDGE) {
- iconPath += '_edge';
+ iconPath += '_edge'
}
if (!active) {
- iconPath += '_inactive';
+ iconPath += '_inactive'
}
/* we have to evaluate this every time as the onchange is not
* fired inside background pages, due to https://crbug.com/968651 */
- const useDarkIcon = typeof dark === 'boolean'
- ? dark
- : window.matchMedia('(prefers-color-scheme: dark)').matches;
+ const useDarkIcon =
+ typeof dark === 'boolean'
+ ? dark
+ : window.matchMedia('(prefers-color-scheme: dark)').matches
if (useDarkIcon) {
- iconPath += '_dark';
+ iconPath += '_dark'
}
if (ENV_IS_FIREFOX) {
- return iconPath + '.svg';
+ return iconPath + '.svg'
}
- const iconSizes = ['16', '24', '32', '48'];
+ const iconSizes = ['16', '24', '32', '48']
if (!ENV_IS_EDGE) {
- iconSizes.push('19', '38');
+ iconSizes.push('19', '38')
}
- const iconPaths = {};
+ const iconPaths = {}
for (let i = 0; i < iconSizes.length; i++) {
- const iconSize = iconSizes[i];
- iconPaths[iconSize] = iconPath + '-' + iconSize + '.png';
+ const iconSize = iconSizes[i]
+ iconPaths[iconSize] = iconPath + '-' + iconSize + '.png'
}
- return iconPaths;
+ return iconPaths
}
-function updateActionIcon (tabId, active, dark) {
+function updateActionIcon(tabId, active, dark) {
browserActionApi.setIcon({
path: getIconPath(active, dark),
- tabId: tabId
- });
+ tabId: tabId,
+ })
}
-function getActionableState (tab) {
- if (tab.status !== 'complete') return false;
+function getActionableState(tab) {
+ if (tab.status !== 'complete') return false
- const tabUrl = tab.pendingUrl || tab.url;
- if (!tabUrl) return false;
+ const tabUrl = tab.pendingUrl || tab.url
+ if (!tabUrl) return false
- if (
- !tabUrl.startsWith('https://') &&
- !tabUrl.startsWith('http://')
- ) return false;
+ if (!tabUrl.startsWith('https://') && !tabUrl.startsWith('http://'))
+ return false
if (
tabUrl.startsWith('https://omnivore.app/') &&
tabUrl.startsWith('https://dev.omnivore.app/')
- ) return false;
+ )
+ return false
- return true;
+ return true
}
-function reflectIconState (tab) {
- const tabId = tab && tab.id;
- if (!tabId) return;
+function reflectIconState(tab) {
+ const tabId = tab && tab.id
+ if (!tabId) return
- const active = getActionableState(tab);
+ const active = getActionableState(tab)
- updateActionIcon(tabId, active);
+ updateActionIcon(tabId, active)
}
-function init () {
+function init() {
/* Extension icon switcher on page/tab load status */
browserApi.tabs.onActivated.addListener(({ tabId }) => {
// Due to a chrome bug, chrome.tabs.* may run into an error because onActivated is triggered too fast.
- function checkCurrentTab () {
+ function checkCurrentTab() {
browserApi.tabs.get(tabId, function (tab) {
if (browserApi.runtime.lastError) {
- setTimeout(checkCurrentTab, 150);
+ setTimeout(checkCurrentTab, 150)
}
- reflectIconState(tab);
- });
+ reflectIconState(tab)
+ })
}
- checkCurrentTab();
- });
+ checkCurrentTab()
+ })
/* Extension icon switcher on page/tab load status */
browserApi.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
/* Not an update while this tab is active so we skip updating icon */
- if (!changeInfo.status || !tab || !tab.active) return;
+ if (!changeInfo.status || !tab || !tab.active) return
- reflectIconState(tab);
- });
+ reflectIconState(tab)
+ })
browserApi.tabs.onRemoved.addListener((tabId) => {
/* cleanup any previous saveInProgress state for the tab */
- cleanupTabState(tabId);
- });
+ cleanupTabState(tabId)
+ })
- browserActionApi.onClicked.addListener(handleActionClick);
+ browserActionApi.onClicked.addListener(handleActionClick)
// forward messages from grab-iframe-content.js script to tabs
browserApi.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.forwardToTab) {
- delete request.forwardToTab;
- browserApi.tabs.sendRequest(sender.tab.id, request);
- return;
+ delete request.forwardToTab
+ browserApi.tabs.sendRequest(sender.tab.id, request)
+ return
}
if (request.action === ACTIONS.RefreshDarkMode) {
- updateActionIcon(sender.tab.id, request.payload.value);
+ updateActionIcon(sender.tab.id, request.payload.value)
}
- });
+
+ if (request.action === ACTIONS.EditTitle) {
+ pendingRequests.push({
+ id: uuidv4(),
+ type: 'EDIT_TITLE',
+ tabId: sender.tab.id,
+ title: request.payload.title,
+ clientRequestId: request.payload.ctx.requestId,
+ })
+
+ processPendingRequests(sender.tab.id)
+ }
+
+ if (request.action === ACTIONS.SetLabels) {
+ pendingRequests.push({
+ id: uuidv4(),
+ type: 'SET_LABELS',
+ tabId: sender.tab.id,
+ labelIds: request.payload.labelIds,
+ clientRequestId: request.payload.ctx.requestId,
+ })
+
+ processPendingRequests(sender.tab.id)
+ }
+ })
// set initial extension icon
browserActionApi.setIcon({
- path: getIconPath(true)
- });
+ path: getIconPath(true),
+ })
browserApi.contextMenus.create({
- id: "save-selection",
- title: "Save to Omnivore",
- contexts: ["link"],
- onclick: async function(obj) {
+ id: 'save-selection',
+ title: 'Save to Omnivore',
+ contexts: ['link'],
+ onclick: async function (obj) {
executeAction(async function (currentTab) {
await saveUrl(currentTab, obj.linkUrl)
})
},
- });
+ })
}
-init();
+init()
diff --git a/pkg/extension/src/scripts/common.js b/pkg/extension/src/scripts/common.js
new file mode 100644
index 000000000..36303f828
--- /dev/null
+++ b/pkg/extension/src/scripts/common.js
@@ -0,0 +1,120 @@
+'use strict'
+
+window.browserApi =
+ (typeof chrome === 'object' && chrome && chrome.runtime && chrome) ||
+ (typeof browser === 'object' && browser) ||
+ {} // eslint-disable-line no-undef
+window.browserActionApi =
+ browserApi.action || browserApi.browserAction || browserApi.pageAction
+window.browserScriptingApi = browserApi.scripting || browserApi.tabs
+
+window.ENV_EXTENSION_ORIGIN = browserApi.runtime
+ .getURL('PATH/')
+ .replace('/PATH/', '')
+window.ENV_IS_FIREFOX = ENV_EXTENSION_ORIGIN.startsWith('moz-extension://')
+window.ENV_IS_EDGE = navigator.userAgent.toLowerCase().indexOf('edg') > -1
+window.ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS =
+ /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
+
+window.SELECTORS = {
+ CANONICAL_URL: ["head > link[rel='canonical']"],
+ TITLE: ["head > meta[property='og:title']"],
+}
+
+window.ACTIONS = {
+ Ping: 'PING',
+
+ ShowMessage: 'SHOW_MESSAGE',
+ GetContent: 'GET_CONTENT',
+
+ AddIframeContent: 'ADD_IFRAME_CONTENT',
+ RefreshDarkMode: 'REFRESH_DARK_MODE',
+ GetAuthToken: 'GET_AUTH_TOKEN',
+ LabelCacheUpdated: 'LABEL_CACHE_UPDATED',
+
+ ShowToolbar: 'SHOW_TOOLBAR',
+ UpdateStatus: 'UPDATE_STATUS',
+
+ EditTitle: 'EDIT_TITLE',
+ SetLabels: 'SET_LABELS',
+}
+
+window.SAVE_URL_QUERY = `mutation SaveUrl ($input: SaveUrlInput!) {
+ saveUrl(input:$input){
+ ... on SaveSuccess {
+ url
+ clientRequestId
+ }
+ ... on SaveError {
+ errorCodes
+ }
+ }
+}`
+
+window.SAVE_FILE_QUERY = `mutation SaveFile ($input: SaveFileInput!) {
+ saveFile(input:$input){
+ ... on SaveSuccess {
+ url
+ clientRequestId
+ }
+ ... on SaveError {
+ errorCodes
+ }
+ }
+}`
+
+window.SAVE_PAGE_QUERY = `mutation SavePage ($input: SavePageInput!) {
+ savePage(input:$input){
+ ... on SaveSuccess {
+ url
+ clientRequestId
+ }
+ ... on SaveError {
+ errorCodes
+ }
+ }
+}`
+
+function handleBackendUrl(url) {
+ try {
+ const FORCE_CONTENT_FETCH_URLS = [
+ // twitter status url regex
+ /twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)(?:\/.*)?/,
+ /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/,
+ ]
+ return FORCE_CONTENT_FETCH_URLS.some((regex) => regex.test(url))
+ } catch (error) {
+ console.log('error checking url', url)
+ }
+ return false
+}
+
+/* storage helper functions */
+function getStorage(keyOrKeys) {
+ return new Promise((resolve) => {
+ browserApi.storage.local.get(keyOrKeys || null, (result) => {
+ resolve(result || {})
+ })
+ })
+}
+
+function getStorageItem(singleKey) {
+ return new Promise((resolve) => {
+ browserApi.storage.local.get(singleKey, (result) => {
+ const finalResult = (result && result[singleKey]) || null
+ resolve(finalResult)
+ })
+ })
+}
+
+function setStorage(itemsToSet) {
+ return new Promise((resolve) => {
+ browserApi.storage.local.set(itemsToSet, resolve)
+ })
+}
+
+function removeStorage(itemsToRemove) {
+ return new Promise((resolve) => {
+ browserApi.storage.local.remove(itemsToRemove, resolve)
+ })
+}
diff --git a/pkg/extension/src/scripts/constants.js b/pkg/extension/src/scripts/constants.js
deleted file mode 100644
index d99b15948..000000000
--- a/pkg/extension/src/scripts/constants.js
+++ /dev/null
@@ -1,114 +0,0 @@
-'use strict';
-
-window.browserApi = (typeof chrome === 'object' && chrome && chrome.runtime && chrome) || (typeof browser === 'object' && browser) || {}; // eslint-disable-line no-undef
-window.browserActionApi = browserApi.action || browserApi.browserAction || browserApi.pageAction;
-window.browserScriptingApi = browserApi.scripting || browserApi.tabs;
-
-window.ENV_EXTENSION_ORIGIN = browserApi.runtime.getURL('PATH/').replace('/PATH/', '');
-window.ENV_IS_FIREFOX = ENV_EXTENSION_ORIGIN.startsWith('moz-extension://');
-window.ENV_IS_EDGE = navigator.userAgent.toLowerCase().indexOf('edg') > -1;
-window.ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
-
-window.SELECTORS = {
- CANONICAL_URL: ["head > link[rel='canonical']"],
- TITLE: ["head > meta[property='og:title']"]
-};
-
-window.ACTIONS = {
- ShowMessage: 'SHOW_MESSAGE',
- GetContent: 'GET_CONTENT',
- GetPageInfo: 'GET_PAGE_INFO',
- Ping: 'PING',
- AddIframeContent: 'ADD_IFRAME_CONTENT',
- RefreshDarkMode: 'REFRESH_DARK_MODE',
- GetAuthToken: 'GET_AUTH_TOKEN',
-};
-
-window.DONT_REMOVE_ELEMENTS = [
- 'meta',
- 'script',
- 'title'
-];
-
-window.SAVE_URL_QUERY = `mutation SaveUrl ($input: SaveUrlInput!) {
- saveUrl(input:$input){
- ... on SaveSuccess {
- url
- }
- ... on SaveError {
- errorCodes
- }
- }
-}`
-
-window.SAVE_FILE_QUERY = `mutation SaveFile ($input: SaveFileInput!) {
- saveFile(input:$input){
- ... on SaveSuccess {
- url
- }
- ... on SaveError {
- errorCodes
- }
- }
-}`
-
-window.SAVE_PAGE_QUERY = `mutation SavePage ($input: SavePageInput!) {
- savePage(input:$input){
- ... on SaveSuccess {
- url
- }
- ... on SaveError {
- errorCodes
- }
- }
-}`
-
-window.CREATE_ARTICLE_QUERY = `mutation CreateArticle ($input: CreateArticleInput!){
- createArticle(input:$input){
- ... on CreateArticleSuccess{
- createdArticle{
- id
- title
- slug
- hasContent
- }
- user {
- id
- profile {
- id
- username
- }
- }
-}
- ... on CreateArticleError{
- errorCodes
- }
-}
-}`;
-
-window.CREATE_ARTICLE_SAVING_REQUEST_QUERY = `mutation CreateArticleSavingRequest ($input: CreateArticleSavingRequestInput!){
- createArticleSavingRequest(input:$input){
- ... on CreateArticleSavingRequestSuccess{
- articleSavingRequest{
- id
- }
- }
- ... on CreateArticleSavingRequestError{
- errorCodes
- }
- }
-}`;
-
-function handleBackendUrl(url) {
- try {
- const FORCE_CONTENT_FETCH_URLS = [
- // twitter status url regex
- /twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)(?:\/.*)?/,
- /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/,
- ]
- return FORCE_CONTENT_FETCH_URLS.some((regex) => regex.test(url))
- } catch (error) {
- console.log('error checking url', url)
- }
- return false
-}
diff --git a/pkg/extension/src/scripts/content/content-listener-script.js b/pkg/extension/src/scripts/content/content-listener-script.js
index 8fc3f0b73..b5c6d9cee 100644
--- a/pkg/extension/src/scripts/content/content-listener-script.js
+++ b/pkg/extension/src/scripts/content/content-listener-script.js
@@ -1,53 +1,54 @@
/* global
browserApi
- showMessage
+ showToolbar
prepareContent
getPageInfo
ACTIONS
*/
-'use strict';
-
-(function () {
- const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
+'use strict'
+;(function () {
+ const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
if (darkModeQuery) {
darkModeQuery.onchange = function (ev) {
browserApi.runtime.sendMessage({
action: ACTIONS.RefreshDarkMode,
payload: {
- value: ev.matches
- }
- });
- };
+ value: ev.matches,
+ },
+ })
+ }
}
- browserApi.runtime.onMessage.addListener(({ action, payload }, sender, sendResponse) => {
- if (action === ACTIONS.GetContent) {
- prepareContent().then((pageContent) => {
- sendResponse({
- type: pageContent.type,
- doc: pageContent.content || '',
- uploadContentObjUrl: pageContent.uploadContentObjUrl,
- pageInfo: getPageInfo()
- });
- });
+ browserApi.runtime.onMessage.addListener(
+ ({ action, payload }, sender, sendResponse) => {
+ if (action === ACTIONS.GetContent) {
+ prepareContent().then((pageContent) => {
+ sendResponse({
+ type: pageContent.type,
+ doc: pageContent.content || '',
+ uploadContentObjUrl: pageContent.uploadContentObjUrl,
+ pageInfo: getPageInfo(),
+ })
+ })
- /* return true to signify handlers above can asynchronously invoke the response callback */
- return true;
- }
+ return true
+ }
- /* other actions */
- if (action === ACTIONS.Ping) {
- sendResponse({ pong: true });
- } else if (action === ACTIONS.ShowMessage) {
- showMessage(payload);
- } else if (action === ACTIONS.GetPageInfo) {
- const pageInfo = getPageInfo();
- sendResponse(pageInfo);
- } else if (action === ACTIONS.AddIframeContent) {
- // do nothing, handled by prepare-content.js
- } else {
- console.warn('Unknown message has been taken');
+ console.log('handling ', action, payload)
+ if (action === ACTIONS.Ping) {
+ sendResponse({ pong: true })
+ } else if (action === ACTIONS.ShowToolbar) {
+ showToolbar(payload)
+ } else if (action === ACTIONS.UpdateStatus) {
+ updateStatus(payload)
+ } else if (action === ACTIONS.LabelCacheUpdated) {
+ updateLabelsFromCache(payload)
+ } else if (action === ACTIONS.AddIframeContent) {
+ // do nothing, handled by prepare-content.js
+ } else {
+ console.warn('Unknown message has been taken')
+ }
}
- });
-})();
+ )
+})()
diff --git a/pkg/extension/src/scripts/content/toast.js b/pkg/extension/src/scripts/content/toast.js
index 10885147e..370a46724 100644
--- a/pkg/extension/src/scripts/content/toast.js
+++ b/pkg/extension/src/scripts/content/toast.js
@@ -1,216 +1,572 @@
/* global ENV_EXTENSION_ORIGIN */
-
-'use strict';
-
-(function () {
- let currentToastEl;
- let currentIconEl;
- let currentTextEl;
- let hideToastTimeout;
+;('use strict')
+;(function () {
+ let currentToastEl
+ let labels = []
+ let ctx = undefined
+ let doNotHide = false
+ let hideToastTimeout = undefined
const systemIcons = {
- spinner: '