New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

atyourcommand

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

atyourcommand - npm Package Compare versions

Comparing version 0.1.1 to 0.1.2

.bowerrc

365

data/ExpandableInputs.js
/*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();
}());

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

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc