atyourcommand
Advanced tools
Comparing version 0.1.1 to 0.1.2
/*globals jml */ | ||
/*jslint vars:true*/ | ||
/** | ||
@@ -9,2 +9,7 @@ * @class ExpandableInputs | ||
// DEBUGGING | ||
function l (str) { | ||
console.log(str); | ||
} | ||
// STATIC VARS | ||
@@ -15,3 +20,3 @@ var ns = 0; // Used to prevent conflicts if the user does not supply their own namespace | ||
Array.from = function (arg) { | ||
return [].slice.call(arg); | ||
return [].slice.call(arg); | ||
}; | ||
@@ -26,3 +31,3 @@ | ||
function $ (sel) { | ||
return document.querySelector(sel); | ||
return document.querySelector(sel); | ||
} | ||
@@ -34,3 +39,3 @@ /** | ||
function $$ (sel) { | ||
return document.querySelectorAll(sel); | ||
return document.querySelectorAll(sel); | ||
} | ||
@@ -45,3 +50,3 @@ | ||
* @param {string} [cfg.namespace] Namespace for this set | ||
of expandable inputs. If none is supplied, an incrementing value will be used. | ||
of expandable inputs. If none is supplied, an incrementing value will be used. | ||
* @param {string} [cfg.label="%s:"] The label to be shown. (See cfg.pattern for the regular expression used to do substitutions.) | ||
@@ -56,150 +61,230 @@ * @param {string} [cfg.pattern=/%s/g] The regular expression for finding numbers within labels. | ||
function ExpandableInputs (cfg) { | ||
if (!(this instanceof ExpandableInputs)) { | ||
return new ExpandableInputs(cfg); | ||
} | ||
if (!cfg || typeof cfg !== 'object' || !cfg.table) { | ||
throw 'A config object with a table ID must be supplied to ExpandableInputs'; | ||
} | ||
this.table = cfg.table; | ||
this.prefix = ((cfg.prefix && cfg.prefix.replace(/-$/, '')) || 'ei') + '-'; | ||
this.ns = ((cfg.namespace && cfg.namespace.replace(/-$/, '')) || (ns++).toString()) + '-'; | ||
this.label = cfg.label || "%s:"; | ||
this.pattern = cfg.pattern || /%s/g; | ||
this.inputType = cfg.inputType && cfg.inputType !== 'file' ? cfg.inputType : 'text'; | ||
this.selects = cfg.selects || false; | ||
this.inputSize = cfg.inputSize || 50; | ||
if (cfg.rows !== undef) { | ||
this.rows = cfg.rows; | ||
} | ||
this.locale = cfg.locale || { | ||
browse: "Browse\u2026", | ||
directory: "Directory?", | ||
plus: "+", | ||
minus: "-", | ||
reveal: "" // We use a background-image of a folder instead of text | ||
}; | ||
if (!(this instanceof ExpandableInputs)) { | ||
return new ExpandableInputs(cfg); | ||
} | ||
if (!cfg || typeof cfg !== 'object' || !cfg.table) { | ||
throw 'A config object with a table ID must be supplied to ExpandableInputs'; | ||
} | ||
this.table = cfg.table; | ||
this.prefix = ((cfg.prefix && cfg.prefix.replace(/-$/, '')) || 'ei') + '-'; | ||
this.ns = ((cfg.namespace && cfg.namespace.replace(/-$/, '')) || (ns++).toString()) + '-'; | ||
this.label = cfg.label || "%s:"; | ||
this.pattern = cfg.pattern || /%s/g; | ||
this.inputType = cfg.inputType && cfg.inputType !== 'file' ? cfg.inputType : 'text'; | ||
this.selects = cfg.selects || false; | ||
this.inputSize = cfg.inputSize || 50; | ||
if (cfg.rows !== undef) { | ||
this.rows = cfg.rows; | ||
} | ||
this.locale = cfg.locale || { | ||
browse: "Browse\u2026", | ||
directory: "Directory?", | ||
plus: "+", | ||
minus: "-", | ||
reveal: "" // We use a background-image of a folder instead of text | ||
}; | ||
// State variables | ||
this.fileType = cfg.inputType === 'file'; | ||
this.id = 1; | ||
this.num = 1; | ||
// State variables | ||
this.fileType = cfg.inputType === 'file'; | ||
this.resetCount(); | ||
} | ||
ExpandableInputs.prototype.resetCount = function () { | ||
this.id = 1; | ||
this.num = 1; | ||
}; | ||
ExpandableInputs.prototype.getLabel = function (num) { | ||
return this.label.replace(this.pattern, num); | ||
return this.label.replace(this.pattern, num); | ||
}; | ||
ExpandableInputs.prototype.getPrefixedNamespace = function () { | ||
return this.prefix + this.ns; | ||
return this.prefix + this.ns; | ||
}; | ||
ExpandableInputs.prototype.remove = function (id) { | ||
var prefixedNS = this.getPrefixedNamespace(), | ||
that = this, | ||
rowIDSel = '#' + prefixedNS + 'row-' + id; | ||
if ($$('.' + prefixedNS + 'row').length === 1) { // Don't delete if only one remaining | ||
return; | ||
} | ||
$(rowIDSel).parentNode.removeChild($(rowIDSel)); | ||
// Renumber to ensure inputs remain incrementing by one | ||
this.num = 1; | ||
Array.from($$('.' + prefixedNS + 'number')).forEach(function (numHolder) { | ||
numHolder.replaceChild(document.createTextNode(that.getLabel(that.num++)), numHolder.firstChild); | ||
}); | ||
var prefixedNS = this.getPrefixedNamespace(), | ||
rowIDSel = '#' + prefixedNS + 'row-' + id; | ||
if ($$('.' + prefixedNS + 'row').length === 1) { // Don't delete if only one remaining | ||
return true; | ||
} | ||
$(rowIDSel).parentNode.removeChild($(rowIDSel)); | ||
// Renumber to ensure inputs remain incrementing by one | ||
this.num = 1; | ||
Array.from($$('.' + prefixedNS + 'number')).forEach(function (numHolder) { | ||
numHolder.replaceChild(document.createTextNode(this.getLabel(this.num++)), numHolder.firstChild); | ||
}, this); | ||
return false; | ||
}; | ||
ExpandableInputs.prototype.addTableEvent = function () { | ||
var that = this; | ||
$('#' + this.table).addEventListener('click', function (e) { | ||
var dataset = e.target.dataset; | ||
if (!dataset || !dataset.ei_type) { | ||
return; | ||
} | ||
switch (dataset.ei_type) { | ||
case 'remove': | ||
var noneToRemove = that.remove(dataset.ei_id); | ||
// Allow DOM listening for removal | ||
if (!noneToRemove) { | ||
var e = document.createEvent('HTMLEvents'); | ||
e.initEvent('change', true, true); | ||
$('#' + that.table).dispatchEvent(e); | ||
} | ||
break; | ||
case 'add': | ||
that.add(); | ||
break; | ||
} | ||
}); | ||
}; | ||
ExpandableInputs.prototype.getValues = function (type) { | ||
var selector = '.' + this.getPrefixedNamespace() + type; | ||
return Array.from($$(selector)).map(function (arg) { | ||
if (arg.type === 'checkbox') { | ||
return arg.checked; | ||
} | ||
return arg.value; | ||
}); | ||
}; | ||
ExpandableInputs.prototype.getTextValues = function () { | ||
return this.getValues('input'); | ||
}; | ||
ExpandableInputs.prototype.setValues = function (type, storage) { | ||
// We could simplify this by allowing add() to take an initial value | ||
var prefixedNS = this.getPrefixedNamespace(); | ||
var selector = '.' + prefixedNS + type; | ||
storage = storage || []; | ||
if (Array.from($$(selector)).length !== storage.length) { // Don't remove if already the right number | ||
Array.from($$('.' + prefixedNS + 'row')).forEach(function (row) { | ||
row.parentNode.removeChild(row); | ||
}); | ||
this.resetCount(); | ||
if (!storage.length) { | ||
this.add(); | ||
return; | ||
} | ||
storage.forEach(function () { | ||
this.add(); | ||
}, this); | ||
} | ||
Array.from($$(selector)).forEach(function (arg, i) { | ||
var data = storage[i]; | ||
if (arg.type === 'checkbox') { | ||
arg.checked = data || false; | ||
} | ||
else { | ||
arg.value = data || ''; | ||
} | ||
}); | ||
}; | ||
ExpandableInputs.prototype.setTextValues = function (storage) { | ||
return this.setValues('input', storage); | ||
}; | ||
ExpandableInputs.prototype.add = function () { | ||
var that = this, | ||
prefixedNS = this.getPrefixedNamespace(); | ||
$('#' + this.table).appendChild(jml( | ||
'tr', { | ||
'id': prefixedNS + 'row-' + this.id, | ||
'class': prefixedNS + 'row' | ||
}, [ | ||
['td', [ | ||
['label', { | ||
'for': prefixedNS + 'input-' + this.id, | ||
'class': prefixedNS + 'number' | ||
}, [this.getLabel(this.num)]] | ||
]], | ||
['td', [ | ||
(this.fileType && this.selects ? | ||
($$('.' + prefixedNS + 'presets').length > 0 ? | ||
(function () { | ||
var select = $('.' + prefixedNS + 'presets').cloneNode(true); | ||
select.id = prefixedNS + 'select-' + that.id; | ||
select.dataset.sel = '#' + prefixedNS + 'input-' + that.id; | ||
return select; | ||
}()) : | ||
['select', { | ||
id: prefixedNS + 'select-' + this.id, | ||
'class': prefixedNS + 'presets', | ||
dataset: {sel: '#' + prefixedNS + 'input-' + this.id} | ||
}] | ||
) : | ||
'' | ||
), | ||
[(this.hasOwnProperty('rows') ? 'textarea' : 'input'), (function () { | ||
var atts = { | ||
id: prefixedNS + 'input-' + that.id, | ||
'class': prefixedNS + 'input ' + prefixedNS + 'path' | ||
}; | ||
if (that.hasOwnProperty('rows')) { // textarea | ||
atts.cols = that.inputSize; | ||
atts.rows = that.rows; | ||
} | ||
else { // input | ||
atts.size = that.inputSize; | ||
atts.type = that.inputType; | ||
atts.value = ''; | ||
} | ||
if (that.fileType) { | ||
atts.list = prefixedNS + 'fileDatalist-' + that.id; | ||
atts.autocomplete = 'off'; | ||
} | ||
return atts; | ||
}())], | ||
(this.fileType ? | ||
// Todo: resolve any issues with fragments and Jamilih and use here to reduce need for jml() call here | ||
jml( | ||
'datalist', {id: prefixedNS + 'fileDatalist-' + this.id}, | ||
'input', { | ||
type: 'button', | ||
'class': prefixedNS + 'picker', | ||
dataset: { | ||
sel: '#' + prefixedNS + 'input-' + this.id, | ||
directory: '#' + prefixedNS + 'directory' + this.id | ||
}, | ||
value: this.locale.browse | ||
}, | ||
'input', {type: 'button', 'class': prefixedNS + 'revealButton', value: this.locale.reveal, dataset: {sel: '#' + prefixedNS + 'input-' + this.id}}, | ||
'label', [ | ||
['input', { | ||
id: prefixedNS + 'directory' + this.id, | ||
type: 'checkbox', | ||
'class': prefixedNS + 'directory' | ||
}], | ||
this.locale.directory | ||
], | ||
null | ||
) : | ||
'' | ||
) | ||
]], | ||
['td', [ | ||
['button', { | ||
'class': prefixedNS + 'add' | ||
}, [this.locale.plus]] | ||
]], | ||
['td', [ | ||
['button', { | ||
'class': prefixedNS + 'remove', | ||
dataset: {id: this.id} | ||
}, [this.locale.minus]] | ||
]] | ||
], null | ||
) | ||
); | ||
this.id++; | ||
this.num++; | ||
var that = this, | ||
prefixedNS = this.getPrefixedNamespace(); | ||
if (!this.tableEventAdded) { | ||
this.addTableEvent(); | ||
this.tableEventAdded = true; | ||
} | ||
$('#' + this.table).appendChild(jml( | ||
'tr', { | ||
'id': prefixedNS + 'row-' + this.id, | ||
'class': prefixedNS + 'row' | ||
}, [ | ||
['td', [ | ||
['label', { | ||
'for': prefixedNS + 'input-' + this.id, | ||
'class': prefixedNS + 'number' | ||
}, [this.getLabel(this.num)]] | ||
]], | ||
['td', [ | ||
(this.fileType && this.selects ? | ||
($$('.' + prefixedNS + 'presets').length > 0 ? | ||
(function () { | ||
var select = $('.' + prefixedNS + 'presets').cloneNode(true); | ||
select.id = prefixedNS + 'select-' + that.id; | ||
select.dataset.ei_sel = '#' + prefixedNS + 'input-' + that.id; | ||
return select; | ||
}()) : | ||
['select', { | ||
id: prefixedNS + 'select-' + this.id, | ||
'class': prefixedNS + 'presets', | ||
dataset: {ei_sel: '#' + prefixedNS + 'input-' + this.id} | ||
}] | ||
) : | ||
'' | ||
), | ||
[(this.hasOwnProperty('rows') ? 'textarea' : 'input'), (function () { | ||
var atts = { | ||
id: prefixedNS + 'input-' + that.id, | ||
'class': prefixedNS + 'input ' + prefixedNS + 'path' | ||
}; | ||
if (that.hasOwnProperty('rows')) { // textarea | ||
atts.cols = that.inputSize; | ||
atts.rows = that.rows; | ||
} | ||
else { // input | ||
atts.size = that.inputSize; | ||
atts.type = that.inputType; | ||
atts.value = ''; | ||
} | ||
if (that.fileType) { | ||
atts.list = prefixedNS + 'fileDatalist-' + that.id; | ||
atts.autocomplete = 'off'; | ||
} | ||
return atts; | ||
}())], | ||
(this.fileType ? | ||
{'#': [ | ||
['datalist', {id: prefixedNS + 'fileDatalist-' + this.id}], | ||
['input', { | ||
type: 'button', | ||
'class': prefixedNS + 'picker', | ||
dataset: { | ||
ei_sel: '#' + prefixedNS + 'input-' + this.id, | ||
ei_directory: '#' + prefixedNS + 'directory' + this.id | ||
}, | ||
value: this.locale.browse | ||
}], | ||
['input', {type: 'button', 'class': prefixedNS + 'revealButton', value: this.locale.reveal, dataset: {ei_sel: '#' + prefixedNS + 'input-' + this.id}}], | ||
['label', [ | ||
['input', { | ||
id: prefixedNS + 'directory' + this.id, | ||
type: 'checkbox', | ||
'class': prefixedNS + 'directory' | ||
}], | ||
this.locale.directory | ||
]] | ||
]} : | ||
'' | ||
) | ||
]], | ||
['td', [ | ||
['button', { | ||
'class': prefixedNS + 'add', | ||
dataset: {ei_type: 'add'} | ||
}, [this.locale.plus]] | ||
]], | ||
['td', [ | ||
['button', { | ||
'class': prefixedNS + 'remove', | ||
dataset: {ei_id: this.id, ei_type: 'remove'} | ||
}, [this.locale.minus]] | ||
]] | ||
], null | ||
) | ||
); | ||
this.id++; | ||
this.num++; | ||
}; | ||
@@ -206,0 +291,0 @@ |
/*globals self*/ | ||
/*jslint vars: true*/ | ||
self.on('click', function (node, data) {'use strict'; | ||
var msg = { | ||
type: data, | ||
pageURL: document.URL, | ||
pageTitle: document.title, | ||
pageHTML: document.documentElement.outerHTML, | ||
bodyText: document.body.textContent, | ||
selectedHTML: node.outerHTML, | ||
selectedText: node.textContent | ||
}; | ||
var nodeName = node.nodeName.toLowerCase(); | ||
if (nodeName === 'a' && node.hasAttribute('href')) { | ||
msg.linkPageURL = node.href; // Includes base URI | ||
// We retrieve "linkPageTitle", "linkBodyText", and "linkPageHTML" only as needed | ||
} | ||
else if (nodeName === 'img' && node.hasAttribute('src')) { | ||
msg.imageURL = node.src; // Includes base URI | ||
} | ||
self.postMessage(msg); // We need privs on the dialogs we open | ||
var msg = { | ||
type: data, | ||
pageURL: document.URL, | ||
pageTitle: document.title, | ||
pageHTML: document.documentElement.outerHTML, | ||
bodyText: document.body.textContent, | ||
selectedHTML: node.outerHTML, | ||
selectedText: node.textContent | ||
}; | ||
var nodeName = node.nodeName.toLowerCase(); | ||
if (nodeName === 'a' && node.hasAttribute('href')) { | ||
msg.linkPageURL = node.href; // Includes base URI | ||
// We retrieve "linkPageTitle", "linkBodyText", and "linkPageHTML" only as needed | ||
} | ||
else if (nodeName === 'img' && node.hasAttribute('src')) { | ||
msg.imageURL = node.src; // Includes base URI | ||
} | ||
self.postMessage(msg); // We need privs on the dialogs we open | ||
}); |
@@ -1,40 +0,49 @@ | ||
/*globals ExpandableInputs, self, jml */ | ||
/*globals ExpandableInputs, self, jml, $ */ | ||
/*jslint vars:true, todo:true, browser:true, devel:true */ | ||
var $J = $; | ||
$.noConflict(); | ||
(function () {'use strict'; | ||
var | ||
emit = self.port.emit, | ||
on = self.port.on, | ||
options = self.options, | ||
locale = options.locale, | ||
ei_locale = options.ei_locale, | ||
args = new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'executableTable', | ||
namespace: 'args', | ||
label: ei_locale.args_num, | ||
inputSize: 60, | ||
rows: 1 // Might perhaps make this optional to save space, but this triggers creation of a textarea so args could be more readable (since to auto-escape newlines as needed) | ||
}), | ||
urls = new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'URLArguments', | ||
namespace: 'urls', | ||
label: ei_locale.url_num, | ||
inputSize: 40, | ||
inputType: 'url' | ||
}), | ||
files = new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'fileArguments', | ||
namespace: 'files', | ||
label: ei_locale.file_num, | ||
inputSize: 25, | ||
inputType: 'file', | ||
selects: true | ||
}); | ||
currentName = '', optionData = {}, | ||
createNewCommand = true, | ||
changed = false, | ||
nameChanged = false, | ||
emit = self.port.emit, | ||
on = self.port.on, | ||
options = self.options, | ||
locale = options.locale, | ||
oldStorage = options.oldStorage, | ||
ei_locale = options.ei_locale, | ||
inputs = { | ||
args: new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'executableTable', | ||
namespace: 'args', | ||
label: ei_locale.args_num, | ||
inputSize: 60, | ||
rows: 1 // Might perhaps make this optional to save space, but this triggers creation of a textarea so args could be more readable (since to auto-escape newlines as needed) | ||
}), | ||
urls: new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'URLArguments', | ||
namespace: 'urls', | ||
label: ei_locale.url_num, | ||
inputSize: 40, | ||
inputType: 'url' | ||
}), | ||
files: new ExpandableInputs({ | ||
locale: ei_locale, | ||
table: 'fileArguments', | ||
namespace: 'files', | ||
label: ei_locale.file_num, | ||
inputSize: 25, | ||
inputType: 'file', | ||
selects: true | ||
}) | ||
}; | ||
// POLYFILLS | ||
Array.from = function (arg) { | ||
return [].slice.call(arg); | ||
return [].slice.call(arg); | ||
}; | ||
@@ -44,213 +53,442 @@ | ||
function l (msg) { | ||
console.log(msg); | ||
console.log(msg); | ||
} | ||
function $ (sel) { | ||
return document.querySelector(sel); | ||
return document.querySelector(sel); | ||
} | ||
function $$ (sel) { | ||
return document.querySelectorAll(sel); | ||
return document.querySelectorAll(sel); | ||
} | ||
function forSel (sel, cb) { | ||
Array.from($$(sel)).forEach(cb); | ||
Array.from($$(sel)).forEach(cb); | ||
} | ||
function _ (key) { | ||
return locale[key] || '(Non-internationalized string--FIXME!)'; | ||
return locale[key] || '(Non-internationalized string--FIXME!)' + key; | ||
} | ||
// ADD EVENTS | ||
function addOptions (type) { | ||
var paths = optionData[type].paths, | ||
sel = type === 'executables' ? '#' + type : '.ei-files-presets', | ||
selects = $$(sel); | ||
Array.from(selects).forEach(function (select) { | ||
while (select.firstChild) { | ||
select.removeChild(select.firstChild); | ||
} | ||
paths.forEach(function (pathInfo) { | ||
var option = document.createElement('option'); | ||
option.text = pathInfo[0]; | ||
option.value = pathInfo[1]; | ||
select.appendChild(option); | ||
}); | ||
if (type === 'temps') { | ||
setSelectOfValue(select, $('#' + select.id.replace('-select-', '-input-')).value); | ||
} | ||
}); | ||
} | ||
function handleOptions (data) { | ||
optionData[data.type] = data; | ||
addOptions(data.type); | ||
} | ||
function setSelectOfValue(sel, val) { | ||
var names = typeof sel === 'string' ? $(sel) : sel; | ||
var idx = Array.from(names.options).findIndex(function (option) { | ||
return option.value === val; | ||
}); | ||
names.selectedIndex = idx === -1 ? 0 : idx; | ||
} | ||
function resetChanges () { | ||
changed = false; | ||
nameChanged = false; | ||
} | ||
function populateEmptyForm () { | ||
$('#selectNames').selectedIndex = 0; // Unlike populateFormWithStorage, we will always need to set the name | ||
$('#executablePath').focus(); | ||
createNewCommand = true; | ||
currentName = ''; | ||
$('#delete').style.display = 'none'; | ||
$('#command-name').value = ''; | ||
$('#command-name').defaultValue = ''; | ||
$('#executables').selectedIndex = 0; | ||
$('#executablePath').value = ''; | ||
['args', 'urls', 'files'].forEach(function (inputType) { | ||
inputs[inputType].setTextValues(); | ||
}); | ||
inputs.files.setValues('directory'); | ||
addOptions('temps'); // Todo: make a way for the select to be populated through the ExpandableInputs API | ||
resetChanges(); | ||
} | ||
function populateFormWithStorage (name) { | ||
createNewCommand = false; | ||
currentName = name; | ||
$('#delete').style.display = 'inline'; | ||
$('#command-name').value = name; | ||
$('#command-name').defaultValue = name; | ||
var executablePath = oldStorage[currentName].executablePath; | ||
setSelectOfValue('#executables', executablePath); | ||
$('#executablePath').value = executablePath; | ||
['args', 'urls', 'files'].forEach(function (inputType) { | ||
inputs[inputType].setTextValues(oldStorage[currentName][inputType]); | ||
}); | ||
inputs.files.setValues('directory', oldStorage[currentName].dirs); | ||
addOptions('temps'); // Todo: make a way for the select to be populated through the ExpandableInputs API | ||
resetChanges(); | ||
} | ||
function fileOrDirResult (data) { | ||
var path = data.path, | ||
selector = data.selector; | ||
if (path) { | ||
$(selector).value = path; | ||
} | ||
} | ||
function rebuildCommandList () { | ||
while ($('#selectNames').firstChild) { | ||
$('#selectNames').removeChild($('#selectNames').firstChild); | ||
} | ||
jml({'#': Object.keys(oldStorage).sort().reduce( | ||
function (opts, commandName) { | ||
opts.push(['option', [commandName]]); | ||
return opts; | ||
}, | ||
[ | ||
['option', {value: '', selected: 'selected'}, [_("create_new_command")]] | ||
] | ||
)}, $('#selectNames')); | ||
} | ||
function finished () { | ||
$('#processExecuted').style.display = 'block'; | ||
if (!$('#keepOpen').checked) { | ||
emit('buttonClick', {id: 'cancel'}); | ||
} | ||
else { | ||
setTimeout(function () { | ||
$('#processExecuted').style.display = 'none'; | ||
}, 2000); | ||
} | ||
} | ||
function newStorage (data) { | ||
oldStorage = data.commands; | ||
rebuildCommandList(); | ||
setSelectOfValue('#selectNames', data.name); | ||
populateFormWithStorage(data.name); // Important to update other flags even if just changed, so convenient to just re-run | ||
} | ||
function removeStorage (data) { | ||
oldStorage = data.commands; | ||
rebuildCommandList(); | ||
if (!data.keepForm) { | ||
populateEmptyForm(); | ||
} | ||
} | ||
// ADD INITIAL CONTENT | ||
document.title = _("title"); | ||
jml('div', [ | ||
['div', {id: 'processExecuted', style: 'visibility:hidden;'}, [ | ||
_("Process executed") | ||
]], | ||
['div', {id: 'substitutions-explanation-container'}, [ | ||
['h3', [_("Substitutions explained")]], | ||
['div', {id: 'substitutions-explanation'}, [ | ||
_("Substitution_sequences_allow"), | ||
_("prefixes_can_be_applied"), | ||
['dl', [ | ||
'save-temp', 'ucencode-', 'uencode-', 'escquotes-' | ||
].reduce(function (children, prefix) { | ||
children.push(['dt', [prefix]]); | ||
children.push(['dd', [_("prefix_" + prefix)]]); | ||
return children; | ||
}, []) | ||
], | ||
['b', [_("Sequences")]], | ||
['dl', [ | ||
'eval', 'pageURL', 'pageTitle', 'pageHTML', 'bodyText', | ||
'selectedHTML', 'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', | ||
'linkBodyText', 'linkPageHTML', 'imageURL', | ||
'imageDataURL', 'imageDataBinary' | ||
].reduce(function (children, seq) { | ||
children.push(['dt', [seq]]); | ||
children.push(['dd', [_("seq_" + seq)]]); | ||
return children; | ||
}, []) | ||
] | ||
]] | ||
]], | ||
['div', {id: 'substitutions-used-container'}, [ | ||
['h3', [_("Substitutions used")]], | ||
['div', {id: 'substitutions-used'}, [ | ||
_("currently_available_sequences"), | ||
['br'],['br'], | ||
/* | ||
['dl', [ | ||
['dt', ['save-temp-']], ['dd'], | ||
['dt', ['ucencode-']], ['dd'], | ||
['dt', ['uencode-']], ['dd'], | ||
['dt', ['escquotes-']], ['dd'], | ||
]], | ||
*/ | ||
['b', [_("Sequences")]], | ||
['dl', [ | ||
'eval', 'pageURL', 'pageTitle', 'pageHTML', 'bodyText', | ||
'selectedHTML', 'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', | ||
'linkBodyText', 'linkPageHTML', 'imageURL', | ||
'imageDataURL', 'imageDataBinary' | ||
].reduce(function (children, seq) { | ||
children.push(['dt', [seq]]); | ||
children.push(['dd']); | ||
return children; | ||
}, []) | ||
] | ||
]] | ||
]], | ||
['table', [ | ||
/* | ||
['tr', [ | ||
['td', [ | ||
['label', [_("Label:")]] | ||
]], | ||
['td', [ | ||
['input', {id: 'label'}] | ||
]] | ||
]] | ||
*/ | ||
['tr', [ | ||
['td', [ | ||
['label', {'for': 'executablePath'}, [_("Path of executable")]] | ||
]], | ||
['td', [ | ||
['select', {id: 'executables', 'class': 'ei-exe-presets', dataset: {sel: '#executablePath'}}], | ||
['input', {type: 'text', size: '55', id: 'executablePath', 'class': 'ei-exe-path', list: 'datalist', autocomplete: 'off', value: '', required:'required'}], | ||
['input', {type: 'button', id: 'executablePick', dataset: {sel: '#executablePath', 'default-extension': 'exe', 'class': 'ei-exe-picker'}, value: _("Browse")}], | ||
['datalist', {id: 'datalist'}], | ||
['input', {type: 'button', 'class': 'ei-exe-revealButton', dataset: {sel: '#executablePath'}}] | ||
]] | ||
]] | ||
]], | ||
['div', {id: 'executableTableContainer'}, [ | ||
['table', {id: 'executableTable'}] | ||
]], | ||
['div', {id: 'fileAndURLArgumentContainer'}, [ | ||
['b', [_("Hard-coded files and URLs")]], | ||
['br'], | ||
['table', {id: 'fileArguments'}], | ||
['table', {id: 'URLArguments'}] | ||
]], | ||
['br'], | ||
['div', {'class': 'focus'}, [ | ||
/* | ||
['button', {id: 'saveAndExecute'}, [_("Save and execute")]], | ||
['button', {id: 'saveAndClose'}, [_("Save and close")]] | ||
['br'], | ||
['br'] | ||
*/ | ||
['label', [_("keep_dialog_open"), ['input', {type: 'checkbox', id: 'keepOpen'}]]], | ||
['br'], | ||
['button', {id: 'execute'}, [_("Execute")]], | ||
['button', {id: 'cancel'}, [_("Cancel")]] | ||
]] | ||
['div', (function (options) { | ||
var atts = {id: 'names'}; | ||
if (options.itemType === 'one-off') { | ||
atts.hidden = true; | ||
} | ||
return atts; | ||
}(options)), [ | ||
['select', {id: 'selectNames', size: 39, $on: {click: function (e) { | ||
if (changed) { | ||
var abandonUnsaved = confirm(_("have_unsaved_changes")); | ||
if (!abandonUnsaved) { | ||
setSelectOfValue('#selectNames', currentName); | ||
return; | ||
} | ||
} | ||
var name = e.target.value; | ||
if (name === '') { // Create new command | ||
populateEmptyForm(); | ||
} | ||
else { | ||
populateFormWithStorage(name); | ||
} | ||
}}} | ||
]]], | ||
['div', (function (options) { | ||
var atts = {id: 'main', $on: {change: function (e) { | ||
changed = true; | ||
if (e.target.id === 'command-name') { | ||
nameChanged = true; | ||
} | ||
}}}; | ||
atts.className = options.itemType === 'one-off' ? 'closed' : 'open'; | ||
return atts; | ||
}(options)), [ | ||
['button', {id: 'showNames', $on: {click: function () { | ||
$('#names').hidden = !$('#names').hidden; | ||
var showNames = $('#showNames'); | ||
if (!$('#names').hidden) { | ||
$('#main').className = 'open'; | ||
showNames.replaceChild(document.createTextNode(_("lt")), showNames.firstChild); | ||
} | ||
else { | ||
$('#main').className = 'closed'; | ||
showNames.replaceChild(document.createTextNode(_("gt")), showNames.firstChild); | ||
} | ||
}}}, [ | ||
_(options.itemType === 'one-off' ? "gt" : "lt") | ||
]], | ||
['div', {id: 'processExecuted', style: 'display:none;'}, [ | ||
_("Process executed") | ||
]], | ||
['br'], | ||
['div', {id: 'substitutions-explanation-container'}, [ | ||
['h3', [_("Substitutions explained")]], | ||
['div', {id: 'substitutions-explanation'}, [ | ||
_("Substitution_sequences_allow"), | ||
['br'],['br'], | ||
_("prefixes_can_be_applied"), | ||
['dl', [ | ||
'save-temp', 'ucencode-', 'uencode-', 'escquotes-' | ||
].reduce(function (children, prefix) { | ||
children.push(['dt', [prefix]]); | ||
children.push(['dd', [_("prefix_" + prefix)]]); | ||
return children; | ||
}, []) | ||
], | ||
['b', [_("Sequences")]], | ||
['dl', [ | ||
'eval', 'pageURL', 'pageTitle', 'pageHTML', 'bodyText', | ||
'selectedHTML', 'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', | ||
'linkBodyText', 'linkPageHTML', 'imageURL', | ||
'imageDataURL', 'imageDataBinary' | ||
].reduce(function (children, seq) { | ||
children.push(['dt', [seq]]); | ||
children.push(['dd', [_("seq_" + seq)]]); | ||
return children; | ||
}, []) | ||
] | ||
]] | ||
]], | ||
['div', {id: 'substitutions-used-container'}, [ | ||
['h3', [_("Substitutions used")]], | ||
['div', {id: 'substitutions-used'}, [ | ||
_("currently_available_sequences"), | ||
['br'],['br'], | ||
/* | ||
['dl', [ | ||
['dt', ['save-temp-']], ['dd'], | ||
['dt', ['ucencode-']], ['dd'], | ||
['dt', ['uencode-']], ['dd'], | ||
['dt', ['escquotes-']], ['dd'], | ||
]], | ||
*/ | ||
['b', [_("Sequences")]], | ||
['dl', [ | ||
'eval', 'pageURL', 'pageTitle', 'pageHTML', 'bodyText', | ||
'selectedHTML', 'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', | ||
'linkBodyText', 'linkPageHTML', 'imageURL', | ||
'imageDataURL', 'imageDataBinary' | ||
].reduce(function (children, seq) { | ||
children.push(['dt', [seq]]); | ||
children.push(['dd']); | ||
return children; | ||
}, []) | ||
] | ||
]] | ||
]], | ||
['div', {id: 'command-name-section'}, [ | ||
['label', {title: _("if_present_command_saved")}, [ | ||
_("Command name") + ' ', | ||
['input', (function (options) { | ||
var atts = {id: 'command-name', size: '35'}; | ||
if (options.itemType === 'commands') { | ||
atts.autofocus = 'autofocus'; | ||
} | ||
return atts; | ||
}(options))] | ||
]], | ||
' ', | ||
['label', [ | ||
_("Restrict contexts") + ' ', | ||
['select', {multiple: 'multiple', id: 'restrict-contexts', $on: {click: function (e) { | ||
// Not sure why we're losing focus or the click event is going through here but not in my multiple-select demo | ||
// ms.focus(); | ||
e.stopPropagation(); | ||
}}}, [ | ||
['option', ['canvas']], | ||
['option', ['text']], | ||
['option', ['image']] | ||
]] | ||
]] | ||
]], | ||
['table', [ | ||
/* | ||
['tr', [ | ||
['td', [ | ||
['label', [_("Label:")]] | ||
]], | ||
['td', [ | ||
['input', {id: 'label'}] | ||
]] | ||
]] | ||
*/ | ||
['tr', [ | ||
['td', [ | ||
['label', {'for': 'executablePath'}, [_("Path of executable")]] | ||
]], | ||
['td', [ | ||
['select', {id: 'executables', 'class': 'ei-exe-presets', dataset: {ei_sel: '#executablePath'}}], | ||
['input', { | ||
type: 'text', size: '55', id: 'executablePath', 'class': 'ei-exe-path', | ||
list: 'datalist', autocomplete: 'off', value: '', required:'required' | ||
}], | ||
['input', {type: 'button', id: 'executablePick', 'class': 'ei-exe-picker', dataset: {ei_sel: '#executablePath', 'ei_default-extension': 'exe'}, value: _("Browse")}], | ||
['datalist', {id: 'datalist'}], | ||
['input', {type: 'button', 'class': 'ei-exe-revealButton', dataset: {ei_sel: '#executablePath'}}] | ||
]] | ||
]] | ||
]], | ||
['div', {id: 'executableTableContainer'}, [ | ||
['table', {id: 'executableTable'}] | ||
]], | ||
['div', {id: 'fileAndURLArgumentContainer'}, [ | ||
['b', [_("Hard-coded files and URLs")]], | ||
['br'], | ||
['table', {id: 'fileArguments'}], | ||
['table', {id: 'URLArguments'}] | ||
]], | ||
['br'], | ||
['div', {'class': 'focus'}, [ | ||
['label', [_("keep_dialog_open"), ['input', {type: 'checkbox', id: 'keepOpen'}]]], | ||
['br'], | ||
['button', {'class': 'passData save'}, [_("Save")]], | ||
['button', {id: 'delete', 'class': 'passData delete', hidden: true}, [_("Delete")]], | ||
['br'], | ||
['button', {'class': 'passData execute'}, [_("Execute")]], | ||
['button', {id: 'cancel'}, [_("Cancel")]] | ||
]] | ||
]] | ||
], $('body')); | ||
// ADD EVENTS | ||
var ms = $J('#restrict-contexts').multipleSelect({filter: true, filterAcceptOnEnter: true, width: '150'}); | ||
$('body').addEventListener('click', function (e) { | ||
var val, sel, selVal, | ||
target = e.target, | ||
dataset = target.dataset || {}, | ||
cl = target.classList; | ||
function getValues (type, expInput) { | ||
var sel = '.' + expInput.getPrefixedNamespace() + type; | ||
return Array.from($$(sel)).map(function (arg) { | ||
if (arg.type === 'checkbox') { | ||
return arg.checked; | ||
} | ||
return arg.value; | ||
}); | ||
} | ||
function getInputValues (expInput) { | ||
return getValues('input', expInput); | ||
} | ||
var val, sel, selVal, | ||
target = e.target, | ||
dataset = target.dataset || {}, | ||
cl = target.classList; | ||
if (cl.contains('ei-files-presets') || (target.parentNode && target.parentNode.classList.contains('ei-files-presets')) || | ||
cl.contains('ei-exe-presets') || (target.parentNode && target.parentNode.classList.contains('ei-exe-presets'))) { | ||
val = target.value; | ||
if (!val) { | ||
return; | ||
} | ||
sel = dataset.sel || (target.parentNode && target.parentNode.dataset.sel); | ||
if (sel) { | ||
$(sel).value = val; | ||
} | ||
} | ||
else if (cl.contains('ei-files-picker') || cl.contains('ei-exe-picker')) { | ||
sel = dataset.sel; | ||
emit('filePick', { | ||
dirPath: $(sel).value, | ||
selector: sel, | ||
defaultExtension: dataset.defaultExtension || undefined, | ||
selectFolder: ($(dataset.directory) && $(dataset.directory).checked) ? true : undefined | ||
}); | ||
} | ||
else if (cl.contains('ei-files-revealButton') || cl.contains('ei-exe-revealButton')) { | ||
sel = dataset.sel; | ||
selVal = sel && $(sel).value; | ||
if (selVal) { | ||
emit('reveal', selVal); | ||
} | ||
} | ||
// Abstract this | ||
else if (cl.contains(files.getPrefixedNamespace() + 'add')) { | ||
files.add(); | ||
} | ||
else if (cl.contains(files.getPrefixedNamespace() + 'remove')) { | ||
files.remove(dataset.id); | ||
} | ||
else if (cl.contains(urls.getPrefixedNamespace() + 'add')) { | ||
urls.add(); | ||
} | ||
else if (cl.contains(urls.getPrefixedNamespace() + 'remove')) { | ||
urls.remove(dataset.id); | ||
} | ||
else if (cl.contains(args.getPrefixedNamespace() + 'add')) { | ||
args.add(); | ||
} | ||
else if (cl.contains(args.getPrefixedNamespace() + 'remove')) { | ||
args.remove(dataset.id); | ||
} | ||
else if (target.id === 'execute') { | ||
emit('buttonClick', { | ||
id: target.id, | ||
executablePath: $('#executablePath').value, | ||
args: getInputValues(args), | ||
files: getInputValues(files), | ||
urls: getInputValues(urls), | ||
dirs: getValues('directory', files) | ||
}); | ||
} | ||
if (cl.contains('ei-files-presets') || (target.parentNode && target.parentNode.classList.contains('ei-files-presets')) || | ||
cl.contains('ei-exe-presets') || (target.parentNode && target.parentNode.classList.contains('ei-exe-presets'))) { | ||
val = target.value; | ||
if (!val) { | ||
return; | ||
} | ||
sel = dataset.ei_sel || (target.parentNode && target.parentNode.dataset.ei_sel); | ||
if (sel) { | ||
$(sel).value = val; | ||
} | ||
} | ||
else if (cl.contains('ei-files-picker') || cl.contains('ei-exe-picker')) { | ||
sel = dataset.ei_sel; | ||
emit('filePick', { | ||
dirPath: $(sel).value, | ||
selector: sel, | ||
defaultExtension: dataset.ei_defaultExtension || undefined, | ||
selectFolder: ($(dataset.ei_directory) && $(dataset.ei_directory).checked) ? true : undefined | ||
}); | ||
} | ||
else if (cl.contains('ei-files-revealButton') || cl.contains('ei-exe-revealButton')) { | ||
sel = dataset.ei_sel; | ||
selVal = sel && $(sel).value; | ||
if (selVal) { | ||
emit('reveal', selVal); | ||
} | ||
} | ||
else if (e.target.id === 'cancel') { | ||
emit('buttonClick', {close: true}); | ||
} | ||
else if (cl.contains('passData')) { | ||
var name = $('#command-name').value; | ||
if (cl.contains('delete')) { | ||
var ok = confirm(_("sure_wish_delete")); | ||
if (ok) { | ||
emit('buttonClick', {name: name, remove: true}); | ||
} | ||
return; | ||
} | ||
if (cl.contains('save')) { | ||
if (!name) { | ||
alert(_("supply_name")); | ||
return; | ||
} | ||
if (nameChanged) { | ||
if (oldStorage[name]) { | ||
var overwrite = confirm(_("name_already_exists_overwrite")); | ||
if (!overwrite) { | ||
return; | ||
} | ||
} | ||
else if (!createNewCommand) { | ||
var renameInsteadOfNew = confirm(_("have_unsaved_name_change")); | ||
if (!renameInsteadOfNew) { // User wishes to create a new record (or cancel) | ||
$('#selectNames').selectedIndex = 0; | ||
nameChanged = false; | ||
return; // Return so that user has some way of correcting or avoiding (without renaming) | ||
} | ||
} | ||
// Proceed with rename, so first delete old value (todo: could ensure first added) | ||
emit('buttonClick', {name: $('#command-name').defaultValue, remove: true, keepForm: true}); | ||
} | ||
else if (!changed && !cl.contains('execute')) { | ||
alert(_("no_changes_to_save")); | ||
return; | ||
} | ||
} | ||
var data = { | ||
name: name, | ||
storage: { | ||
executablePath: $('#executablePath').value, | ||
args: inputs.args.getTextValues(), | ||
files: inputs.files.getTextValues(), | ||
urls: inputs.urls.getTextValues(), | ||
dirs: inputs.files.getValues('directory') | ||
} | ||
}; | ||
if (cl.contains('execute')) { | ||
data.execute = true; | ||
} | ||
emit('buttonClick', data); | ||
} | ||
}); | ||
$('body').addEventListener('input', function (e) { | ||
var target = e.target, val = e.target.value; | ||
if (target.classList.contains('ei-files-path')) { | ||
emit('autocompleteValues', { | ||
value: val, | ||
listID: target.getAttribute('list') | ||
}); | ||
} | ||
var target = e.target, val = e.target.value; | ||
if (target.classList.contains('ei-files-path') || target.classList.contains('ei-exe-path')) { | ||
emit('autocompleteValues', { | ||
value: val, | ||
listID: target.getAttribute('list') | ||
}); | ||
} | ||
}); | ||
@@ -260,64 +498,37 @@ | ||
on('autocompleteValuesResponse', function (data) { | ||
var datalist = document.getElementById(data.listID); | ||
while (datalist.firstChild) { | ||
datalist.removeChild(datalist.firstChild); | ||
} | ||
data.optValues.forEach(function (optValue) { | ||
var option = jml('option', { | ||
// text: optValue, | ||
value: optValue | ||
}); | ||
datalist.appendChild(option); | ||
}); | ||
var datalist = document.getElementById(data.listID); | ||
while (datalist.firstChild) { | ||
datalist.removeChild(datalist.firstChild); | ||
} | ||
data.optValues.forEach(function (optValue) { | ||
var option = jml('option', { | ||
// text: optValue, | ||
value: optValue | ||
}); | ||
datalist.appendChild(option); | ||
}); | ||
}); | ||
on('finished', function () { | ||
$('#processExecuted').style.visibility = 'visible'; | ||
if (!$('#keepOpen').checked) { | ||
emit('buttonClick', {id: 'cancel'}); | ||
} | ||
else { | ||
setTimeout(function () { | ||
$('#processExecuted').style.visibility = 'hidden'; | ||
}, 2000); | ||
} | ||
}); | ||
function fileOrDirResult (data) { | ||
var path = data.path, | ||
selector = data.selector; | ||
if (path) { | ||
$(selector).value = path; | ||
} | ||
} | ||
on('finished', finished); | ||
on('filePickResult', fileOrDirResult); | ||
on('executables', handleOptions); | ||
on('temps', handleOptions); | ||
on('newStorage', newStorage); | ||
on('removeStorage', removeStorage); | ||
// SETUP | ||
// INITIAL BEHAVIORS | ||
// Insert this as a class, so it works for others inserted into doc | ||
$('#dynamicStyleRules').sheet.insertRule( | ||
'.ei-files-revealButton, .ei-exe-revealButton {background-image: url("' + options.folderImage + '");}', 0 | ||
'.ei-files-revealButton, .ei-exe-revealButton {background-image: url("' + options.folderImage + '");}', 0 | ||
); | ||
function handleOptions (data) { | ||
var paths = data.paths, | ||
type = data.type, | ||
sel = type === 'executables' ? '#' + type : '.ei-files-presets'; | ||
rebuildCommandList(); | ||
paths.forEach(function (pathInfo) { | ||
var option = document.createElement('option'); | ||
option.text = pathInfo[0]; | ||
option.value = pathInfo[1]; | ||
$(sel).appendChild(option); | ||
}); | ||
} | ||
on('executables', handleOptions); | ||
on('temps', handleOptions); | ||
// Todo: For prefs when prev. values stored, call multiple times and populate and reduce when not used | ||
['args', 'urls', 'files'].forEach(function (inputType) { | ||
inputs[inputType].add(); | ||
}); | ||
// Todo: For prefs when prev. values stored, call multiple times and populate | ||
args.add(); | ||
urls.add(); | ||
files.add(); | ||
}()); |
615
lib/main.js
@@ -1,387 +0,254 @@ | ||
/*globals exports, require */ | ||
/*jslint vars: true*/ | ||
/*globals exports, require, console */ | ||
/*jslint vars: true, todo:true */ | ||
(function () {'use strict'; | ||
var chrome = require('chrome'), | ||
Cc = chrome.Cc, | ||
Ci = chrome.Ci, | ||
_ = require('sdk/l10n').get, | ||
file = require('sdk/io/file'), | ||
tabs = require('sdk/tabs'), | ||
XRegExp = require('./node_modules/xregexp/xregexp-all').XRegExp; | ||
function l (msg) { | ||
console.log(msg); | ||
console.log(msg); | ||
} | ||
function createProcess (aNsIFile, args, observer) { | ||
var process = Cc['@mozilla.org/process/util;1'].createInstance(Ci.nsIProcess); | ||
observer = (observer && observer.observe) ? | ||
observer : | ||
{observe: function (aSubject, aTopic, data) {}}; | ||
process.init(aNsIFile); | ||
process.runAsync(args, args.length, observer); | ||
} | ||
function getFile (path) { | ||
var localFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); | ||
localFile.initWithPath(path); | ||
return localFile; | ||
} | ||
function createProcessAtPath (path, args, observer) { | ||
try { | ||
var file = getFile(path); | ||
createProcess(file, args, observer); | ||
} | ||
catch (e) { | ||
if (observer && observer.errorHandler) { | ||
observer.errorHandler(e); | ||
} | ||
} | ||
} | ||
function getHardFile (dir) { | ||
return Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get(dir, Ci.nsIFile); | ||
} | ||
/** | ||
* @see getHardPaths() | ||
*/ | ||
function getHardPath (dir) { | ||
return getHardFile(dir).path; | ||
} | ||
function getFirefoxExecutable () { | ||
var file = getHardFile('CurProcD'); | ||
file = file.parent; // Otherwise, points to "browser" subdirectory | ||
file.append('firefox.exe'); | ||
return file; | ||
} | ||
function getTempPaths () { | ||
return { | ||
type: 'temps', | ||
paths: [ | ||
['System temp', getHardPath('TmpD')] | ||
] | ||
}; | ||
} | ||
function getExePaths () { | ||
return { | ||
type: 'executables', | ||
paths: [ | ||
['Firefox', getFirefoxExecutable().path], | ||
['Command prompt', file.join(getHardPath('SysD'), 'cmd.exe')] | ||
] | ||
}; | ||
} | ||
var | ||
_ = require('sdk/l10n').get, | ||
XRegExp = require('./node_modules/xregexp/xregexp-all').XRegExp, | ||
data = require('sdk/self').data, | ||
cm = require('sdk/context-menu'), | ||
ss = require('sdk/simple-storage').storage, | ||
openDialog = require('./openDialog'), | ||
fileHelpers = require('./fileHelpers'), getExePaths = fileHelpers.getExePaths, getTempPaths = fileHelpers.getTempPaths, autocompleteValues = fileHelpers.autocompleteValues, picker = fileHelpers.picker, reveal = fileHelpers.reveal; | ||
function autocompleteValues (data, emit) { | ||
var optValues, | ||
userVal = data.value, | ||
dir = file.dirname(userVal), | ||
base = file.basename(userVal); | ||
if (file.exists(userVal)) { | ||
if (userVal.match(/(?:\/|\\)$/)) { | ||
optValues = file.list(userVal).map(function (fileInDir) { | ||
return file.join(userVal, fileInDir); | ||
}); | ||
} | ||
else { | ||
optValues = [userVal]; | ||
} | ||
} | ||
else if (file.exists(dir)) { | ||
optValues = file.list(dir).filter(function (fileInDir) { | ||
return fileInDir.indexOf(base) === 0; | ||
}).map(function (fileInDir) { | ||
return file.join(dir, fileInDir); | ||
}); | ||
} | ||
optValues = data.dirOnly ? | ||
optValues.filter(function (optValue) { | ||
try { | ||
return getFile(optValue).isDirectory(); | ||
} | ||
catch (e) { | ||
return false; | ||
} | ||
}) : | ||
optValues; | ||
return { | ||
listID: data.listID, | ||
optValues: optValues, | ||
userVal: userVal // Just for debugging on the other side | ||
}; | ||
if (!ss.commands) { | ||
ss.commands = {}; | ||
} | ||
function reveal (path) { | ||
var localFile = getFile(path); | ||
localFile.reveal(); | ||
if (!ss.windowCoords) { | ||
ss.windowCoords = {outerWidth: 1180, outerHeight: 670}; | ||
} | ||
var filePickerLocale = ['pickFolder', 'pickFile'].reduce(function (locale, key) { | ||
locale[key] = _("filepicker_" + key); | ||
return locale; | ||
}, {}); | ||
// Todo: Apply these changes in other add-ons using it; also add this as a filterMap where needed [{type: '*.ico', message: "Icon file"}] | ||
function picker (data, filterMap, locale, emit) { | ||
// Note: could use https://developer.mozilla.org/en-US/docs/Extensions/Using_the_DOM_File_API_in_chrome_code | ||
// but this appears to be less feature rich | ||
var dir, | ||
dirPath = data.dirPath, | ||
selector = data.selector, | ||
selectFolder = data.selectFolder, | ||
defaultExtension = data.defaultExtension, | ||
windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator), | ||
nsIFilePicker = Ci.nsIFilePicker, | ||
fp = Cc['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker); | ||
if (!selectFolder) { | ||
fp.defaultExtension = defaultExtension; | ||
//fp.appendFilter('ICO (.ico)', '*.ico'); | ||
//fp.appendFilter('SVG (.svg)', '*.svg'); | ||
//fp.appendFilter('Icon file', '*.ico; *.svg'); | ||
(filterMap || []).forEach(function (filters) { | ||
fp.appendFilter(filters.message, filters.type); | ||
}); | ||
} | ||
if (dirPath) { | ||
try { | ||
dir = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); | ||
dir.initWithPath(dirPath); | ||
if (!dir.isDirectory()) { // Todo: Return this change to other add-ons | ||
dir.initWithPath(file.dirname(dirPath)); | ||
} | ||
fp.displayDirectory = dir; | ||
} | ||
catch(e) { | ||
l('initWithPath error: '+ e); | ||
} | ||
} | ||
fp.init( | ||
windowMediator.getMostRecentWindow(null), | ||
selectFolder ? locale.pickFolder : locale.pickFile, | ||
selectFolder ? nsIFilePicker.modeGetFolder : nsIFilePicker.modeOpen | ||
); | ||
fp.open({done: function (rv) { | ||
var file, path, | ||
res = ''; | ||
if (rv === nsIFilePicker.returnOK || rv === nsIFilePicker.returnReplace) { | ||
file = fp.file; | ||
path = file.path; | ||
res = path; | ||
} | ||
if (selectFolder) { | ||
emit('dirPickResult', {path: res, selector: selector, selectFolder: selectFolder}); | ||
} | ||
else { | ||
emit('filePickResult', {path: res, selector: selector}); | ||
} | ||
return false; | ||
}}); | ||
/* | ||
var rv = fp.show(); | ||
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { | ||
var file = fp.file; | ||
var path = fp.file.path; | ||
}*/ | ||
function save(name, data) { | ||
ss.commands[name] = data; | ||
} | ||
var data = require('sdk/self').data, | ||
cm = require('sdk/context-menu'), | ||
winUtils = require('sdk/window/utils'), | ||
windows = require('sdk/windows').browserWindows; | ||
exports.main = function () { | ||
/* | ||
var prefsPanel = require('sdk/panel').Panel({ | ||
width: 240, | ||
height: 160, | ||
contentURL: data.url('prefs.html') | ||
}); | ||
var prefsWidget = require('sdk/widget').Widget({ | ||
id: 'atyourcommand-prefs', | ||
label: 'AYC', | ||
// contentURL: data.url('.png'), | ||
content: 'AYC', | ||
// width: 50 | ||
panel: prefsPanel | ||
}); | ||
*/ | ||
// Open context menu with a specific command line, a new command line, or prefs | ||
// Todo: Also support text selection, URL, image, and custom context(s) | ||
cm.Menu({ | ||
label: _("At Your Command"), | ||
context: cm.PageContext(), | ||
contentScriptFile: data.url('main-context-menu.js'), | ||
items: [ | ||
// cm.Item({ label: 'Open in WebAppFind edit mode', data: '' }), // Todo: Put this into default storage | ||
// Todo: invite (or utilize?) sharing of i18n with AppLauncher | ||
cm.Item({ label: _("Send a one-off command"), data: 'one-off' }) | ||
// cm.Item({ label: _("Open preferences"), data: 'prefs' }) | ||
], | ||
onMessage: function (e) { | ||
var itemType = e.type; | ||
switch (itemType) { | ||
case 'one-off': | ||
// We can't use panels because: | ||
// 1. Its lack of persistence is WON'T-FIXED per https://bugzilla.mozilla.org/show_bug.cgi?id=595040 | ||
// 2. Tooltips and autocomplete don't show up. | ||
// However, see http://stackoverflow.com/questions/22002010/addon-sdk-way-to-make-a-dialog/ | ||
var win = winUtils.openDialog({ | ||
// url: data.url('one-off.html'), | ||
// For more, see https://developer.mozilla.org/en-US/docs/Web/API/window.open#Position_and_size_features | ||
features: Object.keys({ | ||
chrome: true, | ||
centerscreen: true, // Needs chrome per docs; not working for some reason (even though it does work when calling via chrome code) | ||
resizable: true, | ||
scrollbars: true | ||
}).join() + ',width=850,height=650', | ||
name: _("One-off") | ||
// parent: | ||
// args: | ||
}); | ||
win.addEventListener('load', function () { | ||
tabs.activeTab.on('ready', function (tab) { | ||
var worker = tab.attach({ | ||
contentScriptFile: ['node_modules/jamilih/jml.js', 'ExpandableInputs.js', 'one-off.js'].map(function (script) { | ||
return data.url(script); | ||
}), | ||
contentScriptWhen: 'ready', | ||
contentScriptOptions: { // any JSON-serializable key/values | ||
folderImage: data.url('Yellow_folder_icon_open.png'), | ||
// Todo: Find way to parse locale file and pass to remove need for all of these? | ||
locale: [ | ||
'title', 'Substitutions used', | ||
'currently_available_sequences', | ||
'Process executed', 'Substitutions explained', | ||
'Substitution_sequences_allow', | ||
'prefixes_can_be_applied' | ||
].concat(['eval', | ||
'pageURL', 'pageTitle', 'pageHTML', | ||
'bodyText', 'selectedHTML', | ||
'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', 'linkBodyText', 'linkPageHTML', 'imageURL', 'imageDataURL', 'imageDataBinary' | ||
].map(function (key) { | ||
return 'seq_' + key; | ||
})).concat([ | ||
'Sequences', 'Path of executable', 'Browse', | ||
'Hard-coded files and URLs', | ||
'keep_dialog_open', 'Execute', 'Cancel' | ||
]).concat([ | ||
'save-temp', 'ucencode-', 'uencode-', 'escquotes-' | ||
].map(function (key) { | ||
return 'prefix_' + key; | ||
})).reduce(function (locale, key) { | ||
locale[key] = _(key); | ||
return locale; | ||
}, {}), | ||
ei_locale: [ | ||
'browse', 'directory', 'plus', 'minus', 'reveal', | ||
'args_num', 'url_num', 'file_num' | ||
].reduce(function (locale, key) { | ||
locale[key] = _("expandable_inputs_" + key); | ||
return locale; | ||
}, {}) | ||
} | ||
}); | ||
var port = worker.port, | ||
on = port.on.bind(port), | ||
emit = port.emit.bind(port); | ||
// Todo: Allow saving by user | ||
emit('executables', getExePaths()); | ||
emit('temps', getTempPaths()); | ||
on('autocompleteValues', function (data) { | ||
emit('autocompleteValuesResponse', autocompleteValues(data)); | ||
}); | ||
on('filePick', function (data) { | ||
picker(data, null, filePickerLocale, function (arg1, arg2) { | ||
emit(arg1, arg2); | ||
}); | ||
emit('filePickResponse'); | ||
}); | ||
on('reveal', function (data) { | ||
reveal(data); | ||
}); | ||
on('buttonClick', function (data) { | ||
var id = data.id; | ||
switch (id) { | ||
case 'saveAndClose': | ||
case 'cancel': | ||
worker.destroy(); | ||
win.close(); | ||
break; | ||
case 'saveAndExecute': | ||
case 'execute': | ||
createProcessAtPath( | ||
data.executablePath, // Todo: Apply same substitutions within executable path in case it is dynamic based on selection? | ||
// Todo: handle hard-coded data.files, data.urls, data.dirs; ability to invoke with | ||
// link to or contents of a sequence of hand-typed (auto-complete drop-down) | ||
// local files and/or URLs (including option to encode, etc.) | ||
// Todo: If data.dirs shows something is a directory, confirm the supplied path is also (no UI enforcement on this currently) | ||
data.args.map(function (argVal) { | ||
// We use <> for escaping | ||
// since these are disallowed anywhere | ||
// in URLs anywhere (unlike ampersands) | ||
return XRegExp.replace(argVal, '', function (match) { | ||
}); | ||
return argVal.replace( | ||
// Todo: Might use XRegExp to avoid the redundancy here | ||
// Todo: Ensure substitutions take place within eval() first | ||
// Todo: Ensure escaping occurs in proper order | ||
/<((?:save-temp (\s+?:overwrite=yes|no|prompt)?(?:\s+continue=yes|no)?)?(?:(?:uc)?encode-)?(?:escquotes-)?)(?:(eval: [^>]*)|(pageURL|pageTitle|pageHTML)|(bodyText)|(selectedHTML|selectedText)|(linkPageURL|linkPageURLAsNativePath|linkPageTitle|linkBodyText|linkPageHTML)|(imageURL|imageDataURL|imageDataBinary))>/g, | ||
function (n0, evl, page, body, selected, link, image) { | ||
var saveTemp, overwrite, encode, escquotes; | ||
/* | ||
/<(?:save-temp (\s+?:overwrite=(yes|no|prompt))?(?:\s+continue=(yes|no))?)?(?:(uc)?(encode-))?(escquotes-)?/ | ||
*/ | ||
// ucencode needs encodeURIComponent applied | ||
// For linkPageURLAsNativePath, convert to native path | ||
// Allow eval() | ||
// Todo: Implement save-temp and all arguments | ||
// Retrieve "linkPageTitle", "linkBodyText", or "linkPageHTML" as needed and cache | ||
// Retrieve "imageDataBinary" and "imageDataURL" (available via canvas?) as needed (available from cache?) | ||
// Move ones found to be used here to the top of the list/mark in red/asterisked | ||
} | ||
).split('').reverse().join('').replace(/(?:<|>)(?!\\)/g, '').split('').reverse().join('').replace(/\\(<|>)/g, '$1'); | ||
// Todo: Escape newlines (since allowable with textarea args)? | ||
}), | ||
{ | ||
errorHandler: function (err) { | ||
throw (err); | ||
}, | ||
observe: function (aSubject, aTopic, data) { | ||
if (aTopic === 'process-finished') { | ||
emit('finished'); | ||
} | ||
} | ||
} | ||
); | ||
break; | ||
} | ||
}); | ||
}); | ||
tabs.activeTab.url = data.url('one-off.html'); | ||
}); | ||
break; | ||
case 'prefs': | ||
// Detachment of panel upon opening in this manner is a bug that is WONT-FIXED | ||
// since widgets to be deprecated per https://bugzilla.mozilla.org/show_bug.cgi?id=638142 | ||
// prefsWidget.panel.show(); | ||
break; | ||
} | ||
} | ||
label: _("At Your Command"), | ||
context: cm.PageContext(), | ||
contentScriptFile: data.url('main-context-menu.js'), | ||
items: [ | ||
// cm.Item({label: 'Open in WebAppFind edit mode', data: ''}), // Todo: Put this into default storage | ||
// Todo: invite (or utilize?) sharing of i18n with AppLauncher | ||
cm.Item({label: _("Send a one-off command"), data: 'one-off'}), | ||
cm.Item({label: _("Commands"), data: 'commands'}) | ||
// cm.Item({label: _("Open preferences"), data: 'prefs'}) | ||
], | ||
onMessage: function (e) { | ||
var win; | ||
var itemType = e.type; | ||
switch (itemType) { | ||
case 'commands': case 'one-off': | ||
win = openDialog({ | ||
name: _("One-off"), | ||
dataURL: 'one-off.html', | ||
outerWidth: ss.windowCoords.outerWidth, | ||
outerHeight: ss.windowCoords.outerHeight, | ||
contentScript: { | ||
files: [ | ||
'bower_components/jquery/dist/jquery.min.js', 'bower_components/multiple-select-brett/jquery.multiple.select.js', | ||
'node_modules/jamilih/jml.js', 'ExpandableInputs.js', 'one-off.js' | ||
], | ||
when: 'ready', | ||
options: { // any JSON-serializable key/values | ||
itemType: itemType, | ||
oldStorage: ss.commands, | ||
folderImage: data.url('Yellow_folder_icon_open.png'), | ||
// Todo: Get path to locale file, parse, and pass here to remove need for all of these? (and to avoid the data-* approach) | ||
locale: [ | ||
'title', 'lt', 'gt', 'create_new_command', 'Substitutions used', | ||
'if_present_command_saved', 'Command name', | ||
'Restrict contexts', | ||
'have_unsaved_changes', 'have_unsaved_name_change', | ||
'no_changes_to_save', 'sure_wish_delete', | ||
'Save', 'Delete', 'name_already_exists_overwrite', | ||
'supply_name', 'currently_available_sequences', | ||
'Process executed', 'Substitutions explained', | ||
'Substitution_sequences_allow', | ||
'prefixes_can_be_applied' | ||
].concat(['eval', | ||
'pageURL', 'pageTitle', 'pageHTML', | ||
'bodyText', 'selectedHTML', | ||
'selectedText', 'linkPageURL', | ||
'linkPageURLAsNativePath', 'linkPageTitle', 'linkBodyText', 'linkPageHTML', 'imageURL', 'imageDataURL', 'imageDataBinary' | ||
].map(function (key) { | ||
return 'seq_' + key; | ||
})).concat([ | ||
'Sequences', 'Path of executable', 'Browse', | ||
'Hard-coded files and URLs', | ||
'keep_dialog_open', 'Execute', 'Cancel' | ||
]).concat([ | ||
'save-temp', 'ucencode-', 'uencode-', 'escquotes-' | ||
].map(function (key) { | ||
return 'prefix_' + key; | ||
})).reduce(function (locale, key) { | ||
locale[key] = _(key); | ||
return locale; | ||
}, {}), | ||
ei_locale: [ | ||
'browse', 'directory', 'plus', 'minus', 'reveal', | ||
'args_num', 'url_num', 'file_num' | ||
].reduce(function (locale, key) { | ||
locale[key] = _("expandable_inputs_" + key); | ||
return locale; | ||
}, {}) | ||
} | ||
}, | ||
ready: function (worker, on, emit) { | ||
on({ | ||
autocompleteValues: function (data) { | ||
emit('autocompleteValuesResponse', autocompleteValues(data)); | ||
}, | ||
filePick: function (data) { | ||
picker(data, null, ['pickFolder', 'pickFile'].reduce(function (locale, key) { | ||
locale[key] = _("filepicker_" + key); | ||
return locale; | ||
}, {}), | ||
function (arg1, arg2) { | ||
emit(arg1, arg2); | ||
} | ||
); | ||
emit('filePickResponse'); | ||
}, | ||
reveal: function (data) { | ||
reveal(data); | ||
}, | ||
buttonClick: function (data) { | ||
if (data.remove) { | ||
delete ss.commands[data.name]; | ||
emit('removeStorage', {commands: ss.commands, keepForm: data.keepForm}); | ||
} | ||
if (data.storage) { | ||
save(data.name, data.storage); | ||
emit('newStorage', {name: data.name, commands: ss.commands}); | ||
} | ||
if (data.execute) { | ||
/* | ||
createProcessAtPath( | ||
data.executablePath, // Todo: Apply same substitutions within executable path in case it is dynamic based on selection? | ||
// Todo: handle hard-coded data.files, data.urls, data.dirs; ability to invoke with | ||
// link to or contents of a sequence of hand-typed (auto-complete drop-down) | ||
// local files and/or URLs (including option to encode, etc.) | ||
// Todo: If data.dirs shows something is a directory, confirm the supplied path is also (no UI enforcement on this currently) | ||
*/ | ||
l( | ||
data.args.map(function (argVal) { | ||
// We use <> for escaping | ||
// since these are disallowed anywhere | ||
// in URLs (unlike ampersands) | ||
return XRegExp.replace( | ||
argVal, | ||
// Begin special syntax | ||
new XRegExp('<' + | ||
// saveTemp with its options | ||
'(?:(?<saveTemp>save-temp)' + | ||
'(\\s+?:overwrite=(?<overwrite>yes|no|prompt))?' + | ||
'(?:\\s+continue=(?<cont>yes|no))?' + | ||
'\\s+)?' + | ||
// Encoding | ||
'(?:(?<ucencode>ucencode-)|(?<uencode>uencode-))?' + | ||
// Escaping | ||
'(?<escquotes>escquotes-)?' + | ||
// Begin main grouping | ||
'(?:' + | ||
// Eval with body | ||
'(?:eval: (?<evl>[^>]*))|' + | ||
// Other flags | ||
([ | ||
'pageURL', 'pageTitle', 'pageHTML', | ||
'bodyText', 'selectedHTML', 'selectedText', | ||
'linkPageURL', 'linkPageURLAsNativePath', | ||
'linkPageTitle', 'linkBodyText', 'linkPageHTML', | ||
'imageURL', 'imageDataURL', 'imageDataBinary' | ||
].reduce(function (str, key) { | ||
return str + '|(?<' + XRegExp.escape(key) + '>' + XRegExp.escape(key) + ')'; | ||
}, '').slice(1)) + | ||
// End the main grouping | ||
')' + | ||
// End special syntax | ||
'>'), | ||
function (arg) { | ||
if (arg.saveTemp) { | ||
// arg.overwrite | ||
// arg.cont | ||
} | ||
/* | ||
arg.ucencode | ||
arg.uencode | ||
arg.escquotes | ||
arg.evl | ||
arg.pageURL | ||
arg.pageTitle | ||
arg.pageHTML | ||
arg.bodyText | ||
arg.selectedHTML | ||
arg.selectedText | ||
arg.linkPageURL | ||
arg.linkPageURLAsNativePath | ||
arg.linkPageTitle | ||
arg.linkBodyText | ||
arg.linkPageHTML | ||
arg.imageURL | ||
arg.imageDataURL | ||
arg.imageDataBinary | ||
*/ | ||
// Todo: Ensure substitutions take place within eval() first | ||
// Todo: Ensure escaping occurs in proper order | ||
// ucencode needs encodeURIComponent applied | ||
// For linkPageURLAsNativePath, convert to native path | ||
// Allow eval() | ||
// Todo: Implement save-temp and all arguments | ||
// Retrieve "linkPageTitle", "linkBodyText", or "linkPageHTML" as needed and cache | ||
// Retrieve "imageDataBinary" and "imageDataURL" (available via canvas?) as needed (available from cache?) | ||
// Move ones found to be used here to the top of the list/mark in red/asterisked | ||
}, | ||
'all' | ||
// Todo: Escape newlines (since allowable with textarea args)? | ||
).split('').reverse().join('').replace(/(?:<|>)(?!\\)/g, '').split('').reverse().join('').replace(/\\(<|>)/g, '$1'); | ||
}) | ||
/* | ||
, // Todo: Reenable? | ||
{ | ||
errorHandler: function (err) { | ||
throw (err); | ||
}, | ||
observe: function (aSubject, aTopic, data) { | ||
if (aTopic === 'process-finished') { | ||
emit('finished'); | ||
} | ||
} | ||
} | ||
); | ||
*/ | ||
); | ||
} | ||
if (data.close) { | ||
worker.destroy(); | ||
win.close(); | ||
} | ||
} | ||
}); | ||
emit({ | ||
executables: getExePaths(), | ||
temps: getTempPaths() | ||
}); | ||
} | ||
}); | ||
win.addEventListener('resize', function () { | ||
ss.windowCoords.outerWidth = win.outerWidth; | ||
ss.windowCoords.outerHeight = win.outerHeight; | ||
}); | ||
break; | ||
case 'prefs': | ||
// Detachment of panel upon opening in this manner is a bug that is WONT-FIXED | ||
// since widgets to be deprecated per https://bugzilla.mozilla.org/show_bug.cgi?id=638142 | ||
// prefsWidget.panel.show(); | ||
break; | ||
} | ||
} | ||
}); | ||
@@ -388,0 +255,0 @@ |
{ | ||
"name": "atyourcommand", | ||
"title": "atyourcommand", | ||
"id": "jid1-Q5dsJJMAvBaWVw", | ||
"description": "Open web content into the command line", | ||
"author": "Brett Zamir", | ||
"license": "MIT", | ||
"version": "0.1.1", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/brettz9/atyourcommand.git" | ||
}, | ||
"scripts": { | ||
"prepublish": "npm install jamilih@0.x -g --prefix ./data && npm install xregexp@2.x -g --prefix ./lib" | ||
} | ||
"name": "atyourcommand", | ||
"title": "atyourcommand", | ||
"id": "jid1-Q5dsJJMAvBaWVw", | ||
"description": "Open web content into the command line", | ||
"author": "Brett Zamir", | ||
"license": "MIT", | ||
"version": "0.1.2", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/brettz9/atyourcommand.git" | ||
}, | ||
"scripts": { | ||
"prepublish": "npm install jamilih@0.x -g --prefix ./data && npm install xregexp@2.x -g --prefix ./lib && bower install bootstrap#3.x.x && bower install multiple-select-brett#brett-demo" | ||
} | ||
} |
183
README.md
@@ -15,4 +15,6 @@ # atyourcommand | ||
One can directly install the bundled XPI file or install with npm: | ||
Install the bundled XPI file. | ||
To develop with source, install with npm: | ||
`npm install atyourcommand` | ||
@@ -22,9 +24,30 @@ | ||
1. Behavior additions | ||
1. Finish behavior providing substitution of current | ||
page contents, URL, etc. (see todos in main.js under "buttonClick" "execute" event) | ||
1. Support defaults (including empty ones) | ||
1. Display of commands in context menu or with list of commands | ||
in edit mode (with the list of commands pre-opened) or in | ||
new command mode (with the list pre-closed) | ||
# Likely to-dos | ||
1. Finish behavior providing substitution of current page contents, | ||
URL, etc. (see todos in main.js under "buttonClick" "execute" event) | ||
1. Submit to AMO | ||
1. Support defaults (including empty ones) and document | ||
1. Also support text selection, URL, image, and custom context(s) | ||
1. Option to have context menu items, based on the substitutions used (or | ||
user choice), cause them to only appear under certain, correct conditions | ||
(but ensure commands can utilize multiple components (e.g., highlighted | ||
link and page HTML) | ||
1. Give user choice on whether to provide content only in certain | ||
selector-based contexts (but making necessary JSON-friendly | ||
conversions, e.g., canvas to data URL, for them); video, audio, | ||
object/embed/applet URL, <a>, etc. | ||
1. Optional pre-processing of highlighted contents esp. if cannot convert to | ||
HTML (e.g., toDataURL on canvas) | ||
1. Ensure, as with QR Secret Decoder Ring, that we can get a privileged | ||
toDataURL canvas snapshot as an argument. | ||
1. Any way to create convention to get data out of a plug-in by | ||
right-click (and demo)? | ||
# Higher priority to-dos (longer) | ||
1. Opinion piece on great importance of data ownership and decoupling of local | ||
@@ -46,7 +69,3 @@ or remote data from applications (also discuss need for return to (user-approved) | ||
desktop in connection with the web) is itself not decoupled. Also cover the | ||
ideas for PUT requests (for decoupled saving) and interfaces to the | ||
likes of Firefox's SQLite database (including for access to its localStorage | ||
contents) or to FileWriter/FileSystem files and HTTPQuery/PATCH requests | ||
for a potentially friendly and | ||
uniform approach (which could subsume the local SQLite API as well) | ||
ideas for PUT requests (for decoupled saving), SQL/local file/cache (see below) | ||
toward allowing universal and | ||
@@ -64,2 +83,13 @@ neutral APIs to obtain and save *portions* of documents as well as whole | ||
HTML/XML document is itself a kind of database). | ||
1. Add interfaces to the likes of Firefox's SQLite database (including | ||
for access to its localStorage contents) or to FileWriter/FileSystem | ||
and cached files (e.g., when right-clicking on a document, getting its | ||
HTML cache or resource file cache files, or its localStorage, etc. so | ||
no data is inaccessible) and HTTPQuery/PATCH requests for a | ||
potentially friendly and uniform approach (which could subsume the | ||
local SQLite API as well) | ||
1. AtYourCommand to include HTTPQuery (partial) retrieval of remote content | ||
(though delegate partial saving back to webappfind?) | ||
1. Conditional operator to check whether PUT, HTTPQuery, etc. is supported, | ||
and if so, change text sent to command line accordingly (convenience) | ||
1. Idea for command line apps to align themselves with a uniform, | ||
@@ -69,3 +99,19 @@ atyourcommand-friendly syntax to simplify supplying of data (and to allow for | ||
wiki projects supporting. (Or better yet, parse existing help files or | ||
command line help flag commands, if structured enough.) | ||
command line help flag commands, if structured enough.) Also | ||
allow joining of commands. This could actually work with WebAppFind, | ||
e.g., to make flags prefixed with webappfind- along with its own modes | ||
(e.g., view, edit, binaryedit) or custom modes--see next todo. | ||
1. Make desktop app (e.g., Notepad++ plugin? though ideally also a | ||
database app to demo with data that is typically otherwise "locked | ||
away" to other apps) which allows right-click | ||
of text or a URL, etc., and then displays commands stored by AtYourCommand | ||
(in files which themselves might be openable in a WebAppFind filetypes.json | ||
manner), determining relevance of commands by reverse detecting their | ||
"<text>" or whatever substitutions, demoing how a desktop app can in | ||
turn allow contextual snippets to be shuffled off to other applications | ||
including web-based ones (via WebAppFind). See also todo for WebAppFind | ||
re: possible command line syntax within filetypes.json. | ||
# Higher priority to-dos (shorter) | ||
1. Add demo of data page being opened into WebAppFind and sent to web app | ||
@@ -76,10 +122,4 @@ which feeds data to a plug-in and receives data back for a PUT save back to | ||
editing, of a document, including online editing). | ||
1. Ensure, as with QR Secret Decoder Ring, that we can get a privileged | ||
toDataURL canvas snapshot as an argument. | ||
1. As per AppLauncher feature request, default to a specific, configurable | ||
executable path (or save multiple options for drop-down) | ||
1. One-off command line dialog: add optionally savable label (for saving) | ||
1. One-off command line dialog: add options to "save and execute", | ||
"save and close" and add context menu link to prefs dialog | ||
1. Prefs: label list: add, edit, delete, move up/down (adapt for AYW also?) | ||
1. Include pre-sets for opening into WebAppFind (and Firefox) and | ||
@@ -90,6 +130,2 @@ example like Notepad++ | ||
links on the currently loaded file (optionally with args) | ||
1. AtYourCommand to include HTTPQuery (partial) retrieval of remote content | ||
(though delegate partial saving back to webappfind?) | ||
1. Conditional operator to check whether PUT, HTTPQuery, etc. is supported, | ||
and if so, change text sent to command line accordingly (convenience) | ||
1. To handle file:// URLs and c:\ paths that are right-clicked (or currently | ||
@@ -101,6 +137,14 @@ loaded) to: expose folder (or copy folder/file path), bare execution on | ||
separate add-on. | ||
1. Allow storage of own "path" environment for greater portability across OS. | ||
1. Might add items like JSON-stringified array of current <script src>'s, | ||
<link href>'s or <html manifest> string. | ||
# Possible to-dos | ||
1. If a link type of command is chosen but no link is selected, find first | ||
link in page. Same with images, videos, script files, stylesheets, etc. | ||
1. Display of commands in dialog: move up/down instead of alphabetical? | ||
1. Create icons, etc. | ||
1. If item is required, could prevent "submit" (currently not | ||
using <form>, so no submit). | ||
1. Might allow selection of submenus, separators, etc. | ||
@@ -110,51 +154,45 @@ 1. Any other command line necessities (besides quoted string escaping)? | ||
modify to work with main menu, app-bar, or key command as well | ||
1. Option to have context menu items, based on the substitutions used (or | ||
user choice), cause them to only appear under certain, correct conditions | ||
(but ensure commands can utilize multiple components (e.g., highlighted | ||
link and page HTML) | ||
1. Ability to confirm selected text content is a path, URL or file URL, etc.? | ||
1. Optional pre-processing of highlighted contents esp. if cannot convert to | ||
HTML (e.g., toDataURL on canvas) | ||
1. Allow atyourcommand to send content to web apps directly through WebAppFind | ||
code when present (as opposed to through command line)? | ||
1. Remote site supply of commands | ||
1. Way for websites to register commands or groups of commands upon | ||
user introspection and permission | ||
1. Served with special content type and protocol meant for external launching? | ||
1. Create protocol to force dialog asking to launch app (so if user | ||
clicks link, will get asked), optionally with args, and optionally with | ||
desktop file or remote URLs, etc. as content; will thereby also be | ||
able to support (and demo) WebAppFind invocation from remote | ||
1. Way for websites to register commands or groups of commands upon | ||
user introspection and permission | ||
1. Served with special content type and protocol meant for external launching? | ||
1. Create protocol to force dialog asking to launch app (so if user | ||
clicks link, will get asked), optionally with args, and optionally with | ||
desktop file or remote URLs, etc. as content; will thereby also be | ||
able to support (and demo) WebAppFind invocation from remote | ||
1. De-coupling of remote content from its executable (as in regular | ||
atyourcommand) but remember upon future loads of the content | ||
1. Modify [Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
add-on to allow launching of a file URL including with own args (and | ||
optional saving/editing of the command for reuse across atyourcommand | ||
content) | ||
1. Overlay | ||
[Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
but make it support site prefs (but not by domain as with Mozilla content prefs!) | ||
(in addition to mapping MIME to commands) | ||
so choice will be remembered (checkbox to remember choice including | ||
any arguments, passing URL and/or file contents); also allow | ||
WebAppFind detection (e.g., remote filetypes.json?) in addition | ||
to MIME detection? | ||
1. Point out potential use in automatically launching WebAppFind-driven | ||
web apps automatically with web data (and with PUT requests back to | ||
server, could get full round-trip decoupling of data and app) | ||
1. Allow all file:// URLs to optionally be opened externally as per https://support.mozilla.org/en-US/questions/758172 | ||
1. Cover usage of http://kb.mozillazine.org/View_source.editor.external and http://kb.mozillazine.org/View_source.editor.path | ||
1. As with my possible todo for | ||
[Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
site prefs, make the filebrowser-enhanced context | ||
menu and right-click on WebAppFind icon (for the opening of the current | ||
browser document into WebAppFind) sensitive to site prefs so right-click | ||
arguments can optionally be remembered; share options across all of these | ||
addons? | ||
1. Modify [Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
add-on to allow launching of a file URL including with own args (and | ||
optional saving/editing of the command for reuse across atyourcommand | ||
content) | ||
1. Overlay | ||
[Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
but make it support site prefs (but not by domain as with Mozilla content prefs!) | ||
(in addition to mapping MIME to commands) | ||
so choice will be remembered (checkbox to remember choice including | ||
any arguments, passing URL and/or file contents); also allow | ||
WebAppFind detection (e.g., remote filetypes.json?) in addition | ||
to MIME detection? | ||
1. Point out potential use in automatically launching WebAppFind-driven | ||
web apps automatically with web data (and with PUT requests back to | ||
server, could get full round-trip decoupling of data and app) | ||
1. Allow all file:// URLs to optionally be opened externally as per https://support.mozilla.org/en-US/questions/758172 | ||
1. Cover usage of http://kb.mozillazine.org/View_source.editor.external and http://kb.mozillazine.org/View_source.editor.path | ||
1. As with my possible todo for | ||
[Open In Browser](https://addons.mozilla.org/En-us/firefox/addon/open-in-browser/) | ||
site prefs, make the filebrowser-enhanced context | ||
menu and right-click on WebAppFind icon (for the opening of the current | ||
browser document into WebAppFind) sensitive to site prefs so right-click | ||
arguments can optionally be remembered; share options across all of these | ||
addons? | ||
1. To make atyourcommand more meaningful, ensure works with a | ||
Gopher-over-HTTP protocol (e.g., one limited to <li> elements and other tags | ||
auto-escaped): | ||
1. Do Gopher system for these files just extra required header; search "Gopher (protocol) over HTTP" (FTP, WebDAV?) | ||
1. Problem with informational message--needs to map to real files; use instead hidden files of given extension with optional sticky coordinates | ||
1. Use WebDAV request (via same-site Ajax or Firefox add-on privileged cross-domain (already with WebDAV add-on?)) for directory (propfind overloaded, was it?) so request for individual file reading or writing (as with directory listing) can be made over HTTP (including reverse webappfind) | ||
1. Do Gopher system for these files just extra required header; search "Gopher (protocol) over HTTP" (FTP, WebDAV?) | ||
1. Problem with informational message--needs to map to real files; use instead hidden files of given extension with optional sticky coordinates | ||
1. Use WebDAV request (via same-site Ajax or Firefox add-on privileged cross-domain (already with WebDAV add-on?)) for directory (propfind overloaded, was it?) so request for individual file reading or writing (as with directory listing) can be made over HTTP (including reverse webappfind) | ||
1. Exporting as batch files, and converting batch files upon import (also in | ||
@@ -166,2 +204,5 @@ conjunction with | ||
http://stackoverflow.com/a/5215844/271577 | ||
1. Have a mechanism to return from a WebAppFind-opened web app | ||
back to the page which was the source of its content (e.g., in case one | ||
accidentally didn't grab enough text or whatever) | ||
@@ -173,11 +214,11 @@ # To-dos related to context-aware power-use or web-desktop interaction but beyond current scope of atyourcommand | ||
toolbar, menu, add-on bar, key command, etc. | ||
1. Break apart functionality to specialize in context menu | ||
text and URL manipulations? (If so, ensure some other way to | ||
have full control over where tools appear; do this by modifying | ||
the Firefox Add-ons SDK itself so capability baked-in?) | ||
1. Integrate with or replicate Greasemonkey behavior also? | ||
1. Get context menu to support hiding items via whitelist or | ||
blacklist until exposed by a key command (so that for normal | ||
browsing, the context menu is light, but can be made more | ||
powerful at a keystroke). | ||
1. Break apart functionality to specialize in context menu | ||
text and URL manipulations? (If so, ensure some other way to | ||
have full control over where tools appear; do this by modifying | ||
the Firefox Add-ons SDK itself so capability baked-in?) | ||
1. Integrate with or replicate Greasemonkey behavior also? | ||
1. Get context menu to support hiding items via whitelist or | ||
blacklist until exposed by a key command (so that for normal | ||
browsing, the context menu is light, but can be made more | ||
powerful at a keystroke). | ||
1. Utilize (JavaScript-based) Blockly for pipelining of any kind of | ||
@@ -184,0 +225,0 @@ command (though avoid baking in UI as UI should be flexible, e.g., |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
4360072
266
45004
236
1
11
1