You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Clipper: Add ability to launch clipper window with shortcut (#2272)
* Clipper: Add ability to launch clipper window with shortcut This change adds a command to the manifest.json file for the web clipper which launches the webclipper rather than clicking on it. Because this is a WebExtensions feature and not something homegrown, users are able to change (or remove) the shortcut using native browser functionality. * Add commands for all clipping options * Remove empty suggestedKeys property from extension manifest * Add ability to focus the webclipper buttons * Remove debug log * Change sendClipMessage warning to error * Refactor to add a sendContentToJoplin command * Update index.js Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
		| @@ -76,3 +76,59 @@ browser_.runtime.onMessage.addListener(async (command) => { | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| async function getActiveTabs() { | ||||
| 	const options = { active: true, currentWindow: true }; | ||||
| 	if (browserSupportsPromises_) return browser_.tabs.query(options); | ||||
|  | ||||
| 	return new Promise((resolve) => { | ||||
| 		browser_.tabs.query(options, (tabs) => { | ||||
| 			resolve(tabs); | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| async function sendClipMessage(clipType) { | ||||
| 	const tabs = await getActiveTabs(); | ||||
| 	if (!tabs || !tabs.length) { | ||||
| 		console.error('No active tabs'); | ||||
| 		return; | ||||
| 	} | ||||
| 	const tabId = tabs[0].id; | ||||
| 	// send a message to the content script on the active tab (assuming it's there) | ||||
| 	const message = { | ||||
| 		shouldSendToJoplin: true, | ||||
| 	}; | ||||
| 	switch (clipType) { | ||||
| 	case 'clipCompletePage': | ||||
| 		message.name = 'completePageHtml'; | ||||
| 		message.preProcessFor = 'markdown'; | ||||
| 		break; | ||||
| 	case 'clipCompletePageHtml': | ||||
| 		message.name = 'completePageHtml'; | ||||
| 		message.preProcessFor = 'html'; | ||||
| 		break; | ||||
| 	case 'clipSimplifiedPage': | ||||
| 		message.name = 'simplifiedPageHtml'; | ||||
| 		break; | ||||
| 	case 'clipUrl': | ||||
| 		message.name = 'pageUrl'; | ||||
| 		break; | ||||
| 	case 'clipSelection': | ||||
| 		message.name = 'selectedHtml'; | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	if (message.name) { | ||||
| 		browser_.tabs.sendMessage(tabId, message); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| browser_.commands.onCommand.addListener(function(command) { | ||||
| 	// We could enumerate these twice, but since we're in here first, | ||||
| 	// why not save ourselves the trouble with this convention | ||||
| 	if (command.startsWith('clip')) { | ||||
| 		sendClipMessage(command); | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -266,12 +266,13 @@ | ||||
|  | ||||
| 	async function prepareCommandResponse(command) { | ||||
| 		console.info(`Got command: ${command.name}`); | ||||
| 		const shouldSendToJoplin = !!command.shouldSendToJoplin; | ||||
|  | ||||
| 		const convertToMarkup = command.preProcessFor ? command.preProcessFor : 'markdown'; | ||||
|  | ||||
| 		const clippedContentResponse = (title, html, imageSizes, anchorNames, stylesheets) => { | ||||
| 			return { | ||||
| 				name: 'clippedContent', | ||||
| 				name: shouldSendToJoplin ? 'sendContentToJoplin' : 'clippedContent', | ||||
| 				title: title, | ||||
| 				html: html, | ||||
| 				base_url: baseUrl(), | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|     "manifest_version": 2, | ||||
|     "name": "Joplin Web Clipper [DEV]", | ||||
|     "version": "1.0.19", | ||||
|     "version": "1.0.20", | ||||
|     "description": "Capture and save web pages and screenshots from your browser to Joplin.", | ||||
|     "homepage_url": "https://joplinapp.org", | ||||
|     "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", | ||||
| @@ -33,6 +33,31 @@ | ||||
|             ] | ||||
|         } | ||||
|     ], | ||||
|     "commands": { | ||||
|         "_execute_browser_action": { | ||||
|             "suggested_key": { | ||||
|                 "default": "Alt+Shift+J" | ||||
|             } | ||||
|         }, | ||||
|         "clipCompletePage": { | ||||
|             "suggested_key": { | ||||
|                 "default": "Alt+Shift+C" | ||||
|             }, | ||||
|             "description": "Clip complete page (uses last selected notebook)" | ||||
|         }, | ||||
|         "clipCompletePageHtml": { | ||||
|             "description": "Clip complete page (HTML) (uses last selected notebook)" | ||||
|         }, | ||||
|         "clipSimplifiedPage": { | ||||
|             "description": "Clip simplified page (uses last selected notebook)" | ||||
|         }, | ||||
|         "clipUrl": { | ||||
|             "description": "Clip url (uses last selected notebook)" | ||||
|         }, | ||||
|         "clipSelection": { | ||||
|             "description": "Clip selection (uses last selected notebook)" | ||||
|         } | ||||
|     }, | ||||
|     "background": { | ||||
|         "scripts": [ | ||||
|             "background.js" | ||||
|   | ||||
| @@ -52,9 +52,11 @@ | ||||
| 	cursor: pointer; | ||||
| 	align-items: center; | ||||
| 	min-height: 31px; | ||||
| 	text-decoration: none; | ||||
| } | ||||
|  | ||||
| .App a.Button:hover { | ||||
| .App a.Button:hover, | ||||
| .App a.Button:focus { | ||||
| 	background-color: #1E89E6; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class PreviewComponent extends React.PureComponent { | ||||
| 				<h2>Title:</h2> | ||||
| 				<input className={'Title'} value={this.props.title} onChange={this.props.onTitleChange}/> | ||||
| 				<p><span>Type:</span> {commandUserString(this.props.command)}</p> | ||||
| 				<a className={'Confirm Button'} onClick={this.props.onConfirmClick}>Confirm</a> | ||||
| 				<a className={'Confirm Button'} href="#" onClick={this.props.onConfirmClick}>Confirm</a> | ||||
| 			</div> | ||||
| 		); | ||||
|  | ||||
| @@ -384,12 +384,12 @@ class AppComponent extends Component { | ||||
| 			<div className="App"> | ||||
| 				<div className="Controls"> | ||||
| 					<ul> | ||||
| 						<li><a className="Button" onClick={this.clipSimplified_click} title={simplifiedPageButtonTooltip}>{simplifiedPageButtonLabel}</a></li> | ||||
| 						<li><a className="Button" onClick={this.clipComplete_click}>Clip complete page</a></li> | ||||
| 						<li><a className="Button" onClick={this.clipCompleteHtml_click}>Clip complete page (HTML) (Beta)</a></li> | ||||
| 						<li><a className="Button" onClick={this.clipSelection_click}>Clip selection</a></li> | ||||
| 						<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li> | ||||
| 						<li><a className="Button" onClick={this.clipUrl_click}>Clip URL</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipSimplified_click} title={simplifiedPageButtonTooltip}>{simplifiedPageButtonLabel}</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipComplete_click}>Clip complete page</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipCompleteHtml_click}>Clip complete page (HTML) (Beta)</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipSelection_click}>Clip selection</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipScreenshot_click}>Clip screenshot</a></li> | ||||
| 						<li><a className="Button" href="#" onClick={this.clipUrl_click}>Clip URL</a></li> | ||||
| 					</ul> | ||||
| 				</div> | ||||
| 				{ foldersComp() } | ||||
|   | ||||
| @@ -6,15 +6,32 @@ class Bridge { | ||||
| 		this.nounce_ = Date.now(); | ||||
| 	} | ||||
|  | ||||
| 	async init(browser, browserSupportsPromises, dispatch) { | ||||
| 	async init(browser, browserSupportsPromises, store) { | ||||
| 		console.info('Popup: Init bridge'); | ||||
|  | ||||
| 		this.browser_ = browser; | ||||
| 		this.dispatch_ = dispatch; | ||||
| 		this.dispatch_ = store.dispatch; | ||||
| 		this.store_ = store; | ||||
| 		this.browserSupportsPromises_ = browserSupportsPromises; | ||||
| 		this.clipperServerPort_ = null; | ||||
| 		this.clipperServerPortStatus_ = 'searching'; | ||||
|  | ||||
| 		function convertCommandToContent(command) { | ||||
| 			return { | ||||
| 				title: command.title, | ||||
| 				body_html: command.html, | ||||
| 				base_url: command.base_url, | ||||
| 				source_url: command.url, | ||||
| 				parent_id: command.parent_id, | ||||
| 				tags: command.tags || '', | ||||
| 				image_sizes: command.image_sizes || {}, | ||||
| 				anchor_names: command.anchor_names || [], | ||||
| 				source_command: command.source_command, | ||||
| 				convert_to: command.convert_to, | ||||
| 				stylesheets: command.stylesheets, | ||||
| 			}; | ||||
| 		} | ||||
|  | ||||
| 		this.browser_notify = async (command) => { | ||||
| 			console.info('Popup: Got command:', command); | ||||
|  | ||||
| @@ -26,30 +43,26 @@ class Bridge { | ||||
| 			} | ||||
|  | ||||
| 			if (command.name === 'clippedContent') { | ||||
| 				const content = { | ||||
| 					title: command.title, | ||||
| 					body_html: command.html, | ||||
| 					base_url: command.base_url, | ||||
| 					source_url: command.url, | ||||
| 					parent_id: command.parent_id, | ||||
| 					tags: command.tags || '', | ||||
| 					image_sizes: command.image_sizes || {}, | ||||
| 					anchor_names: command.anchor_names || [], | ||||
| 					source_command: command.source_command, | ||||
| 					convert_to: command.convert_to, | ||||
| 					stylesheets: command.stylesheets, | ||||
| 				}; | ||||
|  | ||||
| 				const content = convertCommandToContent(command); | ||||
| 				this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content }); | ||||
| 			} | ||||
|  | ||||
| 			if (command.name === 'sendContentToJoplin') { | ||||
| 				const content = convertCommandToContent(command); | ||||
| 				this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content }); | ||||
|  | ||||
| 				const state = this.store_.getState(); | ||||
| 				content.parent_id = state.selectedFolderId; | ||||
| 				if (content.parent_id) { | ||||
| 					this.sendContentToJoplin(content); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (command.name === 'isProbablyReaderable') { | ||||
| 				this.dispatch({ type: 'IS_PROBABLY_READERABLE', value: command.value }); | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		this.browser_.runtime.onMessage.addListener(this.browser_notify); | ||||
|  | ||||
| 		const backgroundPage = await this.backgroundPage(this.browser_); | ||||
|  | ||||
| 		// Not sure why the getBackgroundPage() sometimes returns null, so | ||||
| @@ -354,7 +367,6 @@ class Bridge { | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| const bridge_ = new Bridge(); | ||||
|   | ||||
| @@ -105,7 +105,7 @@ async function main() { | ||||
|  | ||||
| 	console.info('Popup: Init bridge and restore state...'); | ||||
|  | ||||
| 	await bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store.dispatch); | ||||
| 	await bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store); | ||||
|  | ||||
| 	console.info('Popup: Creating React app...'); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user