2011-03-23 01:08:08 +01:00
|
|
|
/**
|
|
|
|
* @requires multirow-row-manager
|
Bring Javelin into Phabricator via git submodule, not copy-and-paste
Summary:
Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt
packages. This is not so great.
Pull it in as a submodule instead and make all the Phabriator resources declare
proper dependency trees. Add Javelin linting.
Test Plan:
I tried to run through pretty much all the JS functionality on the site. This is
still a high-risk change, but I did a pretty thorough test
Differential: inline comments, revealing diffs, list tokenizers, comment
preview, editing/deleting comments, add review action.
Maniphest: list tokenizer, comment actions
Herald: rule editing, tokenizers, add/remove rows
Reviewed By: tomo
Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen
CC: aran, tomo, epriestley
Differential Revision: 223
2011-05-04 00:11:55 +02:00
|
|
|
* javelin-install
|
|
|
|
* javelin-util
|
|
|
|
* javelin-dom
|
|
|
|
* javelin-stratcom
|
|
|
|
* javelin-json
|
2011-07-16 07:30:55 +02:00
|
|
|
* phabricator-prefab
|
2011-03-23 01:08:08 +01:00
|
|
|
* @provides herald-rule-editor
|
|
|
|
* @javelin
|
|
|
|
*/
|
|
|
|
|
|
|
|
JX.install('HeraldRuleEditor', {
|
|
|
|
construct : function(config) {
|
|
|
|
var root = JX.$(config.root);
|
|
|
|
this._root = root;
|
|
|
|
|
|
|
|
JX.DOM.listen(
|
|
|
|
root,
|
|
|
|
'click',
|
|
|
|
'create-condition',
|
|
|
|
JX.bind(this, this._onnewcondition));
|
|
|
|
|
|
|
|
JX.DOM.listen(
|
|
|
|
root,
|
|
|
|
'click',
|
|
|
|
'create-action',
|
|
|
|
JX.bind(this, this._onnewaction));
|
|
|
|
|
|
|
|
JX.DOM.listen(root, 'change', null, JX.bind(this, this._onchange));
|
|
|
|
JX.DOM.listen(root, 'submit', null, JX.bind(this, this._onsubmit));
|
|
|
|
|
|
|
|
var conditionsTable = JX.DOM.find(root, 'table', 'rule-conditions');
|
|
|
|
var actionsTable = JX.DOM.find(root, 'table', 'rule-actions');
|
|
|
|
|
|
|
|
this._conditionsRowManager = new JX.MultirowRowManager(conditionsTable);
|
|
|
|
this._conditionsRowManager.listen(
|
|
|
|
'row-removed',
|
|
|
|
JX.bind(this, function(row_id) {
|
|
|
|
delete this._config.conditions[row_id];
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._actionsRowManager = new JX.MultirowRowManager(actionsTable);
|
|
|
|
this._actionsRowManager.listen(
|
|
|
|
'row-removed',
|
|
|
|
JX.bind(this, function(row_id) {
|
|
|
|
delete this._config.actions[row_id];
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._conditionGetters = {};
|
|
|
|
this._conditionTypes = {};
|
|
|
|
this._actionGetters = {};
|
|
|
|
this._actionTypes = {};
|
|
|
|
|
|
|
|
this._config = config;
|
|
|
|
|
|
|
|
var conditions = this._config.conditions;
|
|
|
|
this._config.conditions = [];
|
|
|
|
|
|
|
|
var actions = this._config.actions;
|
|
|
|
this._config.actions = [];
|
|
|
|
|
|
|
|
this._renderConditions(conditions);
|
|
|
|
this._renderActions(actions);
|
|
|
|
},
|
|
|
|
|
|
|
|
members : {
|
|
|
|
_config : null,
|
|
|
|
_root : null,
|
|
|
|
_conditionGetters : null,
|
|
|
|
_conditionTypes : null,
|
|
|
|
_actionGetters : null,
|
|
|
|
_actionTypes : null,
|
|
|
|
_conditionsRowManager : null,
|
|
|
|
_actionsRowManager : null,
|
|
|
|
|
|
|
|
_onnewcondition : function(e) {
|
|
|
|
this._newCondition();
|
|
|
|
e.kill();
|
|
|
|
},
|
|
|
|
_onnewaction : function(e) {
|
|
|
|
this._newAction();
|
|
|
|
e.kill();
|
|
|
|
},
|
|
|
|
_onchange : function(e) {
|
|
|
|
var target = e.getTarget();
|
|
|
|
|
|
|
|
var row = e.getNode(JX.MultirowRowManager.getRowSigil());
|
|
|
|
if (!row) {
|
|
|
|
// Changing the "when all of / any of these..." dropdown.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JX.Stratcom.hasSigil(target, 'field-select')) {
|
|
|
|
this._onfieldchange(row);
|
|
|
|
} else if (JX.Stratcom.hasSigil(target, 'condition-select')) {
|
|
|
|
this._onconditionchange(row);
|
|
|
|
} else if (JX.Stratcom.hasSigil(target, 'action-select')) {
|
|
|
|
this._onactionchange(row);
|
|
|
|
}
|
|
|
|
},
|
2014-06-23 19:27:47 +02:00
|
|
|
_onsubmit : function() {
|
2011-03-23 01:08:08 +01:00
|
|
|
var rule = JX.DOM.find(this._root, 'input', 'rule');
|
|
|
|
|
|
|
|
var k;
|
|
|
|
|
|
|
|
for (k in this._config.conditions) {
|
|
|
|
this._config.conditions[k][2] = this._getConditionValue(k);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (k in this._config.actions) {
|
|
|
|
this._config.actions[k][1] = this._getActionTarget(k);
|
|
|
|
}
|
2011-07-05 23:17:38 +02:00
|
|
|
rule.value = JX.JSON.stringify({
|
2011-03-23 01:08:08 +01:00
|
|
|
conditions: this._config.conditions,
|
|
|
|
actions: this._config.actions
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_getConditionValue : function(id) {
|
|
|
|
if (this._conditionGetters[id]) {
|
|
|
|
return this._conditionGetters[id]();
|
|
|
|
}
|
|
|
|
return this._config.conditions[id][2];
|
|
|
|
},
|
|
|
|
|
|
|
|
_getActionTarget : function(id) {
|
|
|
|
if (this._actionGetters[id]) {
|
|
|
|
return this._actionGetters[id]();
|
|
|
|
}
|
|
|
|
return this._config.actions[id][1];
|
|
|
|
},
|
|
|
|
|
|
|
|
_onactionchange : function(r) {
|
|
|
|
var target = JX.DOM.find(r, 'select', 'action-select');
|
|
|
|
var row_id = this._actionsRowManager.getRowID(r);
|
|
|
|
|
|
|
|
this._config.actions[row_id][0] = target.value;
|
|
|
|
|
|
|
|
var target_cell = JX.DOM.find(r, 'td', 'target-cell');
|
|
|
|
var target_input = this._renderTargetInputForRow(row_id);
|
|
|
|
|
|
|
|
JX.DOM.setContent(target_cell, target_input);
|
|
|
|
},
|
|
|
|
_onfieldchange : function(r) {
|
|
|
|
var target = JX.DOM.find(r, 'select', 'field-select');
|
|
|
|
var row_id = this._actionsRowManager.getRowID(r);
|
|
|
|
|
|
|
|
this._config.conditions[row_id][0] = target.value;
|
|
|
|
|
|
|
|
var condition_cell = JX.DOM.find(r, 'td', 'condition-cell');
|
|
|
|
var condition_select = this._renderSelect(
|
|
|
|
this._selectKeys(
|
|
|
|
this._config.info.conditions,
|
|
|
|
this._config.info.conditionMap[target.value]),
|
|
|
|
this._config.conditions[row_id][1],
|
|
|
|
'condition-select');
|
|
|
|
|
|
|
|
JX.DOM.setContent(condition_cell, condition_select);
|
|
|
|
|
|
|
|
this._onconditionchange(r);
|
2013-10-04 02:53:12 +02:00
|
|
|
|
|
|
|
var condition_name = this._config.conditions[row_id][1];
|
2013-10-05 00:17:01 +02:00
|
|
|
if (condition_name == 'unconditionally') {
|
|
|
|
JX.DOM.hide(condition_select);
|
2013-10-04 02:53:12 +02:00
|
|
|
}
|
2011-03-23 01:08:08 +01:00
|
|
|
},
|
|
|
|
_onconditionchange : function(r) {
|
|
|
|
var target = JX.DOM.find(r, 'select', 'condition-select');
|
|
|
|
var row_id = this._conditionsRowManager.getRowID(r);
|
|
|
|
|
|
|
|
this._config.conditions[row_id][1] = target.value;
|
|
|
|
|
|
|
|
var value_cell = JX.DOM.find(r, 'td', 'value-cell');
|
|
|
|
var value_input = this._renderValueInputForRow(row_id);
|
|
|
|
JX.DOM.setContent(value_cell, value_input);
|
|
|
|
},
|
|
|
|
|
|
|
|
_renderTargetInputForRow : function(row_id) {
|
|
|
|
var action = this._config.actions[row_id];
|
|
|
|
var type = this._config.info.targets[action[0]];
|
|
|
|
|
|
|
|
var input = this._buildInput(type);
|
|
|
|
var node = input[0];
|
|
|
|
var get_fn = input[1];
|
|
|
|
var set_fn = input[2];
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
JX.Stratcom.addSigil(node, 'action-target');
|
|
|
|
}
|
|
|
|
|
|
|
|
var old_type = this._actionTypes[row_id];
|
|
|
|
if (old_type == type || !old_type) {
|
|
|
|
set_fn(this._getActionTarget(row_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._actionTypes[row_id] = type;
|
|
|
|
this._actionGetters[row_id] = get_fn;
|
|
|
|
|
|
|
|
return node;
|
|
|
|
},
|
|
|
|
|
2015-07-16 23:12:44 +02:00
|
|
|
_buildInput : function(type) {
|
|
|
|
var spec = this._config.info.valueMap[type];
|
|
|
|
|
2015-07-16 23:12:00 +02:00
|
|
|
var input;
|
|
|
|
var get_fn;
|
|
|
|
var set_fn;
|
|
|
|
switch (spec.control) {
|
|
|
|
case 'herald.control.none':
|
|
|
|
input = null;
|
|
|
|
get_fn = JX.bag;
|
|
|
|
set_fn = JX.bag;
|
|
|
|
break;
|
|
|
|
case 'herald.control.text':
|
|
|
|
input = JX.$N('input', {type: 'text'});
|
|
|
|
get_fn = function() { return input.value; };
|
|
|
|
set_fn = function(v) { input.value = v; };
|
|
|
|
break;
|
2017-12-11 20:33:15 +01:00
|
|
|
case 'herald.control.remarkup':
|
|
|
|
input = JX.$N('textarea');
|
|
|
|
get_fn = function() { return input.value; };
|
|
|
|
set_fn = function(v) { input.value = v; };
|
|
|
|
break;
|
2015-07-16 23:12:00 +02:00
|
|
|
case 'herald.control.select':
|
2015-07-16 23:12:44 +02:00
|
|
|
var options;
|
|
|
|
|
|
|
|
// NOTE: This is a hacky special case for "Another Herald Rule",
|
|
|
|
// which we don't currently generate normal options for.
|
|
|
|
|
|
|
|
if (spec.key == 'select.rule') {
|
|
|
|
options = this._config.template.rules;
|
|
|
|
} else {
|
|
|
|
options = spec.template.options;
|
|
|
|
}
|
|
|
|
|
|
|
|
input = this._renderSelect(options);
|
2015-07-16 23:12:00 +02:00
|
|
|
get_fn = function() { return input.value; };
|
|
|
|
set_fn = function(v) { input.value = v; };
|
|
|
|
if (spec.template.default) {
|
|
|
|
set_fn(spec.template.default);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'herald.control.tokenizer':
|
2015-07-16 23:12:44 +02:00
|
|
|
var tokenizer = this._newTokenizer(spec.template.tokenizer);
|
2015-07-16 23:12:00 +02:00
|
|
|
input = tokenizer[0];
|
|
|
|
get_fn = tokenizer[1];
|
|
|
|
set_fn = tokenizer[2];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
JX.$E('No rules to build control "' + spec.control + '".');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [input, get_fn, set_fn];
|
|
|
|
},
|
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
_renderValueInputForRow : function(row_id) {
|
|
|
|
var cond = this._config.conditions[row_id];
|
|
|
|
var type = this._config.info.values[cond[0]][cond[1]];
|
|
|
|
|
|
|
|
var input = this._buildInput(type);
|
|
|
|
var node = input[0];
|
|
|
|
var get_fn = input[1];
|
|
|
|
var set_fn = input[2];
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
JX.Stratcom.addSigil(node, 'condition-value');
|
|
|
|
}
|
|
|
|
|
|
|
|
var old_type = this._conditionTypes[row_id];
|
|
|
|
if (old_type == type || !old_type) {
|
|
|
|
set_fn(this._getConditionValue(row_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._conditionTypes[row_id] = type;
|
|
|
|
this._conditionGetters[row_id] = get_fn;
|
|
|
|
|
|
|
|
return node;
|
|
|
|
},
|
|
|
|
|
2015-07-16 23:12:44 +02:00
|
|
|
_newTokenizer : function(spec) {
|
|
|
|
var tokenizerConfig = {
|
|
|
|
src: spec.datasourceURI,
|
|
|
|
placeholder: spec.placeholder,
|
2018-01-19 23:43:20 +01:00
|
|
|
browseURI: spec.browseURI,
|
|
|
|
limit: spec.limit
|
2015-07-16 23:12:44 +02:00
|
|
|
};
|
2011-03-23 01:08:08 +01:00
|
|
|
|
2015-04-16 23:46:10 +02:00
|
|
|
var build = JX.Prefab.newTokenizerFromTemplate(
|
|
|
|
this._config.template.markup,
|
|
|
|
tokenizerConfig);
|
2014-04-10 21:38:15 +02:00
|
|
|
build.tokenizer.start();
|
2011-03-23 01:08:08 +01:00
|
|
|
|
|
|
|
return [
|
2015-04-16 23:46:10 +02:00
|
|
|
build.node,
|
2011-03-23 01:08:08 +01:00
|
|
|
function() {
|
2014-04-10 21:38:15 +02:00
|
|
|
return build.tokenizer.getTokens();
|
2011-03-23 01:08:08 +01:00
|
|
|
},
|
|
|
|
function(map) {
|
|
|
|
for (var k in map) {
|
2016-01-21 20:08:51 +01:00
|
|
|
var v = map[k];
|
|
|
|
|
|
|
|
// The control value may be set from wire values from the server,
|
|
|
|
// or a transformed value from another control, or a bare string
|
|
|
|
// value from another control.
|
|
|
|
if (typeof v == 'string') {
|
|
|
|
v = v;
|
|
|
|
} else if (!v.hasOwnProperty('id')) {
|
|
|
|
v = JX.Prefab.transformDatasourceResults(v);
|
|
|
|
}
|
|
|
|
|
2015-12-05 20:09:05 +01:00
|
|
|
build.tokenizer.addToken(k, v);
|
2011-03-23 01:08:08 +01:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
},
|
|
|
|
_selectKeys : function(map, keys) {
|
|
|
|
var r = {};
|
|
|
|
for (var ii = 0; ii < keys.length; ii++) {
|
|
|
|
r[keys[ii]] = map[keys[ii]];
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
},
|
|
|
|
_renderConditions : function(conditions) {
|
|
|
|
for (var k in conditions) {
|
|
|
|
this._newCondition(conditions[k]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_newCondition : function(data) {
|
|
|
|
var row = this._conditionsRowManager.addRow([]);
|
|
|
|
var row_id = this._conditionsRowManager.getRowID(row);
|
2016-03-22 17:08:26 +01:00
|
|
|
|
|
|
|
var default_condition = [
|
|
|
|
this._config.default.field,
|
|
|
|
this._config.default.condition,
|
|
|
|
null
|
|
|
|
];
|
|
|
|
this._config.conditions[row_id] = data || default_condition;
|
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
var r = this._conditionsRowManager.updateRow(
|
|
|
|
row_id,
|
|
|
|
this._renderCondition(row_id));
|
|
|
|
|
|
|
|
this._onfieldchange(r);
|
|
|
|
},
|
|
|
|
_renderCondition : function(row_id) {
|
2015-07-16 23:13:13 +02:00
|
|
|
var groups = this._config.info.fields;
|
|
|
|
|
|
|
|
var attrs = {
|
|
|
|
sigil: 'field-select'
|
|
|
|
};
|
|
|
|
|
2019-09-12 21:54:08 +02:00
|
|
|
var field_select = this._renderGroupSelect(
|
|
|
|
groups,
|
|
|
|
attrs,
|
|
|
|
this._config.conditions[row_id][0]);
|
2015-07-16 23:13:13 +02:00
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select);
|
|
|
|
|
|
|
|
var condition_cell = JX.$N('td', {sigil: 'condition-cell'});
|
|
|
|
var value_cell = JX.$N('td', {className : 'value', sigil: 'value-cell'});
|
|
|
|
|
|
|
|
return [field_cell, condition_cell, value_cell];
|
|
|
|
},
|
|
|
|
_renderActions : function(actions) {
|
|
|
|
for (var k in actions) {
|
|
|
|
this._newAction(actions[k]);
|
|
|
|
delete actions[k];
|
|
|
|
}
|
|
|
|
},
|
2015-07-17 19:27:38 +02:00
|
|
|
|
2019-09-12 21:54:08 +02:00
|
|
|
_renderGroupSelect: function(groups, attrs, value) {
|
2015-07-17 19:27:38 +02:00
|
|
|
var optgroups = [];
|
|
|
|
for (var ii = 0; ii < groups.length; ii++) {
|
|
|
|
var group = groups[ii];
|
|
|
|
var options = [];
|
|
|
|
for (var k in group.options) {
|
2019-09-12 21:54:08 +02:00
|
|
|
var option = group.options[k];
|
|
|
|
|
|
|
|
var name = option.name;
|
|
|
|
var available = option.available;
|
|
|
|
|
|
|
|
// See T7961. If the option is not marked as "available", we only
|
|
|
|
// include it in the dropdown if the dropdown already has it as a
|
|
|
|
// value. We want to hide options provided by applications which are
|
|
|
|
// not installed, but do not want to break existing rules.
|
|
|
|
|
|
|
|
if (available || (k === value)) {
|
|
|
|
options.push(JX.$N('option', {value: k}, name));
|
|
|
|
}
|
2015-07-17 19:27:38 +02:00
|
|
|
}
|
2019-09-12 21:54:08 +02:00
|
|
|
if (options.length) {
|
|
|
|
optgroups.push(JX.$N('optgroup', {label: group.label}, options));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var select = JX.$N('select', attrs, optgroups);
|
|
|
|
|
|
|
|
if (value !== undefined) {
|
|
|
|
select.value = value;
|
2015-07-17 19:27:38 +02:00
|
|
|
}
|
|
|
|
|
2019-09-12 21:54:08 +02:00
|
|
|
return select;
|
2015-07-17 19:27:38 +02:00
|
|
|
},
|
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
_newAction : function(data) {
|
2016-03-22 17:08:26 +01:00
|
|
|
var default_action = [
|
|
|
|
this._config.default.action,
|
|
|
|
null
|
|
|
|
];
|
|
|
|
|
|
|
|
data = data || default_action;
|
2011-03-23 01:08:08 +01:00
|
|
|
var temprow = this._actionsRowManager.addRow([]);
|
|
|
|
var row_id = this._actionsRowManager.getRowID(temprow);
|
|
|
|
this._config.actions[row_id] = data;
|
|
|
|
var r = this._actionsRowManager.updateRow(row_id,
|
|
|
|
this._renderAction(data));
|
|
|
|
this._onactionchange(r);
|
|
|
|
},
|
2015-07-17 19:27:38 +02:00
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
_renderAction : function(action) {
|
2015-07-17 19:27:38 +02:00
|
|
|
var groups = this._config.info.actions;
|
|
|
|
var attrs = {
|
|
|
|
sigil: 'action-select'
|
|
|
|
};
|
|
|
|
|
2019-09-12 21:54:08 +02:00
|
|
|
var action_select = this._renderGroupSelect(
|
|
|
|
groups,
|
|
|
|
attrs,
|
|
|
|
action[0]);
|
2015-07-17 19:27:38 +02:00
|
|
|
|
2011-03-23 01:08:08 +01:00
|
|
|
var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select);
|
|
|
|
|
|
|
|
var target_cell = JX.$N(
|
|
|
|
'td',
|
|
|
|
{className : 'target', sigil : 'target-cell'});
|
|
|
|
|
|
|
|
return [action_cell, target_cell];
|
|
|
|
},
|
|
|
|
_renderSelect : function(map, selected, sigil) {
|
2011-07-16 07:30:55 +02:00
|
|
|
var attrs = {
|
|
|
|
sigil : sigil
|
|
|
|
};
|
|
|
|
return JX.Prefab.renderSelect(map, selected, attrs);
|
2011-03-23 01:08:08 +01:00
|
|
|
}
|
|
|
|
}
|
2011-03-23 01:19:52 +01:00
|
|
|
});
|