You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Clipper: Made extension compatible with Chrome and started screenshot clipping
This commit is contained in:
		
							
								
								
									
										40
									
								
								Clipper/joplin-webclipper/background.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Clipper/joplin-webclipper/background.js
									
									
									
									
									
										Normal 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) | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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}" | ||||
| 			} | ||||
| 	 } | ||||
|  | ||||
| } | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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...'); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user