Settings page for port, notification and default tags
This commit is contained in:
parent
d0f7982c4f
commit
a658234cf3
|
@ -14,7 +14,7 @@ No third party dependencies! Just `python 3.6`.
|
||||||
Or alternatively, just run it directly if you don't want to autostart it: `server/grasp_server.py --path /path/to/your/capture.org [--port <custom_port>]`.
|
Or alternatively, just run it directly if you don't want to autostart it: `server/grasp_server.py --path /path/to/your/capture.org [--port <custom_port>]`.
|
||||||
2. Install chrome extension and configure hotkeys
|
2. Install chrome extension and configure hotkeys
|
||||||
|
|
||||||
That's it! **Currently port `12212` is hardcoded** in the extension, but it will be configurable later.
|
That's it! If you're using custom port make sure it's same as in the extension settings.
|
||||||
|
|
||||||
# Motivation
|
# Motivation
|
||||||
Why use org-capture? Well, it's hard to explain, maybe some other time... However if you do know you want to use it instead/alongside your browser bookmarks, by default
|
Why use org-capture? Well, it's hard to explain, maybe some other time... However if you do know you want to use it instead/alongside your browser bookmarks, by default
|
||||||
|
@ -45,6 +45,7 @@ You're gonna need `npm` for building extension.
|
||||||
After that you can find the extension in `build` directory and 'Load unpacked' if necessary. There is also Flow and Eslint set up.
|
After that you can find the extension in `build` directory and 'Load unpacked' if necessary. There is also Flow and Eslint set up.
|
||||||
|
|
||||||
# Permissions used
|
# Permissions used
|
||||||
|
* `storage` for settings
|
||||||
* `notifications` for showing error notification
|
* `notifications` for showing error notification
|
||||||
* `activeTab` for requesting url, title and selected text
|
* `activeTab` for requesting url, title and selected text
|
||||||
* `http://localhost/capture` for talking with backend
|
* `http://localhost/capture` for talking with backend
|
||||||
|
|
19
TODO.org
19
TODO.org
|
@ -1,18 +1,12 @@
|
||||||
* DONE [#B] release somewhere? not sure if necessary
|
|
||||||
CLOSED: [2019-01-07 Mon 23:57]
|
|
||||||
** TODO [#C] post on reddit?
|
|
||||||
* DONE [#B] get rid of kython, run ci for python too?
|
|
||||||
CLOSED: [2019-01-07 Mon 23:57]
|
|
||||||
|
|
||||||
* TODO [#B] doesn't look like hotkey working unless you manually rebind.. wtf?
|
* TODO [#B] doesn't look like hotkey working unless you manually rebind.. wtf?
|
||||||
|
|
||||||
* TODO [#C] update screens and video now that the notification is nicer
|
* TODO [#C] update screens and video now that the notification is nicer
|
||||||
|
|
||||||
* TODO [#C] how to test it? try on different pages?
|
* TODO [#C] how to test it? try on different pages?
|
||||||
* TODO [#C] some sort of delayed append logic? e.g. don't dump immediately and add as subitems?
|
* TODO [#C] some sort of delayed append logic? e.g. don't dump immediately and add as subitems?
|
||||||
* TODO [#C] make notification configurable or only in case of errors
|
|
||||||
* TODO [#C] or, add menu to extension?
|
* TODO [#C] or, add menu to extension?
|
||||||
|
|
||||||
|
* TODO [#C] custom capture template?
|
||||||
* TODO [#D] do not minfy for dev
|
* TODO [#D] do not minfy for dev
|
||||||
* TODO [#D] make manifest templated so we don't have to hardcode actions
|
* TODO [#D] make manifest templated so we don't have to hardcode actions
|
||||||
|
|
||||||
|
@ -36,9 +30,18 @@ Now, the content of `build` folder will be the extension ready to be submitted t
|
||||||
* DONE [#A] entering additional information and tags
|
* DONE [#A] entering additional information and tags
|
||||||
CLOSED: [2019-01-06 Sun 20:41]
|
CLOSED: [2019-01-06 Sun 20:41]
|
||||||
|
|
||||||
|
* DONE [#B] circleci?
|
||||||
|
CLOSED: [2019-01-13 Sun 13:01]
|
||||||
|
* DONE [#B] release somewhere? not sure if necessary
|
||||||
|
CLOSED: [2019-01-07 Mon 23:57]
|
||||||
|
** TODO [#C] post on reddit?
|
||||||
|
* DONE [#B] get rid of kython, run ci for python too?
|
||||||
|
CLOSED: [2019-01-07 Mon 23:57]
|
||||||
|
|
||||||
* DONE [#B] docs on using
|
* DONE [#B] docs on using
|
||||||
CLOSED: [2019-01-06 Sun 21:39]
|
CLOSED: [2019-01-06 Sun 21:39]
|
||||||
* TODO [#B] circleci?
|
* DONE [#C] make notification configurable or only in case of errors
|
||||||
|
CLOSED: [2019-01-13 Sun 13:01]
|
||||||
* DONE [#C] motivation
|
* DONE [#C] motivation
|
||||||
CLOSED: [2019-01-06 Sun 21:35]
|
CLOSED: [2019-01-06 Sun 21:35]
|
||||||
** DONE the benefit is that you can also add anything as a backend, e.g. you might be more of markdown fan or todo.txt
|
** DONE the benefit is that you can also add anything as a backend, e.g. you might be more of markdown fan or todo.txt
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "grasp-extension",
|
"name": "grasp-extension",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"description": "A reliable way of capturing web pages and content",
|
"description": "A reliable way of capturing web pages and content",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --profile --colors",
|
"build": "webpack --progress --profile --colors",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// TODO do I really need to annotate all files with @flow??
|
// TODO do I really need to annotate all files with @flow??
|
||||||
|
|
||||||
import {COMMAND_CAPTURE_SIMPLE, METHOD_CAPTURE_WITH_EXTRAS, showNotification} from './common';
|
import {COMMAND_CAPTURE_SIMPLE, METHOD_CAPTURE_WITH_EXTRAS, showNotification} from './common';
|
||||||
import {defaultTagStr, capture_url} from './options';
|
import {get_options, capture_url} from './options';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -15,12 +15,18 @@ type Params = {
|
||||||
|
|
||||||
function makeCaptureRequest(
|
function makeCaptureRequest(
|
||||||
params: Params,
|
params: Params,
|
||||||
|
options,
|
||||||
) {
|
) {
|
||||||
|
if (params.tag_str == null) {
|
||||||
|
params.tag_str = options.default_tags;
|
||||||
|
}
|
||||||
|
|
||||||
const data = JSON.stringify(params);
|
const data = JSON.stringify(params);
|
||||||
console.log(`[background] capturing ${data}`);
|
console.log(`[background] capturing ${data}`);
|
||||||
|
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open('POST', capture_url(), true);
|
const curl = capture_url(options);
|
||||||
|
request.open('POST', curl, true);
|
||||||
request.onreadystatechange = () => {
|
request.onreadystatechange = () => {
|
||||||
if (request.readyState != 4) {
|
if (request.readyState != 4) {
|
||||||
return;
|
return;
|
||||||
|
@ -36,7 +42,9 @@ function makeCaptureRequest(
|
||||||
const response = JSON.parse(rtext);
|
const response = JSON.parse(rtext);
|
||||||
const path = response.path;
|
const path = response.path;
|
||||||
console.log(`[background] success: ${response}`);
|
console.log(`[background] success: ${response}`);
|
||||||
showNotification(`OK: captured to ${path}`);
|
if (options.notification) {
|
||||||
|
showNotification(`OK: captured to ${path}`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
had_error = true;
|
had_error = true;
|
||||||
error_message = error_message.concat(String(err));
|
error_message = error_message.concat(String(err));
|
||||||
|
@ -45,7 +53,7 @@ function makeCaptureRequest(
|
||||||
} else {
|
} else {
|
||||||
had_error = true;
|
had_error = true;
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
error_message = error_message.concat(` ${capture_url()} must be unavailable `);
|
error_message = error_message.concat(` ${curl} must be unavailable `);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,25 +78,23 @@ function capture(comment: ?string = null, tag_str: ?string = null) {
|
||||||
showNotification('ERROR: trying to capture null');
|
showNotification('ERROR: trying to capture null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tag_str === null) {
|
|
||||||
tag_str = defaultTagStr();
|
|
||||||
}
|
|
||||||
|
|
||||||
const url: string = tab.url;
|
const url: string = tab.url;
|
||||||
const title: ?string = tab.title;
|
const title: ?string = tab.title;
|
||||||
|
|
||||||
// console.log('action!');
|
get_options(opts => {
|
||||||
// ugh.. https://stackoverflow.com/a/19165930/706389
|
// console.log('action!');
|
||||||
chrome.tabs.executeScript( {
|
// ugh.. https://stackoverflow.com/a/19165930/706389
|
||||||
code: "window.getSelection().toString();"
|
chrome.tabs.executeScript( {
|
||||||
}, selections => {
|
code: "window.getSelection().toString();"
|
||||||
const selection = selections == null ? null : selections[0];
|
}, selections => {
|
||||||
makeCaptureRequest({
|
const selection = selections == null ? null : selections[0];
|
||||||
url: url,
|
makeCaptureRequest({
|
||||||
title: title,
|
url: url,
|
||||||
selection: selection,
|
title: title,
|
||||||
comment: comment,
|
selection: selection,
|
||||||
tag_str: tag_str,
|
comment: comment,
|
||||||
|
tag_str: tag_str,
|
||||||
|
}, opts);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -110,3 +116,4 @@ chrome.runtime.onMessage.addListener((message: any, sender: chrome$MessageSender
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO handle cannot access chrome:// url??
|
// TODO handle cannot access chrome:// url??
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,32 @@
|
||||||
|
|
||||||
const capture_endpoint = 'capture';
|
const capture_endpoint = 'capture';
|
||||||
|
|
||||||
function port(): string {
|
type Options = {
|
||||||
return "12212";
|
port: string;
|
||||||
|
default_tags: string;
|
||||||
|
notification: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// none means do not use tags
|
function default_options(): Options {
|
||||||
export function defaultTagStr (): ?string {
|
return {
|
||||||
return 'grasp';
|
port: "12212",
|
||||||
|
default_tags: "grasp",
|
||||||
|
notification: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function capture_url (): string {
|
export function get_options(cb: (Options) => void) {
|
||||||
return `http://localhost:${port()}/${capture_endpoint}`;
|
chrome.storage.local.get(null, res => {
|
||||||
|
res = {...default_options(), ...res};
|
||||||
|
cb(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set_options(opts: Options, cb: () => void) {
|
||||||
|
console.log('Saving %s', JSON.stringify(opts));
|
||||||
|
chrome.storage.local.set(opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capture_url(options: Options): string {
|
||||||
|
return `http://localhost:${options.port}/${capture_endpoint}`;
|
||||||
}
|
}
|
||||||
|
|
46
src/js/options_page.js
Normal file
46
src/js/options_page.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* @flow */
|
||||||
|
import {get_options, set_options} from './options';
|
||||||
|
|
||||||
|
const SAVE_ID = 'save_id';
|
||||||
|
const PORT_ID = 'port_id';
|
||||||
|
const NOTIFICATION_ID = 'notification_id';
|
||||||
|
const DEFAULT_TAGS_ID = 'default_tags_id';
|
||||||
|
// TODO specify capture path here?
|
||||||
|
|
||||||
|
function getPort(): HTMLInputElement {
|
||||||
|
return ((document.getElementById(PORT_ID): any): HTMLInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultTags(): HTMLInputElement {
|
||||||
|
return ((document.getElementById(DEFAULT_TAGS_ID): any): HTMLInputElement);
|
||||||
|
// TODO if empty, return null?
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnableNotification(): HTMLInputElement {
|
||||||
|
return ((document.getElementById(NOTIFICATION_ID): any): HTMLInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSaveButton(): HTMLInputElement {
|
||||||
|
return ((document.getElementById(SAVE_ID): any): HTMLInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreOptions() {
|
||||||
|
get_options(opts => {
|
||||||
|
getPort().value = opts.port;
|
||||||
|
getDefaultTags().value = opts.default_tags;
|
||||||
|
getEnableNotification().checked = opts.notification;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveOptions() {
|
||||||
|
const opts = {
|
||||||
|
port: getPort().value, // TODO validate??
|
||||||
|
default_tags: getDefaultTags().value,
|
||||||
|
notification: getEnableNotification().checked,
|
||||||
|
};
|
||||||
|
set_options(opts, () => { console.log('fewf'); alert('Saved!'); });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', restoreOptions);
|
||||||
|
getSaveButton().addEventListener('click', saveOptions);
|
|
@ -1,6 +1,7 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import {METHOD_CAPTURE_WITH_EXTRAS} from './common';
|
import {METHOD_CAPTURE_WITH_EXTRAS} from './common';
|
||||||
|
import {get_options} from './options';
|
||||||
|
|
||||||
// TODO template it in html too?
|
// TODO template it in html too?
|
||||||
const BUTTON_ID = 'button_id';
|
const BUTTON_ID = 'button_id';
|
||||||
|
@ -63,6 +64,7 @@ function setupPage () {
|
||||||
comment.addEventListener('keydown', ctrlEnterSubmit);
|
comment.addEventListener('keydown', ctrlEnterSubmit);
|
||||||
|
|
||||||
const tags = getTags();
|
const tags = getTags();
|
||||||
|
get_options(opts => {tags.value = opts.default_tags;});
|
||||||
tags.addEventListener('keydown', ctrlEnterSubmit);
|
tags.addEventListener('keydown', ctrlEnterSubmit);
|
||||||
tags.addEventListener('focus', () => moveCaretToEnd(tags)); // to put cursor to the end of tags when tabbed
|
tags.addEventListener('focus', () => moveCaretToEnd(tags)); // to put cursor to the end of tags when tabbed
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
"icons": {
|
"icons": {
|
||||||
"128": "unicorn.png"
|
"128": "unicorn.png"
|
||||||
},
|
},
|
||||||
"options_page": "options.html",
|
"options_ui": {
|
||||||
|
"page": "options.html",
|
||||||
|
"chrome_style": true
|
||||||
|
},
|
||||||
|
|
||||||
"background": {
|
"background": {
|
||||||
"page": "background.html"
|
"page": "background.html"
|
||||||
},
|
},
|
||||||
|
@ -13,6 +17,7 @@
|
||||||
"default_title": "Capture page, with extra information"
|
"default_title": "Capture page, with extra information"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"storage",
|
||||||
"notifications",
|
"notifications",
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"http://localhost/capture",
|
"http://localhost/capture",
|
||||||
|
|
|
@ -5,5 +5,12 @@
|
||||||
<title></title>
|
<title></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<label for="port_id">Port</label>
|
||||||
|
<div><input type="text" id="port_id"/></div>
|
||||||
|
<label for="notification_id">Notification after capturing</label>
|
||||||
|
<div><input type="checkbox" id="notification_id"/></div>
|
||||||
|
<label for="default_tags_id">Default tags</label>
|
||||||
|
<div><input type="text" id="default_tags_id" /></div>
|
||||||
|
<div><button id="save_id">Save</button></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<!-- TODO ugh didn't work -->
|
<!-- TODO ugh didn't work -->
|
||||||
<!-- <%= htmlWebpackPlugin.options.ids.tags %> -->
|
<!-- <%= htmlWebpackPlugin.options.ids.tags %> -->
|
||||||
<div><textarea id="comment_id" rows="4" cols="50" placeholder="Enter your comments; press Ctrl-Enter to capture"></textarea></div>
|
<div><textarea id="comment_id" rows="4" cols="50" placeholder="Enter your comments; press Ctrl-Enter to capture"></textarea></div>
|
||||||
<div><textarea id="tags_id" rows="1" cols="50">grasp</textarea></div>
|
<div><textarea id="tags_id" rows="1" cols="50"></textarea></div>
|
||||||
<div><button id="button_id" type="button" align="right">capture!</button></div>
|
<div><button id="button_id" type="button" align="right">capture!</button></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -26,7 +26,7 @@ var options = {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
entry: {
|
entry: {
|
||||||
popup: path.join(__dirname, "src", "js", "popup.js"),
|
popup: path.join(__dirname, "src", "js", "popup.js"),
|
||||||
options: path.join(__dirname, "src", "js", "options.js"),
|
options: path.join(__dirname, "src", "js", "options_page.js"),
|
||||||
background: path.join(__dirname, "src", "js", "background.js")
|
background: path.join(__dirname, "src", "js", "background.js")
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|
Loading…
Reference in a new issue