diff --git a/packages/web/lib/networking/queries/useGetLabelsQuery.tsx b/packages/web/lib/networking/queries/useGetLabelsQuery.tsx
index a4af02c2c..a4f3cbb67 100644
--- a/packages/web/lib/networking/queries/useGetLabelsQuery.tsx
+++ b/packages/web/lib/networking/queries/useGetLabelsQuery.tsx
@@ -31,7 +31,13 @@ export function useGetLabelsQuery(): LabelsQueryResponse {
}
}
}
- ${labelFragment}
+ fragment LabelFields on Label {
+ id
+ name
+ color
+ description
+ createdAt
+ }
`
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
diff --git a/pkg/extension/src/manifest.json b/pkg/extension/src/manifest.json
index 25b2d5a32..213f4ce8c 100644
--- a/pkg/extension/src/manifest.json
+++ b/pkg/extension/src/manifest.json
@@ -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,16 @@
},
"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",
+ "views/cta-popup.html"
+ ]
+}
\ No newline at end of file
diff --git a/pkg/extension/src/scripts/background.js b/pkg/extension/src/scripts/background.js
index 914399bdc..7a839a3d9 100644
--- a/pkg/extension/src/scripts/background.js
+++ b/pkg/extension/src/scripts/background.js
@@ -11,152 +11,130 @@
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) {
+function getCurrentTab() {
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);
- });
-}
-
-function getCurrentTab () {
- return new Promise((resolve) => {
- browserApi.tabs.query({
- active: true,
- currentWindow: true
- }, function (tabs) {
- resolve(tabs[0] || null);
- });
- });
+ browserApi.tabs.query(
+ {
+ active: true,
+ currentWindow: true,
+ },
+ function (tabs) {
+ resolve(tabs[0] || null)
+ }
+ )
+ })
}
function setupConnection(xhr) {
- xhr.setRequestHeader('Content-Type', 'application/json');
+ xhr.setRequestHeader('Content-Type', 'application/json')
if (authToken) {
- xhr.setRequestHeader('Authorization', 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);
+ const xhr = new XMLHttpRequest()
+ xhr.open('PUT', uploadSignedUrl, true)
+ xhr.setRequestHeader('Content-Type', contentType)
xhr.onerror = () => {
- resolve(undefined);
- };
+ resolve(undefined)
+ }
xhr.onload = () => {
// Uploaded.
- resolve({ id });
- };
- xhr.send(blob);
- });
+ resolve({ id })
+ }
+ xhr.send(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();
+ 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);
+ const { data } = JSON.parse(xhr.response)
if ('errorCodes' in data.uploadFileRequest) {
if (data.uploadFileRequest.errorCodes[0] === 'UNAUTHORIZED') {
- clearClickCompleteState();
+ clearClickCompleteState()
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Unable to save page',
type: 'error',
errorCode: 401,
- url: omnivoreURL
- }
- });
+ url: omnivoreURL,
+ },
+ })
}
}
- if (!data.uploadFileRequest || !data.uploadFileRequest.id || !data.uploadFileRequest.createdPageId || 'errorCodes' in data.uploadFileRequest) {
+ 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'
- }
- });
+ type: 'error',
+ },
+ })
} else {
- const result = await uploadFile(data.uploadFileRequest, contentType, contentObjUrl);
- URL.revokeObjectURL(contentObjUrl);
+ 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,
+ action: ACTIONS.UpdateStatus,
payload: {
- text: 'Saved to Omnivore',
- link: url,
- linkText: 'Read Now',
- type: 'success'
- }
+ status: 'success',
+ requestId: createdPageId,
+ },
})
- return resolve(data.uploadFileRequest);
+ 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'
- }
- });
+ type: 'error',
+ },
+ })
}
- resolve(false);
+ resolve(false)
}
- };
+ }
const data = JSON.stringify({
query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
@@ -176,29 +154,32 @@ function savePdfFile(tab, url, contentType, contentObjUrl) {
url,
contentType,
createPageEntry: true,
- }
- }
- });
+ },
+ },
+ })
- xhr.open('POST', omnivoreGraphqlURL + 'graphql', true);
- setupConnection(xhr);
+ xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
+ setupConnection(xhr)
- xhr.send(data);
- });
+ xhr.send(data)
+ })
}
-function clearClickCompleteState () {
- getStorageItem('postInstallClickComplete').then((postInstallClickComplete) => {
- if (postInstallClickComplete) {
- removeStorage('postInstallClickComplete');
+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 { data } = JSON.parse(xhr.response)
+ console.log('response data: ', data)
const item = data[field]
if (!item) {
return undefined
@@ -206,8 +187,10 @@ function handleSaveResponse(tab, field, xhr) {
if ('errorCodes' in item) {
const messagePayload = {
- text: descriptions[data.createArticle.errorCodes[0]] || 'Unable to save page',
- type: 'error'
+ text:
+ descriptions[data.createArticle.errorCodes[0]] ||
+ 'Unable to save page',
+ type: 'error',
}
if (item.errorCodes[0] === 'UNAUTHORIZED') {
@@ -218,7 +201,7 @@ function handleSaveResponse(tab, field, xhr) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
- payload: messagePayload
+ payload: messagePayload,
})
return undefined
@@ -226,13 +209,11 @@ function handleSaveResponse(tab, field, xhr) {
const url = item['url']
browserApi.tabs.sendMessage(tab.id, {
- action: ACTIONS.ShowMessage,
+ action: ACTIONS.UpdateStatus,
payload: {
- text: 'Saved to Omnivore',
+ status: 'success',
link: url ?? omnivoreURL + '/home',
- linkText: 'Read Now',
- type: 'success'
- }
+ },
})
return item
@@ -241,8 +222,8 @@ function handleSaveResponse(tab, field, xhr) {
action: ACTIONS.ShowMessage,
payload: {
text: 'Unable to save page',
- type: 'error'
- }
+ type: 'error',
+ },
})
return undefined
}
@@ -260,19 +241,21 @@ async function saveUrl(currentTab, url) {
async function saveApiRequest(currentTab, query, field, input) {
browserApi.tabs.sendMessage(currentTab.id, {
- action: ACTIONS.ShowMessage,
+ action: ACTIONS.ShowToolbar,
payload: {
type: 'loading',
- text: 'Saving...'
- }
- });
+ requestId: input.clientRequestId,
+ },
+ })
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
setupConnection(xhr)
- xhr.onerror = (err) => { reject(err) }
+ xhr.onerror = (err) => {
+ reject(err)
+ }
xhr.onload = () => {
try {
@@ -280,7 +263,7 @@ async function saveApiRequest(currentTab, query, field, input) {
if (!res) {
return reject()
}
- resolve(res);
+ resolve(res)
} catch (err) {
reject(err)
}
@@ -289,146 +272,162 @@ async function saveApiRequest(currentTab, query, field, input) {
const data = JSON.stringify({
query,
variables: {
- input
- }
+ input,
+ },
})
- xhr.send(data);
- })
- .catch((err) => {
+ 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
- });
+ })
}
-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;
- }
-
- const requestId = uuidv4()
- var { type } = response;
- const { pageInfo, doc, uploadContentObjUrl } = response;
-
- if (type == 'html' && handleBackendUrl(tab.url)) {
- type = 'url'
- }
-
- 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
+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),
+ 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 +435,365 @@ 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) => {
+ 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 updateLabelsCache(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
+// }
+// `,
+// })
+// return fetch(omnivoreGraphqlURL + 'graphql', {
+// method: 'POST',
+// redirect: 'follow',
+// credentials: 'include',
+// mode: 'cors',
+// headers: {
+// Accept: 'application/json',
+// 'Content-Type': 'application/json',
+// },
+// body: query,
+// })
+// .then((response) => response.json())
+// .then((data) => {
+// const result = data.data.labels.labels
+// return result
+// })
+// .then((labels) => {
+// setStorage({
+// labels: labels,
+// labelsLastUpdated: new Date().toISOString(),
+// })
+// return labels
+// })
+// .then((labels) => {
+// browserApi.tabs.sendMessage(tab.id, {
+// action: ACTIONS.LabelCacheUpdated,
+// payload: {},
+// })
+// })
+// .catch((err) => {
+// console.error('error fetching labels', err, omnivoreGraphqlURL)
+// return undefined
+// })
+// }
+
+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) {
+ console.log('EDITING TITLE REQUEST: ')
+ updatePageTitle(
+ omnivoreGraphqlURL + 'graphql',
+ request.payload.pageId,
+ request.payload.title
+ )
+ }
+ })
// 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/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..5fbdfd6bd 100644
--- a/pkg/extension/src/scripts/content/content-listener-script.js
+++ b/pkg/extension/src/scripts/content/content-listener-script.js
@@ -1,53 +1,55 @@
/* 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');
+ handleMessage(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..dfce411f7 100644
--- a/pkg/extension/src/scripts/content/toast.js
+++ b/pkg/extension/src/scripts/content/toast.js
@@ -1,216 +1,453 @@
/* global ENV_EXTENSION_ORIGIN */
-
-'use strict';
-
-(function () {
- let currentToastEl;
- let currentIconEl;
- let currentTextEl;
- let hideToastTimeout;
+;('use strict')
+;(function () {
+ let currentToastEl
+ let hideToastTimeout
+ let labels = []
+ let requestId
+ let finalURL
const systemIcons = {
- spinner: '