Merge
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "process.env.EXTENSION_NAME",
|
||||
"short_name": "process.env.EXTENSION_NAME",
|
||||
"version": "2.4.4",
|
||||
"version": "2.6.1",
|
||||
"description": "Save PDFs and Articles to your Omnivore library",
|
||||
"author": "Omnivore Media, Inc",
|
||||
"default_locale": "en",
|
||||
@ -11,7 +11,7 @@
|
||||
"url": "https://omnivore.app/"
|
||||
},
|
||||
"homepage_url": "https://omnivore.app/",
|
||||
"content_security_policy": "default-src 'none'; child-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; worker-src 'none'; connect-src https://storage.googleapis.com/ process.env.OMNIVORE_GRAPHQL_URL blob:; frame-src 'none'; font-src 'none'; img-src data:; script-src 'self'; script-src-elem 'self'; script-src-attr 'none'; style-src 'self'; style-src-elem 'self'; style-src-attr 'none'; base-uri 'none'; form-action 'none'; block-all-mixed-content; upgrade-insecure-requests; report-uri https://api.jeurissen.co/reports/csp/webext/omnivore/",
|
||||
"content_security_policy": "default-src 'none'; child-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; worker-src 'none'; connect-src https://storage.googleapis.com/ process.env.OMNIVORE_GRAPHQL_URL blob:; frame-src 'none'; font-src 'none'; img-src data:; script-src 'self'; script-src-elem 'self'; script-src-attr 'none'; style-src 'self'; style-src-elem 'self'; style-src-attr 'none'; base-uri 'none'; form-action 'none'; block-all-mixed-content; upgrade-insecure-requests; report-uri https://api.jeurissen.co/reports/csp/webext/omnivore/",
|
||||
"icons": {
|
||||
"16": "/images/extension/icon-16.png",
|
||||
"24": "/images/extension/icon-24.png",
|
||||
|
||||
@ -98,12 +98,7 @@ async function updatePageTitle(apiUrl, pageId, title) {
|
||||
return data.updatePage.updatePage
|
||||
}
|
||||
|
||||
async function setLabels(apiUrl, pageId, labelIds, createdLabels) {
|
||||
console.log(
|
||||
'setLabels(apiUrl, pageId, labelIds, createdLabels)',
|
||||
labelIds,
|
||||
createdLabels
|
||||
)
|
||||
async function setLabels(apiUrl, pageId, labels) {
|
||||
const mutation = JSON.stringify({
|
||||
query: `mutation SetLabels($input: SetLabelsInput!) {
|
||||
setLabels(input: $input) {
|
||||
@ -123,8 +118,7 @@ async function setLabels(apiUrl, pageId, labelIds, createdLabels) {
|
||||
variables: {
|
||||
input: {
|
||||
pageId,
|
||||
labelIds,
|
||||
labels: createdLabels,
|
||||
labels,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -14,11 +14,51 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
class TaskQueue {
|
||||
constructor() {
|
||||
this.queue = []
|
||||
this.isRunning = false
|
||||
this.isReady = false
|
||||
}
|
||||
|
||||
enqueue(task) {
|
||||
this.queue.push(task)
|
||||
|
||||
// Only run the next task if the queue is ready
|
||||
if (this.isReady) {
|
||||
this.runNext()
|
||||
}
|
||||
}
|
||||
|
||||
async runNext() {
|
||||
if (this.isRunning || this.queue.length === 0 || !this.isReady) return
|
||||
|
||||
this.isRunning = true
|
||||
const task = this.queue.shift()
|
||||
|
||||
try {
|
||||
await task()
|
||||
} catch (err) {
|
||||
console.error('Task failed:', err)
|
||||
} finally {
|
||||
this.isRunning = false
|
||||
if (this.isReady) {
|
||||
this.runNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setReady() {
|
||||
this.isReady = true
|
||||
this.runNext()
|
||||
}
|
||||
}
|
||||
|
||||
let authToken = undefined
|
||||
const queue = new TaskQueue()
|
||||
const omnivoreURL = process.env.OMNIVORE_URL
|
||||
const omnivoreGraphqlURL = process.env.OMNIVORE_GRAPHQL_URL
|
||||
|
||||
let pendingRequests = []
|
||||
let completedRequests = {}
|
||||
|
||||
function getCurrentTab() {
|
||||
@ -135,7 +175,6 @@ async function savePdfFile(
|
||||
contentType,
|
||||
contentObjUrl
|
||||
)
|
||||
console.log(' uploadFileResult: ', uploadFileResult)
|
||||
URL.revokeObjectURL(contentObjUrl)
|
||||
|
||||
if (uploadFileResult && uploadRequestResult.createdPageId) {
|
||||
@ -255,7 +294,7 @@ async function saveApiRequest(currentTab, query, field, input) {
|
||||
console.log('error saving: ', err)
|
||||
}
|
||||
|
||||
processPendingRequests(currentTab.id)
|
||||
queue.setReady()
|
||||
}
|
||||
|
||||
function updateClientStatus(tabId, target, status, message) {
|
||||
@ -312,12 +351,18 @@ async function setLabelsRequest(tabId, request, completedResponse) {
|
||||
return setLabels(
|
||||
omnivoreGraphqlURL + 'graphql',
|
||||
completedResponse.responseId,
|
||||
request.labelIds
|
||||
request.labels
|
||||
)
|
||||
.then(() => {
|
||||
updateClientStatus(tabId, 'labels', 'success', 'Labels updated.')
|
||||
return true
|
||||
})
|
||||
.then(() => {
|
||||
browserApi.tabs.sendMessage(tabId, {
|
||||
action: ACTIONS.LabelCacheUpdated,
|
||||
payload: {},
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
updateClientStatus(tabId, 'labels', 'failure', 'Error updating labels.')
|
||||
return true
|
||||
@ -351,48 +396,49 @@ async function deleteRequest(tabId, request, completedResponse) {
|
||||
})
|
||||
}
|
||||
|
||||
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 'ADD_NOTE':
|
||||
handled = await addNoteRequest(tabId, pr, completed)
|
||||
break
|
||||
case 'SET_LABELS':
|
||||
handled = await setLabelsRequest(tabId, pr, completed)
|
||||
break
|
||||
case 'ARCHIVE':
|
||||
handled = await archiveRequest(tabId, pr, completed)
|
||||
break
|
||||
case 'DELETE':
|
||||
handled = await deleteRequest(tabId, pr, completed)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
const idx = pendingRequests.findIndex((opr) => pr.id === opr.id)
|
||||
if (idx > -1) {
|
||||
pendingRequests.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: need to handle clearing completedRequests also
|
||||
async function processEditTitleRequest(tabId, pr) {
|
||||
const completed = completedRequests[pr.clientRequestId]
|
||||
handled = await editTitleRequest(tabId, pr, completed)
|
||||
console.log('processEditTitleRequest: ', handled)
|
||||
return handled
|
||||
}
|
||||
|
||||
async function saveArticle(tab) {
|
||||
async function processAddNoteRequest(tabId, pr) {
|
||||
const completed = completedRequests[pr.clientRequestId]
|
||||
const handled = await addNoteRequest(tabId, pr, completed)
|
||||
console.log('processAddNoteRequest: ', handled)
|
||||
return handled
|
||||
}
|
||||
|
||||
async function processSetLabelsRequest(tabId, pr) {
|
||||
const completed = completedRequests[pr.clientRequestId]
|
||||
const handled = await setLabelsRequest(tabId, pr, completed)
|
||||
console.log('processSetLabelsRequest: ', handled)
|
||||
return handled
|
||||
}
|
||||
|
||||
async function processArchiveRequest(tabId, pr) {
|
||||
const completed = completedRequests[pr.clientRequestId]
|
||||
const handled = await archiveRequest(tabId, pr, completed)
|
||||
console.log('processArchiveRequest: ', handled)
|
||||
return handled
|
||||
}
|
||||
|
||||
async function processDeleteRequest(tabId, pr) {
|
||||
const completed = completedRequests[pr.clientRequestId]
|
||||
const handled = await deleteRequest(tabId, pr, completed)
|
||||
console.log('processDeleteRequest: ', handled)
|
||||
return handled
|
||||
}
|
||||
|
||||
async function saveArticle(tab, createHighlight) {
|
||||
browserApi.tabs.sendMessage(
|
||||
tab.id,
|
||||
{
|
||||
action: ACTIONS.GetContent,
|
||||
payload: {
|
||||
createHighlight: createHighlight,
|
||||
},
|
||||
},
|
||||
async (response) => {
|
||||
if (!response || typeof response !== 'object') {
|
||||
@ -521,7 +567,8 @@ async function clearPreviousIntervalTimer(tabId) {
|
||||
clearTimeout(intervalTimeoutId)
|
||||
}
|
||||
|
||||
function onExtensionClick(tabId) {
|
||||
function extensionSaveCurrentPage(tabId, createHighlight) {
|
||||
createHighlight = createHighlight ? true : false
|
||||
/* clear any previous timers on each click */
|
||||
clearPreviousIntervalTimer(tabId)
|
||||
|
||||
@ -544,7 +591,7 @@ function onExtensionClick(tabId) {
|
||||
if (onSuccess && typeof onSuccess === 'function') {
|
||||
onSuccess()
|
||||
}
|
||||
await saveArticle(tab)
|
||||
await saveArticle(tab, createHighlight)
|
||||
try {
|
||||
await updateLabelsCache(omnivoreGraphqlURL + 'graphql', tab)
|
||||
browserApi.tabs.sendMessage(tab.id, {
|
||||
@ -577,7 +624,7 @@ function onExtensionClick(tabId) {
|
||||
* 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)
|
||||
await saveArticle(tab, createHighlight)
|
||||
})
|
||||
},
|
||||
(intervalId, timeoutId) => {
|
||||
@ -597,13 +644,12 @@ function checkAuthOnFirstClickPostInstall(tabId) {
|
||||
|
||||
function handleActionClick() {
|
||||
executeAction(function (currentTab) {
|
||||
onExtensionClick(currentTab.id)
|
||||
extensionSaveCurrentPage(currentTab.id)
|
||||
})
|
||||
}
|
||||
|
||||
function executeAction(action) {
|
||||
getCurrentTab().then((currentTab) => {
|
||||
console.log('currentTab: ', currentTab)
|
||||
browserApi.tabs.sendMessage(
|
||||
currentTab.id,
|
||||
{
|
||||
@ -685,65 +731,65 @@ function init() {
|
||||
}
|
||||
|
||||
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)
|
||||
queue.enqueue(() =>
|
||||
processEditTitleRequest(sender.tab.id, {
|
||||
id: uuidv4(),
|
||||
type: 'EDIT_TITLE',
|
||||
tabId: sender.tab.id,
|
||||
title: request.payload.title,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (request.action === ACTIONS.Archive) {
|
||||
pendingRequests.push({
|
||||
id: uuidv4(),
|
||||
type: 'ARCHIVE',
|
||||
tabId: sender.tab.id,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
|
||||
processPendingRequests(sender.tab.id)
|
||||
queue.enqueue(() =>
|
||||
processArchiveRequest(sender.tab.id, {
|
||||
id: uuidv4(),
|
||||
type: 'ARCHIVE',
|
||||
tabId: sender.tab.id,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (request.action === ACTIONS.Delete) {
|
||||
pendingRequests.push({
|
||||
type: 'DELETE',
|
||||
tabId: sender.tab.id,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
|
||||
processPendingRequests(sender.tab.id)
|
||||
queue.enqueue(() =>
|
||||
processDeleteRequest(sender.tab.id, {
|
||||
type: 'DELETE',
|
||||
tabId: sender.tab.id,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (request.action === ACTIONS.AddNote) {
|
||||
pendingRequests.push({
|
||||
id: uuidv4(),
|
||||
type: 'ADD_NOTE',
|
||||
tabId: sender.tab.id,
|
||||
note: request.payload.note,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
|
||||
processPendingRequests(sender.tab.id)
|
||||
queue.enqueue(() =>
|
||||
processAddNoteRequest(sender.tab.id, {
|
||||
id: uuidv4(),
|
||||
type: 'ADD_NOTE',
|
||||
tabId: sender.tab.id,
|
||||
note: request.payload.note,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
queue.enqueue(() =>
|
||||
processSetLabelsRequest(sender.tab.id, {
|
||||
id: uuidv4(),
|
||||
type: 'SET_LABELS',
|
||||
tabId: sender.tab.id,
|
||||
labels: request.payload.labels,
|
||||
clientRequestId: request.payload.ctx.requestId,
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
browserApi.contextMenus.create({
|
||||
id: 'save-selection',
|
||||
id: 'save-link-selection',
|
||||
title: 'Save this link to Omnivore',
|
||||
contexts: ['link'],
|
||||
onclick: async function (obj) {
|
||||
@ -752,6 +798,28 @@ function init() {
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
browserApi.contextMenus.create({
|
||||
id: 'save-page-selection',
|
||||
title: 'Save this page to Omnivore',
|
||||
contexts: ['page'],
|
||||
onclick: async function (obj) {
|
||||
executeAction(function (currentTab) {
|
||||
extensionSaveCurrentPage(currentTab.id)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
browserApi.contextMenus.create({
|
||||
id: 'save-text-selection',
|
||||
title: 'Create Highlight and Save to Omnivore',
|
||||
contexts: ['selection'],
|
||||
onclick: async function (obj) {
|
||||
executeAction(function (currentTab) {
|
||||
extensionSaveCurrentPage(currentTab.id, true)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
@ -23,7 +23,8 @@
|
||||
browserApi.runtime.onMessage.addListener(
|
||||
({ action, payload }, sender, sendResponse) => {
|
||||
if (action === ACTIONS.GetContent) {
|
||||
prepareContent().then((pageContent) => {
|
||||
const createHighlight = payload && payload.createHighlight
|
||||
prepareContent(createHighlight).then((pageContent) => {
|
||||
sendResponse({
|
||||
type: pageContent.type,
|
||||
doc: pageContent.content || '',
|
||||
|
||||
@ -5,37 +5,38 @@
|
||||
ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'use strict'
|
||||
;(function () {
|
||||
const iframes = {}
|
||||
|
||||
(function () {
|
||||
const iframes = {};
|
||||
browserApi.runtime.onMessage.addListener(
|
||||
({ action, payload }, sender, sendResponse) => {
|
||||
if (action !== ACTIONS.AddIframeContent) return
|
||||
const { url, content } = payload
|
||||
iframes[url] = content
|
||||
sendResponse({})
|
||||
}
|
||||
)
|
||||
|
||||
browserApi.runtime.onMessage.addListener(({ action, payload }, sender, sendResponse) => {
|
||||
if (action !== ACTIONS.AddIframeContent) return;
|
||||
const { url, content } = payload;
|
||||
iframes[url] = content;
|
||||
sendResponse({});
|
||||
});
|
||||
|
||||
async function grabPdfContent () {
|
||||
const fileExtension = window.location.pathname.slice(-4).toLowerCase();
|
||||
const hasPdfExtension = fileExtension === '.pdf';
|
||||
async function grabPdfContent() {
|
||||
const fileExtension = window.location.pathname.slice(-4).toLowerCase()
|
||||
const hasPdfExtension = fileExtension === '.pdf'
|
||||
const pdfContentTypes = [
|
||||
'application/acrobat',
|
||||
'application/pdf',
|
||||
'application/x-pdf',
|
||||
'applications/vnd.pdf',
|
||||
'text/pdf',
|
||||
'text/x-pdf'
|
||||
];
|
||||
const isPdfContent = pdfContentTypes.indexOf(document.contentType) !== -1;
|
||||
'text/x-pdf',
|
||||
]
|
||||
const isPdfContent = pdfContentTypes.indexOf(document.contentType) !== -1
|
||||
if (!hasPdfExtension && !isPdfContent) {
|
||||
return Promise.resolve(null);
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
const embedEl = document.querySelector('embed');
|
||||
const embedEl = document.querySelector('embed')
|
||||
if (embedEl && embedEl.type !== 'application/pdf') {
|
||||
return Promise.resolve(null);
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
if (ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS && embedEl.src) {
|
||||
@ -43,115 +44,120 @@
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest()
|
||||
// load `document` from `cache`
|
||||
xhr.open('GET', '', true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.open('GET', '', true)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = function (e) {
|
||||
if (this.status === 200) {
|
||||
resolve({ type: 'pdf', uploadContentObjUrl: URL.createObjectURL(this.response) })
|
||||
resolve({
|
||||
type: 'pdf',
|
||||
uploadContentObjUrl: URL.createObjectURL(this.response),
|
||||
})
|
||||
} else {
|
||||
reject(e);
|
||||
reject(e)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
xhr.send()
|
||||
})
|
||||
}
|
||||
|
||||
function prepareContentPostItem (itemEl) {
|
||||
const lowerTagName = itemEl.tagName.toLowerCase();
|
||||
function prepareContentPostItem(itemEl) {
|
||||
const lowerTagName = itemEl.tagName.toLowerCase()
|
||||
|
||||
if (lowerTagName === 'iframe') {
|
||||
const frameHtml = iframes[itemEl.src];
|
||||
if (!frameHtml) return;
|
||||
const frameHtml = iframes[itemEl.src]
|
||||
if (!frameHtml) return
|
||||
|
||||
const containerEl = document.createElement('div');
|
||||
containerEl.className = 'omnivore-instagram-embed';
|
||||
containerEl.innerHTML = frameHtml;
|
||||
const containerEl = document.createElement('div')
|
||||
containerEl.className = 'omnivore-instagram-embed'
|
||||
containerEl.innerHTML = frameHtml
|
||||
|
||||
const parentEl = itemEl.parentNode;
|
||||
if (!parentEl) return;
|
||||
const parentEl = itemEl.parentNode
|
||||
if (!parentEl) return
|
||||
|
||||
parentEl.replaceChild(containerEl, itemEl);
|
||||
parentEl.replaceChild(containerEl, itemEl)
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (lowerTagName === 'img' || lowerTagName === 'image') {
|
||||
// Removing blurred images since they are mostly the copies of lazy loaded ones
|
||||
const style = window.getComputedStyle(itemEl);
|
||||
const filter = style.getPropertyValue('filter');
|
||||
if (filter.indexOf('blur(') === -1) return;
|
||||
itemEl.remove();
|
||||
return;
|
||||
const style = window.getComputedStyle(itemEl)
|
||||
const filter = style.getPropertyValue('filter')
|
||||
if (filter.indexOf('blur(') === -1) return
|
||||
itemEl.remove()
|
||||
return
|
||||
}
|
||||
|
||||
const style = window.getComputedStyle(itemEl);
|
||||
const backgroundImage = style.getPropertyValue('background-image');
|
||||
const style = window.getComputedStyle(itemEl)
|
||||
const backgroundImage = style.getPropertyValue('background-image')
|
||||
|
||||
// convert all nodes with background image to img nodes
|
||||
const noBackgroundImage = !backgroundImage || backgroundImage === 'none';
|
||||
if (!noBackgroundImage) return;
|
||||
const noBackgroundImage = !backgroundImage || backgroundImage === 'none'
|
||||
if (!noBackgroundImage) return
|
||||
|
||||
const filter = style.getPropertyValue('filter');
|
||||
const filter = style.getPropertyValue('filter')
|
||||
// avoiding image nodes with a blur effect creation
|
||||
if (filter && filter.indexOf('blur(') !== -1) {
|
||||
itemEl.remove();
|
||||
return;
|
||||
itemEl.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// Replacing element only of there are no content inside, b/c might remove important div with content.
|
||||
// Article example: http://www.josiahzayner.com/2017/01/genetic-designer-part-i.html
|
||||
// DIV with class "content-inner" has `url("https://resources.blogblog.com/blogblog/data/1kt/travel/bg_container.png")` background image.
|
||||
|
||||
if (itemEl.src) return;
|
||||
if (itemEl.innerHTML.length > 24) return;
|
||||
if (itemEl.src) return
|
||||
if (itemEl.innerHTML.length > 24) return
|
||||
|
||||
const BI_SRC_REGEXP = /url\("(.+?)"\)/gi;
|
||||
const matchedSRC = BI_SRC_REGEXP.exec(backgroundImage);
|
||||
const BI_SRC_REGEXP = /url\("(.+?)"\)/gi
|
||||
const matchedSRC = BI_SRC_REGEXP.exec(backgroundImage)
|
||||
// Using "g" flag with a regex we have to manually break down lastIndex to zero after every usage
|
||||
// More details here: https://stackoverflow.com/questions/1520800/why-does-a-regexp-with-global-flag-give-wrong-results
|
||||
BI_SRC_REGEXP.lastIndex = 0;
|
||||
BI_SRC_REGEXP.lastIndex = 0
|
||||
|
||||
const targetSrc = matchedSRC && matchedSRC[1];
|
||||
if (!targetSrc) return;
|
||||
const targetSrc = matchedSRC && matchedSRC[1]
|
||||
if (!targetSrc) return
|
||||
|
||||
const imgEl = document.createElement('img');
|
||||
imgEl.src = targetSrc;
|
||||
const parentEl = itemEl.parentNode;
|
||||
if (!parentEl) return;
|
||||
const imgEl = document.createElement('img')
|
||||
imgEl.src = targetSrc
|
||||
const parentEl = itemEl.parentNode
|
||||
if (!parentEl) return
|
||||
|
||||
parentEl.replaceChild(imgEl, itemEl);
|
||||
parentEl.replaceChild(imgEl, itemEl)
|
||||
}
|
||||
|
||||
function prepareContentPostScroll () {
|
||||
const contentCopyEl = document.createElement('div');
|
||||
contentCopyEl.style.position = 'absolute';
|
||||
contentCopyEl.style.left = '-2000px';
|
||||
contentCopyEl.style.zIndex = '-2000';
|
||||
contentCopyEl.innerHTML = document.body.innerHTML;
|
||||
function prepareContentPostScroll() {
|
||||
const contentCopyEl = document.createElement('div')
|
||||
contentCopyEl.style.position = 'absolute'
|
||||
contentCopyEl.style.left = '-2000px'
|
||||
contentCopyEl.style.zIndex = '-2000'
|
||||
contentCopyEl.innerHTML = document.body.innerHTML
|
||||
|
||||
// Appending copy of the content to the DOM to enable computed styles capturing ability
|
||||
// Without adding that copy to the DOM the `window.getComputedStyle` method will always return undefined.
|
||||
document.documentElement.appendChild(contentCopyEl);
|
||||
document.documentElement.appendChild(contentCopyEl)
|
||||
|
||||
Array.from(contentCopyEl.getElementsByTagName('*')).forEach(prepareContentPostItem);
|
||||
Array.from(contentCopyEl.getElementsByTagName('*')).forEach(
|
||||
prepareContentPostItem
|
||||
)
|
||||
|
||||
/*
|
||||
* Grab head and body separately as using clone on entire document into a div
|
||||
* removes the head and body tags while grabbing html in them. Instead we
|
||||
* capture them separately and concatenate them here with head and body tags
|
||||
* preserved.
|
||||
*/
|
||||
const contentCopyHtml = `<html><head>${document.head.innerHTML}</head><body>${contentCopyEl.innerHTML}</body></html>`;
|
||||
* Grab head and body separately as using clone on entire document into a div
|
||||
* removes the head and body tags while grabbing html in them. Instead we
|
||||
* capture them separately and concatenate them here with head and body tags
|
||||
* preserved.
|
||||
*/
|
||||
const contentCopyHtml = `<html><head>${document.head.innerHTML}</head><body>${contentCopyEl.innerHTML}</body></html>`
|
||||
// Cleaning up the copy element
|
||||
contentCopyEl.remove();
|
||||
return contentCopyHtml;
|
||||
contentCopyEl.remove()
|
||||
return contentCopyHtml
|
||||
}
|
||||
|
||||
function createBackdrop () {
|
||||
const backdropEl = document.createElement('div');
|
||||
backdropEl.className = 'webext-omnivore-backdrop';
|
||||
function createBackdrop() {
|
||||
const backdropEl = document.createElement('div')
|
||||
backdropEl.className = 'webext-omnivore-backdrop'
|
||||
backdropEl.style.cssText = `all: initial !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
@ -164,74 +170,171 @@
|
||||
transition: opacity 0.3s !important;
|
||||
-webkit-backdrop-filter: blur(4px) !important;
|
||||
backdrop-filter: blur(4px) !important;
|
||||
`;
|
||||
return backdropEl;
|
||||
`
|
||||
return backdropEl
|
||||
}
|
||||
|
||||
function clearExistingBackdrops () {
|
||||
const backdropCol = document.querySelectorAll('.webext-omnivore-backdrop');
|
||||
const getQuoteText = (containerNode) => {
|
||||
const nonParagraphTagsRegEx =
|
||||
/^(a|b|basefont|bdo|big|em|font|i|s|small|span|strike|strong|su[bp]|tt|u|code|mark)$/i
|
||||
|
||||
let textResult = ''
|
||||
let newParagraph = false
|
||||
|
||||
const getTextNodes = (node) => {
|
||||
let isPre = false
|
||||
const nodeElement =
|
||||
node instanceof HTMLElement ? node : node.parentElement
|
||||
if (nodeElement) {
|
||||
isPre = window
|
||||
.getComputedStyle(nodeElement)
|
||||
.whiteSpace.startsWith('pre')
|
||||
}
|
||||
|
||||
if (node.nodeType == 3) {
|
||||
const text = isPre ? node.nodeValue : node.nodeValue.replace(/\n/g, '')
|
||||
textResult += text
|
||||
} else if (node != containerNode) {
|
||||
if (!nonParagraphTagsRegEx.test(node.tagName)) {
|
||||
textResult += '\n\n'
|
||||
}
|
||||
}
|
||||
|
||||
const children = node.childNodes
|
||||
children.forEach(function (child) {
|
||||
getTextNodes(child)
|
||||
})
|
||||
}
|
||||
|
||||
getTextNodes(containerNode)
|
||||
|
||||
return textResult.trim()
|
||||
}
|
||||
|
||||
const markHighlightSelection = () => {
|
||||
// First remove any previous markers, this would only normally happen during debugging
|
||||
try {
|
||||
const markers = window.document.querySelectorAll(
|
||||
`span[data-omnivore-highlight-start="true"],
|
||||
span[data-omnivore-highlight-end="true"]`
|
||||
)
|
||||
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
markers[i].remove()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('remove marker error: ', error)
|
||||
// This should be OK
|
||||
}
|
||||
try {
|
||||
const sel = window.getSelection()
|
||||
if (sel.rangeCount) {
|
||||
const range = sel.getRangeAt(0)
|
||||
const endMarker = document.createElement('span')
|
||||
const startMarker = document.createElement('span')
|
||||
endMarker.setAttribute('data-omnivore-highlight-end', 'true')
|
||||
startMarker.setAttribute('data-omnivore-highlight-start', 'true')
|
||||
|
||||
var container = document.createElement('div')
|
||||
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
||||
container.appendChild(sel.getRangeAt(i).cloneContents())
|
||||
}
|
||||
|
||||
const endRange = range.cloneRange()
|
||||
endRange.collapse(false)
|
||||
endRange.insertNode(endMarker)
|
||||
|
||||
range.insertNode(startMarker)
|
||||
|
||||
return {
|
||||
highlightHTML: container.innerHTML,
|
||||
highlightText: getQuoteText(container),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('get text error', error)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function clearExistingBackdrops() {
|
||||
const backdropCol = document.querySelectorAll('.webext-omnivore-backdrop')
|
||||
for (let i = 0; i < backdropCol.length; i++) {
|
||||
const backdropEl = backdropCol[i];
|
||||
backdropEl.style.setProperty('opacity', '0', 'important');
|
||||
const backdropEl = backdropCol[i]
|
||||
backdropEl.style.setProperty('opacity', '0', 'important')
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < backdropCol.length; i++) {
|
||||
backdropCol[i].remove();
|
||||
backdropCol[i].remove()
|
||||
}
|
||||
}, 0.5e3);
|
||||
}, 0.5e3)
|
||||
}
|
||||
|
||||
async function prepareContent () {
|
||||
const pdfContent = await grabPdfContent();
|
||||
async function prepareContent(createHighlight) {
|
||||
const pdfContent = await grabPdfContent()
|
||||
if (pdfContent) {
|
||||
return pdfContent
|
||||
}
|
||||
const url = window.location.href;
|
||||
const url = window.location.href
|
||||
try {
|
||||
if (handleBackendUrl(url)) {
|
||||
if (!createHighlight && handleBackendUrl(url)) {
|
||||
return { type: 'url' }
|
||||
}
|
||||
} catch {
|
||||
console.log('error checking url')
|
||||
}
|
||||
|
||||
async function scrollPage (url) {
|
||||
const scrollingEl = (document.scrollingElement || document.body);
|
||||
const lastScrollPos = scrollingEl.scrollTop;
|
||||
const currentScrollHeight = scrollingEl.scrollHeight;
|
||||
console.log('get content: ', createHighlight)
|
||||
if (createHighlight) {
|
||||
console.log('creating highlight while saving')
|
||||
const highlightSelection = markHighlightSelection()
|
||||
console.log('highlightSelection', highlightSelection)
|
||||
}
|
||||
|
||||
async function scrollPage(url) {
|
||||
const scrollingEl = document.scrollingElement || document.body
|
||||
const lastScrollPos = scrollingEl.scrollTop
|
||||
const currentScrollHeight = scrollingEl.scrollHeight
|
||||
|
||||
/* add blurred overlay while scrolling */
|
||||
clearExistingBackdrops();
|
||||
clearExistingBackdrops()
|
||||
|
||||
const backdropEl = createBackdrop();
|
||||
document.body.appendChild(backdropEl);
|
||||
const backdropEl = createBackdrop()
|
||||
document.body.appendChild(backdropEl)
|
||||
|
||||
/*
|
||||
* check below compares scrollTop against initial page height to handle
|
||||
* pages with infinite scroll else we shall be infinitely scrolling here.
|
||||
* stop scrolling if the url has changed in the meantime.
|
||||
*/
|
||||
while (scrollingEl.scrollTop <= (currentScrollHeight - 500) && window.location.href === url) {
|
||||
const prevScrollTop = scrollingEl.scrollTop;
|
||||
scrollingEl.scrollTop += 500;
|
||||
while (
|
||||
scrollingEl.scrollTop <= currentScrollHeight - 500 &&
|
||||
window.location.href === url
|
||||
) {
|
||||
const prevScrollTop = scrollingEl.scrollTop
|
||||
scrollingEl.scrollTop += 500
|
||||
/* sleep upon scrolling position change for event loop to handle events from scroll */
|
||||
await (new Promise((resolve) => { setTimeout(resolve, 10); }));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10)
|
||||
})
|
||||
if (scrollingEl.scrollTop === prevScrollTop) {
|
||||
/* break out scroll loop if we are not able to scroll for any reason */
|
||||
// console.log('breaking out scroll loop', scrollingEl.scrollTop, currentScrollHeight);
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
scrollingEl.scrollTop = lastScrollPos;
|
||||
scrollingEl.scrollTop = lastScrollPos
|
||||
/* sleep upon scrolling position change for event loop to handle events from scroll */
|
||||
await (new Promise((resolve) => { setTimeout(resolve, 10); }));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10)
|
||||
})
|
||||
}
|
||||
await scrollPage(url);
|
||||
await scrollPage(url)
|
||||
|
||||
clearExistingBackdrops();
|
||||
return { type: 'html', content: prepareContentPostScroll() };
|
||||
clearExistingBackdrops()
|
||||
return { type: 'html', content: prepareContentPostScroll() }
|
||||
}
|
||||
|
||||
window.prepareContent = prepareContent;
|
||||
})();
|
||||
window.prepareContent = prepareContent
|
||||
})()
|
||||
|
||||
@ -192,8 +192,18 @@
|
||||
function updateLabelsFromCache(payload) {
|
||||
;(async () => {
|
||||
await getStorageItem('labels').then((cachedLabels) => {
|
||||
if (labels) {
|
||||
const selectedLabels = labels.filter((l) => l.selected)
|
||||
selectedLabels.forEach((l) => {
|
||||
const cached = cachedLabels.find((cached) => cached.name == l.name)
|
||||
if (cached) {
|
||||
cached.selected = true
|
||||
} else {
|
||||
cachedLabels.push(l)
|
||||
}
|
||||
})
|
||||
}
|
||||
labels = cachedLabels
|
||||
console.log(' == updated labels', cachedLabels)
|
||||
})
|
||||
})()
|
||||
}
|
||||
@ -279,6 +289,12 @@
|
||||
}
|
||||
|
||||
function toggleRow(rowId) {
|
||||
if (!currentToastEl) {
|
||||
// its possible this was called after closing the extension
|
||||
// so just return
|
||||
return
|
||||
}
|
||||
|
||||
const container = currentToastEl.shadowRoot.querySelector(rowId)
|
||||
const initialState = container?.getAttribute('data-state')
|
||||
const rows = currentToastEl.shadowRoot.querySelectorAll(
|
||||
@ -538,7 +554,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addNote() {
|
||||
function noteCacheKey() {
|
||||
return document.location
|
||||
? `cached-note-${document.location.href}`
|
||||
: undefined
|
||||
}
|
||||
|
||||
async function addNote() {
|
||||
const cachedNoteKey = noteCacheKey()
|
||||
|
||||
cancelAutoDismiss()
|
||||
toggleRow('#omnivore-add-note-row')
|
||||
|
||||
@ -547,7 +571,24 @@
|
||||
)
|
||||
|
||||
if (noteArea) {
|
||||
noteArea.focus()
|
||||
if (cachedNoteKey) {
|
||||
const existingNote = await getStorageItem(cachedNoteKey)
|
||||
noteArea.value = existingNote
|
||||
}
|
||||
|
||||
if (noteArea.value) {
|
||||
noteArea.select()
|
||||
} else {
|
||||
noteArea.focus()
|
||||
}
|
||||
|
||||
noteArea.addEventListener('input', (event) => {
|
||||
;(async () => {
|
||||
const note = {}
|
||||
note[cachedNoteKey] = event.target.value
|
||||
await setStorage(note)
|
||||
})()
|
||||
})
|
||||
|
||||
noteArea.onkeydown = (e) => {
|
||||
e.cancelBubble = true
|
||||
@ -587,7 +628,9 @@
|
||||
})
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropogation()
|
||||
if (event.stopPropogation) {
|
||||
event.stopPropogation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -745,27 +788,32 @@
|
||||
rowElement.setAttribute('data-label-selected', 'off')
|
||||
}
|
||||
|
||||
updateLabels()
|
||||
syncLabelChanges()
|
||||
}
|
||||
|
||||
function syncLabelChanges() {
|
||||
console.log('syncLabels')
|
||||
|
||||
updateStatusBox(
|
||||
'#omnivore-edit-labels-status',
|
||||
'loading',
|
||||
'Updating Labels...',
|
||||
undefined
|
||||
)
|
||||
const labelIds = labels.filter((l) => l['selected']).map((l) => l.id)
|
||||
const setLabels = labels
|
||||
.filter((l) => l['selected'])
|
||||
.map((l) => {
|
||||
return {
|
||||
name: l.name,
|
||||
color: l.color,
|
||||
}
|
||||
})
|
||||
|
||||
// browserApi.runtime.sendMessage({
|
||||
// - action: ACTIONS.SetLabels,
|
||||
// - payload: {
|
||||
// - ctx: ctx,
|
||||
// - labelIds: labelIds,
|
||||
// - },
|
||||
// - })
|
||||
browserApi.runtime.sendMessage({
|
||||
action: ACTIONS.SetLabels,
|
||||
payload: {
|
||||
ctx: ctx,
|
||||
labels: setLabels,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function editLabels() {
|
||||
@ -799,10 +847,14 @@
|
||||
|
||||
if (list) {
|
||||
list.innerHTML = ''
|
||||
labels.forEach(function (label, idx) {
|
||||
const rowHtml = createLabelRow(label)
|
||||
list.appendChild(rowHtml)
|
||||
})
|
||||
labels
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
|
||||
)
|
||||
.forEach(function (label, idx) {
|
||||
const rowHtml = createLabelRow(label)
|
||||
list.appendChild(rowHtml)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,15 +869,22 @@
|
||||
.filter(
|
||||
(l) => l.name.toLowerCase().indexOf(filterValue.toLowerCase()) > -1
|
||||
)
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
|
||||
)
|
||||
.forEach(function (label) {
|
||||
const rowHtml = createLabelRow(label)
|
||||
list.appendChild(rowHtml)
|
||||
})
|
||||
} else {
|
||||
labels.forEach(function (label) {
|
||||
const rowHtml = createLabelRow(label)
|
||||
list.appendChild(rowHtml)
|
||||
})
|
||||
labels
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
|
||||
)
|
||||
.forEach(function (label) {
|
||||
const rowHtml = createLabelRow(label)
|
||||
list.appendChild(rowHtml)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user