Settings page for port, notification and default tags

This commit is contained in:
Dima Gerasimov 2019-01-13 13:08:38 +00:00 committed by Dmitrii Gerasimov
parent d0f7982c4f
commit a658234cf3
11 changed files with 128 additions and 40 deletions

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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}`);
if (options.notification) {
showNotification(`OK: captured to ${path}`); 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,13 +78,10 @@ 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;
get_options(opts => {
// console.log('action!'); // console.log('action!');
// ugh.. https://stackoverflow.com/a/19165930/706389 // ugh.. https://stackoverflow.com/a/19165930/706389
chrome.tabs.executeScript( { chrome.tabs.executeScript( {
@ -89,6 +94,7 @@ function capture(comment: ?string = null, tag_str: ?string = null) {
selection: selection, selection: selection,
comment: comment, comment: comment,
tag_str: tag_str, 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??

View file

@ -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
View 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);

View file

@ -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

View file

@ -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",

View file

@ -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>

View file

@ -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>

View file

@ -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: {