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>]`.
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
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.
# Permissions used
* `storage` for settings
* `notifications` for showing error notification
* `activeTab` for requesting url, title and selected text
* `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 [#C] update screens and video now that the notification is nicer
* 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] make notification configurable or only in case of errors
* TODO [#C] or, add menu to extension?
* TODO [#C] custom capture template?
* TODO [#D] do not minfy for dev
* 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
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
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
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

View file

@ -1,6 +1,6 @@
{
"name": "grasp-extension",
"version": "0.4.0",
"version": "0.4.1",
"description": "A reliable way of capturing web pages and content",
"scripts": {
"build": "webpack --progress --profile --colors",

View file

@ -2,7 +2,7 @@
// TODO do I really need to annotate all files with @flow??
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 = {
url: string,
@ -15,12 +15,18 @@ type Params = {
function makeCaptureRequest(
params: Params,
options,
) {
if (params.tag_str == null) {
params.tag_str = options.default_tags;
}
const data = JSON.stringify(params);
console.log(`[background] capturing ${data}`);
var request = new XMLHttpRequest();
request.open('POST', capture_url(), true);
const curl = capture_url(options);
request.open('POST', curl, true);
request.onreadystatechange = () => {
if (request.readyState != 4) {
return;
@ -36,7 +42,9 @@ function makeCaptureRequest(
const response = JSON.parse(rtext);
const path = response.path;
console.log(`[background] success: ${response}`);
showNotification(`OK: captured to ${path}`);
if (options.notification) {
showNotification(`OK: captured to ${path}`);
}
} catch (err) {
had_error = true;
error_message = error_message.concat(String(err));
@ -45,7 +53,7 @@ function makeCaptureRequest(
} else {
had_error = true;
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');
return;
}
if (tag_str === null) {
tag_str = defaultTagStr();
}
const url: string = tab.url;
const title: ?string = tab.title;
// console.log('action!');
// ugh.. https://stackoverflow.com/a/19165930/706389
chrome.tabs.executeScript( {
code: "window.getSelection().toString();"
}, selections => {
const selection = selections == null ? null : selections[0];
makeCaptureRequest({
url: url,
title: title,
selection: selection,
comment: comment,
tag_str: tag_str,
get_options(opts => {
// console.log('action!');
// ugh.. https://stackoverflow.com/a/19165930/706389
chrome.tabs.executeScript( {
code: "window.getSelection().toString();"
}, selections => {
const selection = selections == null ? null : selections[0];
makeCaptureRequest({
url: url,
title: title,
selection: selection,
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??

View file

@ -2,15 +2,32 @@
const capture_endpoint = 'capture';
function port(): string {
return "12212";
type Options = {
port: string;
default_tags: string;
notification: boolean;
}
// none means do not use tags
export function defaultTagStr (): ?string {
return 'grasp';
function default_options(): Options {
return {
port: "12212",
default_tags: "grasp",
notification: true,
};
}
export function capture_url (): string {
return `http://localhost:${port()}/${capture_endpoint}`;
export function get_options(cb: (Options) => void) {
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 */
import {METHOD_CAPTURE_WITH_EXTRAS} from './common';
import {get_options} from './options';
// TODO template it in html too?
const BUTTON_ID = 'button_id';
@ -63,6 +64,7 @@ function setupPage () {
comment.addEventListener('keydown', ctrlEnterSubmit);
const tags = getTags();
get_options(opts => {tags.value = opts.default_tags;});
tags.addEventListener('keydown', ctrlEnterSubmit);
tags.addEventListener('focus', () => moveCaretToEnd(tags)); // to put cursor to the end of tags when tabbed

View file

@ -4,7 +4,11 @@
"icons": {
"128": "unicorn.png"
},
"options_page": "options.html",
"options_ui": {
"page": "options.html",
"chrome_style": true
},
"background": {
"page": "background.html"
},
@ -13,6 +17,7 @@
"default_title": "Capture page, with extra information"
},
"permissions": [
"storage",
"notifications",
"activeTab",
"http://localhost/capture",

View file

@ -5,5 +5,12 @@
<title></title>
</head>
<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>
</html>

View file

@ -7,7 +7,7 @@
<!-- TODO ugh didn't work -->
<!-- <%= 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="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>
</body>
</html>

View file

@ -26,7 +26,7 @@ var options = {
mode: 'development',
entry: {
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")
},
output: {