@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use a tokenizer, not a gigantic poorly-ordered "<select />", to choose repositories in Owners

Summary: Depends on D19190. Fixes T12590. Ref T13099. Replaces the barely-usable, gigantic, poorly ordered "<select />" control with a tokenizer. Attempts to fix various minor issues.

Test Plan:
- Edited paths: include/exclude paths, from different repositories, different actual paths.
- Used "Add New Path" to add rows, got repository selector prepopulated with last value.
- Used "remove".
- Used validation typeahead, got reasonable behaviors?

The error behavior if you delete the repository for a path is a little sketchy still, but roughly okay.

Maniphest Tasks: T13099, T12590

Differential Revision: https://secure.phabricator.com/D19191

+309 -197
+38 -37
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 12 'core.pkg.css' => '2fa91e14', 13 - 'core.pkg.js' => 'e4d73c62', 13 + 'core.pkg.js' => '32bb68e9', 14 14 'darkconsole.pkg.js' => '1f9a31bc', 15 15 'differential.pkg.css' => '113e692c', 16 16 'differential.pkg.js' => 'f6d809c0', ··· 85 85 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', 86 86 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', 87 87 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 88 - 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 88 + 'rsrc/css/application/owners/owners-path-editor.css' => '9c136c29', 89 89 'rsrc/css/application/paste/paste.css' => '9fcc9773', 90 90 'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34', 91 91 'rsrc/css/application/people/people-profile.css' => '4df76faf', ··· 268 268 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd', 269 269 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9', 270 270 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0', 271 - 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '0fcf201c', 271 + 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'ab9e0a82', 272 272 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa', 273 273 'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178', 274 274 'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7', ··· 418 418 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 419 419 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', 420 420 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e', 421 - 'rsrc/js/application/herald/PathTypeahead.js' => '78039abe', 421 + 'rsrc/js/application/herald/PathTypeahead.js' => '662e9cea', 422 422 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 423 423 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 424 424 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 425 425 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 426 426 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763', 427 - 'rsrc/js/application/owners/OwnersPathEditor.js' => '52b9cbc4', 427 + 'rsrc/js/application/owners/OwnersPathEditor.js' => 'c96502cf', 428 428 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 429 429 'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc', 430 430 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8', ··· 534 534 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 535 535 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 536 536 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 537 - 'rsrc/js/phuix/PHUIXFormControl.js' => '16ad6224', 537 + 'rsrc/js/phuix/PHUIXFormControl.js' => '210a16c1', 538 538 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', 539 539 ), 540 540 'symbols' => array( ··· 744 744 'javelin-typeahead-normalizer' => '185bbd53', 745 745 'javelin-typeahead-ondemand-source' => '013ffff9', 746 746 'javelin-typeahead-preloaded-source' => '54f314a0', 747 - 'javelin-typeahead-source' => '0fcf201c', 747 + 'javelin-typeahead-source' => 'ab9e0a82', 748 748 'javelin-typeahead-static-source' => '6c0e62fa', 749 749 'javelin-uri' => 'c989ade3', 750 750 'javelin-util' => '93cc50d6', ··· 764 764 'maniphest-task-edit-css' => 'fda62a9b', 765 765 'maniphest-task-summary-css' => '11cc5344', 766 766 'multirow-row-manager' => 'b5d57730', 767 - 'owners-path-editor' => '52b9cbc4', 768 - 'owners-path-editor-css' => '2f00933b', 767 + 'owners-path-editor' => 'c96502cf', 768 + 'owners-path-editor-css' => '9c136c29', 769 769 'paste-css' => '9fcc9773', 770 - 'path-typeahead' => '78039abe', 770 + 'path-typeahead' => '662e9cea', 771 771 'people-picture-menu-item-css' => 'a06f7f34', 772 772 'people-profile-css' => '4df76faf', 773 773 'phabricator-action-list-view-css' => '0bcd9a45', ··· 888 888 'phuix-autocomplete' => '7fa5c915', 889 889 'phuix-button-view' => '8a91e1ac', 890 890 'phuix-dropdown-menu' => '04b2ae03', 891 - 'phuix-form-control-view' => '16ad6224', 891 + 'phuix-form-control-view' => '210a16c1', 892 892 'phuix-icon-view' => 'bff6884b', 893 893 'policy-css' => '957ea14c', 894 894 'policy-edit-css' => '815c66f7', ··· 998 998 'javelin-install', 999 999 'javelin-util', 1000 1000 ), 1001 - '0fcf201c' => array( 1002 - 'javelin-install', 1003 - 'javelin-util', 1004 - 'javelin-dom', 1005 - 'javelin-typeahead-normalizer', 1006 - ), 1007 1001 '15d5ff71' => array( 1008 1002 'aphront-typeahead-control-css', 1009 1003 'phui-tag-view-css', 1010 1004 ), 1011 - '16ad6224' => array( 1012 - 'javelin-install', 1013 - 'javelin-dom', 1014 - ), 1015 1005 '17bb8539' => array( 1016 1006 'javelin-behavior', 1017 1007 'javelin-stratcom', ··· 1058 1048 'phabricator-textareautils', 1059 1049 ), 1060 1050 '1fe2510c' => array( 1051 + 'javelin-install', 1052 + 'javelin-dom', 1053 + ), 1054 + '210a16c1' => array( 1061 1055 'javelin-install', 1062 1056 'javelin-dom', 1063 1057 ), ··· 1337 1331 'javelin-vector', 1338 1332 'javelin-typeahead-static-source', 1339 1333 ), 1340 - '52b9cbc4' => array( 1341 - 'multirow-row-manager', 1342 - 'javelin-install', 1343 - 'path-typeahead', 1344 - 'javelin-dom', 1345 - 'javelin-util', 1346 - 'phabricator-prefab', 1347 - ), 1348 1334 '54b612ba' => array( 1349 1335 'javelin-color', 1350 1336 'javelin-install', ··· 1438 1424 'javelin-stratcom', 1439 1425 'javelin-workflow', 1440 1426 'javelin-dom', 1427 + ), 1428 + '662e9cea' => array( 1429 + 'javelin-install', 1430 + 'javelin-typeahead', 1431 + 'javelin-dom', 1432 + 'javelin-request', 1433 + 'javelin-typeahead-ondemand-source', 1434 + 'javelin-util', 1441 1435 ), 1442 1436 '66a6def1' => array( 1443 1437 'javelin-behavior', ··· 1542 1536 'javelin-request', 1543 1537 'javelin-util', 1544 1538 ), 1545 - '78039abe' => array( 1546 - 'javelin-install', 1547 - 'javelin-typeahead', 1548 - 'javelin-dom', 1549 - 'javelin-request', 1550 - 'javelin-typeahead-ondemand-source', 1551 - 'javelin-util', 1552 - ), 1553 1539 '7927a7d3' => array( 1554 1540 'javelin-behavior', 1555 1541 'javelin-quicksand', ··· 1799 1785 'javelin-util', 1800 1786 'phabricator-busy', 1801 1787 ), 1788 + 'ab9e0a82' => array( 1789 + 'javelin-install', 1790 + 'javelin-util', 1791 + 'javelin-dom', 1792 + 'javelin-typeahead-normalizer', 1793 + ), 1802 1794 'acd29eee' => array( 1803 1795 'javelin-behavior', 1804 1796 'javelin-stratcom', ··· 1973 1965 'javelin-reactornode', 1974 1966 'javelin-install', 1975 1967 'javelin-util', 1968 + ), 1969 + 'c96502cf' => array( 1970 + 'multirow-row-manager', 1971 + 'javelin-install', 1972 + 'path-typeahead', 1973 + 'javelin-dom', 1974 + 'javelin-util', 1975 + 'phabricator-prefab', 1976 + 'phuix-form-control-view', 1976 1977 ), 1977 1978 'c989ade3' => array( 1978 1979 'javelin-install',
-13
src/applications/diffusion/controller/DiffusionPathValidateController.php
··· 45 45 'valid' => (bool)$valid, 46 46 ); 47 47 48 - if (!$valid) { 49 - $branch = $drequest->getBranch(); 50 - if ($branch) { 51 - $message = pht('Not found in %s', $branch); 52 - } else { 53 - $message = pht('Not found at %s', 'HEAD'); 54 - } 55 - } else { 56 - $message = pht('OK'); 57 - } 58 - 59 - $output['message'] = $message; 60 - 61 48 return id(new AphrontAjaxResponse())->setContent($output); 62 49 } 63 50 }
+49 -19
src/applications/owners/controller/PhabricatorOwnersPathsController.php
··· 27 27 28 28 $path_refs = array(); 29 29 foreach ($paths as $key => $path) { 30 - if (!isset($repos[$key])) { 30 + if (!isset($repos[$key]) || !strlen($repos[$key])) { 31 31 throw new Exception( 32 32 pht( 33 33 'No repository PHID for path "%s"!', ··· 70 70 $path_refs = mpull($paths, 'getRef'); 71 71 } 72 72 73 - $repos = id(new PhabricatorRepositoryQuery()) 74 - ->setViewer($viewer) 75 - ->execute(); 73 + $template = new AphrontTokenizerTemplateView(); 76 74 77 - $repo_map = array(); 78 - foreach ($repos as $key => $repo) { 79 - $monogram = $repo->getMonogram(); 80 - $name = $repo->getName(); 81 - $repo_map[$repo->getPHID()] = "{$monogram} {$name}"; 75 + $datasource = id(new DiffusionRepositoryDatasource()) 76 + ->setViewer($viewer); 77 + 78 + $tokenizer_spec = array( 79 + 'markup' => $template->render(), 80 + 'config' => array( 81 + 'src' => $datasource->getDatasourceURI(), 82 + 'browseURI' => $datasource->getBrowseURI(), 83 + 'placeholder' => $datasource->getPlaceholderText(), 84 + 'limit' => 1, 85 + ), 86 + ); 87 + 88 + foreach ($path_refs as $key => $path_ref) { 89 + $path_refs[$key]['repositoryValue'] = $datasource->getWireTokens( 90 + array( 91 + $path_ref['repositoryPHID'], 92 + )); 82 93 } 83 - asort($repos); 94 + 95 + $icon_test = id(new PHUIIconView()) 96 + ->setIcon('fa-spinner grey') 97 + ->setTooltip(pht('Validating...')); 98 + 99 + $icon_okay = id(new PHUIIconView()) 100 + ->setIcon('fa-check-circle green') 101 + ->setTooltip(pht('Path Exists in Repository')); 102 + 103 + $icon_fail = id(new PHUIIconView()) 104 + ->setIcon('fa-question-circle-o red') 105 + ->setTooltip(pht('Path Not Found On Default Branch')); 84 106 85 107 $template = new AphrontTypeaheadTemplateView(); 86 108 $template = $template->render(); ··· 88 110 Javelin::initBehavior( 89 111 'owners-path-editor', 90 112 array( 91 - 'root' => 'path-editor', 92 - 'table' => 'paths', 93 - 'add_button' => 'addpath', 94 - 'repositories' => $repo_map, 95 - 'input_template' => $template, 96 - 'pathRefs' => $path_refs, 97 - 98 - 'completeURI' => '/diffusion/services/path/complete/', 99 - 'validateURI' => '/diffusion/services/path/validate/', 113 + 'root' => 'path-editor', 114 + 'table' => 'paths', 115 + 'add_button' => 'addpath', 116 + 'input_template' => $template, 117 + 'pathRefs' => $path_refs, 118 + 'completeURI' => '/diffusion/services/path/complete/', 119 + 'validateURI' => '/diffusion/services/path/validate/', 120 + 'repositoryTokenizerSpec' => $tokenizer_spec, 121 + 'icons' => array( 122 + 'test' => hsprintf('%s', $icon_test), 123 + 'okay' => hsprintf('%s', $icon_okay), 124 + 'fail' => hsprintf('%s', $icon_fail), 125 + ), 126 + 'modeOptions' => array( 127 + 0 => pht('Include'), 128 + 1 => pht('Exclude'), 129 + ), 100 130 )); 101 131 102 132 require_celerity_resource('owners-path-editor-css');
+26 -14
webroot/rsrc/css/application/owners/owners-path-editor.css
··· 3 3 */ 4 4 5 5 .owners-path-editor-table { 6 - margin: 10px; 6 + margin: 10px 0; 7 + width: 100%; 7 8 } 8 9 9 10 .owners-path-editor-table td { ··· 11 12 vertical-align: middle; 12 13 } 13 14 14 - .owners-path-editor-table select.owners-repo { 15 - width: 150px; 15 + .owners-path-editor-table td.owners-path-mode-control { 16 + width: 180px; 16 17 } 17 18 18 - .owners-path-editor-table input { 19 - width: 400px; 19 + .owners-path-editor-table td.owners-path-mode-control select { 20 + width: 100%; 20 21 } 21 22 22 - .owners-path-editor-table div.error-display { 23 - padding: 4px 12px 0; 23 + .owners-path-editor-table td.owners-path-repo-control { 24 + width: 280px; 24 25 } 25 26 26 - .owners-path-editor-table div.validating { 27 - color: {$greytext}; 27 + .owners-path-editor-table td.owners-path-path-control { 28 + width: auto; 28 29 } 29 30 30 - .owners-path-editor-table div.invalid { 31 - color: #aa0000; 31 + .owners-path-editor-table td.owners-path-path-control input { 32 + width: 100%; 32 33 } 33 34 34 - .owners-path-editor-table div.valid { 35 - color: #00aa00; 36 - font-weight: bold; 35 + .owners-path-editor-table td.owners-path-path-control .jx-typeahead-results a { 36 + padding: 4px; 37 + } 38 + 39 + .owners-path-editor-table td.owners-path-icon-control { 40 + width: 18px; 41 + } 42 + 43 + .owners-path-editor-table td.remove-column { 44 + width: 100px; 45 + } 46 + 47 + .owners-path-editor-table td.remove-column a { 48 + display: block; 37 49 }
+7 -2
webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
··· 9 9 10 10 JX.install('TypeaheadSource', { 11 11 construct : function() { 12 - this._raw = {}; 13 - this._lookup = {}; 12 + this.resetResults(); 14 13 this.setNormalizer(JX.TypeaheadNormalizer.normalize); 15 14 this._excludeIDs = {}; 16 15 }, ··· 359 358 } 360 359 return str.split(/\s+/g); 361 360 }, 361 + 362 + resetResults: function() { 363 + this._raw = {}; 364 + this._lookup = {}; 365 + }, 366 + 362 367 _defaultTransformer : function(object) { 363 368 return { 364 369 name : object[0],
+53 -37
webroot/rsrc/js/application/herald/PathTypeahead.js
··· 11 11 12 12 JX.install('PathTypeahead', { 13 13 construct : function(config) { 14 - this._repositorySelect = config.repo_select; 14 + this._repositoryTokenizer = config.repositoryTokenizer; 15 15 this._hardpoint = config.hardpoint; 16 16 this._input = config.path_input; 17 17 this._completeURI = config.completeURI; ··· 19 19 this._errorDisplay = config.error_display; 20 20 this._textInputValues = {}; 21 21 22 + this._icons = config.icons; 23 + 22 24 this._initializeDatasource(); 23 25 this._initializeTypeahead(this._input); 24 26 }, 25 27 members : { 26 - /* 27 - * DOM <select> elem for choosing the repository of a path. 28 - */ 29 - _repositorySelect : null, 28 + _repositoryTokenizer : null, 29 + 30 30 /* 31 31 * DOM parent div "hardpoint" to be passed to the JX.Typeahead. 32 32 */ ··· 79 79 */ 80 80 start : function() { 81 81 if (this._typeahead.getValue()) { 82 - this._textInputValues[this._repositorySelect.value] = 83 - this._typeahead.getValue(); 82 + var phid = this._getRepositoryPHID(); 83 + if (phid) { 84 + this._textInputValues[phid] = this._typeahead.getValue(); 85 + } 84 86 } 85 87 86 88 this._typeahead.listen( 87 89 'change', 88 90 JX.bind(this, function(value) { 89 - this._textInputValues[this._repositorySelect.value] = value; 91 + var phid = this._getRepositoryPHID(); 92 + if (phid) { 93 + this._textInputValues[phid] = value; 94 + } 95 + 90 96 this._validate(); 91 97 })); 92 98 93 - this._typeahead.listen( 94 - 'choose', 95 - JX.bind(this, function() { 96 - setTimeout(JX.bind(this._typeahead, this._typeahead.refresh), 0); 97 - })); 98 - 99 99 var repo_set_input = JX.bind(this, this._onrepochange); 100 100 101 101 this._typeahead.listen('start', repo_set_input); 102 - JX.DOM.listen( 103 - this._repositorySelect, 104 - 'change', 105 - null, 106 - repo_set_input); 102 + 103 + this._repositoryTokenizer.listen('change', repo_set_input); 107 104 108 105 this._typeahead.start(); 109 106 this._validate(); ··· 115 112 this._textInputValues); 116 113 117 114 this._datasource.setAuxiliaryData( 118 - {repositoryPHID : this._repositorySelect.value} 119 - ); 115 + { 116 + repositoryPHID: this._getRepositoryPHID() 117 + }); 118 + 119 + // Since we've changed the repository, reset the results. 120 + this._datasource.resetResults(); 120 121 }, 121 122 122 123 _setPathInputBasedOnRepository : function(typeahead, lookup) { 123 - if (lookup[this._repositorySelect.value]) { 124 - typeahead.setValue(lookup[this._repositorySelect.value]); 124 + var phid = this._getRepositoryPHID(); 125 + if (phid && lookup[phid]) { 126 + typeahead.setValue(lookup[phid]); 125 127 } else { 126 128 typeahead.setValue('/'); 127 129 } ··· 147 149 return ('' + str).replace(/[\/]+/g, '\/'); 148 150 }, 149 151 152 + _getRepositoryPHID: function() { 153 + var tokens = this._repositoryTokenizer.getTokens(); 154 + var keys = JX.keys(tokens); 155 + 156 + if (keys.length) { 157 + return keys[0]; 158 + } 159 + 160 + return null; 161 + }, 162 + 150 163 _validate : function() { 164 + var repo_phid = this._getRepositoryPHID(); 165 + if (!repo_phid) { 166 + return; 167 + } 168 + 151 169 var input = this._input; 152 - var repo_id = this._repositorySelect.value; 153 170 var input_value = input.value; 154 171 var error_display = this._errorDisplay; 155 172 ··· 165 182 166 183 var validation_request = new JX.Request( 167 184 this._validateURI, 168 - function(payload) { 185 + JX.bind(this, function(payload) { 169 186 // Don't change validation display state if the input has been 170 187 // changed since we started validation 171 - if (input.value === input_value) { 172 - if (payload.valid) { 173 - JX.DOM.alterClass(error_display, 'invalid', false); 174 - JX.DOM.alterClass(error_display, 'valid', true); 175 - } else { 176 - JX.DOM.alterClass(error_display, 'invalid', true); 177 - JX.DOM.alterClass(error_display, 'valid', false); 178 - } 179 - JX.DOM.setContent(error_display, payload.message); 188 + if (input.value !== input_value) { 189 + return; 180 190 } 181 - }); 191 + 192 + if (payload.valid) { 193 + JX.DOM.setContent(error_display, JX.$H(this._icons.okay)); 194 + } else { 195 + JX.DOM.setContent(error_display, JX.$H(this._icons.fail)); 196 + } 197 + })); 182 198 183 199 validation_request.listen('finally', function() { 184 - JX.DOM.alterClass(error_display, 'validating', false); 185 200 this._validationInflight = null; 186 201 }); 187 202 188 203 validation_request.setData( 189 204 { 190 - repositoryPHID : repo_id, 205 + repositoryPHID : repo_phid, 191 206 path : input_value 192 207 }); 193 208 194 209 this._validationInflight = validation_request; 210 + JX.DOM.setContent(error_display, JX.$H(this._icons.test)); 195 211 196 212 validation_request.setTimeout(750); 197 213 validation_request.send();
+128 -74
webroot/rsrc/js/application/owners/OwnersPathEditor.js
··· 5 5 * javelin-dom 6 6 * javelin-util 7 7 * phabricator-prefab 8 + * phuix-form-control-view 8 9 * @provides owners-path-editor 9 10 * @javelin 10 11 */ ··· 23 24 JX.bind(this, this._onaddpath)); 24 25 25 26 this._count = 0; 26 - this._repositories = config.repositories; 27 27 this._inputTemplate = config.input_template; 28 + this._repositoryTokenizerSpec = config.repositoryTokenizerSpec; 28 29 29 30 this._completeURI = config.completeURI; 30 31 this._validateURI = config.validateURI; 32 + this._icons = config.icons; 33 + this._modeOptions = config.modeOptions; 31 34 32 35 this._initializePaths(config.pathRefs); 33 36 }, ··· 38 41 _rowManager : null, 39 42 40 43 /* 41 - * Array of objects with 'name' and 'repo_id' keys for 42 - * selecting the repository of a path. 43 - */ 44 - _repositories : null, 45 - 46 - /* 47 44 * How many rows have been created, for form name generation. 48 45 */ 49 46 _count : 0, ··· 65 62 * default for future rows. 66 63 */ 67 64 _lastRepositoryChoice : null, 65 + _icons: null, 66 + _modeOptions: null, 68 67 69 68 /* 70 69 * Initialize with 0 or more rows. ··· 85 84 addPath : function(path_ref) { 86 85 // Smart default repository. See _lastRepositoryChoice. 87 86 if (path_ref) { 88 - this._lastRepositoryChoice = path_ref.repositoryPHID; 87 + this._lastRepositoryChoice = path_ref.repositoryValue; 88 + } else { 89 + path_ref = { 90 + repositoryValue: this._lastRepositoryChoice || {} 91 + }; 89 92 } 90 - path_ref = path_ref || {}; 93 + 94 + var repo = this._newRepoCell(path_ref.repositoryValue); 95 + var path = this._newPathCell(path_ref.display); 96 + var icon = this._newIconCell(); 97 + var mode_cell = this._newModeCell(path_ref.excluded); 98 + 99 + var row = this._rowManager.addRow( 100 + [ 101 + mode_cell, 102 + repo.cell, 103 + path.cell, 104 + icon.cell 105 + ]); 106 + 107 + new JX.PathTypeahead({ 108 + repositoryTokenizer: repo.tokenizer, 109 + path_input : path.input, 110 + hardpoint : path.hardpoint, 111 + error_display : icon.cell, 112 + completeURI : this._completeURI, 113 + validateURI : this._validateURI, 114 + icons: this._icons 115 + }).start(); 116 + 117 + this._count++; 118 + return row; 119 + }, 120 + 121 + _onaddpath : function(e) { 122 + e.kill(); 123 + this.addPath(); 124 + }, 125 + 126 + _newModeCell: function(value) { 127 + var options = this._modeOptions; 91 128 92 - var selected_repository = path_ref.repositoryPHID || 93 - this._lastRepositoryChoice; 94 - var options = this._buildRepositoryOptions(selected_repository); 95 - var attrs = { 96 - name : 'repo[' + this._count + ']', 97 - className : 'owners-repo' 98 - }; 99 - var repo_select = JX.$N('select', attrs, options); 129 + var name = 'exclude[' + this._count + ']'; 100 130 101 - JX.DOM.listen(repo_select, 'change', null, JX.bind(this, function() { 102 - this._lastRepositoryChoice = repo_select.value; 103 - })); 131 + var control = JX.Prefab.renderSelect(options, value, {name: name}); 104 132 105 - var repo_cell = JX.$N('td', {}, repo_select); 106 - var typeahead_cell = JX.$N( 133 + return JX.$N( 107 134 'td', 108 - JX.$H(this._inputTemplate)); 135 + { 136 + className: 'owners-path-mode-control' 137 + }, 138 + control); 139 + }, 109 140 110 - // Text input for path. 111 - var path_input = JX.DOM.find(typeahead_cell, 'input'); 112 - JX.copy( 113 - path_input, 141 + _newRepoCell: function(value) { 142 + var repo_control = new JX.PHUIXFormControl() 143 + .setControl('tokenizer', this._repositoryTokenizerSpec) 144 + .setValue(value); 145 + 146 + var repo_tokenizer = repo_control.getTokenizer(); 147 + var name = 'repo[' + this._count + ']'; 148 + 149 + function get_phid() { 150 + var phids = repo_control.getValue(); 151 + if (!phids.length) { 152 + return null; 153 + } 154 + 155 + return phids[0]; 156 + } 157 + 158 + var input = JX.$N( 159 + 'input', 114 160 { 115 - value : path_ref.display || '', 116 - name : 'path[' + this._count + ']' 161 + type: 'hidden', 162 + name: name, 163 + value: get_phid() 117 164 }); 118 165 119 - // The Typeahead requires a display div called hardpoint. 120 - var hardpoint = JX.DOM.find( 121 - typeahead_cell, 122 - 'div', 123 - 'typeahead-hardpoint'); 166 + repo_tokenizer.listen('change', JX.bind(this, function() { 167 + this._lastRepositoryChoice = repo_tokenizer.getTokens(); 124 168 125 - var error_display = JX.$N( 126 - 'div', 169 + input.value = get_phid(); 170 + })); 171 + 172 + var cell = JX.$N( 173 + 'td', 127 174 { 128 - className : 'error-display validating' 175 + className: 'owners-path-repo-control' 129 176 }, 130 - 'Validating...'); 177 + [ 178 + repo_control.getRawInputNode(), 179 + input 180 + ]); 181 + 182 + return { 183 + cell: cell, 184 + tokenizer: repo_tokenizer 185 + }; 186 + }, 131 187 132 - var error_display_cell = JX.$N('td', {}, error_display); 188 + _newPathCell: function(value) { 189 + var path_cell = JX.$N( 190 + 'td', 191 + { 192 + className: 'owners-path-path-control' 193 + }, 194 + JX.$H(this._inputTemplate)); 133 195 134 - var exclude = JX.Prefab.renderSelect( 135 - {'0' : 'Include', '1' : 'Exclude'}, 136 - path_ref.excluded, 137 - {name : 'exclude[' + this._count + ']'}); 138 - var exclude_cell = JX.$N('td', {}, exclude); 196 + var path_input = JX.DOM.find(path_cell, 'input'); 139 197 140 - var row = this._rowManager.addRow( 141 - [exclude_cell, repo_cell, typeahead_cell, error_display_cell]); 198 + JX.copy( 199 + path_input, 200 + { 201 + value: value || '', 202 + name: 'path[' + this._count + ']' 203 + }); 142 204 143 - new JX.PathTypeahead({ 144 - repo_select : repo_select, 145 - path_input : path_input, 146 - hardpoint : hardpoint, 147 - error_display : error_display, 148 - completeURI : this._completeURI, 149 - validateURI : this._validateURI 150 - }).start(); 205 + var hardpoint = JX.DOM.find( 206 + path_cell, 207 + 'div', 208 + 'typeahead-hardpoint'); 151 209 152 - this._count++; 153 - return row; 210 + return { 211 + cell: path_cell, 212 + input: path_input, 213 + hardpoint: hardpoint 214 + }; 154 215 }, 155 216 156 - _onaddpath : function(e) { 157 - e.kill(); 158 - this.addPath(); 159 - }, 217 + _newIconCell: function() { 218 + var cell = JX.$N( 219 + 'td', 220 + { 221 + className: 'owners-path-icon-control' 222 + }); 160 223 161 - /** 162 - * Helper to build the options for the repository choice dropdown. 163 - */ 164 - _buildRepositoryOptions : function(selected) { 165 - var repos = this._repositories; 166 - var result = []; 167 - for (var k in repos) { 168 - var attr = { 169 - value : k, 170 - selected : (selected == k) 171 - }; 172 - result.push(JX.$N('option', attr, repos[k])); 173 - } 174 - return result; 224 + return { 225 + cell: cell 226 + }; 175 227 } 228 + 176 229 } 230 + 177 231 });
+8 -1
webroot/rsrc/js/phuix/PHUIXFormControl.js
··· 15 15 _valueSetCallback: null, 16 16 _valueGetCallback: null, 17 17 _rawInputNode: null, 18 + _tokenizer: null, 18 19 19 20 setLabel: function(label) { 20 21 JX.DOM.setContent(this._getLabelNode(), label); ··· 70 71 this._valueGetCallback = input.get; 71 72 this._valueSetCallback = input.set; 72 73 this._rawInputNode = input.node; 74 + this._tokenizer = input.tokenizer || null; 73 75 74 76 return this; 75 77 }, ··· 85 87 86 88 getRawInputNode: function() { 87 89 return this._rawInputNode; 90 + }, 91 + 92 + getTokenizer: function() { 93 + return this._tokenizer; 88 94 }, 89 95 90 96 getNode: function() { ··· 168 174 return { 169 175 node: build.node, 170 176 get: get_value, 171 - set: set_value 177 + set: set_value, 178 + tokenizer: build.tokenizer 172 179 }; 173 180 }, 174 181