diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php
index f766a669fb..2c6c26b496 100755
--- a/scripts/celerity_mapper.php
+++ b/scripts/celerity_mapper.php
@@ -101,6 +101,8 @@ foreach ($file_map as $path => $info) {
$provides = array_filter($provides);
$requires = array_filter($requires);
+ var_dump($requires);
+
if (count($provides) !== 1) {
throw new Exception(
"File {$path} must @provide exactly one Celerity target.");
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
index c8bd5852be..193ab071fc 100644
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -81,7 +81,7 @@ celerity_register_resource_map(array(
),
'aphront-side-nav-view-css' =>
array(
- 'uri' => '/res/09b7eb85/rsrc/css/aphront/side-nav-view.css',
+ 'uri' => '/res/4f4c5ca8/rsrc/css/aphront/side-nav-view.css',
'type' => 'css',
'requires' =>
array(
@@ -199,7 +199,7 @@ celerity_register_resource_map(array(
),
'diffusion-commit-view-css' =>
array(
- 'uri' => '/res/4593ecc8/rsrc/css/application/diffusion/commit-view.css',
+ 'uri' => '/res/8c139192/rsrc/css/application/diffusion/commit-view.css',
'type' => 'css',
'requires' =>
array(
@@ -336,6 +336,16 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-workflow.js',
),
+ 'multirow-row-manager' =>
+ array(
+ 'uri' => '/res/330d076b/rsrc/js/application/core/MultirowRowManager.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-lib-dev',
+ ),
+ 'disk' => '/rsrc/js/application/core/MultirowRowManager.js',
+ ),
'javelin-behavior-differential-add-reviewers' =>
array(
'uri' => '/res/330154e4/rsrc/js/application/differential/behavior-add-reviewers.js',
@@ -406,6 +416,26 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/differential/behavior-show-more.js',
),
+ 'javelin-behavior-herald-rule-editor' =>
+ array(
+ 'uri' => '/res/f18bcd5e/rsrc/js/application/herald/herald-rule-editor.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'herald-rule-editor',
+ ),
+ 'disk' => '/rsrc/js/application/herald/herald-rule-editor.js',
+ ),
+ 'herald-rule-editor' =>
+ array(
+ 'uri' => '/res/e71d1d0e/rsrc/js/application/herald/HeraldRuleEditor.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'multirow-row-manager',
+ ),
+ 'disk' => '/rsrc/js/application/herald/HeraldRuleEditor.js',
+ ),
'javelin-behavior-maniphest-transaction-controls' =>
array(
'uri' => '/res/fc6a8722/rsrc/js/application/maniphest/behavior-transaction-controls.js',
@@ -491,7 +521,7 @@ celerity_register_resource_map(array(
), array (
'packages' =>
array (
- 73063447 =>
+ '848f4c9f' =>
array (
'name' => 'core.pkg.css',
'symbols' =>
@@ -511,7 +541,7 @@ celerity_register_resource_map(array(
12 => 'phabricator-remarkup-css',
13 => 'syntax-highlighting-css',
),
- 'uri' => '/res/pkg/73063447/core.pkg.css',
+ 'uri' => '/res/pkg/848f4c9f/core.pkg.css',
'type' => 'css',
),
'76f3c1f8' =>
@@ -545,33 +575,33 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/30d594cf/differential.pkg.js',
'type' => 'js',
),
- '2393c3a4' =>
+ 'eadf6ec3' =>
array (
'name' => 'diffusion.pkg.css',
'symbols' =>
array (
0 => 'diffusion-commit-view-css',
),
- 'uri' => '/res/pkg/2393c3a4/diffusion.pkg.css',
+ 'uri' => '/res/pkg/eadf6ec3/diffusion.pkg.css',
'type' => 'css',
),
),
'reverse' =>
array (
- 'phabricator-core-css' => '73063447',
- 'phabricator-core-buttons-css' => '73063447',
- 'phabricator-standard-page-view' => '73063447',
- 'aphront-dialog-view-css' => '73063447',
- 'aphront-form-view-css' => '73063447',
- 'aphront-panel-view-css' => '73063447',
- 'aphront-side-nav-view-css' => '73063447',
- 'aphront-table-view-css' => '73063447',
- 'aphront-crumbs-view-css' => '73063447',
- 'aphront-tokenizer-control-css' => '73063447',
- 'aphront-typeahead-control-css' => '73063447',
- 'phabricator-directory-css' => '73063447',
- 'phabricator-remarkup-css' => '73063447',
- 'syntax-highlighting-css' => '73063447',
+ 'phabricator-core-css' => '848f4c9f',
+ 'phabricator-core-buttons-css' => '848f4c9f',
+ 'phabricator-standard-page-view' => '848f4c9f',
+ 'aphront-dialog-view-css' => '848f4c9f',
+ 'aphront-form-view-css' => '848f4c9f',
+ 'aphront-panel-view-css' => '848f4c9f',
+ 'aphront-side-nav-view-css' => '848f4c9f',
+ 'aphront-table-view-css' => '848f4c9f',
+ 'aphront-crumbs-view-css' => '848f4c9f',
+ 'aphront-tokenizer-control-css' => '848f4c9f',
+ 'aphront-typeahead-control-css' => '848f4c9f',
+ 'phabricator-directory-css' => '848f4c9f',
+ 'phabricator-remarkup-css' => '848f4c9f',
+ 'syntax-highlighting-css' => '848f4c9f',
'differential-core-view-css' => '76f3c1f8',
'differential-changeset-view-css' => '76f3c1f8',
'differential-revision-detail-css' => '76f3c1f8',
@@ -585,6 +615,6 @@ celerity_register_resource_map(array(
'javelin-behavior-differential-populate' => '30d594cf',
'javelin-behavior-differential-show-more' => '30d594cf',
'javelin-behavior-differential-diff-radios' => '30d594cf',
- 'diffusion-commit-view-css' => '2393c3a4',
+ 'diffusion-commit-view-css' => 'eadf6ec3',
),
));
diff --git a/src/applications/herald/controller/rule/HeraldRuleController.php b/src/applications/herald/controller/rule/HeraldRuleController.php
index 84c0e2b20b..0bf62be8d3 100644
--- a/src/applications/herald/controller/rule/HeraldRuleController.php
+++ b/src/applications/herald/controller/rule/HeraldRuleController.php
@@ -251,6 +251,7 @@ class HeraldRuleController extends HeraldController {
$form = id(new AphrontFormView())
->setUser($user)
+ ->setID('herald-rule-edit-form')
->addHiddenInput('type', $rule->getContentType())
->addHiddenInput('save', true)
->addHiddenInput('rule', '')
@@ -270,16 +271,33 @@ class HeraldRuleController extends HeraldController {
->appendChild(
'
Conditions
'.
''.
- '
Create New Condition'.
+ javelin_render_tag(
+ 'a',
+ array(
+ 'href' => '#',
+ 'class' => 'button green',
+ 'sigil' => 'create-action',
+ ),
+ 'Create New Condition').
'
When '.$must_match.' these conditions are met:
'.
- '
'.
+ javelin_render_tag(
+ 'table',
+ array(
+ 'sigil' => 'rule-conditions',
+ ),
+ '').
'
')
->appendChild(
'Action
'.
''.
'
Create New Action'.
'
Take these actions:
'.
- '
'.
+ javelin_render_tag(
+ 'table',
+ array(
+ 'sigil' => 'rule-actions',
+ ),
+ '').
'
')
->appendChild(
id(new AphrontFormSubmitControl())
@@ -439,11 +457,10 @@ class HeraldRuleController extends HeraldController {
HeraldValueTypeConfig::getValueTypeForAction($action);
}
-/*
Javelin::initBehavior(
'herald-rule-editor',
array(
- 'root' => 'qq',//$form->requireUniqueId(),
+ 'root' => 'herald-rule-edit-form',
'conditions' => (object) $serial_conditions,
'actions' => (object) $serial_actions,
'template' => $this->buildTokenizerTemplates() + array(
@@ -452,8 +469,6 @@ class HeraldRuleController extends HeraldController {
'info' => $config_info,
));
-*/
-
$panel = new AphrontPanelView();
$panel->setHeader('Edit Herald Rule');
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
diff --git a/src/view/form/base/AphrontFormView.php b/src/view/form/base/AphrontFormView.php
index 4722a32f7d..710b223638 100755
--- a/src/view/form/base/AphrontFormView.php
+++ b/src/view/form/base/AphrontFormView.php
@@ -25,6 +25,12 @@ final class AphrontFormView extends AphrontView {
private $encType;
private $user;
private $workflow;
+ private $id;
+
+ public function setID($id) {
+ $this->id = $id;
+ return $this;
+ }
public function setUser(PhabricatorUser $user) {
$this->user = $user;
@@ -66,6 +72,7 @@ final class AphrontFormView extends AphrontView {
'class' => 'aphront-form-view',
'enctype' => $this->encType,
'sigil' => $this->workflow ? 'workflow' : null,
+ 'id' => $this->id,
),
$this->renderDataInputs().
$this->renderChildren());
diff --git a/webroot/rsrc/js/application/core/MultirowRowManager.js b/webroot/rsrc/js/application/core/MultirowRowManager.js
new file mode 100644
index 0000000000..45d1bf368a
--- /dev/null
+++ b/webroot/rsrc/js/application/core/MultirowRowManager.js
@@ -0,0 +1,143 @@
+/**
+ * @requires javelin-lib-dev
+ * @provides multirow-row-manager
+ * @javelin
+ */
+
+
+/**
+ * Give a MultirowRowManager a table DOM elem to manage.
+ * You can add rows, and provide a given ID if you like.
+ * You can update rows by ID.
+ * Rows are automatically equipped with a removal button.
+ * You can listen to the 'row-removed' event on the Manager to get
+ * notifications of these row removals, with the DOM id of the removed
+ * row as event data.
+ */
+JX.install('MultirowRowManager', {
+ /**
+ * @param DOM element root Container for rows
+ */
+ construct : function(root, minRows) {
+ this._root = root;
+ this._rows = [];
+
+ if (typeof minRows !== "undefined") {
+ this._minRows = minRows;
+ } else {
+ this._minRows = 1;
+ }
+
+ JX.DOM.listen(
+ this._root,
+ 'click',
+ JX.MultirowRowManager._removeSigil,
+ JX.bind(this, this._onrowremoved));
+ },
+
+ members : {
+ _count : 0,
+ _nextID : 0,
+ _root : null,
+ _rows : null,
+
+ _generateRowID : function() {
+ return "" + this._nextID++;
+ },
+
+ _wrapRowContents : function(row_id, row_contents) {
+ var row = JX.$N('tr',
+ { sigil : JX.MultirowRowManager.getRowSigil(),
+ meta : { multirow_row_manager_row_id : row_id }
+ },
+ row_contents);
+
+ var removeButton = JX.$N(
+ 'td',
+ {},
+ JX.$N(
+ 'a',
+ { className: "button",
+ sigil: JX.MultirowRowManager._removeSigil
+ },
+ '-'));
+
+ JX.DOM.appendContent(row, removeButton);
+ return row;
+ },
+
+ getRowID : function(row) {
+ return JX.Stratcom.getData(row).multirow_row_manager_row_id;
+ },
+ /**
+ * @param row_contents [DOM elements] New contents of row
+ * @param row_id row ID to update, will throw if this row has been removed
+ */
+ updateRow : function(row_id, row_contents) {
+ if (__DEV__) {
+ if (typeof this._rows[row_id] === "undefined") {
+ throw new Error("JX.MultirowRowManager.updateRow(row_id, " +
+ "row_contents): provided row id does not exist." +
+ " Use addRow to create a new row and make sure " +
+ "not to update rows that have been deleted.");
+ }
+ }
+ var old_row = this._rows[row_id];
+ var new_row = this._wrapRowContents(row_id, row_contents);
+ JX.copy(JX.Stratcom.getData(new_row), JX.Stratcom.getData(old_row));
+
+ JX.DOM.replace(old_row, new_row);
+ this._rows[row_id] = new_row;
+
+ this._oncountchanged(); // Fix the new button.
+ return new_row;
+ },
+
+ addRow : function(row_contents) {
+ var row_id = this._generateRowID();
+ var row = this._wrapRowContents(row_id, row_contents);
+ JX.DOM.appendContent(this._root, row);
+
+ this._count++;
+ this._oncountchanged();
+
+ this._rows[row_id] = row;
+ return row;
+ },
+ _onrowremoved : function(e) {
+ if (!JX.Stratcom.getData(e.getTarget()).enabled) {
+ return;
+ }
+ var row = e.getNode(JX.MultirowRowManager.getRowSigil());
+ var row_id = this.getRowID(row);
+ delete this._rows[row_id];
+ JX.DOM.remove(row);
+
+ this._count--;
+ this._oncountchanged();
+ this.invoke('row-removed', row_id);
+ },
+
+ _oncountchanged : function(e) {
+ var buttons = JX.DOM.scry(
+ this._root,
+ 'a',
+ JX.MultirowRowManager._removeSigil);
+
+ var disable = (this._minRows >= 0 && this._count <= this._minRows);
+ for (var i = 0; i < buttons.length; i++) {
+ var button = buttons[i];
+ JX.DOM.alterClass(button, 'disabled', disable);
+ JX.Stratcom.getData(button).enabled = !disable;
+ }
+ }
+ },
+ events : ['row-removed'],
+ statics : {
+ getRowSigil : function() {
+ return "tools-multirow-row-manager-row";
+ },
+ _removeSigil : "tools-multirow-row-manager-row-remove"
+ }
+});
+
diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
new file mode 100644
index 0000000000..a6bce77dda
--- /dev/null
+++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
@@ -0,0 +1,363 @@
+/**
+ * @requires multirow-row-manager
+ * javelin-lib-dev
+ * javelin-typeahead-dev
+ * @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);
+ }
+ },
+ _onsubmit : function(e) {
+ 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);
+ }
+
+ var acts = this._config.actions;
+ for (k in this._config.actions) {
+ this._config.actions[k][1] = this._getActionTarget(k);
+ }
+
+ rule.value = JX.JSON.serialize({
+ 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);
+ },
+ _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;
+ },
+
+ _buildInput : function(type) {
+ var input;
+ var get_fn;
+ var set_fn;
+ switch (type) {
+ case 'rule':
+ input = this._renderSelect(this._config.template.rules);
+ get_fn = function() { return input.value; };
+ set_fn = function(v) { input.value = v; };
+ break;
+ case 'email':
+ case 'employee':
+ case 'repository':
+ case 'tag':
+ case 'package':
+ var tokenizer = this._newTokenizer(type);
+ input = tokenizer[0];
+ get_fn = tokenizer[1];
+ set_fn = tokenizer[2];
+ break;
+ case 'none':
+ input = '';
+ get_fn = JX.bag;
+ set_fn = JX.bag;
+ break;
+ default:
+ input = JX.$N('input');
+ get_fn = function() { return input.value; };
+ set_fn = function(v) { input.value = v; };
+ break;
+ }
+
+ return [input, get_fn, set_fn];
+ },
+
+ _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;
+ },
+
+ _newTokenizer : function(type) {
+ var template = JX.$N(
+ 'div',
+ new JX.HTML(this._config.template.markup));
+ template = template.firstChild;
+ template.id = '';
+
+ var datasource = new JX.TypeaheadPreloadedSource(
+ this._config.template.source[type]);
+
+ var typeahead = new JX.Typeahead(template);
+ typeahead.setDatasource(datasource);
+
+ var tokenizer = new JX.Tokenizer(template);
+ tokenizer.setTypeahead(typeahead);
+ tokenizer.start();
+
+ return [
+ template,
+ function() {
+ return tokenizer.getTokens();
+ },
+ function(map) {
+ for (var k in map) {
+ tokenizer.addToken(k, map[k]);
+ }
+ }];
+ },
+ _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);
+ this._config.conditions[row_id] = data || [null, null, ''];
+ var r = this._conditionsRowManager.updateRow(
+ row_id,
+ this._renderCondition(row_id));
+
+ this._onfieldchange(r);
+ },
+ _renderCondition : function(row_id) {
+ var field_select = this._renderSelect(
+ this._config.info.fields,
+ this._config.conditions[row_id][0],
+ 'field-select');
+ 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];
+ }
+ },
+ _newAction : function(data) {
+ data = data || [];
+ 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);
+ },
+ _renderAction : function(action) {
+ var action_select = this._renderSelect(
+ this._config.info.actions,
+ action[0],
+ 'action-select');
+ 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) {
+ var select = JX.$N(
+ 'select',
+ {
+ style : {width: '250px', margin: '0 .5em 0 0'},
+ sigil : sigil
+ });
+ for (var k in map) {
+ select.options[select.options.length] = new Option(map[k], k);
+ if (k == selected) {
+ select.value = k;
+ }
+ }
+ select.value = select.value || JX.keys(map)[0];
+ return select;
+ }
+ }
+});
\ No newline at end of file
diff --git a/webroot/rsrc/js/application/herald/herald-rule-editor.js b/webroot/rsrc/js/application/herald/herald-rule-editor.js
new file mode 100644
index 0000000000..ff4f17ecc0
--- /dev/null
+++ b/webroot/rsrc/js/application/herald/herald-rule-editor.js
@@ -0,0 +1,10 @@
+/**
+ * @requires herald-rule-editor
+ * javelin-behavior
+ * @provides javelin-behavior-herald-rule-editor
+ * @javelin
+ */
+
+JX.behavior('herald-rule-editor', function(config) {
+ new JX.HeraldRuleEditor(config);
+});