You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +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) {
|
async function prepareCommandResponse(command) {
|
||||||
console.info(`Got command: ${command.name}`);
|
console.info(`Got command: ${command.name}`);
|
||||||
|
const shouldSendToJoplin = !!command.shouldSendToJoplin;
|
||||||
|
|
||||||
const convertToMarkup = command.preProcessFor ? command.preProcessFor : 'markdown';
|
const convertToMarkup = command.preProcessFor ? command.preProcessFor : 'markdown';
|
||||||
|
|
||||||
const clippedContentResponse = (title, html, imageSizes, anchorNames, stylesheets) => {
|
const clippedContentResponse = (title, html, imageSizes, anchorNames, stylesheets) => {
|
||||||
return {
|
return {
|
||||||
name: 'clippedContent',
|
name: shouldSendToJoplin ? 'sendContentToJoplin' : 'clippedContent',
|
||||||
title: title,
|
title: title,
|
||||||
html: html,
|
html: html,
|
||||||
base_url: baseUrl(),
|
base_url: baseUrl(),
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Joplin Web Clipper [DEV]",
|
"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.",
|
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||||
"homepage_url": "https://joplinapp.org",
|
"homepage_url": "https://joplinapp.org",
|
||||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
"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": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"background.js"
|
"background.js"
|
||||||
|
@@ -52,9 +52,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 31px;
|
min-height: 31px;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App a.Button:hover {
|
.App a.Button:hover,
|
||||||
|
.App a.Button:focus {
|
||||||
background-color: #1E89E6;
|
background-color: #1E89E6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ class PreviewComponent extends React.PureComponent {
|
|||||||
<h2>Title:</h2>
|
<h2>Title:</h2>
|
||||||
<input className={'Title'} value={this.props.title} onChange={this.props.onTitleChange}/>
|
<input className={'Title'} value={this.props.title} onChange={this.props.onTitleChange}/>
|
||||||
<p><span>Type:</span> {commandUserString(this.props.command)}</p>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -384,12 +384,12 @@ class AppComponent extends Component {
|
|||||||
<div className="App">
|
<div className="App">
|
||||||
<div className="Controls">
|
<div className="Controls">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a className="Button" onClick={this.clipSimplified_click} title={simplifiedPageButtonTooltip}>{simplifiedPageButtonLabel}</a></li>
|
<li><a className="Button" href="#" 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" href="#" 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" href="#" 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" href="#" onClick={this.clipSelection_click}>Clip selection</a></li>
|
||||||
<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li>
|
<li><a className="Button" href="#" 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.clipUrl_click}>Clip URL</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{ foldersComp() }
|
{ foldersComp() }
|
||||||
|
@@ -6,15 +6,32 @@ class Bridge {
|
|||||||
this.nounce_ = Date.now();
|
this.nounce_ = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(browser, browserSupportsPromises, dispatch) {
|
async init(browser, browserSupportsPromises, store) {
|
||||||
console.info('Popup: Init bridge');
|
console.info('Popup: Init bridge');
|
||||||
|
|
||||||
this.browser_ = browser;
|
this.browser_ = browser;
|
||||||
this.dispatch_ = dispatch;
|
this.dispatch_ = store.dispatch;
|
||||||
|
this.store_ = store;
|
||||||
this.browserSupportsPromises_ = browserSupportsPromises;
|
this.browserSupportsPromises_ = browserSupportsPromises;
|
||||||
this.clipperServerPort_ = null;
|
this.clipperServerPort_ = null;
|
||||||
this.clipperServerPortStatus_ = 'searching';
|
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) => {
|
this.browser_notify = async (command) => {
|
||||||
console.info('Popup: Got command:', command);
|
console.info('Popup: Got command:', command);
|
||||||
|
|
||||||
@@ -26,30 +43,26 @@ class Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command.name === 'clippedContent') {
|
if (command.name === 'clippedContent') {
|
||||||
const content = {
|
const content = convertCommandToContent(command);
|
||||||
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.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content });
|
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') {
|
if (command.name === 'isProbablyReaderable') {
|
||||||
this.dispatch({ type: 'IS_PROBABLY_READERABLE', value: command.value });
|
this.dispatch({ type: 'IS_PROBABLY_READERABLE', value: command.value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.browser_.runtime.onMessage.addListener(this.browser_notify);
|
this.browser_.runtime.onMessage.addListener(this.browser_notify);
|
||||||
|
|
||||||
const backgroundPage = await this.backgroundPage(this.browser_);
|
const backgroundPage = await this.backgroundPage(this.browser_);
|
||||||
|
|
||||||
// Not sure why the getBackgroundPage() sometimes returns null, so
|
// Not sure why the getBackgroundPage() sometimes returns null, so
|
||||||
@@ -354,7 +367,6 @@ class Bridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bridge_ = new Bridge();
|
const bridge_ = new Bridge();
|
||||||
|
@@ -105,7 +105,7 @@ async function main() {
|
|||||||
|
|
||||||
console.info('Popup: Init bridge and restore state...');
|
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...');
|
console.info('Popup: Creating React app...');
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user