1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Clipper: Made extension compatible with Chrome and started screenshot clipping

This commit is contained in:
Laurent Cozic 2018-05-24 18:32:30 +01:00
parent 9db9d98419
commit 4640d6d6e3
7 changed files with 228 additions and 41 deletions

View File

@ -0,0 +1,40 @@
let browser_ = null;
if (typeof browser !== 'undefined') {
browser_ = browser;
browserSupportsPromises_ = true;
} else if (typeof chrome !== 'undefined') {
browser_ = chrome;
browserSupportsPromises_ = false;
}
async function browserCaptureVisibleTabs(windowId, options) {
return new Promise((resolve, reject) => {
if (browserSupportsPromises_) return browser_.tabs.captureVisibleTab(null, { format: 'jpeg' });
browser_.tabs.captureVisibleTab(null, { format: 'jpeg' }, (image) => {
resolve(image);
});
});
}
chrome.runtime.onInstalled.addListener(function() {
});
browser_.runtime.onMessage.addListener((command) => {
if (command.name === 'screenshotArea') {
browserCaptureVisibleTabs(null, { format: 'jpeg' }).then((image) => {
content = Object.assign({}, command.content);
content.imageBase64 = image;
fetch(command.apiBaseUrl + "/notes", {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(content)
});
});
}
});

View File

@ -5,6 +5,15 @@
console.info('jopext: Loading content script');
let browser_ = null;
if (typeof browser !== 'undefined') {
browser_ = browser;
browserSupportsPromises_ = true;
} else if (typeof chrome !== 'undefined') {
browser_ = chrome;
browserSupportsPromises_ = false;
}
function pageTitle() {
const titleElements = document.getElementsByTagName("title");
if (titleElements.length) return titleElements[0].text.trim();
@ -63,6 +72,8 @@
}
async function prepareCommandResponse(command) {
console.info('Got command: ' + command.name);
if (command.name === "simplifiedPageHtml") {
let article = null;
@ -98,17 +109,101 @@
url: location.origin + location.pathname,
};
} else if (command.name === 'screenshot') {
const overlay = document.createElement('div');
overlay.style.opacity = '0.5';
overlay.style.background = 'black';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.zIndex = 99999999;
overlay.style.top = 0;
overlay.style.left = 0;
overlay.style.position = 'fixed';
document.body.appendChild(overlay);
const selection = document.createElement('div');
selection.style.opacity = '0.5';
selection.style.background = 'blue';
selection.style.zIndex = overlay.style.zIndex - 1;
selection.style.top = 0;
selection.style.left = 0;
selection.style.position = 'fixed';
document.body.appendChild(selection);
let isDragging = false;
let draggingStartPos = null;
let selectionArea = {};
function updateSelection() {
selection.style.left = selectionArea.x;
selection.style.top = selectionArea.y;
selection.style.width = selectionArea.width;
selection.style.height = selectionArea.height;
}
function setSelectionSizeFromMouse(event) {
selectionArea.width = Math.max(1, event.pageX - draggingStartPos.x);
selectionArea.height = Math.max(1, event.pageY - draggingStartPos.y);
updateSelection();
}
function selection_mouseDown(event) {
selectionArea = { x: event.pageX - document.body.scrollLeft, y: event.pageY - document.body.scrollTop, width: 0, height: 0 }
draggingStartPos = { x: event.pageX, y: event.pageY };
isDragging = true;
updateSelection();
}
function selection_mouseMove(event) {
if (!isDragging) return;
setSelectionSizeFromMouse(event);
}
function selection_mouseUp(event) {
setSelectionSizeFromMouse(event);
isDragging = false;
overlay.removeEventListener('mousedown', selection_mouseDown);
overlay.removeEventListener('mousemove', selection_mouseMove);
overlay.removeEventListener('mouseup', selection_mouseUp);
document.body.removeChild(overlay);
document.body.removeChild(selection);
const content = {
title: pageTitle(),
area: selectionArea,
url: location.origin + location.pathname,
};
browser_.runtime.sendMessage({
name: 'screenshotArea',
content: content,
apiBaseUrl: command.apiBaseUrl,
});
}
overlay.addEventListener('mousedown', selection_mouseDown);
overlay.addEventListener('mousemove', selection_mouseMove);
overlay.addEventListener('mouseup', selection_mouseUp);
return {};
} else {
throw new Error('Unknown command', command);
throw new Error('Unknown command: ' + JSON.stringify(command));
}
}
async function execCommand(command) {
const response = await prepareCommandResponse(command);
browser.runtime.sendMessage(response);
browser_.runtime.sendMessage(response);
}
browser.runtime.onMessage.addListener((command) => {
browser_.runtime.onMessage.addListener((command) => {
console.info('jopext: Got command:', command);
execCommand(command);

View File

@ -1,39 +1,48 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper",
"version": "1.0",
"manifest_version": 2,
"name": "Joplin Web Clipper",
"version": "1.0",
"description": "Gets and saves content from your browser to Joplin.",
"description": "Gets and saves content from your browser to Joplin.",
"homepage_url": "https://joplin.cozic.net",
"homepage_url": "https://joplin.cozic.net",
"icons": {
"48": "icons/48.png",
"96": "icons/96.png"
},
"icons": {
"48": "icons/48.png",
"96": "icons/96.png"
},
"permissions": [
"activeTab"
],
"permissions": [
"activeTab",
"tabs",
"http://*/",
"https://*/",
"<all_urls>"
],
"browser_action": {
"default_icon": "icons/32.png",
"default_title": "Joplin Web Clipper",
"default_popup": "popup/build/index.html"
},
"browser_action": {
"default_icon": "icons/32.png",
"default_title": "Joplin Web Clipper",
"default_popup": "popup/build/index.html"
},
"content_scripts": [
{
"matches": ["*://*/"],
"js": ["main.js"]
}
],
"content_scripts": [
{
"matches": ["*://*/"],
"js": ["main.js"]
}
],
"applications": {
"gecko": {
"id": "{8419486a-54e9-11e8-9401-ac9e17909436}"
}
}
"background": {
"scripts": ["background.js"],
"persistent": false
},
"applications": {
"gecko": {
"id": "{8419486a-54e9-11e8-9401-ac9e17909436}"
}
}
}

View File

@ -6,7 +6,7 @@
display: flex;
flex-direction: column;
background-color: #162b3d;
font-size: 1em;
font-size: 16px;
}
.App h2 {
@ -39,13 +39,15 @@
.App a.Button {
background-color: #0269c2;
padding: 8px 14px;
padding: 0 14px;
display: flex;
flex: 1;
color: #eee;
font-weight: normal;
font-size: .8em;
cursor: pointer;
align-items: center;
min-height: 31px;
}
.App a.Button:hover {

View File

@ -26,7 +26,7 @@ class AppComponent extends Component {
}
}
async clipSimplified_click() {
clipSimplified_click() {
bridge().sendCommandToActiveTab({
name: 'simplifiedPageHtml',
});
@ -38,10 +38,17 @@ class AppComponent extends Component {
});
}
clipScreenshot_click() {
bridge().sendCommandToActiveTab({
name: 'screenshot',
apiBaseUrl: 'http://127.0.0.1:9967',
});
}
async loadContentScripts() {
await Global.browser().tabs.executeScript({file: "/content_scripts/JSDOMParser.js"});
await Global.browser().tabs.executeScript({file: "/content_scripts/Readability.js"});
await Global.browser().tabs.executeScript({file: "/content_scripts/index.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/JSDOMParser.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/Readability.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/index.js"});
}
async componentDidMount() {
@ -103,6 +110,7 @@ class AppComponent extends Component {
<ul>
<li><a className="Button" onClick={this.clipSimplified_click}>Clip simplified page</a></li>
<li><a className="Button" onClick={this.clipComplete_click}>Clip complete page</a></li>
<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li>
</ul>
</div>
{ warningComponent }

View File

@ -1,10 +1,11 @@
class Bridge {
init(browser, dispatch) {
init(browser, browserSupportsPromises, dispatch) {
console.info('Popup: Init bridge');
this.browser_ = browser;
this.dispatch_ = dispatch;
this.browserSupportsPromises_ = browserSupportsPromises;
this.browser_notify = async (command) => {
console.info('Popup: Got command: ' + command.name);
@ -39,8 +40,38 @@ class Bridge {
return this.dispatch_(action);
}
async tabsExecuteScript(options) {
if (this.browserSupportsPromises_) return this.browser().tabs.executeScript(options);
return new Promise((resolve, reject) => {
this.browser().tabs.executeScript(options, () => {
resolve();
});
})
}
async tabsQuery(options) {
if (this.browserSupportsPromises_) return this.browser().tabs.query(options);
return new Promise((resolve, reject) => {
this.browser().tabs.query(options, (tabs) => {
resolve(tabs);
});
});
}
async tabsSendMessage(tabId, command) {
if (this.browserSupportsPromises_) return this.browser().tabs.sendMessage(tabId, command);
return new Promise((resolve, reject) => {
this.browser().tabs.sendMessage(tabId, command, (result) => {
resolve(result);
});
});
}
async sendCommandToActiveTab(command) {
const tabs = await this.browser().tabs.query({ active: true, currentWindow: true });
const tabs = await this.tabsQuery({ active: true, currentWindow: true });
if (!tabs.length) {
console.warn('No valid tab');
return;
@ -48,7 +79,9 @@ class Bridge {
this.dispatch({ type: 'CONTENT_UPLOAD', operation: null });
await this.browser().tabs.sendMessage(tabs[0].id, command);
console.info('Sending message ', command);
await this.tabsSendMessage(tabs[0].id, command);
}
async sendContentToJoplin(content) {

View File

@ -45,7 +45,7 @@ function reducer(state = defaultState, action) {
const store = createStore(reducer);
bridge().init(window.browser, store.dispatch);
bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store.dispatch);
console.info('Popup: Creating React app...');